美文网首页Flutter
Flutter 与 Android 的交互

Flutter 与 Android 的交互

作者: Kuky_xs | 来源:发表于2019-05-13 15:27 被阅读446次

    该文已授权公众号 「码个蛋」,转载请指明出处

    Flutter 说到底只是一个 UI 框架,很多功能都需要通过原生的 Api 来实现,那么就会涉及到 Flutter 和 Native 的交互,因为本人不懂 iOS 开发,所以只能讲下 Flutter 同 Android 的交互。

    Android 项目配置 Flutter 依赖

    既然是互相交互,那么需要准备一个 Android 项目。接着就需要创建 flutter module,让 Android 项目依赖,创建的方法可以参考官网 Flutter Wiki,虽然是官网提供的方法,但是完全按照这个步骤来,还是会有坑的,这边就慢慢一步步解决坑。

    如果你用的是 Android Studio 进行开发的话,直接打开底部的 Terminal,直接创建 flutter module 依赖

    flutter create -t module flutter_native_contact 至于 module 名可以随意填写,module 创建完后结构大概是这样的

    flutter module.png

    接着切换到 module 下的 .android 文件夹,接着有坑来了,官网提供的方法是 ./gradlew flutter:assembleDebug 可能会提示命令不存在,那么直接通过 gradlew flutter:assembleDebug 来运行,等它自动跑完后,打开根目录下的 settings.gradle 文件,加入官网提供的 gradle 代码

    setBinding(new Binding([gradle: this]))                                 // new
    evaluate(new File(                                                      // new
      settingsDir.parentFile,                                               // new
      'flutter_native_contact/.android/include_flutter.groovy'              // new
    ))                                                                      // new
    

    你以为这里没坑,真是图样图森破,没坑是不可能的,编译器大爷可能会给你甩这么个错误

    error.png

    很明显可以看出是找不到我们的文件,所以把文件名路径给补全

    evaluate(new File(                                                      // new
      settingsDir.parentFile,                                               // new
      'FlutterNativeContactDemo/flutter_native_contact/.android/include_flutter.groovy' // 这里补全路径
    ))
    

    接着打开原有项目下,原有项目下,原有项目下的 app 中的 build.gradle 文件,在 android 下加上如下代码

    compileOptions {
      sourceCompatibility 1.8
      targetCompatibility 1.8
    }
    

    这个必须要加,不要问为什么,我也不知道为什么,最后在项目下添加 flutter module 的依赖就完成了。这个过程告诉我们一个什么道理呢?*不要以为官网的都对,官网讲的也不是完全可信的,时不时给你来个坑就能卡你老半天。

    原生界面加载 Flutter 页面

    那么如何在原生界面显示 Flutter 界面呢,这个就需要通过 FlutterView 来实现了,Flutter 这个类提供了 createViewcreateFragment 两个方法,分别用于返回 FlutterView 和 FlutterFragment 实例,FlutterFragment 的实现原理也是通过 FlutterView 来实现的,可以简单看下 FlutterFragment 的源码

    /**
     * A {@link Fragment} managing a {@link FlutterView}.
     *
     * <p><strong>Warning:</strong> This file is auto-generated by Flutter tooling.
     * DO NOT EDIT.</p>
     */
    public class FlutterFragment extends Fragment {
      public static final String ARG_ROUTE = "route";
      private String mRoute = "/";
    
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 获取传入的路由值,默认为 '/'
        if (getArguments() != null) {
          mRoute = getArguments().getString(ARG_ROUTE);
        }
      }
    
      @Override
      public FlutterView onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // 最后还是挺过 createView 方法来生成页面,只不过直接放在 fragment,
        // 放在 fragment 会比直接 使用 FlutterView 更方便管理,例如实现 ViewPager 等
        return Flutter.createView(getActivity(), getLifecycle(), mRoute);
      }
    }
    
    createFragment 方式加载

    在原生页面显示 Flutter 界面的第一种方式就是加载 FlutterFragment,看个比较简单的例子吧

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <!-- 这个布局用于加载 fragment -->
        <FrameLayout
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/flutter_fragment"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="20dp"
            android:layout_marginBottom="50dp"
            android:src="@drawable/ic_add_white_36dp"
            app:fabSize="auto"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    

    在 Activity 可以直接通过返回 FlutterFragment 加载到 FrameLayout 即可

    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, Flutter.createFragment("route_flutter"))
                .commit()
        }
    }
    

    这样就把 Flutter 页面加载到原生界面了,会通过传递的路由值在 dart 层进行查找,所以接着就需要编写 Flutter 界面

    /// runApp 内部值也可以直接传入 _buildWidgetForNativeRoute 方法
    /// 这边在外层嵌套一层 MaterialApp 主要是防止一些不必要的麻烦,
    /// 例如 MediaQuery 这方面的使用等
    void main() => runApp(FlutterApp());
    
    class FlutterApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: _buildWidgetForNativeRoute(window.defaultRouteName),
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primaryColor: Color(0XFF008577),
            accentColor: Color(0xFFD81B60),
            primaryColorDark: Color(0xFF00574B),
            iconTheme: IconThemeData(color: Color(0xFFD81B60)),
          ),
        );
      }
    }
    
    /// 该方法用于判断原生界面传递过来的路由值,加载不同的页面
    Widget _buildWidgetForNativeRoute(String route) {
      switch (route) {
        case 'route_flutter':
          return GreetFlutterPage();
        // 默认的路由值为 '/',所以在 default 情况也需要返回页面,否则 dart 会报错,这里默认返回空页面
        default: 
          return Scaffold();
      }
    }
    
    class GreetFlutterPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('NativeMessageContactPage'),
          ),
          body: Center(
            child: Text(
              'This is a flutter fragment page',
              style: TextStyle(fontSize: 20.0, color: Colors.black),
            ),
          ),
        );
      }
    }
    

    运行后可以看到页面加载出来了,不过会有一段时间的空白,这个在正式打包后就不会出现,所以不必担心。最后的页面应该是这样的

    flutter fragment.png
    createView 方式加载

    接着看下 createView 方法,说白了,第一种方法最后还是会通过该方式实现

      @NonNull
      public static FlutterView createView(@NonNull final Activity activity, @NonNull final Lifecycle lifecycle, final String initialRoute) {
        // 交互前的一些初始化工作,需要完成才可以继续下一步,同时需要保证当前线程为主线程
        // Looper.myLooper() == Looper.getMainLooper(),否则会甩你一脸的 IllegalStateException 
        FlutterMain.startInitialization(activity.getApplicationContext());
        FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), null);
        final FlutterNativeView nativeView = new FlutterNativeView(activity);
        // 将 flutter 页面绑定到相应的 activity
        final FlutterView flutterView = new FlutterView(activity, null, nativeView) {
            // ......
        };
        // 将路由值传到 flutter 层,并加载相应的页面,
        if (initialRoute != null) {
          flutterView.setInitialRoute(initialRoute);
        }
        
        // 绑定 lifecycle,方便生命周期管理,同 activity 绑定
        // 不熟悉 LifeCycle 的同学可以自行网上查找资料
        lifecycle.addObserver(new LifecycleObserver() {
          @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
          public void onCreate() {
            // 配置一些参数,传递到 flutter 层
            final FlutterRunArguments arguments = new FlutterRunArguments();
            arguments.bundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
            arguments.entrypoint = "main";
            // 最终会调用方法 nativeRunBundleAndSnapshotFromLibrary,这是一个 native 方法,进行交互
            flutterView.runFromBundle(arguments);
            // 进行注册
            GeneratedPluginRegistrant.registerWith(flutterView.getPluginRegistry());
          }
        // ......
        });
    
        return flutterView;
      }
    

    通过 createView 方法返回的 FlutterView,通过设置 Layoutparams 参数就可以添加到相应的布局上,还有一种直接通过 addContentView 方式进行加载,这里直接修改原有代码,

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // setContentView(R.layout.activity_main) 不需要这一步了
            val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_flutter")
            val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
            addContentView(flutterView, lp) // 直接加载到 activity 页面
        }
    

    但是通过这样加载的话,那么整个页面都是 flutter 的页面。那么之前的效果的 FAB 则不会被加载出来了,即使没有省略 setContentView(R.layout.activity_main) 方法,这个页面的 xml 布局也会被覆盖。

    PlantformChannel

    那么能够在原生界面显示 flutter 页面了,如何互相交互呢,这就需要通过 PlantformChannel 来执行了,PlantformChannel 主要有三种类型,BasicMessageChannel,MethodChannel,EventChannel。通过查看源码可以发现,三个 Channel 的实现机制类似,都是通过 BinaryMessenger 进行信息交流,每个 Channel 通过传入的 channel name 进行区分,所以在注册 Channel 的时候必须要保证 channel name 是唯一的,同时需要传入一个 BinaryMessageHandler 实例,用于传递信息的处理,当 Handler 处理完信息后,会返回一个 result,然后通过 BinaryMessenger 将 result 返回到 Flutter 层。如果需要深入理解这边推荐一篇文章深入理解Flutter PlatformChannel

    接下来直接看例子吧,在创建 PlatformChannel 的时候需要传入一个 BinaryMessenger 实例,通过查看 FlutterView 的源码可以发现,FlutterView 就是一个 BinaryMessenger 在 Android 端的实现,所以呢,可以直接通过前面介绍的 Flutter.createView 方法获取注册 Channel 时的 BinaryMessenger 实例了,真是得来全部费工夫~因为通信的方法可能在多个界面会使用,所以还是封装一个通用类来处理会比较合理

    BasicMessageChannel

    BasicMessageChannel 用于传递字符串和半结构化的信息。

    class FlutterPlugin(private val flutterView: FlutterView) :BasicMessageChannel.MessageHandler<Any>{
        companion object {
            private const val TAG = "FlutterPlugin"
    
            @JvmStatic
            fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
                // channel name 需要保持两侧一致
                val messageChannel =
                   BasicMessageChannel(flutterView, Constant.MESSAGE_CHANNEL_NAME, StandardMessageCodec.INSTANCE) // MessageCodec 有多种实现方式,可以参考推荐的文章
    
                val instance = FlutterPlugin(flutterView)
                messageChannel.setMessageHandler(instance) // 注册处理的 Hnadler
    
                return instance
            }
        }
    
        override fun onMessage(`object`: Any?, reply: BasicMessageChannel.Reply<Any>?) {
            // 简单的将从 Flutter 传过来的消息进行吐司,同时返回自己的交互信息
            // `object` 中包含的就是 Flutter 层传递过来的信息,reply 实例用于传递信息到 Flutter 层
            Toast.makeText(flutterView.context, `object`.toString(), Toast.LENGTH_LONG).show()
            reply?.reply("\"Hello Flutter\"--- an message from Android")
        }
    }
    

    接着就需要有个 FlutterView 用来注册,新建一个 Activity,用于加载 Flutter 页面

    class ContactActivity : AppCompatActivity() {
        private lateinit var plugin: FlutterPlugin
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // 传入路由值,需要在 flutter 层生成相应的界面
            val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_contact")
            val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
            addContentView(flutterView, lp)
    
            plugin = FlutterPlugin.registerPlugin(flutterView)
        }
    
        override fun onDestroy() {
            super.onDestroy()
        }
    }
    

    那么我们就要在 Flutter 界面的 _buildWidgetForNativeRoute 方法加入新路由值对应的界面

    Widget _buildWidgetForNativeRoute(String route) {
      switch (route) {
        // ...
              
        case 'route_contact':
          return FlutterContactPage();
    
        default:
          return Scaffold();
      }
    }
    
    class FlutterContactPage extends StatelessWidget {
      // 注册对应的 channel,要保证 channel name 和原生层是一致的
      final BasicMessageChannel _messageChannel =
          BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter Page'),
          ),
          // 简单放一个按钮,通过 channel 传输消息过去,同时将原生层返回的消息打印出来
          body: RaisedButton(
            onPressed: () {
              _messageChannel
                  .send('"Hello Native" --- an message from flutter')
                  .then((str) {
                print('Receive message: $str');
              });
            },
            child: Text('Send Message to Native'),
          ),
        );
      }
    }
    

    最后的效果小伙伴可以自行执行,点击按钮后会弹出吐司,吐司内容就是 Flutter 传递的信息,同时在控制台可以看到从原生层返回的信息。

    MethodChannel

    MethodChannel 用于传递方法调用(method invocation)

    直接在上述例子中进行修改,例如在 Flutter 页面中实现 Activity 的 finish 方法,并传递参数到前一个界面,先做 Flutter 页面的修改,在 AppBar 上增加一个返回按钮,用于返回上层页面

    class FlutterContactPage extends StatelessWidget {
      // 注册对应的 channel,要保证 channel name 和原生层是一致的
      final BasicMessageChannel _messageChannel =
          BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
      final MethodChannel _methodChannel = MethodChannel(METHOD_CHANNEL_NAME);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: InkWell(
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 20.0),
                child: Icon(Icons.arrow_back),
              ),
              onTap: () {
                _methodChannel
                    // invokeMethod 第一个值用于传递方法名,第二个值用于传递参数,
                    // 这边简单的传递一个字符串,当然也可以传递别的类型,map,list 等等
                    .invokeMethod<bool>('finishActivity', 'Finish Activity')
                    .then((result) { // 这边会返回一个结果值,通过判断是否成功来打印不同的信息
                  print('${result ? 'has finish' : 'not finish'}');
                });
              },
            ),
            title: Text('Flutter Page'),
          ),
            
          body: // ...
        );
      }
    }
    

    同时,我们需要在 FlutterPlugin 这个类中,做些必要的修改,首先需要实现 MethodCallHandler 接口,该接口中需要实现 onMethodCall 方法,通过获取调用的方法名和参数值,进行相应的处理

    class FlutterPlugin(private val flutterView: FlutterView) :
        MethodChannel.MethodCallHandler, BasicMessageChannel.MessageHandler<Any> {
    
        companion object {
            private const val TAG = "FlutterPlugin"
    
            @JvmStatic
            fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
                val instance = FlutterPlugin(flutterView)
                val methodChannel = MethodChannel(flutterView, Constant.METHOD_CHANNEL_NAME)
                // ...
                messageChannel.setMessageHandler(instance)
                return instance
            }
        }
            
        // ....
    
        // call 中携带了 Flutter 层传递过来的方法名和参数信息
        // 可以分别通过 call.method 和 call.arguments 来获取
        override fun onMethodCall(call: MethodCall?, result: MethodChannel.Result?) {
            when (call?.method) {
                "finishActivity" -> {
                    val activity = flutterView.context as Activity
                    val info = call.arguments.toString()
                    
                    val intent = Intent().apply {
                        putExtra("info", info)
                    }
    
                    activity.setResult(Activity.RESULT_OK, intent)
                    activity.finish()
                    
                    // 成功时候通过 result.success 返回值,
                    // 如果发生异常,通过 result.error 返回异常信息
                    // Flutter 通过 invokeMethod().then() 来处理正常结束的逻辑
                    // 通过 catchError 来处理发生异常的逻辑
                    result?.success(true)
                }
    
                // 如果未找到对应的方法名,则通过 result.notImplemented 来返回异常
                else -> result?.notImplemented()
            }
        }
    

    最终的效果,当点击返回按钮的时候,会将 Flutter 层通过 invokeMethod 传递的 arguments 属性吐司出来,同时,控制台会打印出 "has finish" 的信息

    EventChannel

    EventChannel 用于数据流(event streams)的通信

    EventChannel 的实现方式也类似,EventChannel 可以持续返回多个信息到 Flutter 层,在 Flutter 层的表现就是一个 stream,原生层通过 sink 不断的添加数据,Flutter 层接收到数据的变化就会作出新相应的处理。在 Android 端实现状态的监听可以通过广播来实现。直接看例子,还是修改上述代码

    class FlutterPlugin(private val flutterView: FlutterView) :
        MethodChannel.MethodCallHandler, EventChannel.StreamHandler, BasicMessageChannel.MessageHandler<Any> {
    
        private var mStateChangeReceiver: BroadcastReceiver? = null
    
        companion object {
            private const val TAG = "FlutterPlugin"
            const val STATE_CHANGE_ACTION = "com.demo.plugins.action.StateChangeAction"
            const val STATE_VALUE = "com.demo.plugins.value.StateValue"
    
            @JvmStatic
            fun registerPlugin(flutterView: FlutterView): FlutterPlugin {
                // ... 
                val streamChannel = EventChannel(flutterView, Constant.STREAM_CHANNEL_NAME)
    
                val instance = FlutterPlugin(flutterView)
                methodChannel.setMethodCallHandler(instance)
                streamChannel.setStreamHandler(instance)
                messageChannel.setMessageHandler(instance)
    
                return instance
            }
        }
    
        // 实现 StreamHandler 需要重写 onListen 和 onCancel 方法
        // onListen 不会每次数据改变就会调用,只在 Flutter 层,eventChannel 订阅广播
        // 的时候调用,当取消订阅的时候则会调用 onCancel,
        // 所以当开始订阅数据的时候,注册接收数据变化的关闭,
        // 在取消订阅的时候,将注册的广播注销,防止内存泄漏
        override fun onListen(argument: Any?, sink: EventChannel.EventSink?) {
            mStateChangeReceiver = createEventListener(sink)
            flutterView.context.registerReceiver(mStateChangeReceiver, IntentFilter(STATE_CHANGE_ACTION))
        }
    
        override fun onCancel(argument: Any?) {
            unregisterListener()
        }
    
        // 在 activity 被销毁的时候,FlutterView 不一定会调用销毁生命周期,或者会延时调用
        // 这就需要手动去注销一开始注册的广播了
        fun unregisterListener() {
            if (mStateChangeReceiver != null) {
                flutterView.context.unregisterReceiver(mStateChangeReceiver)
                mStateChangeReceiver = null
            }
        }
    
        private fun createEventListener(sink: EventChannel.EventSink?):
                BroadcastReceiver = object : BroadcastReceiver() {
    
            override fun onReceive(context: Context?, intent: Intent?) {
                if (TextUtils.equals(intent?.action, STATE_CHANGE_ACTION)) {
                    // 这边广播只做简单的接收一个整数,然后通过 sink 传递到 Flutter 层
                    // 当然,sink 还有 error 方法,用于传递发生的错误信息,
                    // 以及 endOfStream 方法,用于结束接收
                    // 在 Flutter 层分别有 onData 对应 success 方法,onError 对应 error 方法
                    // onDone 对应 endOfStream 方法,根据不同的回调处理不同的逻辑
                    sink?.success(intent?.getIntExtra(STATE_VALUE, -1))
                }
            }
        }
    }
    

    在 Flutter 层,通过对 stream 的监听,对返回的数据进行处理,为了体现出变化,这边修改成 SatefulWidget 来存储状态

    class FlutterContactPage extends StatefulWidget {
      @override
      _FlutterContactPageState createState() => _FlutterContactPageState();
    }
    
    class _FlutterContactPageState extends State<FlutterContactPage> {
      final MethodChannel _methodChannel = MethodChannel(METHOD_CHANNEL_NAME);
      final EventChannel _eventChannel = EventChannel(STREAM_CHANNEL_NAME);
      final BasicMessageChannel _messageChannel =
          BasicMessageChannel(MESSAGE_CHANNEL_NAME, StandardMessageCodec());
      StreamSubscription _subscription;
      var _receiverMessage = 'Start receive state'; // 初始的状态值
    
      @override
      void initState() {
        super.initState();
        // 当页面生成的时候就开始监听数据的变化
        _subscription = _eventChannel.receiveBroadcastStream().listen((data) {
          setState(() {
            _receiverMessage = 'receive state value: $data'; // 数据变化了,则修改数据
          });
        }, onError: (e) {
          _receiverMessage = 'process error: $e'; // 发生错误则显示错误信息
        }, onDone: () {
          _receiverMessage = 'receive data done'; // 发送完毕则直接显示完毕
        }, cancelOnError: true);
      }
    
      @override
      void dispose() {
        super.dispose();
        _subscription.cancel(); // 当页面销毁的时候需要将订阅取消,防止内存泄漏
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: InkWell(
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 20.0),
                child: Icon(Icons.arrow_back),
              ),
              onTap: () {
                // MethodChannel demo
                _methodChannel
                    .invokeMethod<bool>('finishActivity', _receiverMessage)
                    .then((result) {
                  print('${result ? 'has finish' : 'not finish'}');
                }).catchError((e) {
                  print('error happend: $e');
                });
              },
            ),
            title: Text('Flutter Page'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  // EventChannel demo,页面直接显示信息的变化
                  child: Text(
                    _receiverMessage,
                    style: TextStyle(fontSize: 20.0, color: Colors.black),
                  ),
                ),
                // BasicMessageChannel demo
                RaisedButton(
                  onPressed: () {
                    _messageChannel
                        .send('"Hello Native" --- an message from flutter')
                        .then((str) {
                      print('Receive message: $str');
                    });
                  },
                  child: Text('Send Message to Native'),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    同时,需要在 Activity 层调用一个定时任务不断的发送广播

    class ContactActivity : AppCompatActivity() {
    
        private var timer: Timer? = null
        private var task: TimerTask? = null
        private lateinit var random: Random
        private lateinit var plugin: FlutterPlugin
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            random = Random() // 生成随机整数
            val flutterView = Flutter.createView(this@ContactActivity, lifecycle, "route_contact")
            val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
            addContentView(flutterView, lp)
    
            plugin = FlutterPlugin.registerPlugin(flutterView)
    
            timer = Timer() // 定时器
            task = timerTask { // 定时任务
                sendBroadcast(Intent(FlutterPlugin.STATE_CHANGE_ACTION).apply {
                    putExtra(FlutterPlugin.STATE_VALUE, random.nextInt(1000))
                })
            }
            timer?.schedule(task, 3000, 2000) // 延时 3s 开启定时器,并 2s 发送一次广播
        }
    
        override fun onDestroy() {
            super.onDestroy()
    
            // 页面销毁的时候需要将定时器,定时任务销毁
            // 同时注销 Plugin 中注册的广播,防止内存泄漏
            timer?.cancel()
            timer = null
    
            task?.cancel()
            task = null
    
            plugin.unregisterListener()
        }
    }
    

    最后的实现效果大概是这样的

    event channel.gif

    Flutter 同 Android 端的交互到这讲的差不多了,和 iOS 的交互其实也类似,只不过在 Android 端通过 FlutterNativeView 来作为 Binarymessenger 的实现,在 iOS 端通过 FlutterBinaryMessenger 协议实现,原理是一致的。至于 Flutter 插件,其实现也是通过以上三种交互方式来实现的,可能我们目前通过 FlutterView 来作为 BinaryMessenger 实例,插件会通过 PluginRegistry.Registrar 实例的 messenger() 方法来获取 BinaryMessenger 实例。

    需要了解插件的写法也可以直接查看官方提供的检测电量插件:Flutter Battery Plugin

    相关文章

      网友评论

        本文标题:Flutter 与 Android 的交互

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