一.开发环境:
[√] 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版本升级需要重新编译这些重复工作,极大的提高开发效率。