因為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載入的原理,對其中的細節進行調整,也可以實現熱修復。