一、Flutter代码的启动起点
咱们在大都的事务场景下,运用的都是FlutterActivity
、FlutterFragment
。在在背后,咱们知道有着FlutterEnigine、DartExecutor等等多个部件在支撑它们的作业。咱们所要探求的,便是,它们是怎么启动的,Dart代码是从何而来的,以完结动态替换libapp.so。
以官方的计数器Demo为例,默许的Activity宿主,是完结了FlutterActivity的子类,关于一个Activity,咱们最应该关心的便是它的onCreate办法:
- FlutterActivity# onCreate
protected void onCreate(@Nullable Bundle savedInstanceState) {
switchLaunchThemeForNormalTheme();
super.onCreate(savedInstanceState);
delegate = new FlutterActivityAndFragmentDelegate(this);
delegate.onAttach(this);
delegate.onRestoreInstanceState(savedInstanceState);
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
configureWindowForTransparency();
setContentView(createFlutterView());
configureStatusBarForFullscreenFlutterExperience();
}
其实进程很简单,FlutterActivity在这里做了一些主题的设置,由于毕竟FlutterActivity也是一个惯例的Activity,它就有必要依照Android的Activity的一些标准来进行设置。
第三行代码开端,就创建了一个咱们所说的**FlutterActivityAndFragmentDelegate
**目标,FlutterActivity将绝大大都的Flutter初始化相关逻辑委托给了它,而自身则专心于设置主题、窗口、StatusBar等等。
咱们对delegate.onAttach(
this
);
这一行代码的盯梢,最终能走到如下的一个创建流程:
FlutterActivity->
FlutterActivityAndFragmentDelegate->
onAttach()->
setupFlutterEngine->
1.测验去Cache中获取Engine
2.测验从Host中获取Engine
3.都没有的话创建一个新的Engine->
Engine #Constructor->
1. 会对Assets、DartExecutor、各种Channel、FlutterJNI做处理
2. 还会对FlutterLoader做处理->
startInitialization办法做初始化
-> 1. 有必要在主线程初始化Flutter
-> 2. 先检查settings变量;
-> 3. 获取全局的ApplicationContext避免内存走漏
-> 4. VsyncWaiter目标的初始化
-> 5. 最后会生成一个initTask交给线程池去执行
1.1 initTask目标
initTask是一个Callable目标,和Runnable类似的,咱们能够将它理解成一个使命,也便是一段代码,他最终会被交给线程池去执行:
initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
initTask的代码如下
// Use a background thread for initialization tasks that require disk access.
Callable<InitResult> initTask =
new Callable<InitResult>() {
@Override
public InitResult call() {
ResourceExtractor resourceExtractor = initResources(appContext);
flutterJNI.loadLibrary();
// Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread.
Executors.newSingleThreadExecutor()
.execute(
new Runnable () {
@Override
public void run () {
flutterJNI.prefetchDefaultFontManager();
}
}
);
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
}
return new InitResult(
PathUtils.getFilesDir(appContext),
PathUtils.getCacheDirectory(appContext),
PathUtils.getDataDirectory(appContext)
);
}
};
咱们能够抓一下其间的关键字:
- ResourceExtractor
- FlutterJNI.loadLibrary
- FlutterJNI.prefetchDefaultFontManager
- PathUtils
不难发现,主要是在做一些资源的预取。
ResourceExtractor主要是针对在DEBUG或者是JIT形式下,针对安装包内资源的提取逻辑。
在DEBUG或者JIT形式下,需求提取Assets目录下的资源文件到存储中,Assets本质上仍是Zip压缩包的一部分,没有自己的物理途径,所以需求提取,并返回真实在的物理途径。在DEBUG和JIT形式下,FlutterSDK和事务代码将被构建成Kernel格局的二进制文件,Engine将通过文件内存映射的办法进行加载。
详见:「三、libflutter.so和libapp.so」
1.2 ResourceExtractor
libflutter.so和libapp.so
在DEBUG | JIT形式下,咱们是没有libapp.so的,而在release形式下,是有libapp.so文件的,咱们分别解包两个不同的Apk文件,能够很清楚地看到这一点:
咱们知道,libflutter.so是寄存flutter的一些基础类库的so文件,而libapp.so则是寄存咱们事务代码的so文件,那假如在DEBUG|JIT形式下,没有libapp.so,那么咱们的事务代码存储在哪里呢?
此刻,咱们就要看看ResourceExtractor的initResources办法,终究干了些什么:
/** Extract assets out of the APK that need to be cached as uncompressed files on disk. */
private ResourceExtractor initResources(@NonNull Context applicationContext) {
ResourceExtractor resourceExtractor = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
final String packageName = applicationContext.getPackageName();
final PackageManager packageManager = applicationContext.getPackageManager();
final AssetManager assetManager = applicationContext.getResources().getAssets();
resourceExtractor =
new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
// In debug/JIT mode these assets will be written to disk and then
// mapped into memory so they can be provided to the Dart VM.
resourceExtractor
.addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
.addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
resourceExtractor.start();
}
return resourceExtractor;
}
其间的addResource办法,分别供给了VM的快照数据、iSolate的快照数据DEFAULT_KERNEL_BLOB的数据。由于Flutter自身支撑热重载的特性,保存状况和快照(Snapshot)之间必然是不可分割的。
而DEFAULT_KERNEL_BLOB是一个字符串常量: “kernel_blob.bin”,结合前面的内容:
FlutterSDK和事务代码将被构建成Kernel格局的二进制文件
咱们有理由猜想, “kernel_blob.bin” ,便是咱们的事务代码,Flutter是支撑逻辑代码热重载的,所以这个字面量加载的资源相同或许会被重新加载。
这也是为什么,假如咱们在State中,新增了某个变量作为Widget的某个状况,在initState中调用了,然后运用热重载之后,会导致State中找不到这个变量,由于initState在初次启动时就被调用过了,后续的热重载只会将之前的Snapshot康复回来,而不会走initState的逻辑。
咱们能够在app-debug.apk的assets中,找到”kernel_blob.bin”文件,相同也能够找到isolate_snapshot_data、vm_snapshot_data文件,所以ResourceExtractor加载的,基本上都是这个文件夹中的文件。
可是,在非DEBUG|JIT形式下,就不需求通过ResourceExtractor来进行加载了。
回到initTask办法,只在resourceExtractor != null时,会去等待它的完结。
ResourceExtractor resourceExtractor = initResources(appContext);
flutterJNI.loadLibrary();
// Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread.
Executors.newSingleThreadExecutor()
.execute(
new Runnable() {
@Override
public void run() {
flutterJNI.prefetchDefaultFontManager();
}
});
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
}
1.3 FlutterJNI#loadLibrary
public void loadLibrary() {
if (FlutterJNI.loadLibraryCalled) {
Log.w(TAG, "FlutterJNI.loadLibrary called more than once" );
}
System.loadLibrary( "flutter" );
FlutterJNI.loadLibraryCalled = true;
}
代码比较简单,无非便是调用System.loadLibrary去加载Library文件。需求留意的是,表面上找到是flutter,可是在Native(C++)层中,会为它拼接上前缀和后缀:lib和.so,所以,实际上load行为查找的是坐落apk包下的lib目录下的对应架构文件夹下的libflutter.so
。
initTask使命提交给线程池之后,就相当于startInitialization走完了。
你会发现有个问题,在Debug形式下,咱们加载事务代码是从二进制文件:”kernel_blob.bin”中加载的,而Release形式下,实在libapp.so中加载的,上面现已呈现了加载”kernel_blob.bin”和libflutter.so ,那么在release形式下,另一个Library文件:libapp.so是什么时候加载的呢?
所以,就要进入咱们的第二个关键办法:ensureInitializationComplete
二、ensureInitializationComplete
实际上,ensureInitializationComplete和startInitialization在FlutterEngine的初始化代码中
flutterLoader.startInitialization(context.getApplicationContext());
flutterLoader.ensureInitializationComplete(context, dartVmArgs);
代码一百多行,可是大多都是一些配置性的代码:
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 {
InitResult result = initResultFuture.get();
List<String> shellArgs = new ArrayList<>();
shellArgs.add( "--icu-symbol-prefix=_binary_icudtl_dat" );
shellArgs.add(
"--icu-native-lib-path="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ DEFAULT_LIBRARY);
if (args != null) {
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
String snapshotAssetPath =
result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
shellArgs.add( "--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
shellArgs.add( "--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
shellArgs.add(
"--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
} else {
shellArgs.add(
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.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
+ "="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
}
shellArgs.add( "--cache-dir-path=" + result.engineCachesPath);
if (!flutterApplicationInfo.clearTextPermitted) {
shellArgs.add( "--disallow-insecure-connections" );
}
if (flutterApplicationInfo.domainNetworkPolicy != null) {
shellArgs.add( "--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
}
if (settings.getLogTag() != null) {
shellArgs.add( "--log-tag=" + settings.getLogTag());
}
ApplicationInfo applicationInfo =
applicationContext
.getPackageManager()
.getApplicationInfo(
applicationContext.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
int oldGenHeapSizeMegaBytes =
metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0;
if (oldGenHeapSizeMegaBytes == 0) {
// default to half of total memory.
ActivityManager activityManager =
(ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memInfo);
oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
}
shellArgs.add( "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
if (metaData != null && metaData.getBoolean(ENABLE_SKPARAGRAPH_META_DATA_KEY)) {
shellArgs.add( "--enable-skparagraph" );
}
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
flutterJNI.init(
applicationContext,
shellArgs.toArray(new String[0]),
kernelPath,
result.appStoragePath,
result.engineCachesPath,
initTimeMillis);
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed." , e);
throw new RuntimeException(e);
}
}
明显,ensureInitializationComplete也有必要在主线程中进行调用,而且有必要在startInitialization之后进行调用。此外,咱们要留意别的一个东西:shellArgs。
2.1 ShellArgs
Shell是什么咱们并不陌生,在计算机中,Shell一般作为体系调用和用户操作之间的那么个东西,它存在的形式在Linux/Mac中一般便是一个Shell软件,一般运转在终端傍边(你能够粗略地就将Shell 和终端划等号 )。
所以,Flutter的Shell自然而然地旨在设置Flutter运转的一个「基底」,ShellArgs,则是咱们运用这么个「基底」的参数。
和之前说到的ResourceExtractor在JIT|DEBUG形式下主动去加载VM和Isoalte快照数据类似地,ShellArgs会在DEBUG和JIT形式下,去设置VM快照数据、Isolate快照数据和Kernel的地址。
别忘了,Kernel即上述的“kernel_blob.bin”二进制文件,是在Debug阶段咱们的事务代码,和libapp.so是相对的。
而在除上述之外的条件下,Flutter设置了一个AOT_SHARED_LIBRARY_NAME的途径:
shellArgs.add(
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
shellArgs.add(
"--"
+ AOT_SHARED_LIBRARY_NAME
+ "="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
在运转时,这个向shareArgs这个List中添加内容的两个字符串的内容,大致上便是指定了装载在体系的Apk安装包中的so文件的途径。
--aot-shared-library-name=libapp.so
--aot-shared-library-name=/data/app/~~RjRJYnLhVBHYW8pHHPeX2g==/com.example.untitled1-wcMTYW1VkfGA2LxW62gUFA==/lib/arm64/libapp.so
由于Tinker自身是支撑二进制SO库的动态化的,之前测验曩昔动态修改
aotSharedLibraryName
的值和途径,希望FlutterLoader从该地址去加载libapp.so
,以完结Android侧借助Tinker热修正Flutter代码,可是并没有细看源码,打了N个Debug包去测验,成果现在发现这逻辑压根没走。
除了上述的两个libapp.so的称号和途径之外,在DEBUG | JIT形式下的ShellArgs的全家福大致如下:
其实你细心看看,上述的Kernel的Path并没有在这里面,由于它作为参数,传递给了flutterJNI.init函数。
三、实践:自定义libapp.so的加载
至此,咱们今日最开端的一个论题:Embdder和代码Dart代码从何而来, 便有了成果 。结合上述的内容,咱们能够做一个小小的实践,咱们通过传入ShellArgs,来加载指定的 libapp.so
文件。
回到咱们开始的流程:
FlutterActivity->
FlutterActivityAndFragmentDelegate->
onAttach()->
setupFlutterEngine->
……
startInitialization
ensureInitializationComplete // alpha
咱们需求在上述的进程的alpha之前,完结对***AOT_SHARED_LIBRARY_NAME
*** 对应的途径(一模一样,也是 AOT_SHARED_LIBRARY_NAME
)这两个字符串的内容替换,比如:
--aot-shared-library-name=libapp.so
--aot-shared-library-name= /data/app/~~RjRJYnLhVBHYW8pHHPeX2g==/com.example.untitled1-wcMTYW1VkfGA2LxW62gUFA==/lib/arm64/libapp.so
咱们希望替换成:
--aot-shared-library-name=libfixedapp.so
--aot-shared-library-name= /temp/lib/arm64/libfixedapp.so
3.1 flutterApplicationInfo和FlutterActivity#getShellArgs()
这是FlutterLoader的一个实例目标,它在startInitialization阶段被赋值:
public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
// ……
try {
final Context appContext = applicationContext.getApplicationContext();
// ……
flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
……
所以,咱们只需求在合适的机遇去修改这个值即可。
可是并没有合适的机遇,由于Flutter并没有为咱们供给能够侵入去反射设置它的机遇,假如在startInitialization,咱们唯一能够侵入的机遇是attach()函数,可是会让咱们反射设置的值被覆盖掉。
可是,咱们重视一下,在setupFlutterEngine时,咱们new FlutterEngine的参数:
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false,
/*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
此处的host,便是咱们的FlutterActivity,由于FlutterActivity自身便是FlutterActivityAndFragmentDelegate.Host
接口的完结类,而这个host.getFlutterShellArgs().toArray()
,最终会作为咱们在FlutterActivity预设的参数,在所其他体系预设参数被参加之前被参加到咱们的shellArgs数组中。
所以,咱们在FlutterActivity的子类,也便是MainActivity下,重写getFlutterShellArgs()办法:
class MainActivity: FlutterActivity() {
override fun getFlutterShellArgs(): FlutterShellArgs {
return super.getFlutterShellArgs().apply {
this.add( "--aot-shared-library-name=libfixedapp.so" )
this.add( "--aot-shared-library-name=/data/data/com.example.untitled1/libfixedapp.so" )
}
}
}
咱们能够在debug形式下debug,看看有没有作用:
明显,是有作用的。
由于只能从几个特定的目录中去加载so库文件,咱们能够将补丁SO文件放在
/data/data/com.example.untitled1
对应的目录之下。
接下来,咱们先写一个有bug的Flutter代码,咱们把标题改成:This is Counter Title with bug
, 而且新增一个 _decrementCounter()
, 并把计数器的加法按钮对应的增加按钮,改成减少调用。
然后在Flutter项目根目录运用安装Release包:
flutter build apk --release
adb install build/app/outputs/flutter-apk/app-release.apk
然后咱们修正Bug,将代码康复到最开端的默许状况,然后:
flutter build apk --release
open build/app/outputs/flutter-apk/
解压apk,然后把对应的so文件移出来,放到对应的文件夹下: /data/data/com.example.untitled1/libfixedapp.so
。完结之后,重新启动程序,即可从新的、咱们指定的途径加载新的 libapp.so
了: