Flutter 笔记 | Flutter 核心原理(六)Embedder 启动流程(Android)

在这里插入图片描述
Embedder是Flutter接入原生平台的关键,其位于整个Flutter架构的底层,负责Engine的创建、管理与销毁,同时也为Engine提供绘制UI的接口,那么底层的实现细节如何?本文将详细分析。

Embedder关键类分析

在正式分析Embedder的启动流程之前,我们需要明确Embedder的关键类及其关系,这样在分析到具体流程时才能理解其逻辑,知晓其目的。Embedder层的关键类结构如图4-1所示。

图4-1 Embedder层的关键类结构

在Embedder中,FlutterActivityFlutterFragment是开发者最常接触的类,例如默认生成的Counter App中,MainActivity正是继承自FlutterActivity,这两个类的父类ActivityFragment是Android中常见的用于实现一屏UI的单位,因而这两个类的职责就是显示Flutter Engine绘制的UI。那么隐匿在这两个类之下的逻辑又是什么呢?

由于FlutterActivityFlutterFragment的大部分逻辑和职责都是相同的,因此它们共同持有一个FlutterActivityAndFragmentDelegate(后面内容简称Delegate)对象,用于相同逻辑的处理,并共同实现了Host接口用于自身的功能抽象。

Delegate持有两个关键的类:FlutterEngineFlutterView,前者负责 Flutter Enginelibflutter.so)在 Embedder 中的调用和管理,而后者则负责 Flutter Engine 中UI数据的上屏显示。FlutterView也会用到Engine的功能,因此也持有FlutterEngine

此外,FlutterView还持有RenderSurface的具体实现。RenderSurface顾名思义就是渲染 Flutter UI 的接口,它有3个实现:

  • FlutterSurfaceView基于 Android 的SurfaceView实现,性能最佳,但它不在 Android 的 View Hierarchy 中,一般用于整页的 Flutter UI 展示,默认优先使用;
  • FlutterTextureView基于 Android 的TextureView实现,性能不如前者,但是使用体验更接近 Android 中一个普通的View,比较适合一些Flutter嵌入原生UI的场景;
  • FlutterImageView通常用于存在Platform View的场景中。

RenderSurface会通过FlutterRender对象调用 Engine 的相关绘制能力,FlutterRender对 Engine 的绘制能力做了抽象封装,便于 Embedder 使用。此外FlutterEngine会通过DartExecutor调用Engine 中 Dart Runtime 相关的逻辑。这两个类中,RenderSurface负责UI相关工作,DartExecutor负责逻辑相关的工作,但它们最终都要调用Engine的具体Native方法,因而都会持有FlutterJNI对象,该对象集中了大部分Embedder(Java代码)和 Engine(C++代码)的相互调用接口。

后面内容中将多次出现以下几个概念:Surface、SurfaceTexture、SurfaceView、TextureView。为避免混淆,在此统一说明。

  • Surface是一个比较抽象的概念,表示一块渲染缓冲区的句柄(Handle),它通常由渲染数据的消费者(比如SurfaceTexture、MediaRecorder)创建,并作为参数传递给渲染数据的生产者(比如OpenGL ES)。
  • SurfaceTextureSurfaceOpenGL ES纹理的组合,即OpenGL ES通过绘制指令生产的纹理需要一个输出,而SurfaceTexture正是一个典型的渲染输出。SurfaceTexture内部包含一个BufferQueue实例,负责连接渲染数据的生产者和消费者。通常情况下,BufferQueue会以OpenGL ES等作为生产者,以TextureView等作为消费者。
  • TextureView类结合了ViewSurfaceTexture,它可以消费SurfaceTexture中的纹理数据,并通过重写Viewdraw方法的形式显示到屏幕中。
  • SurfaceView在Android API中的出现时间比TextureView更早,它在使用上和普通的View一致,但在底层却拥有自己独立的Surface,这样的好处是对这个Surface的渲染可以放到单独线程去做,渲染时可以有自己的上下文环境。这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。但它也有缺点,因为这个Surface不在默认的View Tree中,它的显示也不受View的属性控制,所以不能进行平移、缩放等变换,也不能放在其他ViewGroup中,一些View中的特性也无法使用。

总的来说,TextureView虽然灵活,但是性能更低,SurfaceView虽然存在一些限制,但是性能更加高效。对 Flutter UI 而言,如果是一个独立的、全屏的界面(默认情况),应该优先使用SurfaceView作为渲染的输出。

此外,Flutter中还有一种使用TextureLayer展示Platform View的虚拟显示模式,称为Virtual Display,它仅在Android平台支持。

以上内容从空间的角度自顶向下、由表及里分析了Embedder的结构,接下来将从时间的角度分析Embedder在整个Flutter启动流程中所扮演的角色和发挥的作用(注:后面大部分内容都将遵循此顺序,先分析空间结构,后分析时序流程)。

启动准备阶段

这里以flutter create命令默认创建的Counter App为例进行介绍。启动后首先会触发FlutterApplication中的onCreate回调,如代码清单4-1所示。

// 代码清单4-1 engine/shell/platform/android/io/flutter/app/FlutterApplication.java
public class FlutterApplication extends Application {
   
  @Override
  @CallSuper
  public void onCreate() {
   
    super.onCreate();
    FlutterInjector.instance().flutterLoader().startInitialization(this);// 注意,这里依赖注入式的设计,通过解耦以提升代码的可测试性
  } 
}

