Flutter Android 端啟動流程淺析

Flutter程式設計開發 發表於 2019-10-19

這篇文章主要是分析一下 Flutter Android 端的啟動流程,主要是上層程式碼的分析,不涉及底層 c/c++ 等的分析。同時,不同 Flutter 版本的 sdk ,程式碼也會有所不同,但是整體流程和原理不會有太大的不同。

1、FlutterApplication

Android 端 app 的啟動,一定會先初始化 Application,再去載入預設的第一個類 MainActivity。Flutter 專案對應的 Android 端應用程式,application 預設指定為 FlutterApplication。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_share">
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="flutter_share"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
複製程式碼

1.1 FlutterMain.startInitialization(this);

FlutterApplication 中 onCreate 核心初始化程式碼只有一行

        FlutterMain.startInitialization(this);
複製程式碼

FlutterMain 這個類在 flutter.jar 包中。不同的平臺對應不同的 flutter.jar 包,這個檔案在 flutter sdk 的路徑位置為: flutterSdkPath\flutter\bin\cache\artifacts\engine 。

Flutter Android 端啟動流程淺析

接下來看 FlutterMain 中的程式碼

    public static void startInitialization(@NonNull Context applicationContext) {
        if (!isRunningInRobolectricTest) {
            startInitialization(applicationContext, new io.flutter.view.FlutterMain.Settings());
        }
    }

    public static void startInitialization(@NonNull Context applicationContext, @NonNull io.flutter.view.FlutterMain.Settings settings) {
        if (!isRunningInRobolectricTest) {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("startInitialization must be called on the main thread");
            } else if (sSettings == null) {
                sSettings = settings;
                long initStartTimestampMillis = SystemClock.uptimeMillis();
                initConfig(applicationContext);
                initResources(applicationContext);
                System.loadLibrary("flutter");
                VsyncWaiter.getInstance((WindowManager)applicationContext.getSystemService("window")).init();
                long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
                FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
            }
        }
    }
複製程式碼

主要做了如下幾件事情:

  • startInitialization 這個初始化函式必須在在主執行緒執行,否則丟擲異常。
  • sSettings 這個變數只會初始化一次。
  • initConfig 初始化配置資訊。
  • initResources 初始化資源。
  • System.loadLibrary("flutter") 載入 flutter 核心庫 libflutter.so。這個庫也在 flutter.jar 中,編譯 flutter 專案中的時候,這個庫會複製到 apk 中。
  • FlutterJNI.nativeRecordStartTimestamp 主要是呼叫底層方法記錄初始化時間。

1.2 initConfig

這個方法如下:

    private static void initConfig(@NonNull Context applicationContext) {
        Bundle metadata = getApplicationInfo(applicationContext).metaData;
        if (metadata != null) {
            sAotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, "libapp.so");
            sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, "flutter_assets");
            sVmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, "vm_snapshot_data");
            sIsolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "isolate_snapshot_data");
        }
    }
複製程式碼

主要是解析 metadata 初始化預設配置,如果沒有設定則使用預設值。

  • sAotSharedLibraryName : libapp.so
  • sFlutterAssetsDir: flutter_assets
  • sVmSnapshotData:vm_snapshot_data
  • sIsolateSnapshotData:isolate_snapshot_data

1.3 initResources

程式碼如下:

    private static void initResources(@NonNull Context applicationContext) {
        (new ResourceCleaner(applicationContext)).start();
        String dataDirPath = PathUtils.getDataDirectory(applicationContext);
        String packageName = applicationContext.getPackageName();
        PackageManager packageManager = applicationContext.getPackageManager();
        AssetManager assetManager = applicationContext.getResources().getAssets();
        sResourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
        sResourceExtractor.addResource(fromFlutterAssets(sVmSnapshotData)).addResource(fromFlutterAssets(sIsolateSnapshotData)).addResource(fromFlutterAssets("kernel_blob.bin"));
        sResourceExtractor.start();
    }

複製程式碼

主要的作用就兩個:

  • 通過 ResourceCleaner 清理快取檔案
  • 通過 sRecourceExtractor 載入指定目錄下的資原始檔,通過這些檔案將進行 flutter engine 和 Darv Vm 的初始化。

上面就是 Application 的初始化過程。接下來看 MainActivity 的執行。

2、MainActivity

Android 端預設啟動的第一個類就是 MainActivity,而建立的 Flutter 的專案中,MainActivity 這個類都是自動生成的,程式碼如下:


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

複製程式碼

可以看到,MainActivity 繼承自 Flutter 自己實現的 FlutterActivity。

2.1 GeneratedPluginRegistrant.registerWith(this)

