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

news2024/12/25 12:32:54

在这里插入图片描述
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,用以提取Asset资源

FlutterEngine 初始化

注意 FlutterEngineFlutter Embedder 中的Java类,Flutter Enginelibflutter.so所对应的逻辑,是相对于 Flutter EmbedderFlutter Framework 而言的。

接下来,主线程会进入FlutterActivity的生命周期回调,首先是onCreate,具体逻辑如代码清单4-6所示。

// 代码清单4-6 engine/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
  switchLaunchThemeForNormalTheme(); // 主题切换
  super.onCreate(savedInstanceState);
  delegate = new FlutterActivityAndFragmentDelegate(this);
  delegate.onAttach(this); // 创建FlutterEngine
  delegate.onRestoreInstanceState(savedInstanceState); // 状态存储入口
  lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); // 生命周期
  configureWindowForTransparency(); // 配置窗口
  setContentView(createFlutterView()); // 创建FlutterView,并设为根View,见代码清单4-12
  configureStatusBarForFullscreenFlutterExperience(); // 全屏状态栏
}

以上逻辑中,switchLaunchThemeForNormalTheme方法负责从闪屏页的主题切换到主页面的主题,然后新建一个Delegate对象,FlutterActivity将自身的大多数逻辑都委托给该对象,onAttach方法将完成FlutterEngine的初始化。configureWindowForTransparency方法负责配置透明主题,createFlutterView方法将调用FlutterActivityAndFragmentDelegateonCreateView方法,并返回Flutter实际进行绘制的View作为Activity根View,后面将详细分析这部分内容。

首先分析onAttach方法,如代码清单4-7所示。

// 代码清单4-7 engine/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
void onAttach(@NonNull Context context) {
  ensureAlive();
  if (flutterEngine == null) {
    setupFlutterEngine(); // 初始化FlutterEngine,见代码清单4-8
  }
  if (host.shouldAttachEngineToActivity()) { // 通知Activity Attach完成
    flutterEngine.getActivityControlSurface()
        .attachToActivity(this, host.getLifecycle());
  }
  // PlatformPlugin会提供宿主基础的原生能力,如剪贴板
  platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
  // 为Host提供一个感知FlutterEngine初始化完成的时机
  host.configureFlutterEngine(flutterEngine); // 开发者一般在此进行Platform Channel注册等操作 
}

以上逻辑负责初始化FlutterEngine,其在Embedder中表现为实例化一个FlutterEngine对象并完成其相关成员变量的配置,后面内容将详细分析。接着会完成PlatformPlugin的创建,FlutterActivity提供了默认的实现。然后通过attachToActivity方法告知所有实现了ActivityControlSurface接口的插件,其所注册的FlutterEngine已经完成和宿主Activity的绑定(Attach)。最后通过configureFlutterEngine方法给FlutterActivity提供一个回调,开发者通常需要在该回调中完成涉及FlutterEngine的初始化工作,比如Platform View、Platform Channel的注册。

接下来深入分析FlutterEngine的创建过程,即setupFlutterEngine方法的逻辑,如代码清单4-8所示。

// 代码清单4-8 engine/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
void setupFlutterEngine() {
  String cachedEngineId = host.getCachedEngineId();
  if (cachedEngineId != null) { // 第1步,如果指明使用缓存,则尝试获取
    flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
    isFlutterEngineFromHost = true;
    if (flutterEngine == null) { throw new IllegalStateException .....} // 找不到缓存,抛出异常
    return;
  }
  flutterEngine = host.provideFlutterEngine(host.getContext());
  if (flutterEngine != null) { // 第2步,如果Host提供了Engine,则直接使用
    isFlutterEngineFromHost = true;
    return;
  }
  flutterEngine = new FlutterEngine( // 第3步,新建一个FlutterEngine实例
      host.getContext(),
      host.getFlutterShellArgs().toArray(), // 外部参数
      /*automaticallyRegisterPlugins=*/ false,
      host.shouldRestoreAndSaveState());
  isFlutterEngineFromHost = false;
}

以上逻辑分3种情况完成FlutterEngine的初始化。

  • 第1种情况,对于提供了缓存 idHost,会查找 id 对应的FlutterEngine,如果没有则抛出异常,而不是继续后面的兜底逻辑。
  • 第2种情况,对于Host自己提供FlutterEngine实例的情况,直接使用即可。
  • 第3种情况,如果以上两种情况都不满足则会新建一个FlutterEngine实例。

由于前两种情况FlutterEngine的具体实例都可以由Host控制(通过实现getCachedEngineId或者provideFlutterEngine),因此其isFlutterEngineFromHost字段为true

注意:对于提供了CachedEngineId却获取FlutterEngine失败的场景,如果不抛出异常,继续下面的逻辑,虽然可以保证一定可以成功设置一个FlutterEngine实例,但是使用者的逻辑可能依赖于缓存的FlutterEngine对象的一些特有功能,因此不抛出异常而进行兜底实际上是隐藏了风险(使用者期望获得和实际返回的Engine不一致),增加了后续排查问题的难度,所以这里直接抛出了异常。

一般情况下,默认逻辑是新建一个FlutterEngine实例,其最终将触发的构造函数如代码清单4-9所示。

