本文方针

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与Android通信,Bridge桥梁框架封装(简单易用)

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

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

你会发现它里边包括.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项目放在同一级

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

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

 minSdkVersion 16

添加Java8编译选项

compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
}

 implementation project(':flutter')

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

//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')

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

至此,咱们现已为咱们的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