Flutter動態(tài)化-Android(一)

Flutter動態(tài)化-Android(一)

上篇我們講了flutter engine編譯環(huán)境搭建颠黎,這篇我們正式來看下如何修改flutter engine源碼,實現(xiàn)動態(tài)化滞项。

本篇文章將分為兩部分來講狭归,本篇講述有哪些核心代碼需要修改的,如何編譯engine源碼文判,以及在flutter應用中該如何使用过椎;下一篇會講解如何打包編譯成aar,以方便混合開發(fā)時使用戏仓。

一潭流、flutter engine核心代碼修改

思路:分析libapp.so和flutter_assets的加載過程,在加載之前修改源碼替換需要成自己的路徑

1.1柜去、FlutterLoaderensureInitializationComplete

從前面的文章我們可以知道灰嫉,這個方法主要是構建shellArgs列表,在C層代碼中會調(diào)用此配置初始化參數(shù)

先看下面這段代碼:

if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
        String snapshotAssetPath =
                PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir;
    kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
    shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
    shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
    shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData);
}

在debug和JIT模式時會配置flutter_assets嗓奢、vm_snapshot_data讼撒、isolate_snapshot_data的位置,我們知道在libapp.so文件的本質(zhì)是相當于vm_snapshot_data股耽、isolate_snapshot_data的打包合集根盒,從這里我們可以找到想到將libapp.soflutter_assets文件放到debug或者JIT模式時的文件路徑下,也就是PathUtils.getDataDirectory(applicationContext)目錄下物蝙。

我們再來看如何修改這個代碼對應的else部分的代碼:

// 查看/user/0/package/app_flutter目錄是否存在libapp.so文件炎滞,如果存在就傳遞這個新的路徑,否則就還使用默認路徑(也就是lib/arm/或者lib/arm64/)的libapp.so文件
File appFile = new File(PathUtils.getDataDirectory(applicationContext) + File.separator + aotSharedLibraryName);
String aotSharedLibraryPath = applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName;
if(appFile.exists()){
  aotSharedLibraryPath = appFile.getPath();
}
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryPath);

如果不需要動態(tài)替換flutter_assets文件诬乞,其實上面就修改就足夠動態(tài)替換libapp.so了册赛。還有一種不用修改engine源碼的方法就是在繼承FlutterActivity時重寫getFlutterShellArgs方法钠导,把AOT_SHARED_LIBRARY_NAME傳遞成自定義的路徑,親測有效森瘪,小伙伴們自行嘗試牡属,有問題可以在留言區(qū)交流。

附上AOT_SHARED_LIBRARY_NAME在C層使用的代碼

// 代碼位置shell-->common-->switches.cc
if (aot_shared_library_name.size() > 0) {
  // 循環(huán)會導致最后一個shellArgs中AOT_SHARED_LIBRARY_NAME生效扼睬,不用擔心設置了多個AOT_SHARED_LIBRARY_NAME
  for (std::string_view name : aot_shared_library_name) {
    settings.application_library_path.emplace_back(name);
  }
}

1.2逮栅、FlutterJNInativeAttach

這是一個調(diào)用libflutter.so的jni方法,通過這個方法窗宇,我們可以傳遞一個路徑到C層措伐,在C層的初始化AndroidShellHolder之前將自定義的路徑配置進去

修改后的nativeAttach方法體以及相關代碼,如下

private native long nativeAttach(@NonNull FlutterJNI flutterJNI, String dynamicPath, boolean isBackgroundView);

 public void attachToNative(String dynamicPath, boolean isBackgroundView) {
    ensureRunningOnMainThread();
    ensureNotAttachedToNative();
        // 因為當前類中無法獲得Contenxt军俊,所以需要FlutterNativeView和FlutterEngine調(diào)用attachToNative方法時傳入路徑   
    nativePlatformViewId = nativeAttach(this, dynamicPath, isBackgroundView);
 }

1.3废士、platform_view_android_jni.cc修改