// 代码清单4-9 engine/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
public FlutterEngine( ...... ) {
  AssetManager assetManager;
  try { // 优先使用一个独立的AssetManager
    assetManager = context.createPackageContext(context.getPackageName(), 0).
        getAssets();
  } catch (NameNotFoundException e) {
    assetManager = context.getAssets(); // 若失败则使用当前的
  } // 第1步,DartExecutor创建,负责Embedder和Framework的通信
  this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
  // 设置FlutterJNI中处理Framework消息的PlatformMessageHandler
  this.dartExecutor.onAttachedToJNI();
  DeferredComponentManager deferredComponentManager =
          FlutterInjector.instance().deferredComponentManager();
  // 第2步,提供给Framework的平台能力的封装
  accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
  deferredComponentChannel = new DeferredComponentChannel(dartExecutor);
  // SKIP
  this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);
  this.flutterJNI = flutterJNI;
  if (flutterLoader == null) {
    flutterLoader = FlutterInjector.instance().flutterLoader();
  }
  if (!flutterJNI.isAttached()) { // 第3步,FlutterEngine的启动逻辑
    flutterLoader.startInitialization(context.getApplicationContext()); // 见代码清单4-2
    flutterLoader.ensureInitializationComplete(context, dartVmArgs); // 见代码清单4-10
  } // 第4步,设置FlutterJNI的关键成员字段
  flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
  flutterJNI.setPlatformViewsController(platformViewsController);
  flutterJNI.setLocalizationPlugin(localizationPlugin);
  flutterJNI.setDeferredComponentManager(
      FlutterInjector.instance().deferredComponentManager());
  if (!flutterJNI.isAttached()) { // 至此,Embedder侧完全准备好和Engine交互
    attachToJni(); // 建立Embedder和Engine的连接,见代码清单4-24
  } // 第5步,Engine绘制相关能力的初始化
  this.renderer = new FlutterRenderer(flutterJNI); // 该类代表了Engine绘制相关的能力
  this.platformViewsController = platformViewsController;  
  this.platformViewsController.onAttachedToJNI();
  this.pluginRegistry = new FlutterEngineConnectionRegistry(  
              context.getApplicationContext(), this, flutterLoader);
  if (automaticallyRegisterPlugins) {
    registerPlugins(); // 自动完成插件的注册
  }
}

以上逻辑中,

  • 第1步会完成DartExecutor的创建,该对象将负责Embedder和Framework的通信。
  • 第2步会完成诸多平台功能封装类的初始化,它们大多是 Platform Channel 的二次封装,在此不再赘述。
  • 第3步会通过ensureInitializationComplete方法完成FlutterEngine的初始化参数的配置,注意,此时FlutterEngine中大部分关键类都尚未开始创建,因为Embedder的FlutterJNI还没有完成初始化。
  • 第4步会对FlutterJNI对象的相关成员进行初始化,并调用attachJni方法完成Embedder和Engine的绑定,Engine的大部分逻辑由attachJni方法触发,后面将详细分析这部分内容。
  • 第5步会完成FlutterRenderer的初始化以及Platform ViewPlugin的相关初始化工作。

下面继续分析FlutterEngine的初始化工作,如代码清单4-10所示。

// 代码清单4-10 engine/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
public void ensureInitializationComplete(
    @NonNull Context applicationContext, @Nullable String[] args) {
  if (initialized) { return; } // 已经完成
  if (Looper.myLooper() != Looper.getMainLooper()) { ...... }
  if (settings == null) { ...... } // 没有调用startInitialization,退出
  try {
    InitResult result = initResultFuture.get(); // 阻塞至代码清单4-4完成
    List<String> shellArgs = new ArrayList<>(); // 开始拼接各种启动参数
    // SKIP
    if (args != null) { Collections.addAll(shellArgs, args); } // 透传外部参数
    String kernelPath = null;
    if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
      // SKIP Kernel Snapshot的名称及位置
    } else { // Release模式,so文件的名称,默认为libapp.so
      shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME 
                      + "=" + flutterApplicationInfo.aotSharedLibraryName);
      shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME // 兜底逻辑,传入完整路径
              + "=" + flutterApplicationInfo.nativeLibraryDir
              + File.separator + flutterApplicationInfo.aotSharedLibraryName);
    }
    // SKIP 其他各种参数
    long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
    flutterJNI.init(applicationContext, shellArgs.toArray(new String[0]), // 见代码清单4-23
        kernelPath, result.appStoragePath, result.engineCachesPath,
        initTimeMillis); // Embedder初始化至此的耗时
    initialized = true; // FlutterLoader 初始化完成
  } catch (Exception e) { ...... }
}

以上逻辑中,主要是初始化一系列参数,并通过JNI调用传递给Engine。其中,需要关注的是Framework和相关Dart业务代码的加载情况。在Debug模式下会加载 3 个字段,其具体值及作用如下:

  • SNAPSHOT_ASSET_PATH_KEYsnapshot-asset-path):默认值为flutter_assets,表示下面两个资源的所在路径。

  • VM_SNAPSHOT_DATA_KEYvm-snapshot-data):默认值为vm_snapshot_data,表示vm isolate的数据段和代码段。

  • ISOLATE_SNAPSHOT_DATA_KEYisolate-snapshot-data):默认值为isolate_snapshot_data,表示主isolate(业务代码及其所依赖的Framework)的数据段和代码段。

而在Release模式下则只会加载AOT_SHARED_LIBRARY_NAME,其默认值为libapp.so,其相当于vm_snapshot_dataisolate_snapshot_data的和,只是前者是Release模式的AOT产物,而后两者则是Debug模式下Kernel文件序列化的产物,libapp.so的本质是一个elf格式的文件,因而可以通过相关工具查看其内容,如代码清单4-11所示。

代码清单4-11 app.so文件的内部结构

$ greadelf -s --wide libapp.so
Symbol table '.dynsym' contains 6 entries:
   Num:    Value  Size    Type    Bind   Vis      Ndx Name
   0: 00000000 0        NOTYPE LOCAL  DEFAULT UND 
   1: 00001000 12       FUNC   GLOBAL DEFAULT 1 _kDartBSSData
   2: 00002000 9616     FUNC   GLOBAL DEFAULT 2 _kDartVmSnapshotInstructions
   3: 00005000 24400    FUNC   GLOBAL DEFAULT 3 _kDartVmSnapshotData
   4: 0000b000 0x1ed9d8 FUNC   GLOBAL DEFAULT 4 _kDartIsolateSnapshotInstructions
   5: 001f9000 0x175860 FUNC   GLOBAL DEFAULT 5 _kDartIsolateSnapshotData

