flutter在android端啟動流程和熱修復

wzqlldy 發表於 2021-03-10

因為flutter和原聲Android啟動流程不一樣所以如果使用android熱修復框架比如tinker會有一些區別,所以先要弄明白flutter在android端的啟動流程,然後我們才可以進行鍼對性的處理和熱修復。

flutter在android端啟動流程

首先flutter程式的入口和android一樣都是application類,這個在android程式碼中的manifast檔案中可以找到,預設都是flutterappllication,因為flutter在android端執行其實也就是和原生用了不一樣的引擎,大概的架構思路都是一樣的,預設的flutterappllication也比較簡單,其中在oncreat中有比較重要的一行程式碼 FlutterInjector.instance().flutterLoader().startInitialization(this); 這行程式碼是呼叫了flutterLoader的startInitialization(this)方法來完成對flutter的初始化,這是flutterLoader的官方介紹 Finds Flutter resources in an application APK and also loads Flutter's native library. 說明這個類就是為了找到apk中的各種資原始檔,而且會載入flutter的本地庫,相當於對配置了flutter在android本地的環境和資源。 接下來我們繼續看startInitialization(this)方法,這裡擷取一些重要的程式碼並進行說明

/*首先還是來看方法的解讀,這個方法主要是載入flutter引擎來支援本地的JNI響應,
找到和解讀我們寫在app這個APK裡面的dart程式碼,而且這個方法多次呼叫是沒有效果的
關於這點,因為flutterLoader本身被設計成一個單例,其裡面有一個settings變數,會在這個方法中
被賦值,下次來如果已經初始化來就不會執行方法
*/
   * Starts initialization of the native system.
   *
   * <p>This loads the Flutter engine's native library to enable subsequent JNI calls. This also
   * starts locating and unpacking Dart resources packaged in the app's APK.
   *
   * <p>Calling this method multiple times has no effect.

//首先會判斷程式是否執行在主執行緒,這裡用android 的Looper是否是MainLooper()來判斷。
   if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }

//其餘的主要邏輯都是執行了一個callable的回撥,在後臺執行緒中執行一些初始化的邏輯
 Callable<InitResult> initTask =new Callable<InitResult>() 
//這裡選取了一些主要邏輯
     if (FlutterInjector.instance().shouldLoadNative()) {
//載入flutter核心庫
              System.loadLibrary("flutter");
            }

// Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread.
//大概就是對引擎的預先載入,防止引擎啟動的時候太耗費時間
Executors.newSingleThreadExecutor()
                .execute(
                    new Runnable() {
                      @Override
                      public void run() {
                        FlutterJNI.nativePrefetchDefaultFontManager();
                      }
                    });
複製程式碼

這些就是startInitialization(this)主要做的事情,此時application初始化完畢,接下來就會執行我們指定的mainactivity了,flutter預設的mainactivity是繼承自FlutterActivity,而開啟flutteractivity我們不難發現,它其實本身也沒有很多邏輯程式碼,主要主要是執行了FlutterActivityAndFragmentDelegate這個代理類的方法,在每一個activity的生命週期都是呼叫了代理的方法,比如他的oncreate如下。

 @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    switchLaunchThemeForNormalTheme();

    super.onCreate(savedInstanceState);

    lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
//獲取代理類
    delegate = new FlutterActivityAndFragmentDelegate(this);
    delegate.onAttach(this);
//呼叫代理類的方法執行oncreate邏輯
    delegate.onActivityCreated(savedInstanceState);
//activity基礎操作
    configureWindowForTransparency();
    setContentView(createFlutterView());
    configureStatusBarForFullscreenFlutterExperience();
  }
複製程式碼

所以我們要跟進其代理類FlutterActivityAndFragmentDelegate

