日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

flutter不支持热更新_Flutter 在安卓上可以实现热更新了

發布時間:2025/3/12 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 flutter不支持热更新_Flutter 在安卓上可以实现热更新了 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文由 句號君 授權投稿
原文鏈接:https://blog.csdn.net/qizewei123/article/details/102963340

Flutter 官方在 GitHub 上聲明是暫時不支持熱更新的,但是在 Flutter 的源碼里,是有一部分預埋的熱更新相關的代碼,并且通過一些我們自己的手段,在Android端是能夠實現動態更新的功能的。

Flutter 產物的探究

不論是創建完全的 Flutter項目,還是 Native以 Moudle的方式集成 Flutter,亦或是 Native以 aar方式集成 ?Flutter,最終 ?Flutter在 Andorid端的 App 都是以 Native項目+ Flutter 的UI產物存在的。所以在這里拆開一個 Flutter在 release模式下編譯后生成 aar包來做分析:

我們關注重點在 assets,jni,libs 這 3 個目錄中,其他的文件都是 Nactive層殼工程的產物。

jni :該目錄下存在文件 libflutter.so,該文件為 Flutter Engine (引擎) 層的 C++實現,提供skia(繪制引擎),Dart,Text(紋理繪制)等支持。

libs:該目錄下存在文件為 flutter.jar,該文件為 Flutter embedding (嵌入) 層的 Java實現,該層提供給 Flutter 許多Native層平臺系統功能的支持,比如創建線程。