以上信息中,_kDartIsolateSnapshotInstructions中包含了大部分的逻辑信息;_kDartIsolateSnapshotData 中包含了大部分的数据信息。

总的来说,在FlutterEngine初始化过程中会触发FlutterJNI中的两个关键Engine调用——nativeInit方法和nativeAttach方法。

总结

在这里插入图片描述
其中,

setupFlutterEngine() 的逻辑:

  1. 若指定使用缓存,则缓存获取 FlutterEngine,获取不到抛异常
  2. 获取宿主Host提供的 FlutterEngine,获取到直接使用
  3. 否则,创建一个 FlutterEngine 实例

FlutterEngine 构造函数的逻辑:

  1. 创建 DartExecutor
  2. 给 Flutter Framework 提供平台能力的封装
  3. 配置 FlutterEngine 的初始化参数
  4. FlutterJNI 初始化
  5. FlutterRenderer等绘制能力相关初始化

FlutterView 初始化

在完成FlutterEngine的初始化之后会进行FlutterView的初始化,如代码清单4-12所示。

// 代码清单4-12 engine/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
View onCreateView(LayoutInflater inflater,
    @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    ensureAlive();
    if (host.getRenderMode() == RenderMode.surface) { // 第1步,判断Host所提供的RenderMode
    FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView( // 见代码清单4-13
      host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
    // 为Host预留一个接口,用于在FlutterSurfaceView创建完成后提供自定义配置入口
    host.onFlutterSurfaceViewCreated(flutterSurfaceView);
    flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
  } else { ...... } // SKIP FlutterTextureView逻辑类似
  // 第2步,间接为Host提供首帧渲染/停止渲染的回调
  flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
  flutterSplashView = new FlutterSplashView(host.getContext()); // 第3步,闪屏相关逻辑
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    flutterSplashView.setId(View.generateViewId());
  } else {
    flutterSplashView.setId(486947586);
  } // 开始渲染首帧,见代码清单4-15
  flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provide
     SplashScreen());
  // 注意,FlutterEngine和FlutterView建立连接,这是后面能够渲染的基础
  flutterView.attachToFlutterEngine(flutterEngine); // 第4步,见代码清单4-17
  return flutterSplashView;
}

以上逻辑可大致分为4步:

  • 第1步判断Host所提供的RenderMode,一般默认是Surface,因而会初始化一个FlutterSurfaceView的实例,并以此为参数初始化FlutterView。该过程的核心逻辑如代码清单4-13和代码清单4-14所示。
  • 第2步向FlutterView注册一个flutterUiDisplayListener 对象,用于向Host提供首帧渲染以及停止渲染的回调,相关调用链可自行分析。
  • 第3步完成闪屏的初始化,并调用displayFlutterViewWithSplash方法用于从闪屏到首帧的过渡,相关逻辑如代码清单4-15所示。
  • 第4步中FlutterView会调用attachToFlutterEngine方法完成最后的初始化工作,相关逻辑如代码清单4-17所示。

接下来依次分析前面内容提及的4个步骤。首先是FlutterSurfaceView的初始化,其最终会调用init方法,如代码清单4-13所示。

// 代码清单4-13 engine/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
private void init() {
  if (renderTransparently) { // 透明
    getHolder().setFormat(PixelFormat.TRANSPARENT);
    setZOrderOnTop(true);
  } // 监听Embedder中Surface的状态变化,在回调中调用Engine对应的处理逻辑
  getHolder().addCallback(surfaceCallback);
  setAlpha(0.0f); // 保持透明,防止黑屏
}
private final SurfaceHolder.Callback surfaceCallback =
  new SurfaceHolder.Callback() {
    @Override // Embedder中,在Surface完成创建之后触发
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
      isSurfaceAvailableForRendering = true;
      if (isAttachedToFlutterRenderer) {
        connectSurfaceToRenderer(); // 通过本回调通知Engine开始渲染UI,见代码清单4-18
      } 
    }
    @Override public void surfaceChanged( ...... ) { ...... }
    @Override public void surfaceDestroyed( ...... ) { ...... }
};

以上逻辑会为当前Surface添加一个回调,用于在Surface创建完成后触发和FlutterRender的绑定,其中isAttachedToFlutterRenderer字段表示RederSurface是否绑定到FlutterRender,默认为false,设置为true的时机将在代码清单4-18中介绍。connectSurfaceToRenderer 方法也将在后面内容详细分析。

以上是FlutterSurfaceView的初始化逻辑。接着分析FlutterView的初始化逻辑,核心逻辑也在自身的init方法中,如代码清单4-14所示。

// 代码清单4-14 flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
private void init() {
  if (flutterSurfaceView != null) { // 默认情况下用于一个完整独立的Flutter UI
    addView(flutterSurfaceView);
  } else if (flutterTextureView != null) { // Flutter UI作为原生UI的一部分时
    addView(flutterTextureView);
  } else { // Flutter UI作为宿主,Platform View作为其一部分 
    addView(flutterImageView);
  }
  setFocusable(true);
  setFocusableInTouchMode(true);
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
  }
}

以上是FlutterView的初始化逻辑,FlutterViewFrameLayout的子类,它会选择RenderSurface 的具体实现之一作为自己的子View,并设置焦点、自动填充等属性。FlutterView初始化完成后,将会执行其展示逻辑,如代码清单4-15所示。

// 代码清单4-15 engine/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java
public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
  if (this.flutterView != null) { // 清理之前的FlutterView
    this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
    removeView(this.flutterView);
  }
  if (splashScreenView != null) { // 清理之前的闪屏
    removeView(splashScreenView);
  }
  this.flutterView = flutterView;
  addView(flutterView); // 显示FlutterView
  this.splashScreen = splashScreen;
  if (splashScreen != null) { // 存在闪屏资源
    if (isSplashScreenNeededNow()) { // 立即显示闪屏,并通过Listener触发切换
      splashScreenView = splashScreen.createSplashView(getContext(), 
          splashScreenState);
      addView(this.splashScreenView); // 加入View Tree
      flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener); // 见代码清单4-16
    } else if (isSplashScreenTransitionNeededNow()) { // 立即进入:从闪屏切换到FlutterView状态
      splashScreenView = splashScreen.createSplashView(getContext(), 
          splashScreenState);
      addView(splashScreenView); // 显示闪屏
      transitionToFlutter(); // 然后立即开始切换到FlutterView
    } else if (!flutterView.isAttachedToFlutterEngine()) { // 见代码清单4-16
        // 若当前FlutterView尚未与FlutterEngine绑定,则监听其绑定状态,完成绑定时触发回调
      flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener);
    }
  } // 若不存在闪屏资源,则一直显示FlutterView
}
private void transitionToFlutter() {
  transitioningIsolateId = flutterView.getAttachedFlutterEngine().
      getDartExecutor().getIsolateServiceId();
 splashScreen.transitionToFlutter(onTransitionComplete);
}

以上逻辑中,FlutterView表示最终将显示UI的ViewsplashScreenView表示首帧渲染前显示的闪屏UI,其也继承自FrameLayoutdisplayFlutterViewWithSplash方法首先会完成两者的清理工作,其次将FlutterView添加到布局中。如果存在闪屏资源,则会分成如下3种情况进行处理。

  • 当前需要展示闪屏:将splashScreenView添加到布局中,并注册flutterUiDisplayListener 用于在首帧渲染后触发切换,具体逻辑如代码清单4-16所示。

  • 当前需要从闪屏切换到首帧:将splashScreenView添加到布局中,并立即触发切换到首帧的动画。

  • FlutterView暂时还没有绑定(attached)到FlutterEngine:为FlutterView注册一个flutterEngineAttachmentListener监听器,用于FlutterView调用attachToFlutterEngine方法时触发,这部分内容将在代码清单4-17中详细介绍。

flutterUiDisplayListenerflutterEngineAttachmentListener的具体逻辑见代码清单4-16。

// 代码清单4-16 engine/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java
@NonNull 
private final FlutterView.FlutterEngineAttachmentListener 
  flutterEngineAttachmentListener = new FlutterView.FlutterEngineAttachmentListener() {
    @Override // FlutterView和FlutterEngine绑定成功后触发
    public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {
      flutterView.removeFlutterEngineAttachmentListener(this);
      displayFlutterViewWithSplash(flutterView, splashScreen); // 见代码清单4-15
    }
    @Override
    public void onFlutterEngineDetachedFromFlutterView() {}
  };
@NonNull 
private final FlutterUiDisplayListener flutterUiDisplayListener =
    new FlutterUiDisplayListener() {
      @Override // FlutterView开始渲染时触发
      public void onFlutterUiDisplayed() {
        if (splashScreen != null) {
          transitionToFlutter(); // Flutter UI开始渲染时从闪屏开始变换
        }
      }
      @Override public void onFlutterUiNoLongerDisplayed() {  }
    };

以上逻辑中,onFlutterUiDisplayed即首帧完成渲染的时机,该回调中会触发前面内容提到的transitionToFlutter,用于触发从闪屏到首帧的过渡动画。onFlutterEngineAttachedToFlutterViewFlutterViewFlutterEngine完成绑定的时机,该回调中将会再次触发displayFlutterViewWithSplash 的逻辑。

下面分析FlutterViewFlutterEngine的绑定过程,其发起点位于代码清单4-12的第4步,即attachToFlutterEngine方法,其逻辑如代码清单4-17所示。

// 代码清单4-17 engine/shell/platform/android/io/flutter/embedding/android/FlutterView.java
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
  Log.v(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
  if (isAttachedToFlutterEngine()) {
    if (flutterEngine == this.flutterEngine) { return; }
    detachFromFlutterEngine(); // 解除绑定
  }
  this.flutterEngine = flutterEngine; 
  // 获取FlutterEngine对于FlutterEngine绘制能力的封装:FlutterRender
  FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
  isFlutterUiDisplayed = flutterRenderer.isDisplayingFlutterUi();
  // FlutterView和FlutterEngine进行绑定的本质是RenderSurface与FlutterRender的attach
  renderSurface.attachToRenderer(flutterRenderer); // 见代码清单4-18
  flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener); // 对外通知
  // SKIP 创建和初始化各种与UI相关的桥接功能类
  sendUserSettingsToFlutter(); // 发送与屏幕相关的配置给Engine
  localizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
  sendViewportMetricsToFlutter(); // 发送窗口大小、设备分辨率等信息
  flutterEngine.getPlatformViewsController().attachToView(this);
  for (FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
    listener.onFlutterEngineAttachedToFlutterView(flutterEngine); // 通知绑定完成
  }
  if (isFlutterUiDisplayed) { // 如果已经在展示Flutter UI,则直接触发回调
    flutterUiDisplayListener.onFlutterUiDisplayed();
  }
}

由代码清单4-12可知,attachToFlutterEngine方法在displayFlutterViewWithSplash逻辑之后。因此,存在闪屏时通常会先触发代码清单4-15中的第3种情况,然后触发第1种情况。下面开始分析attachToFlutterEngine方法自身的逻辑。

以上逻辑主要是FlutterViewFlutterEngine的绑定,共分为3步。

  • 第1步是FlutterView中各成员的赋值。
  • 第2步是RenderSurfaceFlutterRender的绑定,具体如代码清单4-18所示,这部分内容将在后面详细分析。
  • 第3步会完成各种平台能力、Platform View的初始化,sendUserSettingsToFluttersendViewportMetricsToFlutter会将平台属性等信息通过FlutterJNI打包发送给Engine,在此不再赘述。

最后将会触发绑定完成的通知,用于前面内容的闪屏逻辑触发等操作。

下面详细分析RenderSurfaceFlutterRender的绑定。这里以FlutterSurfaceView为例,如代码清单4-18所示。

