Flutter 源碼剖析(一)

前言

做技術,只有弄懂了原理,才能遇事不慌,手中無碼,心中有碼。這篇文章主要研究Flutter 在安卓平臺上的啓動流程源碼。

啓動流程

入口Activity

當我們創建一個Flutter app工程時,打開android目錄下的源碼,會發現有一個MainActivity繼承自FlutterActivity,整個MainActivity非常簡單,只在onCreate下加了一行代碼GeneratedPluginRegistrant.registerWith(this),那麼FlutterActivity又是何方神聖呢?FlutterActivity的源碼在Flutter SDK的jar包中,想要研究Flutter源碼,第一件事就是需要下載一套SDK源碼,我們可以在GitHub上下載 engine源碼

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }
}

engine\src\flutter\shell\platform\android\io\flutter\app\FlutterActivity.java
省略部分源碼,刪除註釋後代碼如下

public class FlutterActivity extends Activity implements 
FlutterView.Provider, PluginRegistry, ViewFactory {
    private static final String TAG = "FlutterActivity";
    
    private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);

    private final FlutterActivityEvents eventDelegate = delegate;
    private final FlutterView.Provider viewProvider = delegate;
    private final PluginRegistry pluginRegistry = delegate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        eventDelegate.onCreate(savedInstanceState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        eventDelegate.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        eventDelegate.onResume();
    }

    @Override
    protected void onDestroy() {
        eventDelegate.onDestroy();
        super.onDestroy();
    }

   // ...省略部分源碼...
}

可以看到FlutterActivity繼承自Activity,並實現了三個接口,FlutterActivity的生命週期方法,均由一個代理類FlutterActivityDelegate處理。

engine\src\flutter\shell\platform\android\io\flutter\app\FlutterActivityDelegate.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // 根據當前系統版本設置沉浸式狀態欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = activity.getWindow();
            window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(0x40000000);
            window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
        }
        // 獲取intent傳入的參數信息
        String[] args = getArgsFromIntent(activity.getIntent());
        // 初始化一些參數配置信息,包括打包的flutter代碼路徑、應用存儲目錄、引擎緩存目錄等
        FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
        
        flutterView = viewFactory.createFlutterView(activity);
        // 真正創建contentView的地方
        if (flutterView == null) {
            FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
            flutterView = new FlutterView(activity, null, nativeView);
            flutterView.setLayoutParams(matchParent);
            activity.setContentView(flutterView);
            launchView = createLaunchView();
            if (launchView != null) {
                addLaunchView();
            }
        }

        if (loadIntent(activity.getIntent())) {
            return;
        }

        String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
        if (appBundlePath != null) {
            runBundle(appBundlePath);
        }
    }

我們自然是要找到onCreate方法,以上代碼我增加了一點註釋,我們要快速定位到關鍵代碼,什麼是關鍵代碼,看到了activity.setContentView(flutterView);,這裏就是關鍵代碼,終於見到了我們熟悉的setContentView了。這裏我們不禁要問,這個flutterView到底是個什麼View?

一開始我們就知道ViewFactoryFlutterActivity實現的,這裏createFlutterView方法實現也在FlutterActivity裏,但這個方法始終返回空,再看createFlutterNativeView方法,仍然是返回空

    @Override
    public FlutterNativeView createFlutterNativeView() {
        return null;
    }
    
    @Override
    public FlutterNativeView createFlutterNativeView() {
        return null;
    }

這裏真正的flutterView實際上是通過flutterView = new FlutterView(activity, null, nativeView)這行代碼new出來的,然後傳遞給setContentView

接下來直接看到FlutterView源碼(省略部分代碼)