assets:該目錄下分為兩部分:

  • flutter_assets 目錄:該目錄下存放Flutter 我們應用層的資源,包括images,font等

  • isolate_snapshot_data,isolate_snapshot_instr,vm_snapshot_data,vm_snapshot_instr 文件:這 4 個文件分別對應 isolate、VM 的數據段和指令段文件,這就是我們自己的 Flutter 代碼的產物了。

  • Flutter 代碼的熱更新

    代碼探究

    在我們的 Native 項目中,會在 FlutterMainActivity 中,通過調用 Flutter 這個類來創建 View:

    flutterView?=?Flutter.createView(this,?getLifecycle(),?route);
    layoutParams?=?new?FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
    FrameLayout.LayoutParams.MATCH_PARENT);
    addContentView(flutterView,?layoutParams);

    查看 Flutter 類代碼,發現 Flutter 類主要做了幾件事:

  • 使用 FlutterNative 加載 View,設置路由,使用 lifecycle 綁定生命周期

  • 使用 FlutterMain 初始化,重點關注這里。

  • public?static?FlutterView?createView(@NonNull?final?Activity?activity,?@NonNull?Lifecycle?lifecycle,?String?initialRoute)?{
    FlutterMain.startInitialization(activity.getApplicationContext());
    FlutterMain.ensureInitializationComplete(activity.getApplicationContext(),?(String[])null);
    FlutterNativeView?nativeView?=?new?FlutterNativeView(activity);

    所以,真正初始化的相關代碼是在 FlutterMian 中:

    public?static?void?startInitialization(Context?applicationContext,?FlutterMain.Settings?settings)?{
    ????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);
    ????????initAot(applicationContext);
    ????????initResources(applicationContext);
    ????????System.loadLibrary("flutter");
    ????????long?initTimeMillis?=?SystemClock.uptimeMillis()?-?initStartTimestampMillis;
    ????????nativeRecordStartTimestamp(initTimeMillis);
    ????}
    }

    在 startInitialization 中,主要執行了三個初始化方法 initConfig(applicationContext),initAot(applicationContext),initResources(applicationContext),最后記錄了執行時間。

    在 initConfig 中:

    private?static?void?initConfig(Context?applicationContext)?{
    ????try?{
    ????????Bundle?metadata?=?applicationContext.getPackageManager().getApplicationInfo(applicationContext.getPackageName(),?128).metaData;
    ????????if?(metadata?!=?null)?{
    ????????????sAotSharedLibraryPath?=?metadata.getString(PUBLIC_AOT_AOT_SHARED_LIBRARY_PATH,?"app.so");
    ????????????sAotVmSnapshotData?=?metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_DATA_KEY,?"vm_snapshot_data");
    ????????????sAotVmSnapshotInstr?=?metadata.getString(PUBLIC_AOT_VM_SNAPSHOT_INSTR_KEY,?"vm_snapshot_instr");
    ????????????sAotIsolateSnapshotData?=?metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_DATA_KEY,?"isolate_snapshot_data");
    ????????????sAotIsolateSnapshotInstr?=?metadata.getString(PUBLIC_AOT_ISOLATE_SNAPSHOT_INSTR_KEY,?"isolate_snapshot_instr");
    ????????????sFlx?=?metadata.getString(PUBLIC_FLX_KEY,?"app.flx");
    ????????????sFlutterAssetsDir?=?metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY,?"flutter_assets");
    ?????????}
    ????}?catch?(NameNotFoundException?var2)?{
    ????????throw?new?RuntimeException(var2);
    ????}
    }

    在 initResources 中:

    sResourceExtractor?=?new?ResourceExtractor(applicationContext);
    sResourceExtractor.addResource(fromFlutterAssets(sFlx)).addResource(fromFlutterAssets(sAotVmSnapshotData)).addResource(fromFlutterAssets(sAotVmSnapshotInstr)).addResource(fromFlutterAssets(sAotIsolateSnapshotData)).addResource(fromFlutterAssets(sAotIsolateSnapshotInstr)).addResource(fromFlutterAssets("kernel_blob.bin"));
    if?(sIsPrecompiledAsSharedLibrary)?{
    ????sResourceExtractor.addResource(sAotSharedLibraryPath);
    }?else?{
    ????sResourceExtractor.addResource(sAotVmSnapshotData).addResource(sAotVmSnapshotInstr).addResource(sAotIsolateSnapshotData).addResource(sAotIsolateSnapshotInstr);
    }?

    sResourceExtractor.start();

    在 ResourceExtractor 類中,通過名字就能知道這個類是做資源提取的。把 add 的 Flutter 相關文件從 assets 目錄中取出來,該類中 ExtractTask 的 doInBackground 方法中:

    File dataDir = new File(PathUtils.getDataDirectory(ResourceExtractor.this.mContext))

    這句話指定了資源提取的目的地,即 data/data/包名/app_flutter,如下:

    如圖,可以看到該目錄是的訪問權限是可讀可寫,所以理論上,我們只要把自己的 Flutter 產物下載后,從內存 copy 到這里,便能夠實現代碼的動態更新。

    代碼實現

    public?class?FlutterUtils?{

    ????private?static?String?TAG?=?"FlutterUtils.class";
    ????private?static?String?flutterZipName?=?"flutter-code.zip";
    ????private?static?String?fileSuffix?=?".zip";
    ????private?static?String?zipPath?=?Environment.getExternalStorageDirectory().getPath()?+?"/k12/"?+?flutterZipName;
    ????private?static?String?targetDirPath?=?zipPath.replace(fileSuffix,?"");
    ????private?static?String?targetDirDataPath?=?zipPath.replace(fileSuffix,?"/data");

    ????/**
    ?* Flutter 代碼熱更新第一步:?解壓 Flutter 的壓縮文件
    ?*/
    ????public?static?void?unZipFlutterFile()?{
    ????????Log.i(TAG,?"unZipFile:?Start");
    ????????try?{
    ????????????unZipFile(zipPath,?targetDirPath);
    ????????????Log.i(TAG,?"unZipFile:?Finish");
    ????????}?catch?(Exception?e)?{
    ????????????e.printStackTrace();
    ????????}
    ????}

    ????/**
    ?* Flutter 代碼熱更新第二步:?將 Flutter 的相關文件移動到 AppData 的相關目錄,APP啟動時調用
    ?*
    ?*?@param?mContext?獲取?AppData?目錄需要
    ?*/
    ????public?static?void?copyDataToFlutterAssets(Context?mContext)?{
    ????????String?appDataDirPath?=?PathUtils.getDataDirectory(mContext.getApplicationContext())?+?File.separator;
    ????????Log.d(TAG,?"copyDataToFlutterAssets-filesDirPath:"?+?targetDirDataPath);
    ????????Log.d(TAG,?"copyDataToFlutterAssets-appDataDirPath:"?+?appDataDirPath);
    ????????File?appDataDirFile?=?new?File(appDataDirPath);
    ????????File?filesDirFile?=?new?File(targetDirDataPath);
    ????????File[]?files?=?filesDirFile.listFiles();
    ????????for?(File?srcFile?:?files)?{
    ????????????if?(srcFile.getPath().contains("isolate_snapshot_data")
    ????????????????||?srcFile.getPath().contains("isolate_snapshot_instr")
    ????????????????||?srcFile.getPath().contains("vm_snapshot_data")
    ????????????????||?srcFile.getPath().contains("vm_snapshot_instr"))?{
    ????????????????File?targetFile?=?new?File(appDataDirFile?+?"/"?+?srcFile.getName());
    ????????????????FileUtil.copyFileByFileChannels(srcFile,?targetFile);
    ????????????????Log.i(TAG,?"copyDataToFlutterAssets-copyFile:"?+?srcFile.getPath());
    ????????????}
    ????????}
    ????????Log.i(TAG,?"copyDataToFlutterAssets:?Finish");
    ????}

    ????/**
    ?*?解壓縮文件到指定目錄
    ?*
    ?*?@param?zipFileString?壓縮文件路徑
    ?*?@param?outPathString?目標路徑
    ?*?@throws?Exception
    ?*/
    ????private?static?void?unZipFile(String?zipFileString,?String?outPathString)?{
    ????????try?{
    ????????????ZipInputStream?inZip?=?new?ZipInputStream(new?FileInputStream(zipFileString));
    ????????????ZipEntry?zipEntry;
    ????????????String?szName?=?"";
    ????????????while?((zipEntry?=?inZip.getNextEntry())?!=?null)?{
    ????????????????szName?=?zipEntry.getName();
    ????????????????if?(zipEntry.isDirectory())?{
    ????????????????????szName?=?szName.substring(0,?szName.length()?-?1);
    ????????????????????File?folder?=?new?File(outPathString?+?File.separator?+?szName);
    ????????????????????folder.mkdirs();
    ????????????????}?else?{
    ????????????????????File?file?=?new?File(outPathString?+?File.separator?+?szName);
    ????????????????????if?(!file.exists())?{
    ????????????????????????Log.d(TAG,?"Create?the?file:"?+?outPathString?+?File.separator?+?szName);
    ????????????????????????file.getParentFile().mkdirs();
    ????????????????????????file.createNewFile();
    ????????????????????}
    ????????????????????FileOutputStream?out?=?new?FileOutputStream(file);
    ????????????????????int?len;
    ????????????????????byte[]?buffer?=?new?byte[1024];
    ????????????????????while?((len?=?inZip.read(buffer))?!=?-1)?{
    ????????????????????????out.write(buffer,?0,?len);
    ????????????????????????out.flush();
    ????????????????????}
    ????????????????????out.close();
    ????????????????}
    ????????????}
    ????????????inZip.close();
    ????????}?catch?(Exception?e)?{
    ????????????Log.i(TAG,e.getMessage());
    ????????????e.printStackTrace();
    ????????}
    ????}

    ????/**
    ?*?使用FileChannels復制文件。
    ?*
    ?*?@param?source?原路徑
    ?*?@param?dest?目標路徑
    ?*/
    ????public?static?void?copyFileByFileChannels(File?source,?File?dest)?{
    ????????FileChannel?inputChannel?=?null;
    ????????FileChannel?outputChannel?=?null;
    ????????try?{
    ????????????inputChannel?=?new?FileInputStream(source).getChannel();
    ????????????outputChannel?=?new?FileOutputStream(dest).getChannel();
    ????????????outputChannel.transferFrom(inputChannel,?0,?inputChannel.size());
    ????????????refreshMedia(BaseApplication.getBaseApplication(),?dest);
    ????????}?catch?(Exception?e)?{
    ????????????e.printStackTrace();
    ????????}?finally?{
    ????????????try?{
    ????????????????inputChannel.close();
    ????????????????outputChannel.close();
    ????????????}?catch?(IOException?e)?{
    ????????????????e.printStackTrace();
    ????????????}
    ????????}
    ????}

    ????/**
    ?*?更新媒體庫
    ?*
    ?*?@param?cxt
    ?*?@param?files
    ?*/
    ????public?static?void?refreshMedia(Context?cxt,?File...?files)?{
    ????????for?(File?file?:?files)?{
    ????????????String?filePath?=?file.getAbsolutePath();
    ????????????refreshMedia(cxt,?filePath);
    ????????}
    ????}

    ????public?static?void?refreshMedia(Context?cxt,?String...?filePaths)?{
    ????????MediaScannerConnection.scanFile(cxt.getApplicationContext(),
    ????????????????????????????????????????filePaths,?null,
    ????????????????????????????????????????null);
    ????}
    }

    Flutter 資源的熱更新

    我們的App安裝到手機上后,是很難再修改 Assets 目錄下的資源,所以關于資源的替換,目前的方案是使用 Flutter 的 API :Image.file() 來從存儲卡中讀取圖片。

    通常我們的 Flutter 項目中應當存有關于 App 的圖片,盡量保證在熱更新的時候使用已經存在的圖片。

    其次,我們可以使用 Image.network() 來加載網絡資源的圖片,如果還不能滿足需求,兜底的方案就是使用 Image.file(),將資源圖片放到Zip目錄下一起下發,并在Flutter代碼中使用 Image.file() 來加載。

    • 通過 Native 層方法拿到圖片文件夾的內存地址 dataDir

    • 判斷圖片是否存在,存在則加載,不存在則加載已經存在的圖片占位

    new File(dataDir + 'hotupdate_test.png').existsSync()? Image.file(new File(dataDir + 'hotupdate_test.png')): Image.asset("images/net_error.png"),

    總結

    在 Flutter 代碼產物替換中,因為替換的 4 個文件皆為直接加載到內存中的引擎代碼,所以這部分優化空間有限。但在資源的熱更新中,資源是從Assets取得,所以這里應該有更優的方案。

    Flutter 的熱更新意味著可以實在App的一個入口里,像 H5 一樣無窮的嵌入頁面,但又有和原生媲美的流暢體驗。

    未來 Flutter 熱更新技術如果成熟,應用開發可能只需要 Android端和 IOS端實現本地業務功能模塊的封裝,業務和UI的代碼都放在 Flutter 中,便能夠真正的實現移動兩端一份業務代碼,并且賦予產品在不影響用戶體驗的情況下,擁有動態部署APP內容的能力。

    推薦閱讀
    如何寫出讓同事好維護的代碼?
    一線大廠的程序員職級對標
    真正的強者,敢于在寒冬里裸辭

    編程·思維·職場
    歡迎掃碼關注

    總結

    以上是生活随笔為你收集整理的flutter不支持热更新_Flutter 在安卓上可以实现热更新了的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 美国黄色一级大片 | 久久免| 五月婷综合 | 国产精品久久久久久99 | 国产欧美二区 | 丰满人妻一区二区三区四区53 | 欧美日韩在线免费观看 | sm乳奴虐乳调教bdsm | 色汉综合 | 国产另类xxxxhd高清 | 色伊人av | 国产精品xxxx | 9.1成人看片| 国产中文字幕精品 | 久久久久久久久久久久久av | 国产制服91一区二区三区制服 | 久久久久久国产精品三级玉女聊斋 | 6680新视觉电影免费观看 | av黄在线观看 | 精品无码一级毛片免费 | 久久久午夜影院 | 日韩精品一区二区三区网站 | 婷婷九九 | 在线资源站 | 日韩欧美在线视频免费观看 | 自拍偷拍亚洲欧洲 | 国内自拍第三页 | 日本亚洲欧洲色 | 亚洲精品在线影院 | 蜜桃av一区二区三区 | 日韩一级网站 | 久久精品女人 | 国产伦精品一区二区三区四区 | 国产精品高潮呻吟 | 国产精品第一国产精品 | 神马午夜精品 | 夜色福利视频 | 国产一区亚洲二区 | 色之久久综合 | 色婷婷综合久久久久中文字幕 | 亚洲成人精品一区 | 欧美视频直播网站 | 国产精品乱子伦 | 最近2018年手机中文字幕版 | 你懂的在线观看网址 | 直接看毛片 | 国产精品亚洲一区 | 国产精品视频a | 亚洲国产影视 | mm1313亚洲国产精品美女 | 我要看18毛片 | 精品人妻一区二区三区日产乱码卜 | 夜夜骚av| 黑人干亚洲女人 | 三级黄色网 | 亚洲av永久无码精品三区在线 | 国产精品久久777777毛茸茸 | 长腿校花无力呻吟娇喘的视频 | 中文字幕精品国产 | 乱码av | 国产51视频 | 拔擦8x成人一区二区三区 | 欧美 日韩 国产 激情 | 久久日本精品字幕区二区 | 亚洲天堂av一区二区 | 国产超碰在线 | 日本三级黄色大片 | 国产色啪 | 国产a免费视频 | 亚洲人一区 | 97超碰总站 | 国产一级二级三级在线 | 老司机深夜视频 | 日韩亚洲一区二区三区 | 四虎av在线| 韩日免费视频 | 成年人免费网站视频 | 韩国福利一区 | 一本一道精品欧美中文字幕 | 国产欧美一区二区三区精华液好吗 | 成人尹人 | 欧美一级在线免费观看 | 四虎影城库 | 伊人久久青青草 | 国精产品一区一区三区视频 | 成人黄色片视频 | 男生看的污网站 | 在线一区二区三区四区 | 最新国产网址 | 看黄色一级片 | 亚洲不卡电影 | 日日骚av一区二区 | 天天天天天天天天干 | 日韩精品999 | 色就是色欧美色图 | 激情五月综合 | 天天操天天弄 | 亚洲色图国产精品 | 亚洲天堂色图 |