Flutter 与 Android 的交互

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

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 创建完后结构大概是这样的

接着切换到 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
复制代码

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

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

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),
        ),
      ),
    );
  }
}
复制代码

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

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()
    }
}
复制代码

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

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

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

转载于:https://juejin.im/post/5cd91de4518825686b120921

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FlutterAndroid交互可以通过平台通道(platform channel)来实现。平台通道允许Flutter应用程序与原生Android代码之间进行双向通信。 以下是使用平台通道进行FlutterAndroid交互的一般步骤: 1. 在Flutter应用程序中,使用`flutter create`命令创建一个新的Flutter项目,或者在现有项目中添加一个新的Android模块。 2. 在Flutter应用程序中创建一个`MethodChannel`对象,该对象用于在FlutterAndroid之间进行方法调用通信。例如,在Dart代码中添加以下代码: ```dart import 'package:flutter/services.dart'; // 创建 MethodChannel 对象 MethodChannel channel = MethodChannel('com.example.channelName'); // 在需要的地方调用原生 Android 方法 Future<void> callNativeMethod() async { try { final String result = await channel.invokeMethod('methodName'); print(result); } catch (e) { print('Error: $e'); } } ``` 3. 在Android项目中,创建一个类来处理来自Flutter应用程序的方法调用。例如,在Java代码中添加以下代码: ```java import io.flutter.app.FlutterActivity; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example.channelName"; @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); // 注册方法调用处理器 new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler(new MethodCallHandler() { @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("methodName")) { // 处理来自Flutter的方法调用 String response = someNativeMethod(); result.success(response); } else { result.notImplemented(); } } }); } // 原生 Android 方法的实现 private String someNativeMethod() { // 实现自己的逻辑 return "Response from Android"; } } ``` 在上述代码中,我们首先定义了一个与Flutter应用程序通信的通道名称(CHANNEL),然后注册了一个方法调用处理器。当Flutter应用程序调用`methodName`方法时,处理器将调用`someNativeMethod`方法并返回响应。 4. 最后,您可以在Flutter应用程序中调用`callNativeMethod`方法,这将触发与Android交互,并返回来自Android的响应。 请注意,上述代码只是示例,并且可以根据您的需求进行修改和扩展。此外,您还可以使用平台通道传递参数和接收回调,以实现更复杂的交互逻辑。 这是使用平台通道在FlutterAndroid之间进行基本交互的基本步骤。希望对您有所帮助!如果有任何进一步的问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值