public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
    private static final String TAG = "FlutterView";

    public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
        super(context, attrs);

        Activity activity = getActivity(getContext());
        if (activity == null) {
            throw new IllegalArgumentException("Bad context");
        }

        if (nativeView == null) {
            mNativeView = new FlutterNativeView(activity.getApplicationContext());
        } else {
            mNativeView = nativeView;
        }

        dartExecutor = mNativeView.getDartExecutor();
        flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
        mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
        mMetrics = new ViewportMetrics();
        mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
        setFocusable(true);
        setFocusableInTouchMode(true);

        mNativeView.attachViewAndActivity(this, activity);

        mSurfaceCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                assertAttached();
                mNativeView.getFlutterJNI().onSurfaceDestroyed();
            }
        };
        getHolder().addCallback(mSurfaceCallback);

        mActivityLifecycleListeners = new ArrayList<>();
        mFirstFrameListeners = new ArrayList<>();

        // Create all platform channels
        navigationChannel = new NavigationChannel(dartExecutor);
        keyEventChannel = new KeyEventChannel(dartExecutor);
        lifecycleChannel = new LifecycleChannel(dartExecutor);
        localizationChannel = new LocalizationChannel(dartExecutor);
        platformChannel = new PlatformChannel(dartExecutor);
        systemChannel = new SystemChannel(dartExecutor);
        settingsChannel = new SettingsChannel(dartExecutor);

        // Create and setup plugins
        PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel);
        addActivityLifecycleListener(platformPlugin);
        mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        mTextInputPlugin = new TextInputPlugin(this, dartExecutor);
        androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin);
        androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer);

        // Send initial platform information to Dart
        sendLocalesToDart(getResources().getConfiguration());
        sendUserPlatformSettingsToDart();
    }

到這裏就明白了,FlutterView實際上就是安卓中的SurfaceView,因爲SurfaceView是雙緩衝的,可以在子線程更新UI,性能高效,因此通常是用來做遊戲開發、視頻直播。經過簡單的源碼分析,我們大致能明白Flutter在安卓上的實現方式,整個Flutter開發的app都是在一個Activity中進行渲染的,這就有點像現在前端流行的所謂單頁應用。

還個方法中還創建了各種平臺插件和platform channel,用於Flutter層和原生代碼之間的數據傳遞。

環境初始化

現在我們再回過頭來看一下剛剛漏過的一些代碼,看看它們做了些什麼事
onCreate函數中有調用FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args)

    /**
     * Blocks until initialization of the native system has completed.
     */
    public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
          throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
        }
        if (sSettings == null) {
          throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
        }
        if (sInitialized) {
            return;
        }
        try {
            sResourceExtractor.waitForCompletion();

            List<String> shellArgs = new ArrayList<>();
            shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");

            ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
            shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);

           // ...省略...

            if (sSettings.getLogTag() != null) {
                shellArgs.add("--log-tag=" + sSettings.getLogTag());
            }

            String appBundlePath = findAppBundlePath(applicationContext);
            String appStoragePath = PathUtils.getFilesDir(applicationContext);
            String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
            nativeInit(applicationContext, shellArgs.toArray(new String[0]),
                appBundlePath, appStoragePath, engineCachesPath);

            sInitialized = true;
        } catch (Exception e) {
            Log.e(TAG, "Flutter initialization failed.", e);
            throw new RuntimeException(e);
        }
    }

該方法註釋已經明確告訴我們,這是一個阻塞的方法,直到底層初始化完成。這意味着該方法執行會影響app的啓動速度。總的來說,該方法做了幾件事,它保證初始化操作在主線程運行,調用sResourceExtractor.waitForCompletion()完成資源文件的提取工作,拼接所有相關的shellArgs參數,包括intent中的參數,配置dart代碼編譯產物appBundle路徑,應用存儲、引擎緩存目錄等信息,
最後通過執行nativeInit函數在c++層初始化這些信息

創建 splash view

接下來,還有一個地方值得一說,在創建FlutterView之後,調用了一個createLaunchView方法

    private View createLaunchView() {
        if (!showSplashScreenUntilFirstFrame()) {
            return null;
        }
        final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
        if (launchScreenDrawable == null) {
            return null;
        }
        final View view = new View(activity);
        view.setLayoutParams(matchParent);
        view.setBackground(launchScreenDrawable);
        return view;
    }

