国际惯例先上狗图,以防被打
众所周知flutter在release打包过程中会把dart代码打包成libapp.so文件,我们要做的动态化方案就是在应用打开时用我们自己的libapp.so替换系统文件。
既然这样,我们就开始源码探索吧!!
一.找到libapp.so所在目录和引用
1.1 找到libapp.so所在目录
这个时候我们清楚了目标,就要找到libapp.so文件的目录。通过搜索源码我们定位到了FlutterApplicationInfo文件中的DEFAULT_AOT_SHARED_LIBRARY_NAME常量。当构造函数传递进来的aotSharedLibraryName为空的情况下,它被用来做aotSharedLibraryName常量的初始化(在后面的解读中aotSharedLibraryName默认情况下传递进来都是nul)。
由于DEFAULT_AOT_SHARED_LIBRARY_NAME是被final修饰的就注定不能被反射修改。
1.2 找到aotSharedLibraryName的引用
我们通过定位aotSharedLibraryName变量找到了FlutterLoader中的ensureInitializationComplete方法。在里面我们找到了两处引用。通过文字和注释我们清楚的知道了AOT_SHARED_LIBRARY_NAME这个参数就是系统加载libapp.so路径。AOT_SHARED_LIBRARY_NAME为了保险起见防止应用加载so文件失败,除了基础的so文件名外又提供了一个全路径的so文件地址。
1.3 flutterApplicationInfo的初始化
flutterApplicationInfo的初始化是在FlutterLoader中的startInitialization方法中通过ApplicationInfoLoader.load(appContext)完成的。
走进ApplicationInfoLoader.load方法,发现在New FlutterApplicationInfo实例时传递的参数首先会从ApplicationInfo中获取,而默认情况下该对象的metaData中不存在PUBLIC_AOT_SHARED_LIBRARY_NAME数据,返回为null。回过头来看我们之前截图(1.1),FlutterApplicationInfo构造函数在aotSharedLibraryName为空的情况下会使用常量DEFAULT_AOT_SHARED_LIBRARY_NAME完成实例化。
因为1.1中我们已经明确了DEFAULT_AOT_SHARED_LIBRARY_NAME是不能被反射的,所以我们只能copy一份系统ApplicationInfoLoader代码,重载load方法,将我们的本地的liappfix.so路径传递到FlutterApplicationInfo中,完成FlutterApplicationInfo对象的初始化。
final class KCApplicationInfoLoader {
....
@NonNull
public static FlutterApplicationInfo load(@NonNull Context applicationContext,String path) {
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
// Prior to API 23, cleartext traffic is allowed.
boolean clearTextPermitted = true;
if (android.os.Build.VERSION.SDK_INT >= 23) {
clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
}
return new FlutterApplicationInfo(
path,
getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
getNetworkPolicy(appInfo, applicationContext),
appInfo.nativeLibraryDir,
clearTextPermitted);
}
}
看到这里很多人都应该明白接下来我们需要想办法怎么在ensureInitializationComplete之前,通过反射用我们自己的FlutterApplication实例替换系统默认创建的FlutterApplicationInfo实例。
二.继承FlutterLoader实现ensureInitializationComplete方法
public class KCFlutterLoader extends FlutterLoader {
private static final String TAG = "KCFlutterLoader";
private static KCFlutterLoader flutterLoaderInstance=new KCFlutterLoader();
public static KCFlutterLoader getInstance() {
return flutterLoaderInstance;
}
@Override
public void startInitialization(@NonNull Context applicationContext) {
Log.d(TAG,"KCFlutterLoader===startInitialization");
super.startInitialization(applicationContext);
}
@Override
public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
Log.d(TAG,"KCFlutterLoader===ensureInitializationComplete");
File file=new File(applicationContext.getFilesDir(),"libappfix.so");
if(file.exists()){
String name=file.getAbsolutePath();
FlutterApplicationInfo flutterApplicationInfo = KCApplicationInfoLoader.load(applicationContext, name);
try {
Class FlutterLoaderClass = Class.forName("io.flutter.embedding.engine.loader.FlutterLoader");
Field nameField = FlutterLoaderClass.getDeclaredField("flutterApplicationInfo");
nameField.setAccessible(true);
nameField.set(this,flutterApplicationInfo);
Field initialized = FlutterLoaderClass.getDeclaredField("initialized");
initialized.setAccessible(true);
initialized.set(this,false);
} catch (Exception e) {
e.printStackTrace();
}
}
super.ensureInitializationComplete(applicationContext, args);
}
}
除了替换flutterApplicationInfo外,还需要将FlutterLoader中的initialized变量设置为false。如果initialized为true的话,代表libapp.so文件已经完成本地初始化加载,将不再重新加载本地的so文件。
三.在应用初始化时加载我们的FlutterLoader完成替换
通过上面的步骤,我们已经明白动态化的原理和方案,剩下的就是如何将自定义的FlutterLoader在Flutter初始化时注入进去就大功告成。Flutter的初始化,分为纯flutter应用和混合应用两种
3.1 纯Flutter应用注入
在纯Flutter应用中,我们都会继承FlutterApplication。其中在FlutterApplication的onCreate方法中我们会完成FlutterLoader的初始化,并执行startInitialization方法。查看flutterLoader方法找到来源,发现返回的flutterLoader实例是在FlutterInjector初始化前,通过fillDefaults方法完成创建。
public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) {
this.flutterLoader = flutterLoader;
return this;
}
private void fillDefaults() {
if (flutterLoader == null) {
flutterLoader = new FlutterLoader();
}
}
/**
* Builds a {@link FlutterInjector} from the builder. Unspecified properties will have
* reasonable defaults.
*/
public FlutterInjector build() {
fillDefaults();
System.out.println("should load native is " + shouldLoadNative);
return new FlutterInjector(shouldLoadNative, flutterLoader);
}
虽然在Builder方法里提供了设置setFlutterLoader方法,但其实在正式环境里是不可用的,因为我们通过new Builder().build()创建的FlutterInjector实例是无法调用setInstance方法设置到FlutterInjector.instance中。setInstance方法只能用在测试,看下图。
到这里我们就明白了,只需要在super.onCreate()之前,完成FlutterInjector的初始化,并通过反射修改flutterLoader对象就可以完成动态化。
public class MyApplication extends FlutterApplication {
public static Application application;
@Override
public void onCreate() {
//修改FlutterLoader
FlutterInjector flutterInjector = FlutterInjector.instance();
try {
Class FlutterInjectorClass = Class.forName("io.flutter.FlutterInjector");
Field nameField = FlutterInjectorClass.getDeclaredField("flutterLoader");
nameField.setAccessible(true);
nameField.set(flutterInjector,KCFlutterLoader.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
super.onCreate();
application=this;
}
}
3.2 混合应用应用注入
在混合应用中注入自定义的FlutterLoader相对简单,只需要在FlutterEngine初始化时将自定义FlutterLoader实例传递进去就好了:
FlutterEngine flutterEngine =new FlutterEngine(context, KCFlutterLoader.getInstance(),new FlutterJNI())