// 代码清单4-18 engine/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
  Log.v(TAG, "Attaching to FlutterRenderer.");
  if (this.flutterRenderer != null) {
    this.flutterRenderer.stopRenderingToSurface();
    this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
  }
  this.flutterRenderer = flutterRenderer;
  isAttachedToFlutterRenderer = true; // 开始监听FlutterRender何时渲染出首帧
  this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
  if (isSurfaceAvailableForRendering) { // Surface已经可用,一般这里为false
    connectSurfaceToRenderer(); // 通常由代码清单4-13的surfaceCallback触发
  }
}
private final FlutterUiDisplayListener flutterUiDisplayListener =
    new FlutterUiDisplayListener() {
      @Override // Engine渲染首帧时通过FlutterJNI的onFirstFrame方法触发
      public void onFlutterUiDisplayed() {
        setAlpha(1.0f); // Engine开始完全接管当前Surface的渲染
        if (flutterRenderer != null) {
          flutterRenderer.removeIsDisplayingFlutterUiListener(this);
        }
      }
      @Override public void onFlutterUiNoLongerDisplayed() {  }
    };
private void connectSurfaceToRenderer() {
  if (flutterRenderer == null || getHolder() == null) {
    throw new IllegalStateException(" ...... ");
  } // 告知Engine可以开始渲染
  flutterRenderer.startRenderingToSurface(getHolder().getSurface());
}

以上逻辑和FlutterViewattachToFlutterEngine方法类似,而FlutterSurfaceViewFlutterView中实际负责渲染的类,FlutterRenderFlutterEngine中对底层渲染能力的抽象封装,因此这里的逻辑主要是与渲染相关的。

对Flutter的渲染而言,有两个重要时机:一个是Embedder的Surface创建完成(由Embedder决定);另一个是Engine的首帧数据准备好(由Engine决定)。flutterUiDisplayListener负责监听Engine的首帧数据准备完成时机,并在此时将自身透明度设置为1,表明自身将完全接管当前Surface的渲染。isSurfaceAvailableForRendering 方法用于判断Surface是否准备完成,通常这里为falseconnectSurfaceToRenderer通常在Surface的创建回调中触发,即在代码清单4-13所示的逻辑中,其自身逻辑最终会调用nativeSurfaceCreated方法,用于通知Engine侧:Embedder中的Surface已经准备完毕,可以进行首帧渲染。

需要注意的是,以上大部分逻辑实际上是基于回调触发的,有以下两个关键节点。

  • (1)Embedder创建Surface,在创建完成的回调中告知Engine开始渲染。

  • (2)Engine基于Surface开始渲染,并在首帧完成时通过FlutterJNI告知Embedder首帧数据已经绘制在Surface上。

Engine对于UI的绘制实际上是由Framework驱动的,故以上逻辑其实不是线性的,即Embedder只负责Surface的创建和通知,具体何时完成渲染取决于Engine,而Engine的渲染取决于FrameworkFramework的启动其实又是由Embedder所驱动的。

总结

FlutterActivityAndFragmentDelegateonCreateView()方法中主要做的工作:

  1. 初始化一个FlutterSurfaceView的实例, 并以此为参数创建FlutterViewFrameLayout的子类)
  2. 注册一个flutterUiDisplayListenerHost提供首帧渲染以及停止渲染的回调,首帧渲染完成的回调主要用于触发从闪屏到首帧的过渡动画
  3. 闪屏相关逻辑的初始化
  4. attachToFlutterEngine,将FlutterViewFlutterEngine绑定(包括RenderSurfaceFlutterRender的绑定、各种平台能力、Platform View的初始化)

下面开始分析Framework的启动。

Framework 的启动

至此,Embedder已经完成FlutterEngineFlutterView的初始化,Framework的运行环境也已经准备完毕,可以开始执行Framework的逻辑了,其触发时机位于FlutterActivityonStart中,该方法最终将调用DelegatedoInitialFlutterViewRun方法,如代码清单4-19所示。

// 代码清单4-19 engine/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java
private void doInitialFlutterViewRun() {
  if (host.getCachedEngineId() != null) { return; } // 预加载的Engine无法通过本方法启动
  if (flutterEngine.getDartExecutor().isExecutingDart()) { return; } // 已经在运行
  String initialRoute = host.getInitialRoute(); // 获取初始路由
  if (initialRoute == null) { // 8.7节将详细介绍Flutter的路由体系
    initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
    if (initialRoute == null) { initialRoute = DEFAULT_INITIAL_ROUTE; } // 默认路由
  flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);
  String appBundlePathOverride = host.getAppBundlePath();
  if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
    appBundlePathOverride = FlutterInjector.instance().flutterLoader().
        findAppBundlePath();
  } // 自定义assets资源地址,默认为flutter_assets
  DartExecutor.DartEntrypoint entrypoint = new DartExecutor.DartEntrypoint(
                appBundlePathOverride, host.getDartEntrypointFunctionName());
  flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
}

以上逻辑中,首先会进行相关检查工作。appBundlePathOverride字段用于向开发者提供一个自定义资源加载路径的接口,默认为flutter_assets。以上逻辑最终会构造一个DartExecutor.DartEntrypoint对象,并调用FlutterJNInativeRunBundleAndSnapshotFromLibrary方法完成Framework的启动。

至此,Embedder 完整启动流程如下:

在这里插入图片描述

Engine入口整理

Embedder的启动流程,如图4-2所示。

在这里插入图片描述

在启动流程中,通过API回调或者JNI主动调用,Embedder已经多处调用了Engine的逻辑,其中有 5 处逻辑对于 Engine 的初始化至关重要,整理如下。

  • JNI_OnLoad:在System.loadLibrary时触发,将完成Engine接口和Embedder接口的绑定注册。

  • nativeInitFlutterLoader主动调用,在相关资源准备完成后调用,主要负责向Engine传递必要的初始化参数。

  • nativeAttach:Embedder的FlutterEngine对象在完成必要的初始化后调用,将触发Engine中关键类的初始化逻辑。

  • nativeSurfaceCreated:Java侧的Surface对象(SurfaceView)准备完成后调用,将触发Engine中有关绘制的逻辑的初始化。

  • nativeRunBundleAndSnapshotFromLibraryFlutterEngineFlutterView均初始化完成后,在HostonStart生命周期中主动调用,用于触发Framework的相关初始化工作。

