Flutter与Android通信,Bridge桥梁框架封装(简单易用)

1,557 阅读9分钟

本文目标

Flutter集成到现有的Android应用中,并封装通信框架,简单好用

Flutter集成到现有的Android应用中步骤

  • 首先,创建Flutter module
  • 为已存在 的Android 应用添加Flutter module 依赖

1.创建Flutter module

在做混合开发之前我们首先要创建一个Flutter module项目.
假如你的Native项目是这样的: xxx/flutter_hybrid/Native项目

cd xxx/flutter_hybrid/

flutter create -t module flutter_module

上面的代码会切换到你的Android项目的上一级目录,并创建一个flutter模块

这是创建的flutter_module项目的目录结构

你会发现它里面包含.android 和 .ios,这两个文件夹是隐藏文件,也是这个flutter_module的宿主工程

  • .android - flutter_module的Android宿主工程
  • .ios - flutter_module的iOS宿主工程
  • lib- flutter_module的Dart部分代码
  • pubspec.yaml - flutter_module的项目依赖配置文件

因为宿主工程的存在,我们这个flutter_module在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的AndroidStudio打开这个flutter_module项目,通过运行按钮是可以直接运行的

2.创建Native Android Studio项目(如果还没有)

新创建的 FlutterHybridAndroid项目和 flutter_module项目放在同一级

打开原生工程,照着下图操作
最低minSdkVersion 为16

 minSdkVersion 16

添加Java8编译选项

compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
}

 implementation project(':flutter')

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

//可选,主要作用是可以在当前AS的Project下显示flutter_module以方便查看和编写Dart代码
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')

至此,我们已经为我们的Android项目添加了Flutter所必须的依赖,接下来我们来看如何在java中调用Flutter模块

Andoid和Flutter通信框架

准备工作已经完成,这个时候我们要基于MethodChannel实现Flutter与Android通信架构

Android端

1.定义IFlutterBridge顶层接口
/**
 * Author: 信仰年轻
 * Date: 2021-06-11 12:50
 * Email: hydznsqk@163.com
 * Des:以下定义的方法都是Flutter调用Android
 */
interface IFlutterBridge<P, Callback> {

    /**
     * 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
     */
    fun onBack(p: P?)

    /**
     * 去Android页面或者传递数据到Android这边
     */
    fun goToNative(p: P)

    /**
     * 获取到Android这边的Header信息
     */
    fun getHeaderParams(callback: Callback)

}

以上定义的接口里面的方法都是Flutter调用Android的

  • fun onBack(p: P?) : 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
  • fun goToNative(p: P?) : 去Android页面或者传递数据到Android这边
  • fun getHeaderParams(callback: Callback) : 获取到Android这边的Header信息
2.实现该IFlutterBridge接口
/**
 * Author: 信仰年轻
 * Date: 2021-06-11 12:54
 * Email: hydznsqk@163.com
 * Des: Flutter通信桥梁,实现了MethodChannel.MethodCallHandler和IFlutterBridge接口
 */
class FlutterBridge : MethodChannel.MethodCallHandler, IFlutterBridge<Any?, MethodChannel.Result> {

    //因多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来
    private var methodChannels = mutableListOf<MethodChannel>()

    //单例
    companion object {
        @JvmStatic
        var instance: FlutterBridge? = null
            private set

        @JvmStatic
        fun init(flutterEngine: FlutterEngine): FlutterBridge? {
            val methodChannel = MethodChannel(flutterEngine.dartExecutor, "FlutterBridge")
            if (instance == null) {
                FlutterBridge().also {
                    instance = it
                }
            }
            methodChannel.setMethodCallHandler(instance)
            //因多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来
            instance!!.apply {
                methodChannels.add(methodChannel)
            }
            return instance
        }
    }

///////以下方法为Android调用Flutter/////////////////////////////////////////////////
    /**
     * Android调用flutter
     */
    fun fire(method: String, argument: Any?) {
        methodChannels.forEach {
            it.invokeMethod(method, argument)
        }
    }

