IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android如何设计一个flutter容器 -> 正文阅读

[移动开发]Android如何设计一个flutter容器

无论是纯flutter应用还是flutter混合开发应用,flutter编写的代码都需要一个容器进行加载展示给用户,这与webview从本质上是一样的。因此有必要学习这个容器的主要功能、用法,以及在必要的时候去开发实现一些扩展功能或者修改一些默认行为,以便更好地与APP现有的运行框架融合。本文从flutter提供的embedding包入手分析,通过梳理flutter的加载过程了解其核心代码,以便后期进行定制开发。

embedding包核心代码分析

本节所讨论的代码位于io.flutter.embedding包中,包括.android.engine

FlutterActivity

当我们运行flutter的example工程时,其MainActivity就是继承该类。通过查看源码可以发现这个Activity有两种启动方式:NewEngineCachedEngine,分别对应flutter engine的两种管理策略。我们看下这两种启动方式的参数区别:

NewEngineIntentBuilderCachedEngineIntentBuilder
Class<? extends FlutterActivity> activityClass:启动activity类名,可以是FlutterActivity的子类,因此这里可以自定义Class<? extends FlutterActivity> activityClass
String initialRoute:初始路由地址,默认“/”String cachedEngineId:FlutterEngineCache的存储key
String backgroundMode:背景模式,分为透明(对应FlutterTextureView)和不透明(对应FlutterSurfaceView)两种。默认不透明,除非有明确的需求,否则不建议使用透明String backgroundMode

可以看到除了个别参数不一致,其余参数都是一样的。NewEngine方式因为每次都要构造一个新的engine实例,因此需要指定一个initialRoute;CachedEngine方式由于需要从FlutterEngineCache中获取缓存的engine,因此需要指定cachedEngineId。

onCreate方法开始,我们看到核心逻辑都在FlutterActivityAndFragmentDelegate中,而Activity/Fragment的功能被抽象为FlutterActivityAndFragmentDelegate.Host接口以统一Activity/Fragment二者的行为。这个接口是整个embedding包的重点之一,因此我们重点看下里面有哪些方法。

FlutterActivityAndFragmentDelegate.Host

由于接口方法比较多的,我把这些接口分为几类:

参数配置类

  • @NonNull Context getContext():返回activity本身
  • @Nullable Activity getActivity():返回activity本身
  • @NonNull Lifecycle getLifecycle():返回lifecycle实例
  • @NonNull FlutterShellArgs getFlutterShellArgs():flutter命令参数,参见FlutterShellArgs
  • @Nullable String getCachedEngineId():获取engine缓存key,用于engine复用场景
  • boolean shouldDestroyEngineWithHost():是否当host销毁时同时销毁engine,intent传入
  • @NonNullString getDartEntrypointFunctionName():dart执行入口方法名,默认“main”,manifest中配置
  • @NonNull String getAppBundlePath():自定义bundle路径,仅测试用,实际运行采用的是FlutterLoader#findAppBundlePath()
  • @Nullable String getInitialRoute():初始路由地址,支持intent传入和manifest中配置(当flutter页面作为启动页时)。当这个方法返回null且shouldHandleDeeplinking返回true时,初始路由地址将由Intent.getData()决定
  • boolean shouldHandleDeeplinking():是否处理Deeplinking,默认false。仅当getInitialRoute()返回null时生效,manifest中配置
  • @NonNull RenderMode getRenderMode():surface(默认)/texture两种模式,用于初始化FlutterView,intent传入
  • @NonNull TransparencyMode getTransparencyMode():opaque(默认)/transparent两种模式,用于初始化FlutterView,intent传入
  • boolean shouldAttachEngineToActivity():控制该activity中的FlutterFragment是否自动attach其engine到activity中,默认true。用于engine复用场景
  • boolean shouldRestoreAndSaveState():控制是否执行onRestore/onSave InstanceState方法,默认为true,在engine复用场景需要考虑实际情况

功能类

  • @Nullable SplashScreen provideSplashScreen()
    来源:io.flutter.embedding.android.SplashScreenProvider,提供flutter页面启动屏,默认null。可以通过在manifest中配置drawable资源,或者自己覆写此方法返回完全自定义的SplashScreen,参见DrawableSplashScreen
  • @Nullable FlutterEngine provideFlutterEngine(@NonNull Context context)
    来源:io.flutter.embedding.android.FlutterEngineProvider,提供一个engine实例给FlutterFragment用,通常FlutterFragment不应该自己new一个engine,而应该托付给FlutterActivity去生成。默认返回null,这样FlutterActivityAndFragmentDelegate内部会自己去生成。调用入口在io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#setupFlutterEngine
  • @Nullable PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine)
    提供一个PlatformPlugin实例,在FlutterFragmentFlutterActivity中都是直接new了一个并返回,没有托付关系。
  • boolean popSystemNavigator()
    来源:io.flutter.plugin.platform.PlatformPlugin.PlatformPluginDelegate,处理原生页面栈退出逻辑,默认返回false,交由flutter framework处理,默认逻辑是finish activity或pop fragment(代码在io.flutter.plugin.platform.PlatformPlugin#popSystemNavigator)。如果想自己处理,则可以覆写此方法。

生命周期类

  • void detachFromFlutterEngine()
    当engine被attach到其他activity时回调,此时应当停止当前activity与engine的交互。
  • void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
    来源:io.flutter.embedding.android.FlutterEngineConfigurator,当engine被创建后并attach到FragmentActivity后回调,默认调用GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine)
  • void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine)
    来源:io.flutter.embedding.android.FlutterEngineConfigurator,engine detach或destroy前回调,与configureFlutterEngine()回调呼应,默认空实现。
  • void onFlutterSurfaceViewCreated(@NonNull FlutterSurfaceView flutterSurfaceView)
    FlutterSurfaceView被创建后attach前回调,默认空实现
  • void onFlutterTextureViewCreated(@NonNull FlutterTextureView flutterTextureView)
    FlutterTextureView被创建后attach前回调,默认空实现
  • void onFlutterUiDisplayed()
    FlutterView开始绘制时回调,默认为一个无关实现
  • void onFlutterUiNoLongerDisplayed()
    FlutterView停止绘制时回调,默认空实现

终于把Host的方法讲完了,由于这些方法都是供FlutterActivityAndFragmentDelegate调用的,所以真正的加载流程还得看这个类。

FlutterActivityAndFragmentDelegate

本节介绍FlutterActivityAndFragmentDelegate几个比较重要的方法。

void onAttach(@NonNull Context context)

  • 调用处
  1. activity的onCreate方法
  2. fragment的onAttach方法
    创建FlutterActivityAndFragmentDelegate实例,并随即调用了其onAttach方法。
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onAttach
  void onAttach(@NonNull Context context) {
    ensureAlive();

    // 1. 当且仅当flutterEngine为null时创建engine实例
    if (flutterEngine == null) {
      setupFlutterEngine();
    }

    // 2. 通知flutter插件其已attach到activity中,插件收到onAttachToActivity等回调
    if (host.shouldAttachEngineToActivity()) {
      flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
    }

    // 3. 创建新的PlatformPlugin实例
    platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);

    // 4. 执行engine配置的hook回调
    host.configureFlutterEngine(flutterEngine);
  }

看下FlutterEngine是怎么来的,重点关注其构造参数:

// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#setupFlutterEngine
/* package */ void setupFlutterEngine() {
    // 1. 如果是engine复用,则返回复用的engine
    String cachedEngineId = host.getCachedEngineId();
    if (cachedEngineId != null) {
      flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
      isFlutterEngineFromHost = true;
      if (flutterEngine == null) {
        throw new IllegalStateException(
            "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
                + cachedEngineId
                + "'");
      }
      return;
    }

    // 2. 如果用户自己继承FlutterActivity并重写provideFlutterEngine返回了实例,则返回用户自己的
    flutterEngine = host.provideFlutterEngine(host.getContext());
    if (flutterEngine != null) {
      isFlutterEngineFromHost = true;
      return;
    }

    // 3. 默认创建的engine实例
    flutterEngine =
        new FlutterEngine(
            host.getContext(),
            host.getFlutterShellArgs().toArray(),
            /*automaticallyRegisterPlugins=*/ false,
            /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
    isFlutterEngineFromHost = false;
  }
  • isFlutterEngineFromHost:这个参数会影响到engine的销毁逻辑,可以理解为engine是否为用户自己生成(维护)的一个标记。
  • automaticallyRegisterPlugins:是否在engine构造方法中注册插件。默认的engine填的是false,也就是默认是不会在构造engine时注册插件,通过前面的分析我们知道默认会在Host#configureFlutterEngine中才去注册插件。

接下来是ActivityControlSurface#attachToActivity

//io.flutter.embedding.engine.FlutterEngineConnectionRegistry#attachToActivity(ExclusiveAppComponent<android.app.Activity>, Lifecycle)
  @Override
  public void attachToActivity(@NonNull ExclusiveAppComponent<Activity> exclusiveActivity, @NonNull Lifecycle lifecycle) {
    if (this.exclusiveActivity != null) {
      this.exclusiveActivity.detachFromFlutterEngine();
    }
    // If we were already attached to an app component, detach from it.
    detachFromAppComponent();

    if (this.activity != null) {
      throw new AssertionError("Only activity or exclusiveActivity should be set");
    }
    this.exclusiveActivity = exclusiveActivity;
    attachToActivityInternal(exclusiveActivity.getAppComponent(), lifecycle);
  }


  private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) {
    this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle);

    // 激活PlatformViewsController:初始化PlatformViewsChannel实例,这样PlatformView才真正可用.
    flutterEngine
        .getPlatformViewsController()
        .attach(activity, flutterEngine.getRenderer(), flutterEngine.getDartExecutor());

    // 调用ActivityAware回调,更新attach状态.
    for (ActivityAware activityAware : activityAwarePlugins.values()) {
      if (isWaitingForActivityReattachment) {
        activityAware.onReattachedToActivityForConfigChanges(activityPluginBinding);
      } else {
        activityAware.onAttachedToActivity(activityPluginBinding);
      }
    }
    isWaitingForActivityReattachment = false;
  }

从这里回过头去看host.shouldAttachEngineToActivity()这个值,当我们想要复用engine时,即同一个engine要attach到不同的activity时,应该返回true还是false呢?其实还是应该返回true的,我想这也是这个方法默认返回true的原因。不过也有特殊情况:当同一个activity中包含多个FlutterFragment,当这些FlutterFragment共享同一个engine时,由于PlatformViewsController只能attach一次(见PlatformViewsController#attach),此时第二个FlutterFragment执行flutterEngine.getActivityControlSurface().attachToActivity()时就会奔溃。
怎么办呢?其实就要判断当前activity是否已存在engine实例。FlutterActivity嵌套FlutterFragment的情况是不会发生的,所以我们只要考虑同一个activity多个FlutterFragment的情况。具体代码实现应该是在FlutterFragment的subclass中,重写shouldAttachEngineToActivity:

  1. 判断当前activity是否是FlutterActivity,如果是则返回true或super实现;
  2. 获取activity的fragmentManager,遍历所有fragments,当自身不是第一个FlutterFragment实例时,返回false,否则返回true;
  3. 作为2的备用方案:为activity实例设置一个tag,shouldAttachEngineToActivity()中先读取这个tag,若为null则返回true,随机设置tag为非空;

接着是创建PlatformPlugin,这个比较简单了。由于PlatformPluginFlutterActivityAndFragmentDelegate实例的成员变量且没有共享机制,所以每次都构建新的对象就行了。

最后是configureFlutterEngine()回调的执行,也比较简单,当activity为FlutterActivity及其子类时,最终是执行了FlutterActivity#configureFlutterEngine

// io.flutter.embedding.android.FlutterActivity#configureFlutterEngine
  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
  }

反射调用了:

/**
 * Generated file. Do not edit.
 * This file is generated by the Flutter tool based on the
 * plugins that support the Android platform.
 */
@Keep
public final class GeneratedPluginRegistrant {
  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new XXXPlugin());
    flutterEngine.getPlugins().add(new YYYPlugin());
    ...
  }
}

最终的添加方法:

//io.flutter.embedding.engine.FlutterEngineConnectionRegistry#add(io.flutter.embedding.engine.plugins.FlutterPlugin)
  @Override
  public void add(@NonNull FlutterPlugin plugin) {
    // 先做了Class检查,所以放心,不会把同一个插件的多个实例重复添加
    if (has(plugin.getClass())) {
      return;
    }
    // 缓存plugin Class,并调用插件onAttachedToEngine回调
    // 注意:这里可以知道该回调在engine生命周期内只会调用一次,除非手动remove掉
    plugins.put(plugin.getClass(), plugin);
    plugin.onAttachedToEngine(pluginBinding);

    // 缓存ActivityAware class并调用ActivityAware#onAttachedToActivity回调.
    if (plugin instanceof ActivityAware) {
      ActivityAware activityAware = (ActivityAware) plugin;
      activityAwarePlugins.put(plugin.getClass(), activityAware);

      if (isAttachedToActivity()) {
        activityAware.onAttachedToActivity(activityPluginBinding);
      }
    }

    // 缓存ServiceAware class并调用ServiceAware#onAttachedToService回调.
    if (plugin instanceof ServiceAware) {
      ServiceAware serviceAware = (ServiceAware) plugin;
      serviceAwarePlugins.put(plugin.getClass(), serviceAware);

      if (isAttachedToService()) {
        serviceAware.onAttachedToService(servicePluginBinding);
      }
    }

    // 缓存BroadcastReceiverAware class并调用BroadcastReceiverAware#onAttachedToBroadcastReceiver回调.
    if (plugin instanceof BroadcastReceiverAware) {
      BroadcastReceiverAware broadcastReceiverAware = (BroadcastReceiverAware) plugin;
      broadcastReceiverAwarePlugins.put(plugin.getClass(), broadcastReceiverAware);

      if (isAttachedToBroadcastReceiver()) {
        broadcastReceiverAware.onAttachedToBroadcastReceiver(broadcastReceiverPluginBinding);
      }
    }

    // 缓存ContentProviderAware class并调用ContentProviderAware#onAttachedToContentProvider回调.
    if (plugin instanceof ContentProviderAware) {
      ContentProviderAware contentProviderAware = (ContentProviderAware) plugin;
      contentProviderAwarePlugins.put(plugin.getClass(), contentProviderAware);

      if (isAttachedToContentProvider()) {
        contentProviderAware.onAttachedToContentProvider(contentProviderPluginBinding);
      }
    }
  }

