Flutter在Android端的代码分析

Flutter Android 端 Activity/Fragment 流程源码分析

主要看 io.flutter.embedding.android.FlutterActivity
Flutter工程默认的mainActivity是继承自FlutterActivity,FlutterActivity 直接继承自 Activity,重点看下FlutterActivityAndFragmentDelegate.Host。
FlutterActivityAndFragmentDelegate.Host是 Flutter Android 平台层实现与标准 Activity/Fragment 之间的一个接口约定层,FlutterActivity 实现了这个接口的一系列方法,Flutter 在平台 SDK 层包装都是 FlutterActivityAndFragmentDelegate 来负责的,FlutterActivityAndFragmentDelegate 和核心是 FlutterView 和 FlutterEngine 的创建关联与管理调度。
通常一个 engine 的整个 Flutter Dart 无论页面栈多少级,终归在安卓端都是一个 Activity 或者 Fragment 承载,抑或 View,端侧仅仅算是一个容器而已。

—> FlutterActivity.onCreate()

—> FlutterActivityAndFragmentDelegate.onAttach()

—> FlutterActivityAndFragmentDelegate.setupFlutterEngine();

FlutterEngine 是一个独立的 Flutter 运行环境容器,通过它可以在 Android 应用程序中运行 Dart 代码。FlutterEngine 中的 Dart 代码可以在后台执行,也可以使用附带的 FlutterRenderer 和 Dart 代码将 Dart 端 UI 效果渲染到屏幕上,渲染可以开始和停止,从而允许 FlutterEngine 从 UI 交互转移到仅进行数据处理,然后又返回到 UI 交互的能力。

App 每个进程中创建第一个 FlutterEngine 实例的时候会加载 Flutter 引擎的原生库并启动 Dart VM(VM 存活生命周期跟随进程),随后同进程中其他的 FlutterEngines 将在同一个 VM 实例上运行,但在运行 DartExecutor 时将拥有自己的 Dart Isolate。每个 Isolate 都是一个独立的 Dart 环境,除非通过 Isolate 端口,否则无法相互通信。[参见官方文档]

FlutterEngine 与 DartExecutor、Dart VM、Isolate 的关系

初始化使用到了FlutterInjector、FlutterLoader、ResourceExtractor、ApplicationInfoLoader、VsyncWaiter
接下来我们分析

FlutterInjector 相关分析

FlutterInjector 是 Android 平台与 Flutter Engine 真正桥梁的管理灵魂控制类,一个全局单例的注入管理类角色。
FlutterLoader 这个类的职责是在应用 APK 中查找 Flutter 资源并加载 Flutter 原生库。

public class FlutterLoader {
  //......
  //步骤7、创建实例,传递FlutterJNI实例,下一小节分析FlutterJNI。
  public FlutterLoader() {
    this(new FlutterJNI());
  }
  public FlutterLoader(@NonNull FlutterJNI flutterJNI) {
    this.flutterJNI = flutterJNI;
  }
  //......
  //步骤8、初始化结果结构体。
  private static class InitResult {
    final String appStoragePath;
    final String engineCachesPath;
    final String dataDirPath;
 //......
  }
  
  //步骤9、FlutterEngine实例化中调用,重要开始入口。
  public void startInitialization(@NonNull Context applicationContext) {
    startInitialization(applicationContext, new Settings());
  }

  //步骤10、加载Flutter Engine native so库,定位Dart resources在apk中的路径。
  //必须在主线程调用此方法。
  public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
    //......
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("startInitialization must be called on the main thread");
    }
    final Context appContext = applicationContext.getApplicationContext();
    this.settings = settings;
    initStartTimestampMillis = SystemClock.uptimeMillis();
    //步骤11、通过ApplicationInfoLoader的load获取FlutterApplicationInfo信息。
    //里面主要都是flutterAssetsDir、nativeLibraryDir、aotSharedLibraryName等信息,下一小节专门分析FlutterApplicationInfo。
    flutterApplicationInfo = ApplicationInfoLoader.load(appContext);
    //步骤12、平台VSYNC信号相关初始化设置,下面单独小节分析。
    VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE))
        .init();

    //步骤13、由于需要访问磁盘,异步调用执行初始化相关东西。
    Callable initTask =
        new Callable() {
          @Override
          public InitResult call() {
           //步骤14、异步调用initResources得到ResourceExtractor。
            ResourceExtractor resourceExtractor = initResources(appContext);
   //步骤15、通过flutterJNI调用加载框架so,即libflutter.so
            flutterJNI.loadLibrary();

            //步骤16、异步之上再异步先尽可能快获取默认的font manager。
            Executors.newSingleThreadExecutor()
                .execute(
                    new Runnable() {
                      @Override
                      public void run() {
                        flutterJNI.prefetchDefaultFontManager();
                      }
                    });
   //步骤17、等待ResourceExtractor异步任务结束。
            if (resourceExtractor != null) {
              resourceExtractor.waitForCompletion();
            }
   //步骤18、返回异步执行的结果结构。
            return new InitResult(
                PathUtils.getFilesDir(appContext),
                PathUtils.getCacheDirectory(appContext),
                PathUtils.getDataDirectory(appContext));
          }
        };
    //步骤19、通过线程池提交Callable并返回一个Future实例。
    initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
  }
  //......
  //步骤20、提取apk中assets文件为未压缩的在磁盘中,上面步骤14调用。
  private ResourceExtractor initResources(@NonNull Context applicationContext) {
    ResourceExtractor resourceExtractor = null;
    //步骤21、如果是debug或jit模式resourceExtractor才不为空。
    if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
      //步骤22、路径为context.getDir("flutter"),也就是私有沙盒下的flutter目录。
      final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
      //步骤23、获取apk的应用包名。
      final String packageName = applicationContext.getPackageName();
      final PackageManager packageManager = applicationContext.getPackageManager();
      final AssetManager assetManager = applicationContext.getResources().getAssets();
      //步骤24、拿着一堆apk信息和目录实例化一个ResourceExtractor供后续调用。
      resourceExtractor =
          new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);

      //步骤25、debug及jit模式填充路径。flutterApplicationInfo来自上面步骤11实例化构建。
      //默认资源路径:flutter_assets/vm_snapshot_data
      //默认资源路径:flutter_assets/isolate_snapshot_data
      //默认资源路径:flutter_assets/kernel_blob.bin
      resourceExtractor
          .addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))  
          .addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData)) 
          .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB)); 
   //步骤26、开始执行resourceExtractor。
      resourceExtractor.start();
    }
    return resourceExtractor;
  }
  //......
}

到此初始化就异步开始了,我们需要阻塞等待他的执行结束,如下:

public class FlutterLoader {
  //......
  //步骤27、阻塞直到上面步骤10的startInitialization方法执行完毕。
  //一般紧挨在startInitialization方法后调用。
  public void ensureInitializationComplete(
      @NonNull Context applicationContext, @Nullable String[] args) {
    //......
    try {
      //步骤28、阻塞等待上面步骤19的线程池Callable initTask执行完毕。
      InitResult result = initResultFuture.get();
   //步骤29、构建一堆参数供初始化使用。
      List shellArgs = new ArrayList<>();
      shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
   //步30、flutterApplicationInfo在上面步骤11构建。
   //路径为:[ApplicationInfo.nativeLibraryDir]/libflutter.so
      shellArgs.add(
          "--icu-native-lib-path="
              + flutterApplicationInfo.nativeLibraryDir
              + File.separator
              + DEFAULT_LIBRARY);
      if (args != null) {
        //步31、把启动FlutterActivity或FlutterFragment时intent中传递的dartVmArgs参数加入列表。
        //譬如:trace-startup=true等
        Collections.addAll(shellArgs, args);
      }

      String kernelPath = null;
      //步32、debug、jit模式设置一些参数路径啥的。
      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 {
        //步骤33、release模式重点!!!!
        //前几天还有网友微信我说想让分析flutter appso热更新,这就是核心啊,就这一句话。
        //--aot-shared-library-name=缺省libapp.so,可以通过清单文件meta-data配置so的名称为自定义值,配置name为io.flutter.embedding.engine.loader.FlutterLoader.aot-shared-library-name。
        shellArgs.add(
            "--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName);
        //步骤34、--aot-shared-library-name再配置一个apk安装后包路径下so的全路径地址。
        shellArgs.add(
            "--"
                + AOT_SHARED_LIBRARY_NAME
                + "="
                + flutterApplicationInfo.nativeLibraryDir
                + File.separator
                + flutterApplicationInfo.aotSharedLibraryName);
      }
   //步骤35、一堆同理的参数路径配置啥的。
      shellArgs.add("--cache-dir-path=" + result.engineCachesPath);
      if (flutterApplicationInfo.domainNetworkPolicy != null) {
        shellArgs.add("--domain-network-policy=" + flutterApplicationInfo.domainNetworkPolicy);
      }
      if (settings.getLogTag() != null) {
        shellArgs.add("--log-tag=" + settings.getLogTag());
      }
      //......
   //步骤36、进行框架真正的native层代码初始化,传入我们准备的一切。
      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);
    }
  }
  //......
  //步骤37、dart使用资源resources的路径。
  //本质可以通过meta-data配置name为io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir。
  //缺省为 flutter_assets。
  @NonNull
  public String findAppBundlePath() {
    return flutterApplicationInfo.flutterAssetsDir;
  }
  //......
  //步骤38、flutter plugin是否自动配置注册,默认是true,同样可以通过meta-data配置变更。
  @NonNull
  public boolean automaticallyRegisterPlugins() {
    return flutterApplicationInfo.automaticallyRegisterPlugins;
  }
  //......
}

可以看到,FlutterLoader 在调用 ensureInitializationComplete 方法时会将编译进 apk 中的 Flutter 相关libapp.so、assets 下面资源路径等各种安卓平台路径进行拼接传递给 flutterJNI 的 init 初始化。也就是说,Flutter Engine 拿到的关于 Flutter App 的各种原始资源路径都来自安卓平台解析传递,对于 Engine 来说就是一个 File path 的概念。这也就给我们进行 File path 重定向提供了思路,带来的国内团队骚操作就是衍生出了 Flutter app.so 热更新能力。

ResourceExtractor 相关分析

class ResourceExtractor {
  //......
  //步骤39、依据标准安卓系统获取支持的abi列表
  private static final String[] SUPPORTED_ABIS = getSupportedAbis();
  //步骤40、构造方法,在上面步骤24中被调用。
  ResourceExtractor(
      @NonNull String dataDirPath,
      @NonNull String packageName,
      @NonNull PackageManager packageManager,
      @NonNull AssetManager assetManager) {
    mDataDirPath = dataDirPath;
    mPackageName = packageName;
    mPackageManager = packageManager;
    mAssetManager = assetManager;
    mResources = new HashSet<>();
  }
  //......
  //步骤41、新建一个ExtractTask并执行,本质是一个AsyncTask。
  ResourceExtractor start() {
    //......
    mExtractTask =
        new ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
    mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    return this;
  }
  //......
  private static class ExtractTask extends AsyncTask {
    //......
    @Override
    protected Void doInBackground(Void... unused) {
      final File dataDir = new File(mDataDirPath);
      //......
      //步骤42、从apk提取释放资源,也就是通过流读取assets下flutter资源释放到对应目录下。
      if (!extractAPK(dataDir)) {
        return null;
      }
      //......
      return null;
    }
    //......
  }
}

ResourceExtractor 类主要通过线程池异步解析安装好的 apk 文件,释放 assets 路径下 Flutter 相关的资源,为 Flutter Engine 使用提供便利路径。

ApplicationInfoLoader 相关分析

ApplicationInfoLoader 的职责犹如其名,就是依据配置或者安装好的 apk 进行各种路径、信息拼接获取。