Engine关键类及启动流程

在这里插入图片描述

在图4-3中,Engine最底层的PlatformViewAndroidJNI抽象了与Embedder进行交互的接口,最上层的PlatformConfiguration则抽象了与Framework进行交互的接口。在Flutter中,基于Dart VMFramework能够和基于Java VMEmbedder相互配合,正是得益于连接着PlatformViewAndroidJNIPlatformConfiguration的这条“伟大航路”。例如Vsync信号的注册与响应,以及Platform Channel的运行,都是通过这条航路来完成FrameworkEmbedder的协作。

要彻底理解Engine的运行原理,就要从两个角度理解图4-3所体现的类结构。

  • 第1个角度是从Embedder出发,转发到Framework。在图4-3中,Embedder的入口是PlatformViewAndroidJNI,它通过shell_holder持有AndroidShellHolderAndroidShellHolder又通过shell_字段持有ShellShellEngine的枢纽,它可以通过platform_view_字段转发逻辑到PlatformView,可以通过rasterizer_字段转发逻辑到Rasterizer,可以通过engine_字段转发逻辑到EngineEngineFlutter FrameworkFlutter Engine中的抽象封装,Engine通过runtime_controller_字段持有RuntimeController,顾名思义,RuntimeController就是Flutter Framework运行时的控制者。具体来说,RuntimeController通过vm_字段持有DartVM,又通过root_isolate_字段持有DartIsolate,而后者的父类UIDartState通过platform_configuration_字段持有PlatformConfigurationPlatformConfiguration是和Flutter Framework直接进行交互的类,经过以上路径,一个Embedder的逻辑可以转发到Framework

  • 第2个角度是从Framework出发,转发到Embedder。这就涉及Flutter Engine中代理模式(主要是图4-3中Delegate或者Client结尾的类)的巧妙运用。具体来说,Flutter Framework的逻辑将转发到PlatformConfiguration,该类通过client_字段持有PlatformConfigurationClient,其正是RuntimeController的父类,因此PlatformConfiguration通过代理持有RuntimeController,而RuntimeController又通过RuntimeDelegate持有EngineEngine又通过Engine::Delegate持有ShellShellFlutter Engine的枢纽,它通过platform_view_字段持有PlatformView,而PlatformView的子类PlatformViewAndroid又通过jni_facade_字段持有PlatformViewAndroidJNI, 该类正是和Embedder进行交互的类。经过以上路径,一个Framework逻辑可以转发到Embedder

事实上,以上只是UI线程和Platform线程的交互,Flutter Engine可以通过Shell完成UI线程、Platform线程、I/O线程和Raster线程的相互转发。

在这里插入图片描述

对Android平台而言,AndroidShellHolder通常会创建和管理4个线程,分别是Platform线程、UI线程、Raster线程和I/O线程。

  • Platform线程即Android的主线程,Engine不会在此线程执行特别的任务,主要用于和Embedder的交互和通信,如Platform ChannelVsync监听。
  • UI线程是Framework逻辑所在的线程,UI相关的操作(如3棵树的更新、动画等)都是在UI线程中完成的。
  • Raster线程负责将UI线程生成的数据做进一步处理并通过GPU上屏,之所以设置单独一个线程是为了提升性能。
  • I/O线程负责处理一些繁重的、阻塞性的任务,如图片的解码、资源的存取。

总的来说,Platform线程和UI线程与开发者的关系最为密切,比如Platform ChannelEmbedder中必须通过主线程(Platform线程)发送,否则会失败。

需要注意的是,线程的创建和管理由Engine的宿主(如AndroidShellHolder)决定,对Engine中的Engine类而言,它所持有的只是4个TaskRunner对象,至于这4个TaskRunner对象是来自同一个线程还是4个独立的线程,Engine类是无感知的。

Surface关键类及启动流程

Flutter绘制体系介绍

移动端绘制体系可以分为7层,如图4-5所示。
图4-5 移动端绘制体系

图4-5中,由上至下共分为7层。

  • 第1层是应用层,即开发者编码实现的各种UI界面,供用户进行交互,即使在不同平台,用户仍然可以获得使用上几乎一致的体验。
  • 第2层是框架层,是开发者进行UI开发的基本单位,比如Android中的View和Flutter中的Widget,框架层的特点是为开发者屏蔽了底层的绘制细节,提供了可复用的最小单元。
  • 第3层是高级绘图接口(比如Skia),它们是框架层的基石,也可以基于高级绘图接口进行UI开发,但是需要处理的细节更多,框架层可以认为是高级绘图接口的语义化封装。
  • 第4层是底层绘图接口,需要注意的是,这里的底层是相对Skia等高级绘图接口而言的。底层绘图接口提供了更接近图形学领域的API,还提供了绘制相关的最小单元的指令和属性。对底层绘图接口而言,最大的挑战就是需要处理不同硬件的兼容性问题,即不同厂商提供的硬件接口可能是不一样的,作为软件层,底层绘图接口需要处理。
  • 第5层是桥接层,EGL的全称是Embedded Graphics Library,它是厂商提供的、符合统一规范的API,用以解决第4层在直接操作硬件接口时可能遇到的兼容性问题。
  • 第6层是GPU,它比CPU拥有更多的计算单元和更少的存储单元,因而非常适合并发计算的场景。通常来说,通过CPU进行绘制被称为软绘,通过GPU进行绘制被称为硬绘,现代移动设备大多采用硬绘,因为硬绘的性能更佳。
  • 第7层是渲染区,最典型的就是物理显示器,此外还有一些虚拟的渲染区,比如离屏Buffer、Virtual Display(Android)。应用层的UI实际要经过中间5层的处理,才能最终到达渲染区,完成UI的显示。