    /**
     * Android调用flutter
     */
    fun fire(method: String, argument: Any, callback: MethodChannel.Result?) {
        methodChannels.forEach {
            it.invokeMethod(method, argument, callback)
        }
    }

///////以下方法为Flutter调用Android/////////////////////////////////////////////////
    /**
     * flutter调用原生
     * 处理来自Dart的方法调用
     */
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "onBack" -> onBack(call.arguments)
            "goToNative" -> goToNative(call.arguments)
            "getHeaderParams" -> getHeaderParams(result)
            else -> result.notImplemented()
        }
    }

    /**
     * 返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
     */
    override fun onBack(p: Any?) {
        if (ActivityManager.instance.getTopActivity(true) is MyFlutterActivity) {
            (ActivityManager.instance.getTopActivity(true) as MyFlutterActivity).onBackPressed()
        }
    }

    /**
     * 去Android页面或者传递数据到Android这边
     */
    override fun goToNative(p: Any?) {
        if (p is Map<*, *>) {
            val action = p["action"]
            if (action == "goToDetail") {
                val goodsId = p["goodsId"]

                Toast.makeText(AppGlobals.get(),"商品ID="+goodsId,Toast.LENGTH_LONG).show();
            } else if (action == "goToLogin") {
                Toast.makeText(AppGlobals.get(),"去登录",Toast.LENGTH_LONG).show();
            } else {

            }
        }
    }

    /**
     * 获取到Android这边的Header信息
     */
    override fun getHeaderParams(callback: MethodChannel.Result) {
        val map = HashMap<String, String>()
        map["boarding-pass"] = "boarding-pass"
        map["auth-token"] = "auth-token"
        callback.success(map)
    }
}

Flutter通信桥梁,实现了MethodChannel.MethodCallHandler和IFlutterBridge接口,而且是个单例,参数中接受FlutterEngine,并创建MethodChannel通信渠道,因为支持多Flutter引擎,多FlutterEngine后每个FlutterEngine需要单独注册一个MethodChannel,所以用集合将所有的MethodChannel保存起来,
然后在override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) 方法中接受Flutter那边的调用,
我们要做的就是Android侧和Flutter侧两边的方法名字保持一致

  • onBack(p: Any?) 用于返回到上一页
  • goToNative(p: Any?) 中的参数是个map,然后我们可以定义几个key,首先是action,这个key表示要做的动作,举个例子,action为goToDetail表示去详情页,
    action为goToLogin表示去登录页面,action定义好之后在定义具体要传递的值,比如说定义goodId这个key(用来接收从Flutter侧传过来的参数)
  • getHeaderParams(callback: MethodChannel.Result) 是获取Android侧这边的头信息,因为Flutter那边也要进行网络请求
3.创建Flutter引擎管理类

然后我们这个时候需要创建Flutter引擎了,我们这里有两个需求

  • 如何让预加载不损失"首页"性能?
  • 如何支持多个Flutter引擎并分别加载不同的dart 入口?
/**
 * Author: 信仰年轻
 * Date: 2021-06-11 13:50
 * Email: hydznsqk@163.com
 * Des:Flutter优化提升加载速度,实现秒开Flutter模块
 */
class FlutterCacheManager private constructor() {

    /**
     * 伴生对象,保持单例
     */
    companion object {

        //喜欢页面,默认是flutter启动的主入口
        const val MODULE_NAME_FAVORITE = "main"
        //推荐页面
        const val MODULE_NAME_RECOMMEND = "recommend"

        @JvmStatic
        @get:Synchronized
        var instance: FlutterCacheManager? = null
            get() {
                if (field == null) {
                    field = FlutterCacheManager()
                }
                return field
            }
            private set
    }


    /**
     * 空闲时候预加载Flutter
     */
    fun preLoad(context: Context){
        //在线程空闲时执行预加载任务
        Looper.myQueue().addIdleHandler {
            initFlutterEngine(context, MODULE_NAME_FAVORITE)
            initFlutterEngine(context, MODULE_NAME_RECOMMEND)
            false
        }
    }

    fun hastCached(moduleName: String):Boolean{
        return FlutterEngineCache.getInstance().contains(moduleName)
    }