View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)

  • 调用处
  1. activity的onCreate方法
  2. fragment的onCreateView方法
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onCreateView
@NonNull View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    // 构建FlutterView实例
    if (host.getRenderMode() == RenderMode.surface) {
      FlutterSurfaceView flutterSurfaceView =
          new FlutterSurfaceView(
              host.getActivity(), host.getTransparencyMode() == TransparencyMode.transparent);
      host.onFlutterSurfaceViewCreated(flutterSurfaceView);
      flutterView = new FlutterView(host.getActivity(), flutterSurfaceView);
    } else {
      FlutterTextureView flutterTextureView = new FlutterTextureView(host.getActivity());
      host.onFlutterTextureViewCreated(flutterTextureView);
      flutterView = new FlutterView(host.getActivity(), flutterTextureView);
    }

    // flutter第一帧绘制完成回调
    flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);

    // 创建flutter启动动画
    flutterSplashView = new FlutterSplashView(host.getContext());
    ...
    flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen());

    // flutterView连接engine,初始化各种插件支持交互,attach flutterRenderer准备绘制
    flutterView.attachToFlutterEngine(flutterEngine);

    return flutterSplashView;
  }

这个方法也是最关键的方法之一,从这里我们可以了解到FlutterView的创建和初始化流程。

void onStart()

  • 调用处
  1. activity的onStart方法
  2. fragment的onStart方法
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onStart
  void onStart() {
    ensureAlive();
    doInitialFlutterViewRun();
  }

看下这个doInitialFlutterViewRun()做了什么:

// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#doInitialFlutterViewRun
  private void doInitialFlutterViewRun() {
    // engine复用时,dart运行入口完全交给用户自己掌控,framework就不管了.
    if (host.getCachedEngineId() != null) {
      return;
    }
    // configuration变更导致的方法重入,不处理
    if (flutterEngine.getDartExecutor().isExecutingDart()) {
      return;
    }
    // 运行dart的入口方法前,先设置好initialRoute
    // 1. dart侧通过:window.defaultRouteName获取这个初始路由
    // 2. 同时也说明了,java与dart的通讯不是运行了dart入口方法之后才能进行,而是engine初始化完毕就可以开始
    String initialRoute = host.getInitialRoute();
    if (initialRoute == null) {
      initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
      if (initialRoute == null) {
        initialRoute = DEFAULT_INITIAL_ROUTE;
      }
    }
    flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);

    // 构造dart运行入口,并执行之
    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }
    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }

在这个方法中,真正启动执行了dart代码,也看到了initialRoute是如何设置给engine的。

看到这里不知道你是否跟我一样有个疑问:NavigationChannel#setInitialRoute执行的时候,dart的main方法还没有运行,也就是dart vm那边相关的channel还有被初始化,难道channel消息也具有“sticky”特性吗?后面章节将对此有所介绍。

void onResume()

  • 调用处
  1. activity的onResume方法
  2. fragment的onResume方法
  • 代码分析
  void onResume() {
    ensureAlive();
    // 通知flutter当前界面处于Resumed状态
    flutterEngine.getLifecycleChannel().appIsResumed();
  }

void onPause()

  • 调用处
  1. activity的onPause方法
  2. fragment的onPause方法
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onPause
  void onPause() {
    ensureAlive();
    // 通知flutter当前界面处于Inactive状态
    flutterEngine.getLifecycleChannel().appIsInactive();
  }

void onStop()

  • 调用处
  1. activity的onStop方法
  2. fragment的onStop方法
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onStop
  void onStop() {
    ensureAlive();
    // 通知flutter当前界面处于Paused状态
    flutterEngine.getLifecycleChannel().appIsPaused();
  }

从onStop开始,FlutterActivity在调用delegate的方法前,都要做delegate的非空检查。这是因为在engine复用的场景下,启动一个新的FlutterActivity复用当前页面的engine并finish当前页面时,会触发当前页面的FlutterActivity#detachFromFlutterEngine回调,导致delegate被release掉。

void onDestroyView()

  • 调用处
  1. activity的onDestroy/detachFromFlutterEngine方法
  2. fragment的onDestroyView/detachFromFlutterEngine方法
  • 代码分析
    与onCreateView相呼应,用于FlutterView相应的资源回收工作。
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onDestroyView
  void onDestroyView() {
    ensureAlive();
    // flutterView断开engine,从而释放相关资源
    flutterView.detachFromFlutterEngine();
    flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
  }

void onDetach()

  • 调用处
  1. activity的onDestroy/detachFromFlutterEngine方法
  2. fragment的onDetach/detachFromFlutterEngine方法
  • 代码分析
//io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#onDetach
  void onDetach() {
    ensureAlive();

    // 与onAttach中configureFlutterEngine()相呼应.
    host.cleanUpFlutterEngine(flutterEngine);

    // 通知flutter插件其已从activity detach,插件收到onDetachFromActivity等回调
    if (host.shouldAttachEngineToActivity()) {
      if (host.getActivity().isChangingConfigurations()) {
        flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
      } else {
        flutterEngine.getActivityControlSurface().detachFromActivity();
      }
    }

    // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this
    // Fragment,
    // and this Fragment's Activity.
    if (platformPlugin != null) {
      platformPlugin.destroy();
      platformPlugin = null;
    }

    flutterEngine.getLifecycleChannel().appIsDetached();

    // Destroy our FlutterEngine if we're not set to retain it.
    if (host.shouldDestroyEngineWithHost()) {
      flutterEngine.destroy();

      if (host.getCachedEngineId() != null) {
        FlutterEngineCache.getInstance().remove(host.getCachedEngineId());
      }

      flutterEngine = null;
    }
  }

FlutterFragment

FlutterActivity一样,由于公共逻辑都被抽取到了FlutterActivityAndFragmentDelegate中,包括生命周期方法内的调用逻辑大部分是相同的。而在Host部分FlutterFragment的代码就与FlutterActivity有所区别了,比如下面:

//io.flutter.embedding.android.FlutterFragment
  @Override
  @Nullable
  public SplashScreen provideSplashScreen() {
    FragmentActivity parentActivity = getActivity();
    if (parentActivity instanceof SplashScreenProvider) {
      SplashScreenProvider splashScreenProvider = (SplashScreenProvider) parentActivity;
      return splashScreenProvider.provideSplashScreen();
    }

    return null;
  }

  @Override
  @Nullable
  public FlutterEngine provideFlutterEngine(@NonNull Context context) {
    // Defer to the FragmentActivity that owns us to see if it wants to provide a
    // FlutterEngine.
    FlutterEngine flutterEngine = null;
    FragmentActivity attachedActivity = getActivity();
    if (attachedActivity instanceof FlutterEngineProvider) {
      // Defer to the Activity that owns us to provide a FlutterEngine.
      FlutterEngineProvider flutterEngineProvider = (FlutterEngineProvider) attachedActivity;
      flutterEngine = flutterEngineProvider.provideFlutterEngine(getContext());
    }
    return flutterEngine;
  }  

  @Override
  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    FragmentActivity attachedActivity = getActivity();
    if (attachedActivity instanceof FlutterEngineConfigurator) {
      ((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
    }
  }

   @Override
  public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    FragmentActivity attachedActivity = getActivity();
    if (attachedActivity instanceof FlutterEngineConfigurator) {
      ((FlutterEngineConfigurator) attachedActivity).cleanUpFlutterEngine(flutterEngine);
    }
  }   

  @Override
  public void onFlutterUiDisplayed() {
    FragmentActivity attachedActivity = getActivity();
    if (attachedActivity instanceof FlutterUiDisplayListener) {
      ((FlutterUiDisplayListener) attachedActivity).onFlutterUiDisplayed();
    }
  }

   @Override
  public void onFlutterUiNoLongerDisplayed() {
    FragmentActivity attachedActivity = getActivity();
    if (attachedActivity instanceof FlutterUiDisplayListener) {
      ((FlutterUiDisplayListener) attachedActivity).onFlutterUiNoLongerDisplayed();
    }
  } 

