Flutter原生通信原理概览

  • FlutterActivity

    我们知道,原生连接Flutter需要用到继承FlutterActivity或者FlutterFragment,以FlutterActivity为例,它的onCreate如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
      switchLaunchThemeForNormalTheme();
    
      super.onCreate(savedInstanceState);
    
      delegate = new FlutterActivityAndFragmentDelegate(this);
      delegate.onAttach(this);
      delegate.onRestoreInstanceState(savedInstanceState);
    
      lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    
      configureWindowForTransparency();
    
      setContentView(createFlutterView());
    
      configureStatusBarForFullscreenFlutterExperience();
    }
    

    可见,自定义的子类的onCreate中必须要先调用super.onCreate方法完成FlutterActivity的默认配置。

    之前我们解析了createFlutterView方法,知道了FlutterEngine渲染的UI通过FlutterView来添加到Activity中,现在我们来看前面的FlutterActivityAndFragmentDelegate,和原生通信的逻辑也从它开始。

  • FlutterActivityAndFragmentDelegate

    首先看一下它的onAttach方法:

    void onAttach(@NonNull Context context) {
      ensureAlive();
    
      if (flutterEngine == null) {
        setupFlutterEngine();
      }
    
      if (host.shouldAttachEngineToActivity()) {
        flutterEngine.getActivityControlSurface().attachToActivity(this, host.getLifecycle());
      }
      
      platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine);
    
      host.configureFlutterEngine(flutterEngine);
    }
    

    首先会调用setupFlutterEngine方法保证先创建FlutterEngine,然后调用host的configureFlutterEngine方法来对FlutterEngine进行相关配置,比如可以重写FlutterActivity的子类的configureFlutterEngine来添加自定义的Plugin。

    看一下setupFlutterEngine方法:

    void setupFlutterEngine() {
    
      // First, check if the host wants to use a cached FlutterEngine.
      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;
      }
    
      // Second, defer to subclasses for a custom FlutterEngine.
      flutterEngine = host.provideFlutterEngine(host.getContext());
      if (flutterEngine != null) {
        isFlutterEngineFromHost = true;
        return;
      }
    
      // Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our
      // FlutterView.
      flutterEngine =
          new FlutterEngine(
              host.getContext(),
              host.getFlutterShellArgs().toArray(),
              /*automaticallyRegisterPlugins=*/ false,
              /*willProvideRestorationData=*/ host.shouldRestoreAndSaveState());
      isFlutterEngineFromHost = false;
    }
    

    在方法的一开始,首先会通过FlutterEngineCache.getInstance().get(cachedEngineId)方法尝试从缓存中取FlutterEngine,所以我们可以在子类Activity中重写getCachedEngineId方法来设置在每一次重新打开该Activity的时候都能使用之前的FlutterEngine,这会极大地提高性能。getCachedEngineId方法在FlutterActivity中有默认实现:

    @Override
    @Nullable
    public String getCachedEngineId() {
      return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
    }
    

    可以看到,你也可以通过intent的方式传递cachedEngineId。

    当然我们需要事先创建FlutterEngine然后把它put到FlutterEngineCache中,这一步你可以放在Application类的初始化中,但不是更好的做法,我们接着往下看。

    如果cache中没有拿到之前使用过的FlutterEngine的话就会尝试调用子类Activity重写的provideFlutterEngine方法来获取,所以你可以重写这个方法然后在这里创建FlutterEngine,然后可以在这里把它放到cache中,这就是比上面放在Application中更好的做法,为什么呢?因为放在Application中的话,FlutterEngine并不一定会被使用,有可能使用过程中从没有打开过Flutter页面,这就会导致始终持有这个FlutterEngine对象,造成资源浪费;还有一个好处就是这样一来每个页面都有自己的独立的FlutterEngine,每个FlutterEngine的业务相对独立而且不需要维护其他Flutter业务的相关信息。当然如果是App的所有页面都是Flutter页面或者原生页面占很少一部分的时候在Application中配置会更好。

    最后一步则是new一个新的FlutterEngine对象,context和host相关联。

    使用缓存FlutterEngine的作用一个是明显地提高页面打开速度,再一个会保存之前Flutter页面的操作信息。

    需要注意的是,如果使用缓存id的话,则需要手动设置切入点,哪怕是main的默认入口也要手动设置。因为在FlutterActivity的onStart方法中调用了一个doInitialFlutterViewRun方法:

    private void doInitialFlutterViewRun() {
      // Don't attempt to start a FlutterEngine if we're using a cached FlutterEngine.
      if (host.getCachedEngineId() != null) {
        return;
      }
    
      ... ...
    
      String appBundlePathOverride = host.getAppBundlePath();
      if (appBundlePathOverride == null || appBundlePathOverride.isEmpty()) {
        appBundlePathOverride = FlutterInjector.instance().flutterLoader().findAppBundlePath();
      }
    
      // Configure the Dart entrypoint and execute it.
      DartExecutor.DartEntrypoint entrypoint =
          new DartExecutor.DartEntrypoint(
              appBundlePathOverride, host.getDartEntrypointFunctionName());
      flutterEngine.getDartExecutor().executeDartEntrypoint(entrypoint);
    }
    

    可见,如果使用缓存id的话则不会走到下面的默认切入点设置代码。

  • FlutterPlugin

    我们这里分析的是和原生通信,所以必须要看FlutterPlugin,它是通信的桥梁。

    前面我们说到可以重写FlutterActivity的configureFlutterEngine方法来配置Plugin:

    @Override
        public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
            flutterEngine.getPlugins().add((FlutterPlugin) getPlugin());
            super.configureFlutterEngine(flutterEngine);
        }
    

    getPlugins方法获取的是FlutterEngineConnectionRegistry,它的add方法中有如下代码:

    if (has(plugin.getClass())) {
      return;
    }
    plugins.put(plugin.getClass(), plugin);
    plugin.onAttachedToEngine(pluginBinding);
    

    可见会按照plugin的class作为key保存到plugins中,然后就调用plugin的onAttachedToEngine方法,所以我们通常都会重写这个方法,在里面初始化各种Channel。

    下面看一个demo:

    class MyHomePlugin : FlutterPlugin, EventChannel.StreamHandler, MethodChannel.MethodCallHandler {
    
        private var batteryEventChannel : EventChannel? = null
        private var methodChannel : MethodChannel? = null
        private var batteryEventSink : EventChannel.EventSink? = null
    
        override fun onAttachedToEngine(binding : FlutterPlugin.FlutterPluginBinding) {
            batteryEventChannel = EventChannel(binding.binaryMessenger, "battery")
            batteryEventChannel?.setStreamHandler(this)
    
            methodChannel = MethodChannel(binding.binaryMessenger, "allMethod")
            methodChannel?.setMethodCallHandler(this)
        }
    
        override fun onDetachedFromEngine(binding : FlutterPlugin.FlutterPluginBinding) {
            batteryEventChannel?.setStreamHandler(null)
            batteryEventChannel = null
            methodChannel?.setMethodCallHandler(null)
            methodChannel = null
        }
    
        override fun onListen(arguments : Any?, events : EventChannel.EventSink?) {
            batteryEventSink = events
            events?.success(666)
              //events?.error("007","Wrong~",null)
            //events?.endOfStream()
        }
    
        override fun onCancel(arguments : Any?) {
            batteryEventSink?.endOfStream()
            batteryEventSink = null
        }
    
        override fun onMethodCall(call : MethodCall, result : MethodChannel.Result) {
            when(call.method){
                "getName"->{
                    LogUtil.d(this,"getName() has been called!")
                }
            }
        }
    }
    

    对于EventChannel来说,调用setStreamHandler方法设置一个EventChannel.StreamHandler,重写onListen和onCancel方法后即可接收Flutter发送的数据,onListen回调中,使用一个变量持有events的引用,这样在需要发送给Flutter的时候直接使用batteryEventSink发送即可,和MethodChannel不同,EventSink使用success、error和endOfStream方法发送数据,分别对应Flutter监听端的几个回调,Flutter端的监听写法是:

    @override
    void initState() {
      super.initState();
      eventChannel.receiveBroadcastStream().listen(
        (event) {
          onReceiveBatteryChange(event);
        },
        onError: onReceiveBatteryWrong,
      );
    }
    

    这里调用了listen方法之后就会回调到上面原生代码的onListen方法中,使用方法参数events调用相关方法后,最终又会回调到这里listen方法里设置的对应回调函数。

    对于MethodChannel来说,以Flutter端发送、原生接收的方向来看,Flutter端的发送代码是:

    Future result = await methodChannel.invokeMethod("getName");
    

    原生接收需要调用setMethodCallHandler给MethodChannel设置MethodChannel.MethodCallHandler,重写其onMethodCall回调方法处理接收逻辑。反过来,原生发送、Flutter接收的方向也是一样的,只不过设置的内容交换一下即可。

  • 总结

    本文分析了FlutterActivity、FlutterPlugin、Channel之间的连接关系,以及FlutterEngine的缓存策略,并知道了如何使用相关Channel来使Flutter端和原生交互起来,至于EventChannel和MethodChannel的原理涉及的代码比较多,另起一篇来整理。

你可能感兴趣的:(Flutter原生通信原理概览)