//這裡我們主要看和初始化有關的程式碼,在這個代理類中開始如果發現flutterengine沒有初始化,
//就會呼叫setupFlutterengine方法,其中主要初始化邏輯為
 flutterEngine =
        new FlutterEngine(
            host.getContext(),
            host.getFlutterShellArgs().toArray(),
            /*automaticallyRegisterPlugins=*/ false,
            /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
    isFlutterEngineFromHost = false;
複製程式碼

接著我們跟進flutterengine的初始化,可以看到,初始化的核心最後都歸到了這裡,在引擎初始化的時候進行,抽取出初始化執行的核心程式碼

 if (flutterLoader == null) {
      flutterLoader = FlutterInjector.instance().flutterLoader();
    }
    flutterLoader.startInitialization(context.getApplicationContext());
    flutterLoader.ensureInitializationComplete(context, dartVmArgs);
複製程式碼

可以發現在engine其實是呼叫我們之前說到的 flutterLoader的start和ensure方法來初始化和載入APK資源,我們接著看之前沒有看的ensureInitializationComplete方法

//開頭會判斷另一個引數initialized,保證不會重複呼叫
 if (initialized) {
      return;
    }
//這個shellArgs 是整個方法的核心,這裡的初始化主要就是新建一個shell殼,
//然後將之前初始化和獲取的各種flutter資源新增到殼中,最後將殼中的資料
//傳入FlutterJNI進行載入,我們看一些重要的資源
 List<String> shellArgs = new ArrayList<>();
//這裡的DEFAULT_LIBRARY,就是"libflutter.so"
    shellArgs.add(
          "--icu-native-lib-path="
              + flutterApplicationInfo.nativeLibraryDir
              + File.separator
              + DEFAULT_LIBRARY);
//這裡的flutterApplicationInfo.aotSharedLibraryName就是我們寫的dart程式碼
//打包成的libapp.so
shellArgs.add(
            "--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
//還有一些其他的資源和檔案,當全部加入殼中後,就會初始化 FlutterJNI,並將initialized 置為true
//
 if (FlutterInjector.instance().shouldLoadNative()) {
        FlutterJNI.nativeInit(
            applicationContext,
            shellArgs.toArray(new String[0]),
            kernelPath,
            result.appStoragePath,
            result.engineCachesPath,
            initTimeMillis);
      }

      initialized = true;
複製程式碼

程式執行到這裡,基本已經完成了大部分資源和程式碼的初始化工作,接下來就是FlutterView等搭建flutter渲染的環境以及形成平臺通道,將平臺設定資訊等傳回給dart,以及設定android一些生命週期的監聽和操作等,這裡就不細細研究了。

flutter如何實現熱修復

這裡就可以開始對我們android端的flutter專案進行改動,讓熱修復可以在flutter上面實現。 因為我們平時開發主要程式碼都是寫在libapp.so中,所以我們要找到和這個so資源有關的方法,已經思考如何進行改動。 跟隨初始化的流程,我們知道我們的程式碼是在ensureInitializationComplete中進行載入,同時其地址是儲存在flutterApplicationInfo中,所以我們首先就會想到對flutterApplicationInfo進行更改來實現載入修復後的so庫,但是因為這個類是開始就配置好的,所以我們要利用反射獲取,然後動態的更改其中儲存資源地址,將libapp地址改成修復後的libapp的地址,而且要保證這個更改是在ensureInitializationComplete呼叫之前完成。 解決了資源地址問題我們就要考慮如何將自己的反射方法加入到ensure之前,通過查詢原始碼,可以在flutterActivity(不是代理)中找到一個方法

  @Nullable
  @Override
  public FlutterEngine provideFlutterEngine(@NonNull Context context) {
    // No-op. Hook for subclasses.
    return null;
  }
複製程式碼

這個方法其實繼承自FlutterActivityAndFragmentDelegate.Host,因為flutterActivity本身也繼承自這個Host類,Host類本身是一個介面類,其中實現了一下可以對代理類進行管理的方法

    /**
     * Returns the {@link FlutterEngine} that should be rendered to a {@link FlutterView}.
     *
     * <p>If {@code null} is returned, a new {@link FlutterEngine} will be created automatically.
     */
    @Nullable
    FlutterEngine provideFlutterEngine(@NonNull Context context);
複製程式碼

看到host裡面這個方法的說明我們可以知道這個方法可以為代理類提供flutterengine,如果不實現它的話FlutterEngine會被自動建立。所以我們的目的就可以通過這個方法來自己建立FlutterEngine,利用startInitialization和ensureInitialization方法不會被多次呼叫,在建立FlutterEngine之前呼叫這兩個方法,在中間加入我們的反射方法來修改libapp的路徑就可以了。

 public FlutterEngine provideFlutterEngine(@NonNull Context context){
FlutterInjector.instance().flutterLoader().startInitialization(getApplication());
//這裡加上我們自己的反射方法
hotfixPath();
     FlutterInjector.instance().flutterLoader().ensureInitializationComplete(getApplication(), getFlutterShellArgs().toArray());
//返回flutterEngine,跳過其自動建立。
 return new FlutterEngine(context, getFlutterShellArgs().toArray(),
                false, shouldRestoreAndSaveState());
}
複製程式碼
總結

整個流程就到這裡了,具體的實現要取決於專案的細節和實現,也可以用一些其他的android熱修復框架,只要理解flutter載入的原理,對其中的細節進行調整,也可以實現熱修復。