這個方法,其實就是在Flutter啓動後添加一個splash view。大家知道,在Flutter應用啓動之後,會有一小段白屏的時間,之所以會白屏,就是這個方法導致的。目前的解決辦法,就是手動添加一個設計好的閃屏頁,平滑的過度一下。那麼如何修改默認的白屏頁,設置我們自己的splash view呢?

    private Drawable getLaunchScreenDrawableFromActivityTheme() {
        TypedValue typedValue = new TypedValue();
        if (!activity.getTheme().resolveAttribute(
            android.R.attr.windowBackground,
            typedValue,
            true)) {
            return null;
        }
        if (typedValue.resourceId == 0) {
            return null;
        }
        try {
            return activity.getResources().getDrawable(typedValue.resourceId);
        } catch (NotFoundException e) {
            Log.e(TAG, "Referenced launch screen windowBackground resource does not exist");
            return null;
        }
    }

可以看到,這個Drawable其實是從主題的windowBackground中取的,我們打開app工程中的styles.xml

    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
        <!-- Show a splash screen on the activity. Automatically removed when Flutter draws its first frame -->
        <item name="android:windowBackground">@drawable/launch_background</item>
    </style>

看到,windowBackground其實是指定爲launch_background,在drawable文件夾下找到它

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@android:color/white" />

    <!-- You can insert your own image assets here -->
    <!-- <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item> -->
</layer-list>

這裏默認背景確實被設置成了白色,並且還友好的給出了一個設置圖片的示例,將註釋的代碼打開,就可以設置自己的splash view

Application啓動

我們知道安卓app啓動時,首先調用的是ApplicationonCreate,現在來看一看Flutter應用的Application做了些什麼
flutter\shell\platform\android\io\flutter\app\FlutterApplication.java

public class FlutterApplication extends Application {
    @Override
    @CallSuper
    public void onCreate() {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }

    private Activity mCurrentActivity = null;
    public Activity getCurrentActivity() {
        return mCurrentActivity;
    }
    public void setCurrentActivity(Activity mCurrentActivity) {
        this.mCurrentActivity = mCurrentActivity;
    }
}

整個FlutterApplication比較簡單,主要調用了FlutterMain.startInitialization(this)開啓初始化

    public static void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
          throw new IllegalStateException("startInitialization must be called on the main thread");
        }
        // Do not run startInitialization more than once.
        if (sSettings != null) {
          return;
        }

        sSettings = settings;

        long initStartTimestampMillis = SystemClock.uptimeMillis();
        initConfig(applicationContext);
        initAot(applicationContext);
        initResources(applicationContext);

        System.loadLibrary("flutter");
        long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
        nativeRecordStartTimestamp(initTimeMillis);
    }

該方法註釋表明,它是用來初始化底層的Flutter 引擎的,主要做了幾件事,

  • 初始化配置
  • 獲取是否預編譯模式
  • 初始化並提取資源文件
  • 加載libflutter.so

Java層與Flutter引擎關聯

FlutterApplication中加載了引擎的so,我們不禁要問,那Flutter引擎又是在哪創建的,怎麼與原生Java代碼關聯起來的呢?

FlutterView構造方法中創建了FlutterNativeView,看到FlutterNativeView的構造方法

    public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) {
        mContext = context;
        mPluginRegistry = new FlutterPluginRegistry(this, context);
        mFlutterJNI = new FlutterJNI();
        mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
        this.dartExecutor = new DartExecutor(mFlutterJNI);
        mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
        attach(this, isBackgroundView);
        assertAttached();
    }

在這裏主要創建了FlutterJNI對象,並調用了一個關鍵方法attach,一路跟蹤該方法調用

    private void attach(FlutterNativeView view, boolean isBackgroundView) {
        mFlutterJNI.attachToNative(isBackgroundView);
        dartExecutor.onAttachedToJNI();
    }