可以看到FlutterFragment是把上述方法的实现托付给宿主activity的,虽然FlutterActivity可以满足其需求,但是我们不可能在FlutterActivity里面去嵌套FlutterFragment来使用。我们分析一下,当我们用一个普通的Activity去加载FlutterFragment实例会上述方法会有什么影响:

  • SplashScreen provideSplashScreen()
    此方法返回一个自定义启动动画,FlutterFragment默认没有实现,由于attachedActivity是普通Activity,固然也没有实现SplashScreenProvider接口,于是flutter页面打开时将没有自定义动画,会导致黑屏问题。
  • FlutterEngine provideFlutterEngine(Context context)
    此方法返回一个engine实例,返回null时FlutterFragment的delegate会new一个默认的engine,看似没有问题,但是这个默认的engine的automaticallyRegisterPlugins为false,也就是不会执行插件注册代码,同时FlutterFragment#configureFlutterEngine()中默认也没有执行插件注册,会导致flutter插件失效。
  • void configureFlutterEngine(FlutterEngine flutterEngine)
    配置engine的hook入口,可以不实现,但是就像前面提到的,会使得flutter插件失效。
  • void cleanUpFlutterEngine(FlutterEngine flutterEngine)
    无影响
  • void void onFlutterUiDisplayed()
    无影响
  • void void onFlutterUiNoLongerDisplayed()
    无影响

为解决上述问题,我们有两种选择:

  1. Activity继承于SDK提供的FlutterFragmentActivity
  2. 继续使用我们自己的Activity,自定义FlutterFragment重写相关方法解决相应问题。

使用FlutterFragmentActivity

此类主要是构造一个FlutterFragment并将之添加到页面中,其他Host实现与FlutterActivity完全一致,因此可以解决上面的问题。但是有2点明显的限制:

  1. 由于Java的单继承特性,我们的原生Activity通常都是要继承于BaseActivity的,故无法继承FlutterFragmentActivity
  2. FlutterFragmentActivity#onCreate的实现来看,这个activity也是一个flutter full page的页面,无法显示类似FlutterFragment与其他原生fragment一起放在ViewPager里面进行展示的效果。

出于上述原因,推荐使用自定义FlutterFragment来解决问题。

使用自定义FlutterFragment

明确问题之后,解决方案就简单了:

public class FlutterProxyFragment extends FlutterFragment {

    @Nullable
    @Override
    public FlutterEngine provideFlutterEngine(@NonNull Context context) {
        // 【插件注册问题】方案一:当父类没有生成engine实例时,创建一个,并指定automaticallyRegisterPlugins为true
        FlutterEngine engine = super.provideFlutterEngine(context);
        if (engine == null) {
            // Host activity is not a subclass of flutter activity, do it by ourselves
            engine= new FlutterEngine(
                    getContext(),
                    getFlutterShellArgs().toArray(),
                    /*automaticallyRegisterPlugins=*/ true,
                    /*willProvideRestorationData=*/ shouldRestoreAndSaveState());
        }
        return engine;
    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        FragmentActivity attachedActivity = getActivity();
        if (attachedActivity instanceof FlutterEngineConfigurator) {
           ((FlutterEngineConfigurator) attachedActivity).configureFlutterEngine(flutterEngine);
        } else {
           // 【插件注册问题】方案二:当父类不是FlutterEngineConfigurator时,自己调用注册插件的方法
           GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
        }
    }

    @Override
    public SplashScreen provideSplashScreen() {
        // 【启动动画问题】方案:增加逻辑判断
        SplashScreen splashScreen = super.provideSplashScreen();
        if (splashScreen != null) return splashScreen;
        return new LoadingSplashScreen();
    }

    ...
}

上述代码似乎是解决了问题,但其实还有很多细节没有解决。我们对照FlutterFragmentActivity源码,可以发现下述代码:

  @Override
  public void onPostResume() {
    super.onPostResume();
    flutterFragment.onPostResume();
  }

  @Override
  protected void onNewIntent(@NonNull Intent intent) {
    // Forward Intents to our FlutterFragment in case it cares.
    flutterFragment.onNewIntent(intent);
    super.onNewIntent(intent);
  }

  @Override
  public void onBackPressed() {
    flutterFragment.onBackPressed();
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
  }

  @Override
  public void onUserLeaveHint() {
    flutterFragment.onUserLeaveHint();
  }

  @Override
  public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    flutterFragment.onTrimMemory(level);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    flutterFragment.onActivityResult(requestCode, resultCode, data);
  }

这些需要activity辅助调用的方法都被贴心地打上了@ActivityCallThrough标记,妈妈再也不用担心我有遗漏了!

这些方法,要么不是Fragment固有的回调方法,要么虽是固有回调方法,但是必须通过fragment调用才能生效。以onActivityResult为例,只有调用fragment.startActivityForResult时该回调才会被触发,我们在编写flutter插件时,通常是这样的流程:

public class MyPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener{
    private MethodChannel channel;
    private Activity host;
    ActivityPluginBinding binding;

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result){...}

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
        channel = new MethodChannel(binding.getBinaryMessenger(), "myplugin");
        channel.setMethodCallHandler(this);
        final FlutterEngine engine = binding.getFlutterEngine();
        engine.getPlatformViewsController().getRegistry().registerViewFactory(
                "my_view_factory",
                new MyViewFactory(engine.getDartExecutor().getBinaryMessenger()));
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
        channel = null;
    }

    @Override
    public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
        mDelegate.setActivity(binding.getActivity());
        // 保存activity引用,在onMethodCall中执行相应方法时调用host.startActivityForResult跳转
        // 然后在onActivityResult中处理后续数据回调逻辑
        host = binding.getActivity();
        this.binding = binding;
        // 绑定回调
        binding.addActivityResultListener(this);
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
        onDetachedFromActivity();
    }

    @Override
    public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
        onAttachedToActivity(binding);
    }

    @Override
    public void onDetachedFromActivity() {
        binding.removeActivityResultListener(this);
        binding = null;
        host = null;
    }

    @Override
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {...}

}

也就是说即便使用FlutterFragment,我们还是使用activity.startActivityForResult启动页面,所以FlutterFragment#onActivityResult并不会回调,插件的onActivityResult也就无法回调了。这里提供一些解决思路:

  • 监听返回按键事件
public class FlutterProxyFragment extends FlutterFragment {

    private OnBackPressedCallback mOnBackPressedCallback;
    private OnBackPressedCallback fetchOnBackPressedCallback(){
        if (mOnBackPressedCallback == null) {
            mOnBackPressedCallback = new OnBackPressedCallback(true) {
                @Override
                public void handleOnBackPressed() {
                    onBackPressed();
                }
            };
        }
        return mOnBackPressedCallback;
    }

