Flutter与原生交互(将flutter嵌入已有项目

  • 概述
  • 缘由
  • 思路
  • 开始
  • 配置项目
  • 创建FlutterActivity
  • Flutter中channel配置
  • Flutter main.dart配置与中转页面
  • 使用
  • 原生打开flutter页面 (携带参数)
  • flutter打开原生页面 (携带参数)
  • 原生页面调用 flutter页面上的方法
  • flutter页面调用原生页面上的方法
  • 最后



目前已有android ios 开发好的项目 现在新的需求使用flutter开发 在嵌入搭配已有的原生项目,这样ios和android 开发任务理论上可以节约一半的开发时间

概述

缘由

目前有一些第三方框架解决了 flutter与原生交互,我们之前使用的flutterboost,由于flutterboost长期不更新(版本1.17.4),导致我们flutter也升级不了.所以不想要被第三方框架影响,所以我们自己来处理.

思路

关键是使用channel 交互基本靠它.
原生页面跳转到一个flutter页面 这个flutter页面是一个中转页面 它负责处理路由(具体调整到哪个flutter页面) 携带初始参数.

原生页面也有一个FlutterActivity /FlutterViewController 它负责处理flutter需要调用原生功能.

一共要解决这么几个问题

  1. 原生打开flutter页面 (携带参数)
  2. flutter打开原生页面 (携带参数)
  3. 原生页面调用 flutter页面上的方法
  4. flutter页面调用原生页面上的方法

开始

以android为例

配置项目

  1. 创建Flutter module 项目
  2. 依赖模块
    这里my_flutter 是你的flutter module工程目录的名字 你直接运行原生项目 它会自动编译flutter 当作依赖
include ':app'                                    // assumed existing content
setBinding(new Binding([gradle: this]))                                // new
evaluate(new File(                                                     // new
  settingsDir.parentFile,                                              // new
  'my_flutter/.android/include_flutter.groovy'                         // new
))                                                                     // new

这里都比较简单 我没有写的很细 文档中每步都有 中文文档

创建FlutterActivity

  1. 创建FlutterActivity
    自定义一个Activity 继承于FlutterFragmentActivity或FlutterActivity
    个人建议继承于FlutterFragmentActivity 做过android应该都知道,要不然以后需要supportFragmentManager的时候 头就大了
  2. Activity里面先“重写”withCachedEngine方法 (静态方法不能叫重写 就这么理解吧) 为什么要重写 看下代码就知道了
companion object {
        fun withCachedEngine(cachedEngineId: String): CachedEngineIntentBuilder {
            return CachedEngineIntentBuilder(YDFlutterActivity::class.java, cachedEngineId)
        }
    }

3.定义channel

channel的初始化在configureFlutterEngine中进行
我这里使用了FlutterEngine 它是在application中配置的 具体配置官方文档很详细

这里关键就是channel的两个方法
setMethodCallHandler用于处理flutter页面发送过的消息
invokeMethod 用于发送消息给flutter页面
这里我们关注invokeMethod 我把我要跳转的具体页面path 和初始化参数params 发送给flutter中转页面

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        Log.i("YDFlutterActivity", "执行了configureFlutterEngine")
        initChannel(flutterEngine)
    }

    open fun initChannel(flutterEngine: FlutterEngine) {
        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { call, result ->
            when (call.method) {
                "nativeBack" -> {
                    this@YDFlutterActivity.finish()
                }
                "createFollowUpSuccess" -> {
                    EventBus.getDefault().post(FlutterReceiveEvent("createFollowUpSuccess"))
                }
                "testFun" -> {
                    T.showToast(this@YDFlutterActivity, "原生YDFlutterActivity:testFun")
                    Log.i("YDFlutterActivity", "原生拿到flutter 参数${call.arguments.toString()}")
                    result.success("互调成功")

                    channel.invokeMethod("testFlutterFun", "{原生参数:1231313}")
                }
                "openShare" -> {
                    val args = getMap(call.arguments.toString())
                    Log.i("AssistantFlutter", "弹窗弹窗${args.toString()}")
                }
                "ClientDetailV3" -> {
                    val args = getMap(call.arguments.toString())
                    navigationActivityFromPair(
                        RouterMerchant.CLIENT_DETAIL_V3,
                        Constant.IDK to CustomerDetailDto(
                            type = args["businessType"]?.toString()?.toInt(),
                            customerId = args["businessId"]?.toString()?.toLong()
                        )
                    )
                }
                else -> {
                    result.notImplemented()
                }
            }
        }

        intent?.apply {
            val path = getStringExtra("path")
            val params = getStringExtra("params") ?: ""
            channel.invokeMethod(path, params)
            Log.i("YDFlutterActivity", "执行了invokeMethod path:${path}  params:${params}")
        }



        if(EventBus.getDefault().isRegistered(this)){
            EventBus.getDefault().register(this)
        }

    }

Flutter中channel配置

单独创建一个dart文件 它供全局使用

MethodChannel commonNativeChannel =
    MethodChannel('samples.flutter.dev/battery');

Flutter main.dart配置与中转页面

  • main.dart配置
    flutter的入口 很简单 只有一个默认路由 中转页面 NativeMiddlewarePage
