美文网首页
Flutter在Android端的代码分析

Flutter在Android端的代码分析

作者: Jagtu | 来源:发表于2022-08-31 16:00 被阅读0次

    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<InitResult> initTask =
            new Callable<InitResult>() {
              @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<String> 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<Void, Void, Void> {
        //......
        @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端的代码分析

          本文链接:https://www.haomeiwen.com/subject/btpdnrtx.html