flutter\shell\platform\android\io\flutter\embedding\engine\FlutterJNI.java

  public void attachToNative(boolean isBackgroundView) {
    ensureRunningOnMainThread();
    ensureNotAttachedToNative();
    nativePlatformViewId = nativeAttach(this, isBackgroundView);
  }
  
  private native long nativeAttach(FlutterJNI flutterJNI, boolean isBackgroundView);

最後發現調用了一個native方法,並將FlutterJNI實例對象自身傳入了C++層。這裏,我們怎麼才能找到native方法所對應的C++源碼呢?

我這裏就介紹一種最簡單的傻瓜式方法,不需要太多技巧,大家可以下載一個FileLocatorPro工具,它是Windows平臺上的文本搜索神器,建議最好安裝一個。
在這裏插入圖片描述
爲了搜索更快,我們不僅僅將下載的Flutter engine源碼根路徑設置進去,還要縮寫一點範圍,我這裏將源碼根目錄下的flutter\shell\platform\android設置爲搜索路徑,另外還可以在文件名稱一欄填入*.cc,表示僅搜索以.cc作爲後綴的文件,這裏.cc是C++源文件後綴名。最後我們秒搜到了匹配的內容

flutter\shell\platform\android\platform_view_android_jni.cc

static const JNINativeMethod flutter_jni_methods[] = {
      // Start of methods from FlutterNativeView
      {
          .name = "nativeAttach",
          .signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J",
          .fnPtr = reinterpret_cast<void*>(&AttachJNI),
      }

這是一個結構體數組,可以看到nativeAttach對應的函數指針是AttachJNI,我們繼續在當前文件中找到AttachJNI函數,它就是Java層nativeAttach方法的具體實現

// Called By Java
static jlong AttachJNI(JNIEnv* env,
                       jclass clazz,
                       jobject flutterJNI,
                       jboolean is_background_view) {
  fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
  auto shell_holder = std::make_unique<AndroidShellHolder>(
      FlutterMain::Get().GetSettings(), java_object, is_background_view);
  if (shell_holder->IsValid()) {
    return reinterpret_cast<jlong>(shell_holder.release());
  } else {
    return 0;
  }
}

這裏的C++代碼主要乾了一件事,就是通過make_unique來創建了一個AndroidShellHolder對象,因此我們需要找到AndroidShellHolder類的構造方法

flutter/shell/platform/android/android_shell_holder.cc

AndroidShellHolder::AndroidShellHolder(
    flutter::Settings settings,
    fml::jni::JavaObjectWeakGlobalRef java_object,
    bool is_background_view)
    : settings_(std::move(settings)), java_object_(java_object) {
  static size_t shell_count = 1;
  auto thread_label = std::to_string(shell_count++);

  FML_CHECK(pthread_key_create(&thread_destruct_key_, ThreadDestructCallback) ==
            0);

  if (is_background_view) {
    thread_host_ = {thread_label, ThreadHost::Type::UI};
  } else {
    thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU |
                                      ThreadHost::Type::IO};
  }

  // ...省略...

  // The current thread will be used as the platform thread. Ensure that the
  // message loop is initialized.
  fml::MessageLoop::EnsureInitializedForCurrentThread();
  fml::RefPtr<fml::TaskRunner> gpu_runner;
  fml::RefPtr<fml::TaskRunner> ui_runner;
  fml::RefPtr<fml::TaskRunner> io_runner;
  fml::RefPtr<fml::TaskRunner> platform_runner =
      fml::MessageLoop::GetCurrent().GetTaskRunner();
  if (is_background_view) {
    auto single_task_runner = thread_host_.ui_thread->GetTaskRunner();
    gpu_runner = single_task_runner;
    ui_runner = single_task_runner;
    io_runner = single_task_runner;
  } else {
    gpu_runner = thread_host_.gpu_thread->GetTaskRunner();
    ui_runner = thread_host_.ui_thread->GetTaskRunner();
    io_runner = thread_host_.io_thread->GetTaskRunner();
  }
  flutter::TaskRunners task_runners(thread_label,     // label
                                    platform_runner,  // platform
                                    gpu_runner,       // gpu
                                    ui_runner,        // ui
                                    io_runner         // io
  );

  shell_ =
      Shell::Create(task_runners,             // task runners
                    settings_,                // settings
                    on_create_platform_view,  // platform view create callback
                    on_create_rasterizer      // rasterizer create callback
      );

  platform_view_ = weak_platform_view;
  FML_DCHECK(platform_view_);

  is_valid_ = shell_ != nullptr;

  // ...省略...
}