需要注意的是,以上分层并不是绝对的,即框架层也可能会直接调用底层绘图接口,底层绘图接口也可能直接操作渲染区,但以上分层是大多数UI框架会遵循和追求达到的架构。

对Flutter Engine来说,其绘制相关的关键类如图4-6所示。
图4-6 Surface关键类

图4-6中,PlatformViewAndroidFlutter Engine中负责渲染的关键类,它持有两个关键对象,即通过android_context_字段持有AndroidContext,通过android_surface_字段持有AndroidSurface

AndroidContext提供了绘制相关的上下文,对于OpenGL模式(Android平台默认的模式,下同),其具体子类为AndroidContextGLAndroidContextGL通过EGLContextEGLConfig等对象为Flutter Engine的渲染提供上下文环境。

AndroidSurface提供渲染的输出,是帧数据的消费者,对于OpenGL模式,其具体子类为AndroidSurfaceGL,该类通过onscreen_surface_字段和offscreen_surface_字段持有两个AndroidEGLSurface对象。

AndroidEGLSurfaceFlutter Engine对于EGL API的封装,其内部持有EGLSurfaceEGLDisplayEGLContext等关键实例,它们将负责真正的渲染逻辑。

此外,AndroidNativeWindowEmbedder中创建的SurfaceEngine中的等价表示,AndroidSurfaceGL通过native_window_字段持有该类,用于onscreen_surface_实例的创建。

上述类都位于Platform线程,而Flutter Engine的渲染实际发生在Raster线程,为此,Raster线程中的GPUSurfaceGL将通过delegate_字段持有一个GPUSurfaceGLDelegate,其具体实现正是AndroidSurfaceGL,如此,Raster线程便可以操作AndroidEGLSurface了。

Flutter EngineSurface的启动流程中,将多次调用EGL API,虽然它们分散在各段代码中,但其调用顺序仍然会严格遵循图4-7所体现的流程规范。

图4-7 EGL状态转换

图4-7展示了Flutter Engine中从Surface准备到UI最终上屏所经历的7个关键EGL API调用,具体作用已在图中注明。

Surface相关的初始化流程如下:

在这里插入图片描述

Dart Runtime 关键类及启动流程

Dart Runtime涉及的关键类及其关系如图4-9所示。

在这里插入图片描述

在图4-9中,Shell 是我们已经非常熟悉的类,它通过持有DartVMRef对象,进而持有真正的Dart VM。但从全局来看,真正持有Dart VM的其实是RuntimeController

首先分析图4-9的右半部分,DartVM在创建前会先执行DartVMData的创建,该类包括两个关键字段——vm_snapshot_isolate_snapshot_,这两个字段记录了vm-service Isolate和主Isolate进行启动所需要的Snapshot数据(通过DartSnapshot表示)。DartSnapshotdata_字段和instructions_字段分别持有当前DartIsolate启动所需要的数据序列和指令序列。DartSnapshot的底层表示其实是Mapping的子类,比如文件内存映射(FileMappingDebug模式下),又或者动态链接库的符号映射(SymbolMappingRelease模式下)。

再分析图4-9的左半部分,RuntimeController通过isolate_snapshot_字段持有DartSnapshot,进而可以基于该对象进行DartIsolate的初始化,DartIsolate继承自UIDartState,是Engine对于Dart_IsolateDart VM的API接口)的抽象表示。总的来说,Isolate的启动就是加载并运行对应的Snapshotso文件或者Kernel文件)。

在这里插入图片描述


参考:《Flutter内核源码剖析》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/600198.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python知识|关联两个列表

Python 知识 | 关联两个列表 Python 是一种高效的编程语言&#xff0c;它能够很好地进行数据处理&#xff0c;因此在 SEO 领域得到广泛的应用。关联两个列表是一种基础的数据处理方法&#xff0c;本文将为读者详细介绍如何使用 Python 关联两个列表&#xff0c;并给出一些实例…

Rust每日一练(Leetday0018) N皇后II、最大子数组和、螺旋矩阵

目录 52. N皇后 II N Queens II &#x1f31f;&#x1f31f;&#x1f31f; 53. 最大子数组和 Maximum Subarray &#x1f31f;&#x1f31f; 54. 螺旋矩阵 Spiral Matrix &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏…

chatgpt赋能python:Python关键词匹配:优化你的SEO策略

Python关键词匹配&#xff1a;优化你的SEO策略 在当今数字时代&#xff0c;搜索引擎是许多人获取信息和发现新客户的主要渠道。对于企业或个人网站来说&#xff0c;优化SEO&#xff08;搜索引擎优化&#xff09;策略变得至关重要。在SEO的世界里&#xff0c;关键词匹配是一个重…

springboot+vue编程训练考试测试系统设计与实现

本编程训练系统管理员功能有管理员和用户。管理员功能有个人中心&#xff0c;用户管理&#xff0c;题库资源管理&#xff0c;用户交流&#xff0c;试卷管理&#xff0c;留言板管理&#xff0c;试题管理&#xff0c;系统管理&#xff0c;考试管理。用户可以查看题库资源&#xf…

chatgpt赋能python:使用Python进行人民币兑换-带着您深入了解

使用Python进行人民币兑换 - 带着您深入了解 在当今日益全球化的世界里&#xff0c;进行货币兑换已成为很正常的事情。人民币是世界上最常用的货币之一&#xff0c;而Python作为一种强大的编程语言&#xff0c;可以帮助我们进行人民币兑换计算。本文将介绍如何使用Python进行人…

使用CCProxy搭建windows系统阿里云socket代理服务器 教程

目录 1. 通过windows远程连接阿里云服务器2. 云服务器上安装CCProxy2.1 CCProxy下载安装2.2 设置协议、代理服务、端口号和ip2.3 新建代理用户2.4 确保你的CCProxy启动了服务 3. 在阿里云实例安全组中开放代理端口3.1 前往安全组页面3.2 添加你对应服务的开放端口 总结 欢迎关注…

