美文网首页Flutter
Flutter原生通信原理概览

Flutter原生通信原理概览

作者: 就叫汉堡吧 | 来源:发表于2022-03-08 09:35 被阅读0次
    • 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原生通信原理概览

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