Flutter 動(dòng)態(tài)化初探

簡(jiǎn)介

Flutter : Flutter allows you to build beautiful native apps on iOS and Android from a single codebase. 具有跨平臺(tái), 高性能的優(yōu)勢(shì).

Flutter 產(chǎn)物

Flutter 產(chǎn)物

產(chǎn)物流向:

產(chǎn)物流向

通過分析我們可以發(fā)現(xiàn)
不變的產(chǎn)物有 flutter.jar ,libfluter.so,icudtl.dat, vm_snapshot_data , vm_snapshot_instr, 不跟業(yè)務(wù)代碼相關(guān), 只跟 flutter engine 的版本有關(guān).

變化的產(chǎn)物: flutter_assets , isolate_snapshot_data , isolate_snapshot_instr 主要是業(yè)務(wù)的代碼和資源.
LICENSE 沒用, 可以刪除.
總結(jié):
我們通過將不變的產(chǎn)物集成到APK 中.將變化組成一個(gè)資源包,通過配置下發(fā)下來.

動(dòng)態(tài)化改造

Flutter SDK 改造

修改 Flutter.createView() 方法.

  @NonNull
    public static FlutterView createView(@NonNull final Context activity, @NonNull final Lifecycle lifecycle,
                                         final String initialRoute, String bundlePath) {
        Context context = activity;
        if (!TextUtils.isEmpty(bundlePath)) {
            final AssetManager bundleAsset = AssetManagerUtils.newAssetManager(bundlePath); //  Flutter  組件資源包位置
            context = new ContextWrapper(activity) {
                @Override
                public Resources getResources() {
                    return new Resources(bundleAsset,
                            super.getResources().getDisplayMetrics(), super.getResources().getConfiguration()) {
                    };
                }
            };
        }

        FlutterMain.startInitialization(context); // 使用自定義的 context 
        FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), null);
        final FlutterNativeView nativeView = new FlutterNativeView(context);// 使用自定義的 context 
      ...
        return flutterView;
    }

這里: 通過反射 AssetManger . 同時(shí)將 bundlePath 添加進(jìn)去.
bundlePath 的作用:

  • 用于查找 flutter_assets, bundleAsset 會(huì)在 FlutterNativeView.nativeRunBundleAndSnapshotFromLibrary()傳遞給 Flutter 用于查找 flutter_assets 資源.
  • 用于 copy isolate_snapshot_data , isolate_snapshot_instr 資源.