以上逻辑最终将调用FlutterLoaderstartInitialization方法,如代码清单4-2所示。

// 代码清单4-2 engine/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
public void startInitialization(@NonNull Context applicationContext) {
   
  startInitialization(applicationContext, new Settings());
}
public void startInitialization(@NonNull Context applicationContext, @NonNull 
    Settings settings) {
   
  if (this.settings != null) {
    return; } // 防止多次初始化
  if (Looper.myLooper() != Looper.getMainLooper()) {
    throw ... } // 检查是否在主线程
  // 确保使用的是Global Context,如果使用某个Activity的Context,可能导致内存泄漏
  final Context appContext = applicationContext.getApplicationContext();
  this.settings = settings;
  initStartTimestampMillis = SystemClock.uptimeMillis(); // 用于统计启动耗时
  flutterApplicationInfo = ApplicationInfoLoader.load(appContext); // 加载Manifest信息
  VsyncWaiter.getInstance((WindowManager) appContext // 初始化Vsync监听
      .getSystemService(Context.WINDOW_SERVICE)).init(); // 见代码清单4-3
  Callable<InitResult> initTask = ...... // 见代码清单4-4
  initResultFuture = Executors.newSingleThreadExecutor().submit(initTask);
// 开始执行
}

以上逻辑中,首先会检查是否设置settings变量,以防止多次初始化。这里没有做多线程检查,原因是Flutter的初始化必须在主线程进行,否则会抛出异常。这里之所以要在主线程初始化主要是因为Embedder的主要职责就是为Engine提供绘制UI的接口,而Android中UI相关的操作必须在主线程进行,后面内容中将发现Flutter中Dart的逻辑即渲染管道中3棵树的构建其实都不是在Android的主线程中进行的。

接下来会获取ApplicationContext对象用于后面内容获取ApplicationInfoSystemService,这里有一个细节就是强制调用getApplicationContext以确保使用的是ApplicationContext。如果直接使用Activity等组件的Context会存在内存泄漏的风险,这是因为SystemService会持有调用者的强引用。接下来会完成VsyncWaiter的初始化,具体逻辑如代码清单4-3所示。最后会初始化一个异步的Task任务,并立即启动执行,具体逻辑如代码清单4-4所示。

// 代码清单4-3 engine/shell/platform/android/io/flutter/view/VsyncWaiter.java
public void init() {
   
  FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate); 
// 见代码清单5-31
  float fps = windowManager.getDefaultDisplay().getRefreshRate();
  FlutterJNI.setRefreshRateFPS(fps);
}

以上逻辑的核心是初始化并赋值给FlutterJNI一个AsyncWaitForVsyncDelegate对象,该对象将被Engine主动调用,使用场景是:Engine有一帧UI需要渲染时并不会立即执行,而是会通过Embedder注册一个监听,等到下一个Vsync信号到达后再启动渲染。

下面继续分析initTask变量的具体内容。

// 代码清单4-4 engine/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
Callable<InitResult> initTask =
    new Callable<InitResult>() {
   
      @Override
      public InitResult call() {
   
        ResourceExtractor resourceExtractor = initResources(appContext);
        flutterJNI.loadLibrary(); // 加载libflutter.so,触发JNI_OnLoad,见代码清单4-20
        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对象,用于资源的提取,具体如代码清单4-5所示。其次会加载libflutter.so,即Flutter Engine,该方法会触发一个系统回调,即JNI_OnLoad,这是Engine在启动流程中触发的第1个逻辑。最后该线程会阻塞直至完成ResourceExtractor对象的任务。

// 代码清单4-5 engine/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
private ResourceExtractor initResources(@NonNull Context applicationContext) {
   
  ResourceExtractor resourceExtractor = null;
  // 注意,只有Debug/JIT模式下构建产物才需要提取逻辑,Release模式产物形式为libapp.so
  // 可直接进行动态链接,详见后面内容分析
  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);
    // Flutter SDK和业务代码构建的产物(Kernel)被提取到文件,Flutter Engine负责
    resourceExtractor // 映射到内存,assets目录下的文件无法直接映射,所以需要提取
        .addResource(fullAssetPathFrom(flutterApplicationInfo.vmSnapshotData))
        .addResource(fullAssetPathFrom(flutterApplicationInfo.isolateSnapshotData))
        .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
    resourceExtractor.start(); // 本质上是启动一个AsyncTask
  }
  return resourceExtractor;
}

以上逻辑是ResourceExtractor的初始化逻辑,主要作用是在DebugJIT模式下提取assets目录下的资源文件到内部存储中,这是因为以上两种模式中Flutter SDK和业务代码将被构建成Kernel格式的二进制文件,Engine将通过文件内存映射的方式进行加载,而assets本质上还是zip压缩包的一部分,没有自己的物理路径,因而要在Engine的初始化逻辑之前完成相关资源的提取并返回真实的物理路径,具体的提取复制逻辑还会做一些时间戳的校验,在此不再赘述。以上文件资源的使用将在代码清单4-68中详细分析。

总结一下 FlutterApplicationonCreate中主要做的工作就是三件事:

  1. FlutterJNI一个AsyncWaitForVsyncDelegate对象,以便后续Engine可以向系统注册 Vsync 信号监听驱动Flutter渲染管线的Frame绘制流程
  2. 加载 libflutter.so,触发JNI_OnLoad
  3. 初始化 ResourceExtractor&#
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值