    protected boolean watchOnBackPress(){
        return getArguments().getBoolean("WATCH_ON_BACK_PRESS",false);
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (watchOnBackPress() && context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
            ComponentActivity activity = (ComponentActivity) context;
            activity.getOnBackPressedDispatcher().addCallback(activity, fetchOnBackPressedCallback());
        }
    }

    ...
}

  • 监听ComponentCallbacks2事件
public class FlutterProxyFragment extends FlutterFragment {

    private ComponentCallbacks2 mComponentCallbacks;
    private ComponentCallbacks2 fetchComponentCallbacks2(){
        if (mComponentCallbacks==null){
            mComponentCallbacks=new ComponentCallbacks2() {
                @Override
                public void onTrimMemory(int level) {
                    FlutterProxyFragment.this.onTrimMemory(level);
                }

                @Override
                public void onConfigurationChanged(@NonNull Configuration newConfig) {}

                @Override
                public void onLowMemory() {}
            };
        }
        return mComponentCallbacks;
    }
    final List<Runnable> clearUpTasks = new ArrayList<>();
    private void clearUp(){
        Iterator<Runnable> iterator = clearUpTasks.iterator();
        while (iterator.hasNext()) {
            iterator.next().run();
            iterator.remove();
        }
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof ComponentActivity && !(context instanceof FlutterFragmentActivity)) {
            Application application = (Application) context.getApplicationContext();
            application.registerComponentCallbacks(fetchComponentCallbacks2());
            clearUpTasks.add(() -> application.unregisterComponentCallbacks(fetchComponentCallbacks2()));
        }
    }


    @Override
    public void onDetach() {
        clearUp();
        super.onDetach();
    }

    ...
}
  • 监听其他事件
/**
 * 定义相关事件的接口类
 */
public interface BaseEventListener{
    default void onPostResumeEvent(){}
    default void void onNewIntentEvent(@NonNull Intent intent){}
    default void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){}
    default void void onUserLeaveHintEvent(){}
    default void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data){}
}

/**
 * 定义注册中心
 */
public interface BaseEventRegistry{
  void addBaseEventListener(@NonNull BaseEventListener l);
  void removeBaseEventListener(@NonNull BaseEventListener l);
}

/**
 * 让BaseActivity实现该接口
 */
public class BaseActivity extends Activity implements BaseEventRegistry{
  private Set<BaseEventListener> listeners = new LinkedHashSet();
  @Override
  public void addBaseEventListener(@NonNull BaseEventListener l){
    listeners.add(l);
  }

  @Override
  public void removeBaseEventListener(@NonNull BaseEventListener l){
    listeners.remove(l);
  }

  @Override
  public void onPostResume() {
    super.onPostResume();
    for (BaseEventListener listener : listeners) {
        listener.onPostResumeEvent();
    }
  }

  @Override
  protected void onNewIntent(@NonNull Intent intent) {
    for (BaseEventListener listener : listeners) {
        listener.onNewIntentEvent(intent);
    }
    super.onNewIntent(intent);
  }

  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    for (BaseEventListener listener : listeners) {
        listener.onRequestPermissionsResultEvent(requestCode, permissions, grantResults);
    }
  }

  @Override
  public void onUserLeaveHint() {
    for (BaseEventListener listener : listeners) {
        listener.onUserLeaveHintEvent();
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    for (BaseEventListener listener : listeners) {
        listener.onActivityResultEvent(requestCode, resultCode, data);
    }
  }
}

//准备工作完毕,改造FlutterProxyFragment=======================================================================
public class FlutterProxyFragment extends FlutterFragment {

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        if (context instanceof BaseEventRegistry) {
            ((BaseEventRegistry) context).addBaseEventListener(this);
            clearUpTasks.add(() -> ((BaseEventRegistry) context).removeBaseEventListener(this));
        }
    }

    @Override
    public void onNewIntentEvent(@NonNull Intent intent) {
      this.onNewIntent(intent);
    }

    @Override
    public void onPostResumeEvent() {
        this.onPostResume();
    }
    @Override
    public void onUserLeaveHintEvent() {
        this.onUserLeaveHint();
    }
    @Override
    public void onActivityResultEvent(int requestCode, int resultCode, @Nullable Intent data) {
        this.onActivityResult(requestCode, resultCode, data);
    }
    @Override
    public void onRequestPermissionsResultEvent(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
      this.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
    ...
}

FlutterView

真正渲染flutter页面的控件,归属FlutterActivityAndFragmentDelegate。主要分为两个部分:

  • RenderSurface:对接FlutterEngineFlutterRenderer,用于画面的绘制;
  • Plugins/Bridge/Processor:对接FlutterEngine的各种Channel,用于用户交互事件处理;

由于都需要对接engine,只有attach engine后才能开始工作,当然用完还需要detach。

  • attach调用链
    flutterView.attachToFlutterEngine(FlutterEngine flutterEngine)
    flutterEngine.getPlatformViewsController().attachToView(Veiw view)
  • detach调用链
    flutterView.detachFromFlutterEngine()
    flutterEngine.getPlatformViewsController().detachFromView()

从这一点看,PlatformViewFlutterView流程是一样的

RenderSurface

归属FlutterView。用于呈现flutter的图像数据(FlutterRenderer管理,实例归属engine),作用相当于画布或者屏幕,目前flutter支持三种RenderSurface

  • io.flutter.embedding.android.FlutterImageView:利用普通eView将flutter图像渲染成一张图片,不支持flutter交互;
  • io.flutter.embedding.android.FlutterSurfaceView:利用SurfaceView将flutter图像渲染成界面,支持交互;
  • io.flutter.embedding.android.FlutterTextureView:利用TextureView将flutter图像渲染成界面,支持交互;

FlutterEngine

创建和维护与flutter交互的相关FlutterRenderer、各种Channel等,默认由FlutterActivityAndFragmentDelegate创建和回收实例,也支持由用于在FlutterActivity/FlutterFragment等Host中返回自己维护的engine实例。

XXXChannel

功能型Channel种类和功能简介

这些功能型Channel底层通讯其实都是借助三种通讯型Channel实现的,后面会专门介绍。

engine在构造方法中实例化了各种功能型Channel用于特定领域的原生和dart间的通讯,包括:

  • AccessibilityChannel
  • DeferredComponentChannel
  • KeyEventChannel
  • LifecycleChannel
  • LocalizationChannel
  • MouseCursorChannel
  • NavigationChannel:用于java和dart之间路由交互。
  • PlatformChannel
  • RestorationChannel
  • SettingsChannel
  • SystemChannel
  • TextInputChannel

有些Channel是直接使用,而有些套了一层壳成了XXXPlugin。由于功能型Channel数量比较多,不是本文的重点就不逐一详细介绍了,后面会介绍一些比较常用的Channel。

  • NavigationChannel
    这个是我比较感兴趣的模块,看下Java端代码:
//io.flutter.embedding.engine.systemchannels.NavigationChannel
public class NavigationChannel {

  @NonNull public final MethodChannel channel;

  public NavigationChannel(@NonNull DartExecutor dartExecutor) {
    this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE);
  }

  public void setInitialRoute(@NonNull String initialRoute) {
    // 前面分析过了,这个方法是在run entry point之前调用的
    channel.invokeMethod("setInitialRoute", initialRoute);
  }

  public void pushRoute(@NonNull String route) {
    channel.invokeMethod("pushRoute", route);
  }

  public void popRoute() {
    channel.invokeMethod("popRoute", null);
  }

  public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
    channel.setMethodCallHandler(handler);
  }
}