Map<String, WidgetBuilder> nativeRoutes = {
  '/': (BuildContext context) {
    return NativeMiddlewarePage();
  }
};

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    _initScreenUtil();
  }

  _initScreenUtil() {
    //初始化屏幕适配 当前设计图按照ios一倍图设计
    setDesignWHD(375.0, 667.0, density: 1);
  }

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      routes: nativeRoutes,
      builder: (context, widget) {
        return MediaQuery(
          //设置文字大小不随系统设置改变
          data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
          child: widget,
        );
      },
    );
  }
}
  • 创建中转页面
    就一个空白的Container
    它的作用就是当作路由的中间件
    直接拿到全局的commonNativeChannel 通过setMethodCallHandler判断原生要跳转具体哪个flutter页面 并带参数
class NativeMiddlewarePage extends StatefulWidget {
  @override
  _NativeMiddlewarePageState createState() => _NativeMiddlewarePageState();
}

class _NativeMiddlewarePageState extends State<NativeMiddlewarePage> {

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

  void initNativeRouter() {
    commonNativeChannel.setMethodCallHandler((MethodCall call) async {
      print("flutter拿到原生    method:${call.method} arguments:${call.arguments}");

      Map<String, dynamic> params = {};

      try {
        if (call.arguments != null) {
          if ((call.arguments as String).isNotEmpty) {
            params = json.decode(call.arguments);
          }
        }
      } catch (e) {
        print("json 解析异常 ${e}");
      }

      switch (call.method) {
        case 'testFlutterFun':
          print("flutter拿到原生 参数${call.arguments.toString()}");
          break;
        case 'CustomerRecyclePage':
          skipPage(CustomerRecyclePage());
          break;
        case 'FollowCreatePage':
          skipPage(FollowCreatePage(
              taskId: params['taskId'],
              businessId: params['businessId'],
              taskName: params['taskName']));
          break;
        case 'AssistantHomePage':
          skipPage(AssistantHomePage(
            param: params,
          ));
          break;
        case 'FollowCreateTasksPage':
          skipPage(FollowCreateTasksPage(
            taskId: params['taskId'],
            taskName: params['taskName'],
            taskType: ['taskType'] as int,
          ));
          break;
        case 'FollowListPage':
          skipPage(FollowListPage(
            param: params,
          ));
          break;
        case 'FollowTaskDetailPage':
          skipPage(FollowTaskDetailPage(
            taskId: params['taskId'],
          ));
          break;

        case 'FollowRecordDetailPage':
          skipPage(FollowRecordDetailPage(
            taskId: params['taskId'],
          ));
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
    );
  }

  void skipPage(Widget page) {
    print("跳转至:${page}");
    Get.offAll(page, duration: Duration(milliseconds: 0));
  }
}

到这里 基本就配置完了

使用

原生打开flutter页面 (携带参数)

  • 原生页面直接start到刚才封装的FlutterActivity.withCachedEngine
    path就是你要跳转的具体flutter页面名称
    params携带的参数 这里都用json字符串
context.startActivity(YDFlutterActivity
                .withCachedEngine(NATIVE_MIDDLEWARE)
                .build(context).apply {
                    putExtra("path", url)
                    if (params.isNotEmpty()) {
                        putExtra("params", gson.toJson(params))
                    }
                })
  • flutter中间页处理
    完整代码刚才已经贴出来了
    这里就是解析原生参数 判断call.method 决定跳转哪个页面
commonNativeChannel.setMethodCallHandler((MethodCall call) async {
     print("flutter拿到原生    method:${call.method} arguments:${call.arguments}");

     Map<String, dynamic> params = {};

     try {
       if (call.arguments != null) {
         if ((call.arguments as String).isNotEmpty) {
           params = json.decode(call.arguments);
         }
       }
     } catch (e) {
       print("json 解析异常 ${e}");
     }

     switch (call.method) {
       case 'testFlutterFun':
         print("flutter拿到原生 参数${call.arguments.toString()}");
         break;
       case 'CustomerRecyclePage':
         skipPage(CustomerRecyclePage());
         break;

flutter打开原生页面 (携带参数)

  • 在任何flutter页面直接使用 Channel.invokeMethod
    参数也是json形式
commonNativeChannel.invokeMethod("你要跳转原生页面", 参数);
  • 原生处理
    在自定义的FlutterActivity 中channel.setMethodCallHandler 中处理
channel.setMethodCallHandler { call, result ->
          when (call.method) {
       
              "你要跳转原生页面" -> {
                  val args = getMap(call.arguments.toString())
                  //这里startactivity就行
              }
              else -> {
                  result.notImplemented()
              }
          }
      }

只要是在flutter页面里FlutterActivity 必定存在 它是flutter的载体

原生页面调用 flutter页面上的方法

  • 其实看了页面调整后 调用方法更简单 同理
    原生
channel.invokeMethod(方法名, 参数)

flutter页面 处理channel

commonNativeChannel.setMethodCallHandler((MethodCall call) async {
    print("flutter拿到原生    method:${call.method} arguments:${call.arguments}");
    }

flutter页面调用原生页面上的方法

  • 同理
    flutter
commonNativeChannel.invokeMethod('nativeBack', params);
  • 原生
channel.setMethodCallHandler { call, result ->
          when (call.method) {
              "nativeBack" -> {
                  this@YDFlutterActivity.finish()
              }
          }

最后

  • 为什么要通过中间flutter页面,不直接跳转到对应的页面
    一开始确实 在flutter中配置路由
    原生配置多个FlutterEngine 使用initialRoute 直接跳转到对应flutter页面 但是无法传递初始化参数 还用通过channel传参数
    另外多个FlutterEngine 也影响原生性能
  • 配置多个FlutterActivity ?
    可以 对于有些交互复杂的页面 确实要单独配置一个Activity 要不然交互全写在一个Activity里面
  • flutter的channel的全局的 原生的channel 属于 Activity
  • 可以多个channel