// 1. jni映射方法新增參數(shù)類型
bool RegisterApi(JNIEnv* env) {
  static const JNINativeMethod flutter_jni_methods[] = {
      // Start of methods from FlutterJNI
      {
          .name = "nativeAttach",
            // 新增一個String參數(shù)類型
          .signature = "(Lio/flutter/embedding/engine/FlutterJNI;Ljava/lang/String;Z)J",
          .fnPtr = reinterpret_cast<void*>(&AttachJNI),
      },
    。蝇完。官硝。

// 2. AttachJNI方法修改
static jlong AttachJNI(JNIEnv* env,
                       jclass clazz,
                       jobject flutterJNI,
                       jstring dynamicPath,
                       jboolean is_background_view) {
  fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI);
  const auto dynamic_path = fml::jni::JavaStringToString(env, dynamicPath);
  // 獲取配置
  Settings settings = FlutterMain::Get().GetSettings();
  if(dynamic_path.size() > 0) {
      settings.application_library_path.clear();
        // 再AndroidShellHolder初始化前設置新路徑
      settings.application_library_path.emplace_back(dynamic_path + "/libapp.so");
      settings.assets_path = dynamic_path + "/flutter_assets";
  }

  FML_LOG(INFO) << "settings.assets_path:" << settings.assets_path;
    
  // 將修改后的settings傳遞進去
  auto shell_holder = std::make_unique<AndroidShellHolder>(
      settings, java_object, is_background_view);
  if (shell_holder->IsValid()) {
    return reinterpret_cast<jlong>(shell_holder.release());
  } else {
    return 0;
  }
}

1.4、FlutterNativeView中相關修改的代碼

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

1.5短蜕、FlutterEngine中相關修改的代碼

// 1. 構造函數(shù)中
attachToJni(context);

// 2. attachToJni方法修改
private void attachToJni(Context context) {
        Log.v(TAG, "Attaching to JNI.");
    // TODO(mattcarroll): update native call to not take in "isBackgroundView"
    flutterJNI.attachToNative(PathUtils.getDynamicPath(context), false);

    if (!isAttachedToJni()) {
      throw new RuntimeException("FlutterEngine failed to attach to its native Object reference.");
    }
}

1.6氢架、PathUtils新增getDynamicPath方法

// 獲取動態(tài)化資源文件路徑
public static String getDynamicPath(Context applicationContext){
    String packagePath = getDataDirectory(applicationContext);
    String aotLibFile = packagePath + File.separator + FlutterLoader.DEFAULT_AOT_SHARED_LIBRARY_NAME;
    String flutterAssetsPath = packagePath + File.separator + FlutterLoader.DEFAULT_FLUTTER_ASSETS_DIR;
    File aotFile = new File(aotLibFile);
    File flutterAssetsFile = new File(flutterAssetsPath);
    if (!aotFile.exists() && !flutterAssetsFile.exists()) {
      packagePath = "";
    }
    return packagePath;
}

到此,動態(tài)化所需要的方法基本都修改完了朋魔,具體代碼請看https://github.com/panmin/engine/tree/feature_my_engine岖研,歡迎starwatch,代碼會不定期優(yōu)化更新警检。

二孙援、編譯本地engine

修改完engine的代碼,這一小節(jié)我們就來看看如何將修改后的engine編譯成flutter.jarlibflutter.so

2.1扇雕、編譯相關基礎知識

  • CPU架構

    編譯結果包括arm拓售、arm64x86這幾種架構镶奉,arm對應Android的armeabi-v7a础淤,arm64對應Android的arm64-v8a,x86還是x86一般是模擬器上用的哨苛。

  • 是否優(yōu)化

    未優(yōu)化的engine包是可以添加打印出C層的代碼的鸽凶,engine的C++里用FML_LOG(INFO)打印log;優(yōu)化后的包體積也更小建峭。

  • 運行模式

    根據(jù)flutter的模式是分為debug玻侥、profilerelease這三種模式的亿蒸。

2.2凑兰、常用的編譯參數(shù)

  • --android-cpu: CPU架構掌桩,對應armarm64票摇、x86,如:gn --android-cpu arm
  • --runtime-mode: 運行模式砚蓬,對應debug矢门、profilerelease灰蛙,如:gn --runtime-mode debug
  • --unoptiimized: 是否優(yōu)化祟剔,帶上這個參數(shù)就說明是不優(yōu)化的情況

2.3、編譯開始

# 1摩梧、定位到`engine/src`目錄
cd engine/src
# 2物延、編譯Android對應平臺已優(yōu)化的release代碼,這里大家根據(jù)自己的實際使用情況仅父,合理的使用2.2中提到的編譯參數(shù)
./flutter/tools/gn --android --runtime-mode release --android-cpu arm
# 通過2中的命令會在src目錄下生成一個out/android_release的目錄
# 3叛薯、編譯2中生成的代碼成為flutter.jar和libflutter.so,這一步就最耗時的笙纤,有快有慢耗溜,看電腦性能了
ninja -C out/android_release
# 如果2中使用的CPU架構是arm64時,3中的這一步就要用android_release_arm64文件夾了
# 4省容、編譯Android打包時需要的代碼
./flutter/tools/gn --runtime-mode release --android-cpu arm
# 5抖拴、同樣編譯一下
ninja -C out/host_android
# 如果4中使用的是arm64,這里就需要用host_android_arm64文件夾了

通過上的編譯腥椒,我們可以看到out/android_release文件夾中已經(jīng)生成了flutter.jar和里面已經(jīng)包含了libflutter.so

三阿宅、使用本地engine

這一小節(jié)只講解純flutter項目時如何使用;至于混合開發(fā)的項目中如何使用笼蛛,因為牽扯到一些gradle腳本的修改洒放,我會單獨抽出一篇文章來講。

3.1滨砍、使用本地engine打包apk

# 打包arm平臺的apk
flutter build apk --target-plarform android-arm --split-per-abi --local-engine-src engine/src --local-engine=android-release
# 打包arm64平臺的apk
flutter build apk --target-plarform android-arm --split-per-abi --local-engine-src engine/src --local-engine=android-release_arm64

3.2拉馋、修改代碼后如何查看新的libapp.soflutter_assets文件

對于已經(jīng)安裝完使用本地engine打包的apk的手機來說,想動態(tài)更新新的代碼惨好,需要找到修改后代碼打包生成的libapp.soflutter_assets文件煌茴,這個文件怎么生成和找到的呢?

  1. 修改dart代碼
  2. 使用3.1中的命令打包apk
  3. 在跟lib同級別的目錄build/app/intermediates/flutter/release下找到對應CPU架構的app.so文件日川,將其改名成libapp.so蔓腐,然后在app啟動時復制到PathUtils.getDataDirectory(applicationContext)對應的目錄下,也就是user/0/package/app_flutter目錄下龄句;把build/app/intermediates/flutter/release目錄下的flutter_assets也復制到這個目錄下回论。
  4. 待下次重啟app時即可生效

四散罕、總結

本篇文章講解了flutter engine代碼實現(xiàn)動態(tài)化的代碼修改,以及編譯和使用本地的engine傀蓉,下一篇我會詳細講解在混合開發(fā)時使用本地engine欧漱,以及如何修改bulid aar時的腳本,打包成aar供業(yè)務方使用葬燎,也是伸手黨們的福利误甚,歡迎大家關注和點贊。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谱净,一起剝皮案震驚了整個濱河市窑邦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壕探,老刑警劉巖冈钦,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異李请,居然都是意外死亡瞧筛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門导盅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驾窟,“玉大人,你說我怎么就攤上這事认轨∩鹇纾” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵嘁字,是天一觀的道長恩急。 經(jīng)常有香客問我,道長纪蜒,這世上最難降的妖魔是什么衷恭? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮纯续,結果婚禮上随珠,老公的妹妹穿的比我還像新娘。我一直安慰自己猬错,他們只是感情好窗看,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著倦炒,像睡著了一般显沈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天拉讯,我揣著相機與錄音涤浇,去河邊找鬼。 笑死魔慷,一個胖子當著我的面吹牛只锭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播院尔,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蜻展,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了召边?” 一聲冷哼從身側響起铺呵,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裹驰,失蹤者是張志新(化名)和其女友劉穎隧熙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幻林,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡贞盯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沪饺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躏敢。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖整葡,靈堂內(nèi)的尸體忽然破棺而出件余,到底是詐尸還是另有隱情,我是刑警寧澤遭居,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布啼器,位于F島的核電站,受9級特大地震影響俱萍,放射性物質(zhì)發(fā)生泄漏端壳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一枪蘑、第九天 我趴在偏房一處隱蔽的房頂上張望损谦。 院中可真熱鬧,春花似錦岳颇、人聲如沸照捡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽麻敌。三九已至,卻和暖如春掂摔,著一層夾襖步出監(jiān)牢的瞬間术羔,已是汗流浹背赢赊。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留级历,地道東北人释移。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像寥殖,于是被迫代替她去往敵國和親玩讳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容