上篇咱們講了flutter engine編譯環境搭建,這篇咱們正式來看下如何修改flutter engine源碼,實現動態化。java
本篇文章將分爲兩部分來說,本篇講述有哪些核心代碼須要修改的,如何編譯engine源碼,以及在flutter應用中該如何使用;下一篇會講解如何打包編譯成aar,以方便混合開發時使用。android
思路:分析libapp.so和flutter_assets的加載過程,在加載以前修改源碼替換須要成本身的路徑c++
FlutterLoader
的ensureInitializationComplete
從前面的文章咱們能夠知道,這個方法主要是構建shellArgs列表,在C層代碼中會調用此配置初始化參數git
先看下面這段代碼:github
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
文件的本質是至關於vm_snapshot_data
、isolate_snapshot_data
的打包合集,從這裏咱們能夠找到想到將libapp.so
和flutter_assets
文件放到debug或者JIT模式時的文件路徑下,也就是PathUtils.getDataDirectory(applicationContext)
目錄下。shell
咱們再來看如何修改這個代碼對應的else
部分的代碼:bash
// 查看/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);
複製代碼
若是不須要動態替換flutter_assets
文件,其實上面就修改就足夠動態替換libapp.so
了。還有一種不用修改engine源碼的方法就是在繼承FlutterActivity
時重寫getFlutterShellArgs
方法,把AOT_SHARED_LIBRARY_NAME
傳遞成自定義的路徑,親測有效,小夥伴們自行嘗試,有問題能夠在留言區交流。架構
附上AOT_SHARED_LIBRARY_NAME
在C層使用的代碼app
// 代碼位置shell-->common-->switches.cc
if (aot_shared_library_name.size() > 0) {
// 循環會致使最後一個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);
}
}
複製代碼
FlutterJNI
的nativeAttach
這是一個調用
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調用attachToNative方法時傳入路徑
nativePlatformViewId = nativeAttach(this, dynamicPath, isBackgroundView);
}
複製代碼
platform_view_android_jni.cc
修改// 1. jni映射方法新增參數類型
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterJNI
{
.name = "nativeAttach",
// 新增一個String參數類型
.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;
}
}
複製代碼
FlutterNativeView
中相關修改的代碼private void attach(FlutterNativeView view, boolean isBackgroundView) {
mFlutterJNI.attachToNative(PathUtils.getDynamicPath(mContext), isBackgroundView);
dartExecutor.onAttachedToJNI();
}
複製代碼
FlutterEngine
中相關修改的代碼// 1. 構造函數中
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.");
}
}
複製代碼
PathUtils
新增getDynamicPath方法// 獲取動態化資源文件路徑
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;
}
複製代碼
到此,動態化所須要的方法基本都修改完了,具體代碼請看github.com/panmin/engi…,歡迎star和watch,代碼會不按期優化更新。
修改完engine的代碼,這一小節咱們就來看看如何將修改後的engine編譯成
flutter.jar
和libflutter.so
CPU架構
編譯結果包括arm
、arm64
、x86
這幾種架構,arm對應Android的armeabi-v7a
,arm64對應Android的arm64-v8a
,x86仍是x86
通常是模擬器上用的。
是否優化
未優化的engine包是能夠添加打印出C層的代碼的,engine的C++裏用FML_LOG(INFO)
打印log;優化後的包體積也更小。
運行模式
根據flutter的模式是分爲debug
、profile
、release
這三種模式的。
--android-cpu
: CPU架構,對應arm
、arm64
、x86
,如:gn --android-cpu arm
--runtime-mode
: 運行模式,對應debug
、profile
、release
,如:gn --runtime-mode debug
--unoptiimized
: 是否優化,帶上這個參數就說明是不優化的狀況# 一、定位到`engine/src`目錄
cd engine/src
# 二、編譯Android對應平臺已優化的release代碼,這裏你們根據本身的實際使用狀況,合理的使用2.2中提到的編譯參數
./flutter/tools/gn --android --runtime-mode release --android-cpu arm
# 經過2中的命令會在src目錄下生成一個out/android_release的目錄
# 三、編譯2中生成的代碼成爲flutter.jar和libflutter.so,這一步就最耗時的,有快有慢,看電腦性能了
ninja -C out/android_release
# 若是2中使用的CPU架構是arm64時,3中的這一步就要用android_release_arm64文件夾了
# 四、編譯Android打包時須要的代碼
./flutter/tools/gn --runtime-mode release --android-cpu arm
# 五、一樣編譯一下
ninja -C out/host_android
# 若是4中使用的是arm64,這裏就須要用host_android_arm64文件夾了
複製代碼
經過上的編譯,咱們能夠看到out/android_release
文件夾中已經生成了flutter.jar
和裏面已經包含了libflutter.so
這一小節只講解純flutter項目時如何使用;至於混合開發的項目中如何使用,由於牽扯到一些gradle腳本的修改,我會單獨抽出一篇文章來說。
# 打包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
複製代碼
libapp.so
和flutter_assets
文件對於已經安裝完使用本地engine打包的apk的手機來講,想動態更新新的代碼,須要找到修改後代碼打包生成的
libapp.so
和flutter_assets
文件,這個文件怎麼生成和找到的呢?
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
也複製到這個目錄下。本篇文章講解了flutter engine代碼實現動態化的代碼修改,以及編譯和使用本地的engine,下一篇我會詳細講解在混合開發時使用本地engine,以及如何修改bulid aar
時的腳本,打包成aar供業務方使用,也是伸手黨們的福利,歡迎你們關注和點贊。