public final class ApplicationInfoLoader {
  //步骤43、一堆可以在AndroidManifest.xml中通过meta-data自定义的name属性。
  public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
      FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
  public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
      FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
  public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
      FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
  public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
      FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
  public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
  public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY =
      "io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY;
  //......
  //步骤44、上面步骤11调用获取一个FlutterApplicationInfo实例。
  public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
    ApplicationInfo appInfo = getApplicationInfo(applicationContext);
    //......
    //步骤43、一堆基础路径信息,自定义取不到就用缺省值。
    return new FlutterApplicationInfo(
        getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
        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,
        getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true));
  }
}
image.png

通过分析我们知道,只要能够在flutterLoader.ensureInitializationComplete拼接好后的关键内容是 -- aot-shared-library-name=libapp.so --aot-shared-library-name=nativeLibraryDir/libapp.so,

我已我们就仅需要将我们下发的补丁so文件路径替换成FlutterApplicationInfo.aotSharedLibraryName字段的值,那么flutter启动后就是执行我们下发的内容了。

由于FlutterLoader中的FlutterApplicationInfo是私有的并且FlutterApplicationInfo中的aotSharedLibraryName是final修饰的,所以我们不可能通过常规手段去修改FlutterApplicationInfo.aotSharedLibraryName的值,只能通过反射去修改了


public class MyFlutterLoader extends FlutterLoader {

    private static MyFlutterLoader instance;
    private static final String FIX_SO = "hotlibapp.so";

    @NonNull
    public static MyFlutterLoader getInstance() {
        if (instance == null) {
            instance = new MyFlutterLoader();
        }
        return instance;
    }

    @Override
    public void ensureInitializationComplete(
            @NonNull Context applicationContext, @Nullable String[] args) {

        File filesDir = applicationContext.getFilesDir();
        File soFile = new File(filesDir, FIX_SO);//实际项目就是从服务端获取到so文件,然后放置到这个路径下
        if (soFile.exists()) {

            Log.i("--ensureInitializationComplete--", "补丁放置的路径:" + soFile.getAbsolutePath());

            try {
                //1.拿到flutterApplicationInfo字段
                Field flutterApplicationInfoField = FlutterLoader.class.getDeclaredField("flutterApplicationInfo");
                flutterApplicationInfoField.setAccessible(true);
                FlutterApplicationInfo flutterApplicationInfo = (FlutterApplicationInfo) flutterApplicationInfoField.get(this);

                assert flutterApplicationInfo != null;
                Log.i("========", "--aot-shared-library-name=" + flutterApplicationInfo.nativeLibraryDir + flutterApplicationInfo.aotSharedLibraryName);

                //2.拿到aotSharedLibraryName并修改为我们的so路径
                Field aotSharedLibraryNameField = FlutterApplicationInfo.class.getDeclaredField("aotSharedLibraryName");
                aotSharedLibraryNameField.setAccessible(true);
//                aotSharedLibraryNameField.set(flutterApplicationInfo, File.separator+soFile.getName());
                aotSharedLibraryNameField.set(flutterApplicationInfo, soFile.getAbsolutePath());
                //3.拿到nativeLibraryDir并修改为我们的so路径
//                Field nativeLibraryDirField = FlutterApplicationInfo.class.getDeclaredField("nativeLibraryDir");
//                nativeLibraryDirField.setAccessible(true);
//                nativeLibraryDirField.set(flutterApplicationInfo, applicationContext.getFilesDir().getAbsolutePath());

                Log.i("========", "--aot-shared-library-name=" + flutterApplicationInfo.nativeLibraryDir + flutterApplicationInfo.aotSharedLibraryName);

                super.ensureInitializationComplete(applicationContext, args);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            Log.i("--ensureInitializationComplete--", "load fail. 补丁不存在");
        }

    }
}

参考自codedeveloper的文章 - 知乎

  • 《Flutter Android 工程结构及应用层编译源码深入分析》
  • 《Flutter 命令本质之 Flutter tools 机制源码深入分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码分析》
  • 《Flutter Android 端 Activity/Fragment 流程源码分析》
  • 《Flutter Android 端 FlutterInjector 及依赖流程源码分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码分析》
  • 《Flutter Android 端 FlutterEngine Java 相关流程源码分析》
  • 《Flutter Android 端 FlutterView 相关流程源码分析》

你可能感兴趣的:(Flutter在Android端的代码分析)