Flutter热修复研究

一.开发环境:

[√] Flutter (Channel stable, v1.12.13+hotfix.5, on Microsoft Windows [Version 10.0.17134.1246], locale zh-CN)
    • Flutter version 1.12.13+hotfix.5 at D:\flutter_workspace\Environment\flutter
    • Framework revision 27321ebbad (3 months ago), 2019-12-10 18:15:01 -0800
    • Engine revision 2994f7e1e6
    • Dart version 2.7.0

二.混合开发模式的选择

a.Flutter的产物以aar形式提供给Android使用,实现解耦
b.在1.12.13+hotfix.5版本测试,仅需要build aar即可,不需要再使用fat-aar技术解决以来依赖库未打包问题

三.flutter engine编译相关:

官方说明:https://github.com/flutter/flutter/wiki/Compiling-the-engine

1.准备ubuntu环境
2.配置git

sudo apt-get update 
sudo apt-get install git
git config --global user.name "WoYang"
git config --global user.email "[email protected]"
ssh-keygen -t rsa -b 4096 -C "[email protected]"
cat /root/.ssh/id_rsa.pub

把公钥添加到github

3.配置depot_tools

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
gedit ~/.bashrc
export PATH=$PATH:/home/yang/depot_tools
source ~/.bashrc

4.代理

sudo apt update
sudo apt install shadowsocks-libev
# Edit the configuration file
sudo vim /etc/shadowsocks-libev/config.json
{
    "server":"sg25.nurobiz.com",
    "server_port":63268,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"8XdQ0H",
    "timeout":300,
    "method":"chacha20-ietf",
    "fast_open": false
}
# Start the service
sudo /etc/init.d/shadowsocks-libev start    # for sysvinit, or
sudo systemctl start shadowsocks-libev      # for systemd

或者使用QT版本,参考https://github.com/Shadowsocks-Wiki/shadowsocks/blob/master/zh_CN/shadowsocks/linux-setup-guide.md

查看是否代理成功:

curl ipinfo.io

5.gclient
创建.gclinet文件

solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "[email protected]:WoYang/engine.git",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

因为被墙的原因:
curl: (7) Failed to connect to chrome-infra-packages.appspot.com port 443: Connection refused
在https://site.ip138.com/chrome-infra-packages.appspot.com/找一个可以访问的ip,写到hosts文件中

216.58.200.244  chrome-infra-packages.appspot.com

这个被墙的问题搞了好几天。

gclient sync
cd src/flutter
git remote add upstream [email protected]:flutter/engine.git
git reset --hard 2994f7e1e682039464cb25e31a78b86a3c59b695

cd ..
gclient sync --with_branch_heads --with_tags --verbose

编译依赖安装:

sudo ./build/install-build-deps-android.sh

编译Android arm的release优化版:

flutter/tools/gn --android --runtime-mode=release
ninja -C out/android_release -j 8

四.bugly研究,梳理流程

1.全量升级测试
2.热修复版本升级测试
3.so升级测试
4.资源升级测试 等待进行

五.flutter工程使用定制过的engine库

作为module使用时,项目依赖:



其中flutter_embedding_release.jar是engine的SDK,而armeabi_v7a_release/arm64_v8a_relase/x86_x64_release分别是不同cpu架构的libflutter.so

各个版本后面的2994f7e1e682039464cb25e31a78b86a3c59b695就是对应module依赖的engine的version。因此当flutter module的flutter sdk升级或者更新,对应的engine编译时也需要更新。
1.编译各个版本的flutter engine:

flutter/tools/gn --android --runtime-mode=release
ninja -C out/android_release -j 8
flutter/tools/gn --android --android-cpu=x64 --runtime-mode=release
ninja -C out/android_release_x64 -j 8
flutter/tools/gn --android --android-cpu=arm64 --runtime-mode=release
ninja -C out/android_release_arm64  -j 8

在工程中添加maven,这里使用了本地maven:

repositories {
    ...
    maven {
        url 'C://Users/Allen/.m2/repository'
    }
}

避免出现以下打包问题:

More than one file was found with OS independent path 'lib/armeabi-v7a/libflutter.so'

需要在 app - build.gralde 的 android { } 如下配置:

    packagingOptions{
        pickFirst 'lib/armeabi-v7a/libflutter.so'
    }

flutter build为aar时,不会打入libflutter.so

打包的libapp.so和资源,就是我们的业务模块。
flutter module aar文件结构:

        aar
        │── jni
        │   ├── armeabi-v7a
        │   │   └──libapp.so
        │   ├── arm64-v8a
        │   │   └──libapp.so
        │   └── x86_64
        │       └──libapp.so
        │── libs
        │   └──libs.jar
        │── assets
        │   └──flutter_assets
        │       ├── assets
        │       │   └──...
        │       ├── fonts
        │       │   └──...
        │       ├── packages
        │       │   └──...
        │       ├── AssetManifest.json
        │       ├── FontManifest.json
        │       └── LICENSE
        ├── AndroidManifest.xml
        ├── classes.jar
        ├── R.txt
        ├── flutter_assets
        │       ├── assets
        │       │   └──...
        │       ├── fonts
        │       │   └──...
        │       └── x86_64
        │           └──...
        ├── arm64-v8a
        ├── armeabi-v7a
        └── x86_64