    /**
     * 初始化Flutter
     */
    private fun initFlutterEngine(context: Context, moduleName: String): FlutterEngine {

        val flutterLoader: FlutterLoader = FlutterInjector.instance().flutterLoader()

        //flutter 引擎
        val flutterEngine = FlutterEngine(context,flutterLoader, FlutterJNI())
        //插件注册要紧跟引擎初始化之后,否则会有在dart中调用插件因为还未初始化完成而导致的时序问题
        FlutterBridge.init(flutterEngine)
        FImageViewPlugin.registerWith(flutterEngine)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint(
                flutterLoader.findAppBundlePath(),
                moduleName
            )
        )
        //存到引擎缓存中
        FlutterEngineCache.getInstance().put(moduleName,flutterEngine)
        return flutterEngine
    }

    /**
     * 获取缓存的flutterEngine
     */
    fun getCachedFlutterEngine(context: Context?, moduleName: String):FlutterEngine{
        var flutterEngine = FlutterEngineCache.getInstance()[moduleName]
        if(flutterEngine==null && context!=null){
            flutterEngine=initFlutterEngine(context,moduleName)
        }
        return flutterEngine!!
    }

    /**
     * 销毁FlutterEngine
     */
    fun destroyCached(moduleName: String){
        val map = FlutterEngineCache.getInstance()
        if(map.contains(moduleName)){
            map[moduleName]?.apply {
                destroy()
            }
            map.remove(moduleName)
        }
    }
}

首先这个Flutter引擎管理类是如何实现最开始的两个需求呢?

  • 问题1: 如何让预加载不损失"首页"性能?
    fun preLoad(context: Context) 该方法是在app启动的时候在Application中去调用的,我们这里是在在线程空闲时才去创建Flutter引擎

  • 问题2: 如何支持多个Flutter引擎并分别加载不同的dart 入口?
    我们在初始化Flutter引擎的方法中定义一个moduleName的参数,该参数是在创建Flutter引擎的时候去使用,用以区分是哪个引擎,我们在伴生对象中也有定义 flutter引擎启动的两个入口,main和recommend

4.创建FlutterFragment

通信bridge和Flutter引擎管理类都创建好了,这个时候创建FlutterFragment,用以去加载具体的Flutter页面

/**
 * Author: 信仰年轻
 * Date: 2021-06-11 15:20
 * Email: hydznsqk@163.com
 * Des: fragment的基类
 */
public abstract class BaseFragment extends Fragment  {
    protected View mLayoutView;

    @LayoutRes
    public abstract int getLayoutId();

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mLayoutView = inflater.inflate(getLayoutId(), container, false);
        return mLayoutView;
    }

    public void showToast(String message) {
        if (TextUtils.isEmpty(message)) {
            Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
        }
    }
}

/**
 * Author: 信仰年轻
 * Date: 2021-06-11 14:32
 * Email: hydznsqk@163.com
 * Des:
 */
abstract class FlutterFragment(moduleName: String) : BaseFragment() {

    private val flutterEngine: FlutterEngine?
    private lateinit var flutterView: FlutterView

    private val cached =  FlutterCacheManager.instance!!.hastCached(moduleName)

    init {
        flutterEngine =
            FlutterCacheManager.instance!!.getCachedFlutterEngine(AppGlobals.get(), moduleName)
    }