MainActivity 中的核心程式碼只有一行

GeneratedPluginRegistrant.registerWith(this);
複製程式碼

GeneratedPluginRegistrant 這個類程式碼如下:

/**
 * Generated file. Do not edit.
 */
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    if (alreadyRegisteredWith(registry)) {
      return;
    }
  }

  private static boolean alreadyRegisteredWith(PluginRegistry registry) {
    final String key = GeneratedPluginRegistrant.class.getCanonicalName();
    if (registry.hasPlugin(key)) {
      return true;
    }
    registry.registrarFor(key);
    return false;
  }
}
複製程式碼

可以看到,這個類是自動生成的,並且會 執行 registry.registrarFor(key) 方法。 registry 是一個介面,並且 FlutterActivity 實現了這個接。而 MainActivity 也繼承了 FlutterActivity 。接下來分析 FlutterActivity 。

3、FlutterActivity

public class FlutterActivity extends Activity implements Provider, PluginRegistry, ViewFactory 
複製程式碼

可以看到 FlutterActivity 實現了 Provider、PluginRegistry,ViewFactory 三個介面。

    private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
    private final FlutterActivityEvents eventDelegate;
    private final Provider viewProvider;
    private final PluginRegistry pluginRegistry;

    public FlutterActivity() {
        this.eventDelegate = this.delegate;
        this.viewProvider = this.delegate;
        this.pluginRegistry = this.delegate;
    }
複製程式碼

可以看到,三個引用都指向了同一個物件,也就是 這個 delegate 物件將實現另外三個類宣告的功能。

接著看 FlutterActivity 中的主要方法:

    protected void onStart() {
        super.onStart();
        this.eventDelegate.onStart();
    }

    protected void onResume() {
        super.onResume();
        this.eventDelegate.onResume();
    }
   ...
複製程式碼

可以看到,在 FlutterActivity 中的核心方法中,只是簡單的回撥了 delegate 中對應的方法,也就是 FlutterActivity 這個類只是一個代理,真正實現功能的一定是 delegate 這個物件對應的類,也就是 FlutterActivityDelegate。

4、 FlutterActivityDelegate

public final class FlutterActivityDelegate implements FlutterActivityEvents, Provider, PluginRegistry 
複製程式碼

這個類通過 final 修飾,將不能被繼承,同時確實也實現了三個介面。

4.1 onCreate

    public void onCreate(Bundle savedInstanceState) {
        if (VERSION.SDK_INT >= 21) {
            Window window = this.activity.getWindow();
            window.addFlags(-2147483648);
            window.setStatusBarColor(1073741824);
            window.getDecorView().setSystemUiVisibility(1280);
        }

        String[] args = getArgsFromIntent(this.activity.getIntent());
        FlutterMain.ensureInitializationComplete(this.activity.getApplicationContext(), args);
        this.flutterView = this.viewFactory.createFlutterView(this.activity);
        if (this.flutterView == null) {
            FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView();
            this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView);
            this.flutterView.setLayoutParams(matchParent);
            this.activity.setContentView(this.flutterView);
            this.launchView = this.createLaunchView();
            if (this.launchView != null) {
                this.addLaunchView();
            }
        }

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

        }
    }

複製程式碼

onCreate 是這個類初始化之後執行的第一個函式,主要的作用是:

  • 設定沉浸式狀態列
  • 從 intent 中獲取引數
  • 呼叫 FlutterMain.ensureInitializationComplete 確保資源初始化完成。
  • 建立 FlutterNativeView 和 FlutterView 物件例項。
  • 設定當前 activity 的檢視佈局為 flutterView。
  • runBundle 這裡面將通過一些列方法的呼叫,載入 Dart 程式碼。

4.1 FlutterMain.ensureInitializationComplete

  public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
        if (!isRunningInRobolectricTest) {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
            } else if (sSettings == null) {
                throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
            } else if (!sInitialized) {
                try {
                    if (sResourceExtractor != null) {
                        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 + "libflutter.so");
                    if (args != null) {
                        Collections.addAll(shellArgs, args);
                    }

                    String kernelPath = null;
                    shellArgs.add("--aot-shared-library-name=" + sAotSharedLibraryName);
                    shellArgs.add("--aot-shared-library-name=" + applicationInfo.nativeLibraryDir + File.separator + sAotSharedLibraryName);
                    shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
                    if (sSettings.getLogTag() != null) {
                        shellArgs.add("--log-tag=" + sSettings.getLogTag());
                    }

                    String appStoragePath = PathUtils.getFilesDir(applicationContext);
                    String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
                    FlutterJNI.nativeInit(applicationContext, (String[])shellArgs.toArray(new String[0]), (String)kernelPath, appStoragePath, engineCachesPath);
                    sInitialized = true;
                } catch (Exception var7) {
                    Log.e("FlutterMain", "Flutter initialization failed.", var7);
                    throw new RuntimeException(var7);
                }
            }
        }
    }