這個方法裏面代碼比較多,省略部分代碼,我們看到最重要的幾個地方,首先這裏有四個線程,除了當前線程作爲platform線程,還創建了三個新的線程

  • gpu線程
  • ui線程
  • io線程

此處當配圖,來自閒魚技術博客
在這裏插入圖片描述

四個線程分別持有一個TaskRunner對象,後續會通過這些TaskRunner來將一些操作放到對應的線程中去執行。

  • Platform Task Runner的線程可以理解爲是主線程,它不僅僅處理與Engine交互,它還處理來自平臺的消息。

  • UI Task Runner被用於執行Dart root isolate代碼,簡單說也就是Dart語言的主線程。我們所編寫的Dart代碼基本就是在這個線程運行。因此該線程繁忙會導致UI卡頓,如果有繁重的計算任務,如加密、解壓縮等,應當在Dart中另起一個isolate來執行代碼。需要注意,另啓的isolate是不能與Flutter引擎交互的,表現在開發中,也就是不能在新創建的isolate中調用插件,例如在新的isolate操作SQlite數據庫,如有這種需求,可以使用FlutterIsolate 庫

  • GPU Task Runner 用於執行設備GPU的相關調用。主要是配置管理每一幀繪製所需要的GPU資源。如果該線程卡頓,直接導致程序卡頓。通常來說用平臺代碼和Dart代碼都無法直接操作到該線程

  • IO Runner的主要是從圖片存儲(如磁盤)中讀取壓縮的圖片格式,將圖片數據進行處理,爲GPU Runner的渲染做好準備。也就是處理與磁盤IO相關的事務

最後,看到以下函數的調用,此處即是創建引擎的地方

shell_ =
      Shell::Create(task_runners,             // task runners
                    settings_,                // settings
                    on_create_platform_view,  // platform view create callback
                    on_create_rasterizer      // rasterizer create callback
      );

大家有興趣可以找相關源碼,繼續跟蹤一下源碼,看看引擎是如何創建的

\flutter\shell\common\shell.cc

運行Dart代碼

通過反編譯Flutter生成的apk,我們很清楚的知道,Dart代碼編譯後的產物,包括相關的資源文件,實際上都是打包到安卓的assets目錄下的,那麼Dart代碼是在哪加載執行的呢?

FlutterActivityDelegateonCreate方法的最後有如下代碼

 String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
 if (appBundlePath != null) {
     runBundle(appBundlePath);
 }

這裏首先獲取資源文件提取後在應用所屬目錄下的路徑,然後調用runBundle進行加載執行。runBundle方法最終調用FlutterJNI中的一個native方法

  private native void nativeRunBundleAndSnapshotFromLibrary(
      long nativePlatformViewId,
      @NonNull String[] prioritizedBundlePaths,
      @Nullable String entrypointFunctionName,
      @Nullable String pathToEntrypointFunction,
      @NonNull AssetManager manager
  );

調用過程如下,點擊大圖
在這裏插入圖片描述

總結

  • FlutterActivityDelegateonCreate流程圖
    在這裏插入圖片描述
    對象調用關係圖,點擊大圖
    在這裏插入圖片描述

FlutterMain.ensureInitializationComplete方法調用
在這裏插入圖片描述

FlutterNativeView

歡迎關注我的公衆號:編程之路從0到1

編程之路從0到1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章