    override fun getLayoutId(): Int {
        return R.layout.fragment_flutter
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        // 注册flutter/platform_views 插件以便能够处理native view
        if(!cached){
            flutterEngine?.platformViewsController?.attach(activity,flutterEngine.renderer,flutterEngine.dartExecutor)
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        (mLayoutView as ViewGroup).addView(createFlutterView(activity!!))
    }

    private fun createFlutterView(context: Context): FlutterView {
        val flutterTextureView = FlutterTextureView(activity!!)
        flutterView = FlutterView(context, flutterTextureView)
        return flutterView
    }

    /**
     * 设置标题
     */
    fun setTitle(titleStr: String) {
        rl_title.visibility = View.VISIBLE
        title_line.visibility = View.VISIBLE
        title.text = titleStr
    }

    /**
     * 生命周期告知flutter
     */
    override fun onStart() {
        flutterView.attachToFlutterEngine(flutterEngine!!)
        super.onStart()
    }

    override fun onResume() {
        super.onResume()
        //for flutter >= v1.17
        flutterEngine!!.lifecycleChannel.appIsResumed()
    }

    override fun onPause() {
        super.onPause()
        flutterEngine!!.lifecycleChannel.appIsInactive()
    }

    override fun onStop() {
        super.onStop()
        flutterEngine!!.lifecycleChannel.appIsPaused()
    }

    override fun onDetach() {
        super.onDetach()
        flutterEngine!!.lifecycleChannel.appIsDetached()
    }

    override fun onDestroy() {
        super.onDestroy()
        flutterView.detachFromFlutterEngine()
    }
}

FlutterFragment很简单,就是接受一个moduleName的参数,然后在类初始化的时候去创建flutter引擎,然后fragment创建完毕后去添加FlutterView,这就把Flutter页面加载进来了

5.具体的fragment使用
/**
 * Author: 信仰年轻
 * Date: 2021-06-11 15:20
 * Email: hydznsqk@163.com
 * Des: 收藏页面
 */
class FavoriteFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_FAVORITE) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setTitle(getString(R.string.title_favorite))

        //点击标题,Android 调用Flutter,然后Flutter返回给Android
        title.setOnClickListener {
            FlutterBridge.instance!!.fire(
                "onRefreshFavorite",
                "我是收藏的参数",
                object : MethodChannel.Result {
                    override fun notImplemented() {
                        Toast.makeText(context, "dart那边未实现", Toast.LENGTH_LONG).show()
                    }

                    override fun error(
                        errorCode: String?,
                        errorMessage: String?,
                        errorDetails: Any?
                    ) {
                        Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
                    }

                    override fun success(result: Any?) {
                        if (result != null) {
                            Toast.makeText(context, result as String, Toast.LENGTH_LONG).show()
                        }
                    }
                })
        }
    }
}

/**
 * Author: 信仰年轻
 * Date: 2021-06-11 15:20
 * Email: hydznsqk@163.com
 * Des: 推荐页面
 */
class RecommendFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_RECOMMEND) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setTitle(getString(R.string.title_recommend))


        title.setOnClickListener {

            FlutterBridge.instance!!.fire(
                "onRefreshRecommend",
                "我是推荐的参数",
                object : MethodChannel.Result {
                    override fun notImplemented() {
                        Toast.makeText(context, "dart那边未实现", Toast.LENGTH_LONG).show()
                    }

                    override fun error(
                        errorCode: String?,
                        errorMessage: String?,
                        errorDetails: Any?
                    ) {
                        Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
                    }

                    override fun success(result: Any?) {
                        if (result != null) {
                            Toast.makeText(context, result as String, Toast.LENGTH_LONG).show()
                        }
                    }

                })
        }
    }
}

至此,Android这边的类基本创建完毕

Flutter端

1.创建FlutterBridge
import 'package:flutter/services.dart';

class FlutterBridge {
  static FlutterBridge _instance = FlutterBridge._();

  //该名称还要Android侧的保持一致
  MethodChannel _bridge = const MethodChannel("FlutterBridge");

  var _listenerMap = {}; //map key:String  value:MethodCall方法

  var header;

  FlutterBridge._() {
    _bridge.setMethodCallHandler((MethodCall call) {
      String method = call.method;
      if (_listenerMap[method] != null) {
        return _listenerMap[method](call);
      }
      return null;
    });
  }

  static FlutterBridge getInstance() {
    return _instance;
  }
///////以下方法为Android调用Flutter///////////////////////////////////////////////
  ///注册
  void register(String method, Function(MethodCall) callBack) {
    _listenerMap[method] = callBack;
  }

  ///解除注册
  void unRegister(String method) {
    if (_listenerMap.containsKey(method)) {
      _listenerMap.remove(method);
    }
  }

///////以下方法为Flutter调用Android/////////////////////////////////////////////////
  ///Android页面或者传递数据到Android这边
  void goToNative(Map params) {
    _bridge.invokeMethod("goToNative", params);
  }
 
  ///返回到上一页,一般用于Flutter点击返回按钮,然后关闭原生页面
  void onBack(Map params) {
    _bridge.invokeMethod("onBack", params);
  }
  