複製程式碼

這裡分析的是 release 模式下的程式碼,主要的作用是:

  • 主執行緒執行
  • sSetting 不能為空,否則丟擲異常。
  • 必須保證 sResourceExtractor 資源提取完成。
  • 將所有的配置引數(如 flutter 核心庫的路徑、flutter APP 專案編譯之後生成的 so 庫的路徑)傳遞給 shellArgs,並且這個 List 陣列將會傳遞給底層進行 Dart Vm 的初始等。
  • 通過 FlutterJNI 進行底層初始操作。

4.2 FlutterView 的初始化

public class FlutterView extends SurfaceView implements BinaryMessenger, TextureRegistry {
    ...
    
    public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
        super(context, attrs);
        this.nextTextureId = new AtomicLong(0L);
        this.mIsSoftwareRenderingEnabled = false;
        this.didRenderFirstFrame = false;
        this.onAccessibilityChangeListener = new OnAccessibilityChangeListener() {
            public void onAccessibilityChanged(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
                FlutterView.this.resetWillNotDraw(isAccessibilityEnabled, isTouchExplorationEnabled);
            }
        };
        Activity activity = getActivity(this.getContext());
        if (activity == null) {
            throw new IllegalArgumentException("Bad context");
        } else {
            if (nativeView == null) {
                this.mNativeView = new FlutterNativeView(activity.getApplicationContext());
            } else {
                this.mNativeView = nativeView;
            }

            this.dartExecutor = this.mNativeView.getDartExecutor();
            this.flutterRenderer = new FlutterRenderer(this.mNativeView.getFlutterJNI());
            this.mIsSoftwareRenderingEnabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
            this.mMetrics = new FlutterView.ViewportMetrics();
            this.mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
            this.setFocusable(true);
            this.setFocusableInTouchMode(true);
            this.mNativeView.attachViewAndActivity(this, activity);
            this.mSurfaceCallback = new Callback() {
                public void surfaceCreated(SurfaceHolder holder) {
                    FlutterView.this.assertAttached();
                    FlutterView.this.mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
                }

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

                public void surfaceDestroyed(SurfaceHolder holder) {
                    FlutterView.this.assertAttached();
                    FlutterView.this.mNativeView.getFlutterJNI().onSurfaceDestroyed();
                }
            };
            this.getHolder().addCallback(this.mSurfaceCallback);
            this.mActivityLifecycleListeners = new ArrayList();
            this.mFirstFrameListeners = new ArrayList();
            this.navigationChannel = new NavigationChannel(this.dartExecutor);
            this.keyEventChannel = new KeyEventChannel(this.dartExecutor);
            this.lifecycleChannel = new LifecycleChannel(this.dartExecutor);
            this.localizationChannel = new LocalizationChannel(this.dartExecutor);
            this.platformChannel = new PlatformChannel(this.dartExecutor);
            this.systemChannel = new SystemChannel(this.dartExecutor);
            this.settingsChannel = new SettingsChannel(this.dartExecutor);
            final PlatformPlugin platformPlugin = new PlatformPlugin(activity, this.platformChannel);
            this.addActivityLifecycleListener(new ActivityLifecycleListener() {
                public void onPostResume() {
                    platformPlugin.updateSystemUiOverlays();
                }
            });
            this.mImm = (InputMethodManager)this.getContext().getSystemService("input_method");
            PlatformViewsController platformViewsController = this.mNativeView.getPluginRegistry().getPlatformViewsController();
            this.mTextInputPlugin = new TextInputPlugin(this, this.dartExecutor, platformViewsController);
            this.androidKeyProcessor = new AndroidKeyProcessor(this.keyEventChannel, this.mTextInputPlugin);
            this.androidTouchProcessor = new AndroidTouchProcessor(this.flutterRenderer);
            this.mNativeView.getPluginRegistry().getPlatformViewsController().attachTextInputPlugin(this.mTextInputPlugin);
            this.sendLocalesToDart(this.getResources().getConfiguration());
            this.sendUserPlatformSettingsToDart();
        }
    }
    ...
}

複製程式碼

可以看到,FlutterView 繼承了 SurfaceView ,並且實現了 BinaryMessenger,TextureRegistry 兩個介面。