Copy 資源是由 ResourceExtractor 完成的.
我們需要修改 ResourceExtractor.ExtractTask

   private void extractResources() {
            final File dataDir = new File(PathUtils.getDataDirectory(mContext));
            final AssetManager bundleManger = mContext.getResources().getAssets();
            //獲取 Apk 自身的 AssetsManger
            final AssetManager shareManager = mContext.getApplicationContext().getResources().getAssets();
            final String fluterBundleVersion = getFlutterBundleVersion(bundleManger);
            if (fluterBundleVersion != null) {
                Log.i(TAG, "delete cache fluterBundleVersion " + fluterBundleVersion);
                deleteFiles();
            }
            AssetManager manager;
            byte[] buffer = null;
            for (String asset : mResources) {
                try {
                    final File output = new File(dataDir, asset);

                    if (output.exists()) {
                        continue;
                    }
                    if (output.getParentFile() != null) {
                        output.getParentFile().mkdirs();
                    }

                    manager = bundleManger;
                    if (asset.startsWith("flutter_shared")
                            || asset.equals("vm_snapshot_data")
                            || asset.equals("vm_snapshot_instr")) {
                        manager = shareManager;
                    }
                    ...
                    }
                } catch (FileNotFoundException fnfe) {

                    continue;
                } catch (IOException ioe) {
                    Log.w(TAG, "Exception unpacking resources: " + ioe.getMessage());
                    deleteFiles();
                    return;
                }
            }

            if (fluterBundleVersion != null) {
                try {
                    new File(dataDir, fluterBundleVersion).createNewFile();
                } catch (IOException e) {
                    Log.w(TAG, "Failed to write resource timestamp");
                }
            }
        }

        private String getFlutterBundleVersion(AssetManager bundleAssets) {
            final File dataDir = new File(PathUtils.getDataDirectory(mContext));
            String expectedTimestamp = null;
            try {
                InputStream in = bundleAssets.open("flutter_bundle_version");
                expectedTimestamp = VERSION_PREFIX + IoUtils.toString(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (expectedTimestamp == null) {
                return null;
            }
            final String[] existingTimestamps = getVersionStamps(dataDir);

            if (existingTimestamps == null) {
                return null;
            }

            if (existingTimestamps.length != 1
                    || !expectedTimestamp.equals(existingTimestamps[0])) {
                return expectedTimestamp;
            }

            return null;
        }

主要的修改:

  • 共享的資源由 APK 的 AssetsManger 中獲取. 其他由 bundleManger 中獲取
  • 修改 ResourceExtractor 更新資源邏輯, 由本來的 res_timestamp- {versionCode} -{packageInfo.lastUpdateTime} 不同就更新改為 flutter_bundle_version 不同就更新.

打包插件修改:

組件資源插件

image.png

需要做的事情:

  • 需要組合成一個(gè)APK, 因?yàn)樾枰?AssetManger 識(shí)別.
  • 不需要包含代碼,
  • android 資源盡可能少, 但是需要 AndroidManifest.xml
  • flutter_bundle_version. 每次編譯都應(yīng)不同, 用于 ResourceExtractor 更新版本邏輯.
  • flutter_engine_version 獲取 flutter sdk 版本, 相同版本才能升級(jí), 防止因?yàn)榘姹緦?shí)現(xiàn)不同導(dǎo)致的 bug.
  • flutter_bridge_version 是一個(gè) md5. 是對(duì)所有自定義的 MethodChannel.MethodCallHandler 進(jìn)行 ABI 格式化. 即 APK 具有一樣的擴(kuò)展能力.相同版本才能升級(jí).
  • 保留 isolate_snapshot_data, isolate_snapshot_instr 文件.

lib 插件

Flutter 模塊 一般情況我們是以 aar 的形式依賴. 需要修改上傳到 maven 的AAR 如下.


image.png
  • 保留 vm_snapshot_data, vm_snapshot_instr 文件.
  • 保留 flutter_bundle_version ,flutter_engine_version,flutter_bridge_version 作用如上.

flutter.gradle 修改

  • copy {flutter sdk}\flutter\packages\flutter_tools\gradle\flutter.gradle
  • 修改 release 依賴為自定義的 flutter jar.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市网杆,隨后出現(xiàn)的幾起案子漫萄,更是在濱河造成了極大的恐慌失都,老刑警劉巖莽使,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)荤崇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮针,“玉大人术荤,你說我怎么就攤上這事∶颗瘢” “怎么了瓣戚?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)焦读。 經(jīng)常有香客問我子库,道長(zhǎng),這世上最難降的妖魔是什么矗晃? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任仑嗅,我火速辦了婚禮,結(jié)果婚禮上喧兄,老公的妹妹穿的比我還像新娘无畔。我一直安慰自己,他們只是感情好吠冤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布浑彰。 她就那樣靜靜地躺著,像睡著了一般拯辙。 火紅的嫁衣襯著肌膚如雪郭变。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天涯保,我揣著相機(jī)與錄音诉濒,去河邊找鬼。 笑死夕春,一個(gè)胖子當(dāng)著我的面吹牛未荒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播及志,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼片排,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了速侈?” 一聲冷哼從身側(cè)響起率寡,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倚搬,沒想到半個(gè)月后冶共,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年捅僵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了家卖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庙楚,死狀恐怖篡九,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情醋奠,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布伊佃,位于F島的核電站窜司,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏航揉。R本人自食惡果不足惜塞祈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帅涂。 院中可真熱鬧议薪,春花似錦、人聲如沸媳友。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醇锚。三九已至哼御,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焊唬,已是汗流浹背恋昼。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赶促,地道東北人液肌。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸥滨,于是被迫代替她去往敵國和親嗦哆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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