可以说是简洁得不能再简洁了,看下dart端:

//sdk/packages/flutter/lib/src/services/system_channels.dart:50
  //全局单例的channel实例  
  static const MethodChannel navigation = OptionalMethodChannel(
      'flutter/navigation',
      JSONMethodCodec(),
  );

//sdk/packages/flutter/lib/src/widgets/binding.dart:
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {

  @override
  void initInstances() {
    super.initInstances();
    ...
    // 为navigation设置回调
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    ...
  }

  Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
    // 对比java那边的代码发现:
    // 1. 少了"setInitialRoute"
    // 2. 多了'pushRouteInformation'
    switch (methodCall.method) {
      case 'popRoute':
        return handlePopRoute();
      case 'pushRoute':
        return handlePushRoute(methodCall.arguments as String);
      case 'pushRouteInformation':
        return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
    }
    return Future<dynamic>.value();
  }

  @protected
  Future<void> handlePopRoute() async {
    // 依次挨个关闭页面
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPopRoute())
        return;
    }
    // 没有可以关闭的页面,则直接关闭FlutterActivity
    SystemNavigator.pop();
  }

  @protected
  @mustCallSuper
  Future<void> handlePushRoute(String route) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPushRoute(route))
        return;
    }
  }
  ...
} 

那么这个缺失的setInitialRoute是在哪里实现的呢?后面dart侧启动过程简介对此有分析。

通讯型Channel类型及实现简介

channel可以分为:

  • MethodChannel
  • BasicMessageChannel<T>
  • EventChannel

我们先看下Java和dart端MethodChannel的构造方法:

  • Java端:
  public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
    this.messenger = messenger;
    this.name = name;
    this.codec = codec;
  }

  public BasicMessageChannel(
      @NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MessageCodec<T> codec) {
    this.messenger = messenger;
    this.name = name;
    this.codec = codec;
  }

  public EventChannel(BinaryMessenger messenger, String name, MethodCodec codec) {
    this.messenger = messenger;
    this.name = name;
    this.codec = codec;
  }  
  • dart端:
//sdk/packages/flutter/lib/src/services/platform_channel.dart
  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
      : assert(name != null),
        assert(codec != null),
        _binaryMessenger = binaryMessenger;

  const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
      : assert(name != null),
        assert(codec != null),
        _binaryMessenger = binaryMessenger;
        
  const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
      : assert(name != null),
        assert(codec != null),
        _binaryMessenger = binaryMessenger;                

可以看到在构造Channel实例时,所需的参数是一模一样的,需要传入三个参数:

  • BinaryMessenger messenger
    数据发送通道,无论哪种Channel,走的通道都是这个。BinaryMessenger本身是一个接口,其真正的实现类是DartMessenger,其他均为代理类,而最终发送数据的方法都是走的FlutterJNI实例的native方法。
  • String name
    渠道名称,可以认为是渠道的ID。
  • MethodCodec/MessageCodec<T> codec
    数据编解码器,用于channel通讯时接口实参的编解码。

任何通讯型Channel在最终都是通过BinaryMessenger#send(String, ByteBuffer, BinaryMessenger.BinaryReply)向flutter发送消息,且通过BinaryMessenger#setMessageHandler(String, BinaryMessageHandler)接收回执并分发回调。

了解jsbridge这类Java-JS通讯框架的读者可能闻到了熟悉的味道,是的,流程基本是一样的。

BinaryMessenger

BinaryMessenger本身是一个接口,真正的实现类是DartMessenger,所以channel的整个调用链是:

FunctionChannel(Optional) -> CommunicationChannel -> BinaryMessenger(DartMessenger) -> FlutterJNI

再去扒一下这其中涉及到的FlutterJNI的方法:

  • void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId)
  • void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId)
  • void invokePlatformMessageEmptyResponseCallback(int responseId)
  • void invokePlatformMessageResponseCallback(int responseId, @Nullable ByteBuffer message, int position)

好家伙!四个方法实现了channel核心功能,果然大道至简。既然到了这里了不妨再深入一步,前面提到:

在构造通讯型channel时,需要传入一个name,而这个name是作为ID使用的,那如果编码过程不规范,不同的flutter plguin工程在构造各自的channel时使用了相同的name会有什么后果呢?我们从消息发送侧和消息接收侧两个角度来分别分析:

  • 发送侧
    由于最终都是落到DartMessenger进行发送,该实例由DartExecutor维护,而DartExecutor又归属engine实例,也就是一个engine实例其实对应一个DartMessenger实例,所以发送侧不会有影响。

  • 接收侧
    每个Channel实例setMessageHandler实际都是保存在DartMessenger中,看下它是怎么保存的:

//io.flutter.embedding.engine.dart.DartMessenger#setMessageHandler
@NonNull private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;

  @Override
  public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
    if (handler == null) {
      messageHandlers.remove(channel);
    } else {
      messageHandlers.put(channel, handler);
    }
  }

所以在同一个engine实例中,如果存在两个同名channel,消息接收回调会有覆盖问题,导致前一个回调失效。

通过上面的分析我们可以得出结论:

  1. 在构造Channel时一定要有良好的编码规范,保证name的唯一性;
  2. 要做好Channel的生命周期管理,及时回收Channel,尤其是及时移除MessageHandler,防止内存泄漏;

MethodCodec/MessageCodec<T>

对于MethodCodec,Android端默认提供了两种实现:

- `StandardMethodCodec`: 单例,真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:单例,真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`编解码交给了`StringCodec`实例

与之对应的,flutter端也提供了两种实现:

- `StandardMethodCodec`:真正编解码交给了`StandardMessageCodec`实例
- `JSONMethodCodec`:真正编解码交给了`JSONMessageCodec`实例,而`JSONMessageCodec`实例真正编解码交给了`StringCodec`实例

而对于MessageCodec,flutter framework提供了4种实现:

MessageCodec编解码器java数据类型dart数据类型使用情况
StandardMessageCodec
  • null
  • Booleans
  • Bytes, Shorts, Integers, Longs
  • BigIntegers (see below)
  • Floats, Doubles
  • Strings
  • byte[], int[], long[], double[]
  • Lists of supported values
  • Maps with supported keys and values
  • null: null
  • Boolean: bool
  • Byte, Short, Integer, Long: int
  • Float, Double: double
  • String: String
  • byte[]: Uint8List
  • int[]: Int32List
  • long[]: Int64List
  • double[]: Float64List
  • List: List
  • Map: Map
  • AccessibilityChannel
  • FlutterJNI
JSOMessageCodecUTF-8编码的json对象UTF-8编码的json对象
  • LocalizationChannel
  • NavigationChannel
  • PlatformChannel
  • TextInputChannel
BinaryCodecByteBufferByteData
StringCodecUTF-8编码的StringUTF-8编码的String
  • DartExecutor
  • JSONMessageCodec
  • LifecycleChannel

所以,对于数据编解码器,粗略可以得到下面的结论:

  1. MethodCodec是MessageCodec的包装类,底层其实都是MessageCodec在干活;
  2. java和dart端Channel对应的编解码器必须是对应的才能正常完成传输数据编解码;

FlutterJNI

实例由engine持有和销毁。dart代码调用门面类,所以用的地方有点多:FlutterEngineDartExecutor、各种Channel、FlutterRenderer等等。

DartExecutor

实例由engine创建和维护,用于原生和flutter通信。乍看似乎作用跟FlutterJNI有点重合,但我们作为上层应用开发基本只要和DartExecutor打交道就好了,直接使用FlutterJNI不仅繁杂还容易出现兼容性问题,况且DartExecutor已经为我们封装好了友好的API接口。

DartMessenger

归属DartExecutor,是真正发送消息的BinaryMessenger,前面介绍Channel时已经介绍过,这里略过。

dart侧启动过程简介

本来不想写dart的流程,但由于提到了java层的各种Channel,这里不得不稍微提一下。先看看java是怎么启动dart的main方法的,在前面介绍的FlutterActivityAndFragmentDelegate中:

// io.flutter.embedding.android.FlutterActivityAndFragmentDelegate#doInitialFlutterViewRun
  private void doInitialFlutterViewRun() {
    ...
    // 运行dart的入口方法前,先设置好initialRoute
    // 1. dart侧通过:window.defaultRouteName获取这个初始路由
    // 2. 同时也说明了,java与dart的通讯不是运行了dart入口方法之后才能进行,而是engine初始化完毕就可以开始
    String initialRoute = host.getInitialRoute();
    if (initialRoute == null) {
      initialRoute = maybeGetInitialRouteFromIntent(host.getActivity().getIntent());
      if (initialRoute == null) {
        initialRoute = DEFAULT_INITIAL_ROUTE;
      }
    }
    flutterEngine.getNavigationChannel().setInitialRoute(initialRoute);

    // 构造dart运行入口,并执行之
    String appBundlePathOverride = host.getAppBundlePath();
    if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
      appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
    }
    DartExecutor.DartEntrypoint entrypoint =
        new DartExecutor.DartEntrypoint(
            appBundlePathOverride, host.getDartEntrypointFunctionName());
    flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
  }

默认情况下,我们没有在host.getDartEntrypointFunctionName()中返回自定义的入口点名称,所以就走默认的,即“main”。那如果要自定义dart入口方法该怎么做呢?

  • java侧:
    host.getDartEntrypointFunctionName()返回自定义FlutterActivity/FlutterFragment,覆写getDartEntrypointFunctionName()方法:
class MyFlutterActivity extends FlutterActivity{

  @NonNull
  public String getDartEntrypointFunctionName() {
    // 判断是否使用自定义dart入口  
    boolean useCustomDartEntry = true;
    if(useCustomDartEntry){
        // 比如我们自定义的入口叫"userMain"
        return "userMain";
    } else {
        // 默认从manifest/application的meta-data读取
        return super.getDartEntrypointFunctionName();
    }
  }
}
  • dart侧:
void main() {
    runApp(getFirstScreen(window.defaultRouteName));
}

/// 采用@pragma('vm:entry-point')来标记入口函数
@pragma('vm:entry-point')
void userMain() {
    //耗时方法要异步执行
    SPUtil().init().then((v){
        runApp(MaterialApp(
            home: Text("这是userMain"),
        ));
    });
}

flutter侧那么多的channel又是什么时候初始化的呢?我们先从runApp这个方法为入口开始寻觅:

//sdk/packages/flutter/lib/src/widgets/binding.dart
void runApp(Widget app) {
  //初始化WidgetsBinding实例  
  WidgetsFlutterBinding.ensureInitialized()
    //异步执行WidgetsBinding#attachRootWidget方法,更新root widget
    ..scheduleAttachRootWidget(app)
    //执行SchedulerBinding#scheduleWarmUpFrame立即渲染一个启动页,而不必等系统"Vsync"信号
    ..scheduleWarmUpFrame();
}

WidgetsBinding是所有binding的胶水类,初始化WidgetsBinding实例也就初始化了flutter bindings,但是还是无法解释runApp执行之前,java端navigationChannel.setInitialRoute(initialRoute)如何生效。于是继续扒window.defaultRouteName,这里window是SingletonFlutterWindow的全局实例(单例),defaultRouteName则调用了PlatformDispatcher#defaultRouteName,而PlatformDispatcher也是个单例,相关代码:

/// 调用native方法获取原生侧设置的InitialRoute,接口文档也强调[`FlutterView.setInitialRoute`]
/// 和[`FlutterViewController.setInitialRoute`]必须在运行dart入口方法前调用
String get defaultRouteName => _defaultRouteName();
String _defaultRouteName() native 'PlatformConfiguration_defaultRouteName';

至此终于有了思路,或许是用c层做桥接做了InitialRoute的缓存而已,也不存在runApp执行之前java与dart发生交互的事情。

FlutterRenderer

归属engine,处理flutter图像渲染相关的功能,涉及到相关flutterJNI接口的调用,了解不多,这里略过。

FlutterEngineConnectionRegistry

归属engine,实现了PluginRegistryActivityControlSurfaceServiceControlSurfaceBroadcastReceiverControlSurfaceContentProviderControlSurface接口,是flutter插件的注册中心,并赋予插件与四大组件之间基本的attach/detach回调和组件独有的回调能力。

提到flutter插件需要额外关注插件的注册时机,只有执行了注册,插件才能正常使用。通过前面的代码分析,我们可以总结有以下两个注册时机:

  • FlutterEngine构造方法
    automaticallyRegisterPlugins为true时,会调用FlutterEngine#registerPlugins进行注册;
  • FlutterEngineConfigurator#configureFlutterEngine(@NonNull FlutterEngine flutterEngine)
    FlutterActivityFlutterFragmentActivity的方法重写中,调用了GeneratedPluginRegister#registerGeneratedPlugins()完成了插件注册;

源码看到这里的时候,一度发生逻辑混乱,PluginRegistry的实现类一会只有一个,一会有多个,仔细看发现有两个同名接口:io.flutter.embedding.engine.plugins.PluginRegistryio.flutter.plugin.common.PluginRegistry,而后者已经被废弃。读者在看flutter源码的时候要注意以embedding包为主。

PlatformViewsController

归属engine,用于实现PlatformViews功能,对接engine的PlatformViewsChannel。前面也提到过,需要engine调用getActivityControlSurface().attachToActivity()->PlatformViewsController#attach才真正完成初始化。

其他

上面介绍的几个类是整个flutter运行的核心类(类似于bootstrap class),了解这些类的作用我们就从宏观上了解了flutter的运行细节。剩下就是flutter各个具体的功能模块了,按需了解即可,这里简要介绍一下。

PlatformPlugin

归属FlutterActivityAndFragmentDelegate,对接engine的PlatformChannel。我们从PlatformPlugin#mPlatformMessageHandler可以了解到其主要负责处理一些琐碎的系统服务功能,包括:

  • 触觉的震动、提示音;
  • 屏幕方向;
  • 任务管理器中任务描述信息(TaskDescription);
  • 系统界面overlay相关:状态栏、导航栏显示等;
  • 页面栈回退处理:activity/fragment,对接dart端的SystemNavigator;
  • 读写系统剪贴板内容;

LocalizationPlugin

归属engine,对接engine的LocalizationChannel,处理时区相关逻辑。支持获取当前时区和通知flutter时区变更。

engine管理策略

前面我们把flutter核心模块的代码基本过了一遍,对相应的类功能和用法也有了了解,本节开始将介绍一些开发相关的内容。
首要需要确定的是engine管理策略,因为不同的管理策略的代码实现复杂度差异较大,内存使用和性能方面也有所差异。flutter engine的管理模式有两种:

  • 多engine模式:每次启动flutter页面都创建新的engine实例;
  • 单engine模式:整个APP复用同一个engine实例;

关于engine管理策略的选择我认为没有好坏之分,否则flutter官方没有必要同时支持了这两种方式。这就跟我们在使用WebView时是选择用单例还是创建新的实例一样,应该根据APP的业务场景来定。

多engine策略

广义上的engine隔离策略,每个flutter页面都创建新的engine,由于engine实例之间的堆内存是独立和隔离的,dart侧的缓存不可共享,需要原生搭桥进行通讯。这种策略:

  • 优点:
    1. 实现简单,开发代码量少,不需要维护复杂的页面栈;
    2. flutter framework默认的策略,因此兼容性最好;
  • 缺点:
    1. 无法使用flutter侧内存缓存;
    2. 多engine内存消耗会稍多;
    3. 每次启动flutter页面都要经历engine初始化过程;

实际上针对多engine策略的缺陷,flutter已做了优化工作:

  • FlutterEngine#spawn可用于在已有engine的基础上以极低的内存和时间成本创建第二个engine,详见FlutterEngineGroup,使用方法比较简单这里不详细介绍了;
  • release版的flutter编译成果物engine初始化时间实测是小于300毫秒的,用户几乎感受不到;

页面实例构建

对于activity:

public class FlutterProxyActivity extends FlutterActivity {

    static Intent newEngineIntent(Context context,String pageUri){
        Intent intent = new FlutterActivity.NewEngineIntentBuilder(FlutterProxyActivity.class)
                .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
                .initialRoute(pageUri)
                .build(context);
        if (!(context instanceof Activity)) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        return intent;
    }

    ...
}

对于fragment:

public class FlutterProxyFragment extends FlutterFragment {

    public static Fragment newEngineFragment(String pageUri) {
        return new NewEngineFragmentBuilder(FlutterProxyFragment.class)
                .renderMode(RenderMode.texture)
                .initialRoute(pageUri)
                .build();
    }

    ...
}

这里只列举了最常见的启动参数,完全可以自定义更多参数

引擎管理

flutter默认策略

每次开启一个flutter页面时(包括activity/fragment),都创建新的engine。engine实例生命周期跟随页面实例,即页面创建同时创建engine,页面销毁同时销毁engine。这种方案的优点是实现简单,缺点就像前面提到的,内存占用问题和启动耗时问题。

FlutterEngineGroup方案

从前面关于FlutterEngineFlutterActivityAndFragmentDelegate的分析可知,默认创建engine实例调用的是engine的构造方法。FlutterEngine同时还提供了FlutterEngine#spawn(@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint)来构建engine,从而加速实例化过程和节省内存消耗。那么优化后的方案可以这样:

  1. 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
  2. 启动第二个flutter页面时,走engine#spawn创建新的实例,并缓存engine实例,第n个flutter页面亦如是;
  3. 关闭flutter页面时,同时销毁engine并移除engine缓存,第n个flutter页面亦如是;

这个方案的优点是从第二个flutter页面开始启动速度和内存占用都是比较理想的,并且flutter给我们提供了现成的工具类FlutterEngineGroup。用法也很简单:

// 创建engine并启动dart vm
FlutterEngineGroup engineGroup = new FlutterEngineGroup(this);
DartExecutor.DartEntrypoint dartEntrypoint = new DartExecutor.DartEntrypoint(
        FlutterInjector.instance().flutterLoader().findAppBundlePath(), "entry_0"
);
FlutterEngine topEngine = engineGroup.createAndRunEngine(this, dartEntrypoint);
// engine可以自己也缓存一份,也可以不缓存
FlutterEngineCache.getInstance().put("entry_0", topEngine);
// 如果缓存了engine,就要自己负责移除
FlutterEngineCache.getInstance().remove("entry_0")

强烈建议看下FlutterEngineGroup的实现原理,这样可以帮助我们实现各种自定义多engine方案。

自定义策略

如果对上面的方案不满意,可以实现自己的方案,比如:

  1. 启动第一个flutter页面时,走默认构造方法创建engine实例,并缓存engine实例;
  2. 启动第二个flutter页面时,走engine#spawn创建新的实例,第n个flutter页面亦如是;
  3. 关闭第二个flutter页面时,同时销毁engine,第n个flutter页面亦如是;
  4. 关闭第一个flutter页面时,不销毁engine;
  5. flutter全部关闭,再次启动第一个flutter页面,由于存在一个缓存的engine实例,于是直接使用该实例;
  6. 从第2步开始循环操作;

路由管理

每个engine实例在创建时可以传入initialRoute指定初始页面,并且通过URL可实现待参跳转

窗口管理

在一次flutter会话中,建议直接在当前FlutterActivitypush新的flutter页面。如果这期间打开了原生的activity并覆盖在FlutterActivity之上,此时又需要打开一个flutter页面,那么要分情况讨论:

  • 新的flutter页面跟上一个是同一个会话
    此时应该finishUntil上一个FlutterActivity实例,并在该实例中push新的flutter页面。
  • 新的flutter页面是一个全新的会话
    此时直接创建一个新的FlutterActivity实例并start即可。

不过这种逻辑也并不能绝对的,上述逻辑可以作为默认逻辑,也应该提供配置项让用户强制指定新flutter页面的窗口策略。比如:

  • new:新开FlutterActivity
  • current:直接在上个FlutterActivity打开,不管是否被覆盖;
  • back:对上前面的finishUntil上一个FlutterActivity实例;
  • default:上面的默认逻辑,作为缺省值;

单engine策略

广义上也就是engine复用策略。

页面实例构建

对于activity:

public class FlutterProxyActivity extends FlutterActivity {

    static Intent cachedEngineIntent(Context context, String engineId) {
        Intent intent = new CachedEngineIntentBuilder(FlutterProxyActivity.class, engineId)
                .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.opaque)
                .destroyEngineWithActivity(true)
                .build(context);
        if (!(context instanceof Activity)) {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        return intent;
    }

    ...
}

对于fragment:

public class FlutterProxyFragment extends FlutterFragment {
    private static final String TAG = "FlutterProxyFragment";

    public static Fragment cachedEngineFragment(String engineId) {
        CachedEngineFragmentBuilder builder = new CachedEngineFragmentBuilder(FlutterProxyFragment.class,engineId)
                .renderMode(RenderMode.texture)
                .destroyEngineWithFragment(false);
        return builder.build();
    }

    ...
}

引擎管理

由于整个APP复用一个engine,只需要关注engine的初始化和销毁时机即可。

  • 初始化
    • 懒加载:即应用启动时先不初始化engine,直到第一个flutter页面渲染时才初始化
    • 立即加载:应用启动时立即开启线程初始化一个engine
  • 销毁
    • 销毁:当所有flutter页面销毁时,销毁engine,从而减少APP内存占用
    • 不销毁:保持一个engine实例,不去销毁,随时快速加载flutter页面

路由管理

需要借助自定义channel实现页面更新。

窗口管理

同多engine策略,不过单engine在实现上会困难得多,因为所有FlutterActivity都共享engine,FlutterActivity的页面栈需要随时整体切换刷新。实现上可以参考flutter_boost等一众相关开源项目。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-14 10:04:11  更:2022-05-14 10:04:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 1:24:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码