主要的作用:

  • 建立 FlutterNativeView。
  • 建立 CallBack 例項,當執行 Surface 回撥函式的時候,會通知底層。
  • 建立平臺通道,並將平臺通過設定資訊傳遞給 Dart。

4.3 FlutterNativeView 初始化

public class FlutterNativeView implements BinaryMessenger {
    ...
    public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) {
        this.flutterUiDisplayListener = new FlutterUiDisplayListener() {
            public void onFlutterUiDisplayed() {
                if (FlutterNativeView.this.mFlutterView != null) {
                    FlutterNativeView.this.mFlutterView.onFirstFrame();
                }
            }

            public void onFlutterUiNoLongerDisplayed() {
            }
        };
        this.mContext = context;
        this.mPluginRegistry = new FlutterPluginRegistry(this, context);
        this.mFlutterJNI = new FlutterJNI();
        this.mFlutterJNI.addIsDisplayingFlutterUiListener(this.flutterUiDisplayListener);
        this.dartExecutor = new DartExecutor(this.mFlutterJNI, context.getAssets());
        this.mFlutterJNI.addEngineLifecycleListener(new FlutterNativeView.EngineLifecycleListenerImpl());
        this.attach(this, isBackgroundView);
        this.assertAttached();
    }
    ...
}
複製程式碼
  • 初始化物件FlutterPluginRegistry;
  • 初始化物件FlutterJNI;
  • 初始化物件RenderSurfaceImpl,並賦值給mFlutterJNI的成員變數renderSurface;
  • 初始化物件DartExecutor;
  • 設定引擎生命週期回撥監聽器;
  • 並執行attach方法
    private void attach(FlutterNativeView view, boolean isBackgroundView) {
        this.mFlutterJNI.attachToNative(isBackgroundView);
        this.dartExecutor.onAttachedToJNI();
    }
複製程式碼
  @UiThread
    public void attachToNative(boolean isBackgroundView) {
        this.ensureRunningOnMainThread();
        this.ensureNotAttachedToNative();
        this.nativePlatformViewId = this.nativeAttach(this, isBackgroundView);
    }
複製程式碼

上面兩個函式執行之後,將執行 jni 層方法,進而將執行底層程式碼,進行 Flutter 引擎的初始化和啟動操作。

當 Flutter引擎和 Dart Vm 等初始化完成之後,程式碼將會執行到 FlutterActivityDelegate 中的 runBundle 方法。

4.4 runBundle

    private void runBundle(String appBundlePath) {
        if (!this.flutterView.getFlutterNativeView().isApplicationRunning()) {
            FlutterRunArguments args = new FlutterRunArguments();
            args.bundlePath = appBundlePath;
            args.entrypoint = "main";
            this.flutterView.runFromBundle(args);
        }

    }
複製程式碼

主要是建立 FlutterRunArguments 物件,指定入口函式名稱為 "main",接著執行 FlutterView 中的 runFromBundle 方法。


    public void runFromBundle(FlutterRunArguments args) {
        this.assertAttached();
        this.preRun();
        this.mNativeView.runFromBundle(args);
        this.postRun();
    }
    
        public void runFromBundle(FlutterRunArguments args) {
        if (args.entrypoint == null) {
            throw new AssertionError("An entrypoint must be specified");
        } else {
            this.assertAttached();
            if (this.applicationIsRunning) {
                throw new AssertionError("This Flutter engine instance is already running an application");
            } else {
                this.mFlutterJNI.runBundleAndSnapshotFromLibrary(args.bundlePath, args.entrypoint, args.libraryPath, this.mContext.getResources().getAssets());
                this.applicationIsRunning = true;
            }
        }
    }

複製程式碼

在 runFromBundle 中,主要是進一步呼叫了 jni 層的方法,呼叫的時候,指定了函式名稱,庫路徑,資原始檔等。

  @UiThread
    public void runBundleAndSnapshotFromLibrary(@NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @NonNull AssetManager assetManager) {
        this.ensureRunningOnMainThread();
        this.ensureAttachedToNative();
        this.nativeRunBundleAndSnapshotFromLibrary(this.nativePlatformViewId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, assetManager);
    }
複製程式碼

接著將呼叫底層方法:

    private native void nativeRunBundleAndSnapshotFromLibrary(long var1, @NonNull String var3, @Nullable String var4, @Nullable String var5, @NonNull AssetManager var6);
複製程式碼

底層方法經過一系列方法的呼叫,將會執行到 Dart 中的 main 函式,也就是將會執行到我們寫的 Dart 程式碼。

整個過程如下圖所示。

Flutter Android 端啟動流程淺析

歡迎關注「Flutter 程式設計開發」微信公眾號 。

Flutter Android 端啟動流程淺析