libflutter.so打包在engine库中:

使用Tinker热修复的问题:
1.使用bugly下发热修复补丁有延时,大概5分钟内;
2.bugly多次触发补丁升级问题
3.so的热修复需要重启进程

Tinker组件的集成:

    implementation "com.android.support:multidex:1.0.3" // 多dex配置
    //指定tinker依赖版本(注:应用升级1.3.5版本起,不再内置tinker)
    implementation 'com.tencent.tinker:tinker-android-lib:1.9.14.3'
    implementation 'com.tencent.bugly:crashreport_upgrade:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
    implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.2.0

要支持热修复,应用就需要使用定制后的flutter engine。

注意:签名不能和系统签名一样,不然Selinux dlopen时无法加载data目录下的so。
需要测试:如果必须使用系统签名,可以使用方式二,把tinker目录下的so拷贝到/data/app/package_name/lib/目录下

二.实现Flutter的热修复其实很简单,就是在Dart初始化时替换lib的Path,做好参数透传,在FlutterLoader::ensureInitializationComplete中处理参数:

   public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
        if (initialized) {
            return;
        }
        if (Looper.myLooper() != Looper.getMainLooper()) {
          throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
        }
        if (settings == null) {
          throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
        }
        try {
            if (resourceExtractor != null) {
                resourceExtractor.waitForCompletion();
            }
            
            String hotfix_libapp_path = null;
            String hotfix_libflutter_path = null;
            List shellArgs = new ArrayList<>();
            if (args != null) {
                Collections.addAll(shellArgs, args);
                
                for(int i = 0;i < args.length;i++){
                    String arg = args[i];
                    if(!TextUtils.isEmpty(arg)){                    
                        if(arg.startsWith(FlutterShellArgs.ARG_HOTFIX_LIBAPP_PATH)){
                            hotfix_libapp_path = arg.replaceAll(FlutterShellArgs.ARG_HOTFIX_LIBAPP_PATH + "=","");
                            Log.e(TAG, "Flutter initialization hotfix-libapp-path is:" + hotfix_libapp_path);
                        }else if(arg.startsWith(FlutterShellArgs.ARG_HOTFIX_LIBFLUTTER_PATH)){
                            hotfix_libflutter_path = arg.replaceAll(FlutterShellArgs.ARG_HOTFIX_LIBFLUTTER_PATH + "=","");
                            Log.e(TAG, "Flutter initialization hotfix_libflutter_path is:" + hotfix_libflutter_path);
                        }
                        if(!TextUtils.isEmpty(hotfix_libapp_path) && !TextUtils.isEmpty(hotfix_libflutter_path)){
                            break;
                        }
                    }
                }
            }
                        
            shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
            ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
            if(!TextUtils.isEmpty(hotfix_libflutter_path)){
                shellArgs.add("--icu-native-lib-path=" + hotfix_libflutter_path);
            }else{
                shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
            }

            String kernelPath = null;
            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);
            } else {
                if(!TextUtils.isEmpty(hotfix_libapp_path)){
                    shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + hotfix_libapp_path);
                }else{
                    shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName);
                    // Most devices can load the AOT shared library based on the library name
                    // with no directory path.  Provide a fully qualified path to the library
                    // as a workaround for devices where that fails.
                    shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName);
                }
            }

            shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
            if (settings.getLogTag() != null) {
                shellArgs.add("--log-tag=" + settings.getLogTag());
            }
            
            StringBuffer initArgs = new StringBuffer();
            initArgs.append("Flutter init args[");
            for(String item:shellArgs){
                initArgs.append(item);
                initArgs.append(",");
            }
            initArgs.append("]");
            Log.e(TAG, initArgs.toString());
            
            String appStoragePath = PathUtils.getFilesDir(applicationContext);
            String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
            
            Log.e(TAG, "kernelPath:" + kernelPath);
            Log.e(TAG, "appStoragePath:" + appStoragePath);
            Log.e(TAG, "engineCachesPath:" + engineCachesPath);

            FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
                kernelPath, appStoragePath, engineCachesPath);
            
            initialized = true;
        } catch (Exception e) {
            Log.e(TAG, "Flutter initialization failed.", e);
            throw new RuntimeException(e);
        }
    }

以上过程其实有个优化点:在编译期间通过Transform把需要的处理的逻辑借助ASM来完成,避免engine版本升级需要重新编译这些重复工作,极大的提高开发效率。

你可能感兴趣的:(Flutter热修复研究)