  ///获取到Android这边的Header信息
  Future<Map<String, String>> getHeaderParams() async {
    Map header = await _bridge.invokeMethod("getHeaderParams", {});
    return this.header = Map<String, String>.from(header);
  }

  MethodChannel bridge() {
    return _bridge;
  }
}

很简单,就是创建MethodChannel通信类,然后其渠道参数名字要和Android侧保持一致,在这里都为FlutterBridge,
然后定义register和unRegister方法,这些方法用于Android调用Flutter的时候,Flutter这边来接收信息,当然也支持返回给Android侧

然后一开始我们在Android侧定义了3个方法,goToNative,onBack,getHeaderParams这个时候要在Flutter侧保持一致,这几个方法是Flutter调用Android用的

2.创建具体的Flutter页面

首先是FavoritePage 收藏页面

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_module/bridge/flutter_bridge.dart';

class FavoritePage extends StatefulWidget {
  @override
  _FavoritePageState createState() => _FavoritePageState();
}

class _FavoritePageState extends State<FavoritePage> {
  @override
  bool get wantKeepAlive => true; //保活,切换tab的时候不会重新刷新页面

  @override
  void initState() {
    _registerEvent();
    super.initState();
  }

  var arguments;

  ///注册事件,登录成功后会发射事件到这里
  void _registerEvent() {
    var bridge = FlutterBridge.getInstance();
    //监听onRefresh消息,登录的时候和点击当前页面标题的时候会发射到这里,然后请求数据进行刷新
    bridge.register("onRefreshFavorite", (MethodCall call) {
      setState(() {
        arguments = call.arguments;
      });
      return Future.value("Flutter 收到,我是收藏");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: [
            Text(
              "收藏---${arguments}",
              style: TextStyle(fontSize: 20),
            ),
            MaterialButton(onPressed: (){

              var map = {"action":"goToDetail","goodsId":123456};
              FlutterBridge.getInstance().goToNative(map);
            },child: Text("Flutter 调用 Android"),)
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    FlutterBridge.getInstance().unRegister("onRefreshFavorite");
  }
}


然后是推荐页面

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_module/bridge/flutter_bridge.dart';


class RecommendPage extends StatefulWidget {
  @override
  _RecommendPageState createState() => _RecommendPageState();
}

class _RecommendPageState extends State<RecommendPage> {
  @override
  bool get wantKeepAlive => true; //保活,切换tab的时候不会重新刷新页面


  @override
  void initState() {
    _registerEvent();
    super.initState();
  }
  var arguments;

  ///注册事件,登录成功后会发射事件到这里
  void _registerEvent() {
    var bridge = FlutterBridge.getInstance();
    //监听onRefresh消息,登录的时候和点击当前页面标题的时候会发射到这里,然后请求数据进行刷新
    bridge.register("onRefreshRecommend", (MethodCall call) {
      setState(() {
        arguments = call.arguments;
      });
      return Future.value("Flutter 收到,我是推荐");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: [
            Text(
              "推荐---${arguments}",
              style: TextStyle(fontSize: 20),
            ),
            MaterialButton(onPressed: (){
              var map = {"action":"goToLogin"};
              FlutterBridge.getInstance().goToNative(map);
            },child: Text("Flutter 调用 Android"),)
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    FlutterBridge.getInstance().unRegister("onRefreshRecommend");
  }
}

3.Flutter程序入口

import 'package:flutter/material.dart';
import 'package:flutter_module/page/favorite_page.dart';
import 'package:flutter_module/page/native_page.dart';
import 'package:flutter_module/page/recommend_page.dart';

//至少要有一个入口,而且这下面的man() 和 recommend()函数名字 要和FlutterCacheManager中定义的对应上
void main() => runApp(MyApp(FavoritePage()));

@pragma('vm:entry-point')
void recommend() => runApp(MyApp(RecommendPage()));
class MyApp extends StatelessWidget {

  final Widget page;

  const MyApp(this.page);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: page,
      ),
    );
  }
}


在这里,至少要有一个入口,而且man() 和 recommend()函数名字 要和Android侧FlutterCacheManager中定义的对应上
到这里就可以完全进行通信了,具体可以参考demo