Flutter动态化方案---源码解析(基于1.22以上版本)

3,410 阅读4分钟

国际惯例先上狗图,以防被打

众所周知flutter在release打包过程中会把dart代码打包成libapp.so文件,我们要做的动态化方案就是在应用打开时用我们自己的libapp.so替换系统文件。

so路径.png

既然这样,我们就开始源码探索吧!!

一.找到libapp.so所在目录和引用

1.1 找到libapp.so所在目录

libapp.so所在目录.png 这个时候我们清楚了目标,就要找到libapp.so文件的目录。通过搜索源码我们定位到了FlutterApplicationInfo文件中的DEFAULT_AOT_SHARED_LIBRARY_NAME常量。当构造函数传递进来的aotSharedLibraryName为空的情况下,它被用来做aotSharedLibraryName常量的初始化(在后面的解读中aotSharedLibraryName默认情况下传递进来都是nul)。

由于DEFAULT_AOT_SHARED_LIBRARY_NAME是被final修饰的就注定不能被反射修改。

1.2 找到aotSharedLibraryName的引用

找到aotSharedLibraryName的引用.png 我们通过定位aotSharedLibraryName变量找到了FlutterLoader中的ensureInitializationComplete方法。在里面我们找到了两处引用。通过文字和注释我们清楚的知道了AOT_SHARED_LIBRARY_NAME这个参数就是系统加载libapp.so路径。AOT_SHARED_LIBRARY_NAME为了保险起见防止应用加载so文件失败,除了基础的so文件名外又提供了一个全路径的so文件地址。

1.3 flutterApplicationInfo的初始化

flutterApplicationInfo的初始化.png flutterApplicationInfo的初始化是在FlutterLoader中的startInitialization方法中通过ApplicationInfoLoader.load(appContext)完成的。

ApplicationInfoLoaderload.png

走进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文件。

ensureInitializationComplete.png

三.在应用初始化时加载我们的FlutterLoader完成替换

通过上面的步骤,我们已经明白动态化的原理和方案,剩下的就是如何将自定义的FlutterLoader在Flutter初始化时注入进去就大功告成。Flutter的初始化,分为纯flutter应用和混合应用两种

3.1 纯Flutter应用注入

纯flutter.png 在纯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方法只能用在测试,看下图。

setInstance.png

到这里我们就明白了,只需要在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())

通过FlutterLoader加载更新libapp.so文件完成动态化--代码编写

到这里就完成动态化方案的源码解析,至于说苹果的动态化方案,就算有你们敢用吗...,懂得都懂。谢谢了大家,划了这么多水,最后求个赞QAQ。

image