Java键盘事件处理及监听机制解析

文章目录 概念KeyEventKeyListener代码演示总结 概念 Java事件处理采用了委派事件模型。在这个模型中&#xff0c;当事件发生时&#xff0c;产生事件的对象将事件信息传递给事件的监听者进行处理。在Java中&#xff0c;事件源是产生事件的对象&#xff0c;比如窗口、按钮等&am…

java企业级信息系统开发学习笔记11 利用MyBatis实现条件查询

文章目录 一、学习目标1.对学生表进行条件查询&#xff0c;涉及姓名、性别和年龄三个字段。2.比如查询姓“吴”&#xff0c;性别为“女”&#xff0c;同时年龄为19的学生记录 二、打开上一笔记mybatis项目三、对学生表实现条件查询&#xff08;一&#xff09;创建学生映射器配置…

如何使用wget下载(录制)流媒体或直播推流文件,以及下载出现“正在把输出重定向至 “wget-log.1””错误该怎么办

下载推流文件其实非常简单&#xff0c;就是通常使用的最简单的命令&#xff1a; wget URL -O 输出文件名这里最好设置一下输出文件名&#xff0c;不然很可能下载的文件名称会很奇怪&#xff0c;导致格式识别错误或者其他问题。 不过&#xff0c;如果你直接使用这个命令很可能…

微信小程序nodejs+vue图书馆自习室座位管理系统vax51

系统设计需要从用户和管理员的实际需求开始&#xff0c;以了解他们需要实施哪些功能以及他们可以包括哪些管理工作。 考虑到图书馆座位预约系统小程序系统设计的特点&#xff0c;应满足几个要求&#xff1a;开发语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#…

[MySQL从入门到精通]MySQL概述及安装

前言 你是否想过我们在登录各种各样的网站时候&#xff0c;所需要输入的账号密码&#xff0c;它们存储在哪里&#xff1f;你猜对了&#xff0c;就是今天我们所要说的数据库 目录 前言 1.数据库的概述 1.1 数据 1.2 数据库 1.3数据库的种类 1.4数据库管理系统 2.MySQL的…

CSS 水平垂直居中的方式

目录 在不知道子元素宽高的情况下&#xff0c;水平垂直居中的六种方式&#xff1a; 1、弹性盒子布局方式来实现&#xff08;flex&#xff09;。 2、绝对定位 transform 3、table标签 4、display&#xff1a;table-cell 5、display: grid 6、writing-mode 属性 在不知道子…

chatgpt赋能python:10年Python编程经验的工程师推荐:免费的PythonIDE

10年Python编程经验的工程师推荐&#xff1a;免费的Python IDE 作为一名有着10年Python编程经验的工程师&#xff0c;我一直在寻找可以帮助我提高效率的Python IDE。在这个过程中&#xff0c;我试用了许多付费和免费的IDE&#xff0c;最终发现了一些免费的Python IDE&#xff…

chatgpt赋能python:Python关闭程序语句:顺畅退出程序的方式

Python关闭程序语句&#xff1a;顺畅退出程序的方式 当我们创建一个Python程序时&#xff0c;我们需要确保该程序以正确的方式结束&#xff0c;而不是通过强制终止或强制关闭窗口这样的极端行为。 这种情况可能会导致数据丢失和资源泄漏&#xff0c;从而影响程序的稳定性和可靠…

chatgpt赋能python:Python在计量中的应用

Python在计量中的应用 Python是一种高级编程语言&#xff0c;已经成为了计量学中不可缺少的工具。 Python有一个强大的生态系统&#xff0c;包括庞大的第三方库&#xff0c;这些库提供了丰富的机器学习、数据可视化和分析工具&#xff0c;这些工具在计量学中发挥了极为重要的作…

【vue2+docx-preview】实现docx文档预览(自定义修改样式)

前言 使用vue预览docx的解决方案&#xff0c;过去还有一种Mammoth 。 它旨在转换 .docx 文档&#xff08;例如由 Microsoft Word 创建的文档&#xff09;&#xff0c;并将其转换为 HTML。 不支持样式。实现方式可以参考&#xff1a;Vue Word预览之mammoth.js 因此选择换成支持…

【thingsboard+NodeRed+chirpstack】实现Lora节点设备的数据上下行通讯

本文主要实现基于 thingsboard+NodeRed+chirpstack 实现 lora设备的数据上下行通讯。 NodeRed作为mqtt桥接器,在开源的社区版 thingsboard上实现 这里写目录标题 LoRa 设备上下行通讯方案数据上行数据下行Device 层面创建设备时,要添加 relation规则链层面灯控模块规则链规则…

【libtorch】pytorch源码编译生成c++ 17 libtorch记录

文章目录 1. 问题描述2. 编译安装前准备3. 编译安装4. 编译好之后使用 1. 问题描述 ubuntu20.04 ros2 humble使用1.8.0 libtorch出现coredump&#xff0c;提示加载模型失败&#xff1a; 原因&#xff1a; ros2 humble项目使用c17编译&#xff0c;c11的libtorch的库文件版本不配…

基于springboot+Vue的汽车商城销售4s店服务系统

基于Vue构建一个汽车服务商城&#xff0c;邀请各大商家入住平台&#xff0c;主要包括邀请洗车店、邀请汽车配件商店、邀请4s店入住、邀请汽车美容店入住、邀请汽车修理店入住平台等。这个平台为了给商家和用户提供便利&#xff0c;用户可以更方便体验汽车服务&#xff0c;商家可…

SpringBoot源码分析:SpringBoot启动源码(一)

一、概述 SpringBoot启动的源码分为前期初始化&#xff0c;和后期启动两个部分&#xff0c;我们从这两个部分开始介绍。 二、前期初始化 SpringBoot前期初始化主要由下面三行代码组成。 进入SpringApplication.getSpringFactoriesInstances方法 最终进入SpringFactoriesLoade…