flutter和原生利用pigeon建立通道

首先导入依赖:

dependencies:
pigeon: ^10.0.0

image.png

定义一个文件:

/// 用于定于flutter和平台的桥接方法
/// @HostApi() 标记的,是用于 Flutter 调用原生的方法;
/// @FlutterApi() 标记的,是用于原生调用 Flutter 的方法;
/// @async 如果原生的方法,是步回调那种,你就可以使用这个标记;
/// 在项目根目录,运行以下命令生成平台代码
/// dart run pigeon --input pigeons/messages.dart

import 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
  dartOut: 'lib/pigeons/pigeon.dart',
  dartOptions: DartOptions(),
  // objcHeaderOut: '../isolarCloud/operation4ios/Flutter/SGFlutterBridge.h',
  // objcSourceOut: '../isolarCloud/operation4ios/Flutter/SGFlutterBridge.m',
  // objcOptions: ObjcOptions(),
  kotlinOut:
  '../iSolarCloud/LibBase/src/main/java/com/isolarcloud/libbase/flutter/SGFlutterBridge.kt',
  kotlinOptions: KotlinOptions(),
))
class CommonParams {
  String? pageName;
  Map<String?, Object?>? arguments;
}
class ApiParams {
  String? url;
  Map<String?, Object?>? arguments;
}

/// Flutter调用原生的Api,全部放在一个抽象类中即可
@HostApi()

abstract class SGHostApi {
  /// push至原生页面,参数:页面名称、参数
  void pushNativePage(CommonParams params);
  /// pop出当前页面,预留参数,可通过params.pageName pop到指定页面
  void popPage(CommonParams? params);
  /// 通过Key获取本地化文本数据(同步)
  String getLocalizedText(String? key);
  /// Flutter通过URL和arguments调用原生端接口,异步返回数据给Flutter端
  @async
  Map requestNativeApi(ApiParams apiParams);
  /// 是否允许开启Native页面的原生手势返回效果
  void enablePopRecognizer(bool enable);
}

运行命令行:
dart run pigeon --input pigeons/messages.dart

接着可以运行下:flutter pub get 的命令

自动会生成这个文件:

image.png

里面会实现SGHostApi的定义的方法:比如下面的方法就是自动生成:

/// push至原生页面,参数:页面名称、参数
Future<void> pushNativePage(CommonParams arg_params) async {
  final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
      'dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.pushNativePage', codec,
      binaryMessenger: _binaryMessenger);
  final List<Object?>? replyList =
      await channel.send(<Object?>[arg_params]) as List<Object?>?;
  if (replyList == null) {
    throw PlatformException(
      code: 'channel-error',
      message: 'Unable to establish connection on channel.',
    );
  } else if (replyList.length > 1) {
    throw PlatformException(
      code: replyList[0]! as String,
      message: replyList[1] as String?,
      details: replyList[2],
    );
  } else {
    return;
  }
}

注意打开这个代码注释:意思是在你Android项目的某个路径下生成SGFlutterBridge文件。

kotlinOut:
    '../iSolarCloud/LibBase/src/main/java/com/isolarcloud/flutter/SGFlutterBridge.kt',
kotlinOptions: KotlinOptions(),

然后会在:上面这个路径下自动生成和原生的桥接方法,这里执行的是Android的方法,ios只需要打开:

  // objcHeaderOut: '../isolarCloud/operation4ios/Flutter/SGFlutterBridge.h',
  // objcSourceOut: '../isolarCloud/operation4ios/Flutter/SGFlutterBridge.m',
  // objcOptions: ObjcOptions(),

我们先看看原生和flutter项目的依赖:

image.png

在Android的项目settings.gradle中配置引用:

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

以上操作就把两个项目关联在一起了。下面是自动生成的桥接方法,pigeon自动帮你实现:

/**
 * Flutter调用原生的Api,全部放在一个抽象类中即可
 *
 * Generated interface from Pigeon that represents a handler of messages from Flutter.
 */
interface SGHostApi {
  /** push至原生页面,参数:页面名称、参数 */
  fun pushNativePage(params: CommonParams)
  /** pop出当前页面,预留参数,可通过params.pageName pop到指定页面 */
  fun popPage(params: CommonParams?)
  /** Flutter通过URL和arguments调用原生端接口,异步返回数据给Flutter端 */
  fun requestNativeApi(apiParams: ApiParams, callback: (Result<String?>) -> Unit)
  /** 是否允许开启Native页面的原生手势返回效果 */
  fun enablePopRecognizer(enable: Boolean)
  /**
   * 调用原生toast
   * type:0 老版,1:国内,2:海外样式
   */
  fun showToast(type: AppType, msg: String)
  /**
   * 调用原生toast
   * type:0 老版,1:国内,2:海外样式
   */
  fun showLoading(show: Boolean, type: AppType, msg: String?)
  /** 获取用户信息 */
  fun getUserInfo(): String?

  companion object {
    /** The codec used by SGHostApi. */
    val codec: MessageCodec<Any?> by lazy {
      SGHostApiCodec
    }
    /** Sets up an instance of `SGHostApi` to handle messages through the `binaryMessenger`. */
    @Suppress("UNCHECKED_CAST")
    fun setUp(binaryMessenger: BinaryMessenger, api: SGHostApi?) {
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.pushNativePage", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val paramsArg = args[0] as CommonParams
            var wrapped: List<Any?>
            try {
              api.pushNativePage(paramsArg)
              wrapped = listOf<Any?>(null)
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.popPage", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val paramsArg = args[0] as CommonParams?
            var wrapped: List<Any?>
            try {
              api.popPage(paramsArg)
              wrapped = listOf<Any?>(null)
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.requestNativeApi", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val apiParamsArg = args[0] as ApiParams
            api.requestNativeApi(apiParamsArg) { result: Result<String?> ->
              val error = result.exceptionOrNull()
              if (error != null) {
                reply.reply(wrapError(error))
              } else {
                val data = result.getOrNull()
                reply.reply(wrapResult(data))
              }
            }
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.enablePopRecognizer", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val enableArg = args[0] as Boolean
            var wrapped: List<Any?>
            try {
              api.enablePopRecognizer(enableArg)
              wrapped = listOf<Any?>(null)
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.showToast", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val typeArg = AppType.ofRaw(args[0] as Int)!!
            val msgArg = args[1] as String
            var wrapped: List<Any?>
            try {
              api.showToast(typeArg, msgArg)
              wrapped = listOf<Any?>(null)
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.showLoading", codec)
        if (api != null) {
          channel.setMessageHandler { message, reply ->
            val args = message as List<Any?>
            val showArg = args[0] as Boolean
            val typeArg = AppType.ofRaw(args[1] as Int)!!
            val msgArg = args[2] as String?
            var wrapped: List<Any?>
            try {
              api.showLoading(showArg, typeArg, msgArg)
              wrapped = listOf<Any?>(null)
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
      run {
        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.isolarcloud_flutter.SGHostApi.getUserInfo", codec)
        if (api != null) {
          channel.setMessageHandler { _, reply ->
            var wrapped: List<Any?>
            try {
              wrapped = listOf<Any?>(api.getUserInfo())
            } catch (exception: Throwable) {
              wrapped = wrapError(exception)
            }
            reply.reply(wrapped)
          }
        } else {
          channel.setMessageHandler(null)
        }
      }
    }
  }
}

同步或者异步的去调用自动生成的方法:

Future<void> makeApiCall() async {
  var arguments = {"curPage": 1, "size": 10, "message_scene_code": 2};

  final stopwatch = Stopwatch()
    ..start();

  await SGHostApi()
      .requestNativeApi(ApiParams(arguments: arguments))
      .then((value) {
    print("数据回来的时间:${DateTime
        .now()
        .millisecondsSinceEpoch}");
    stopwatch.stop();
    print("总共花费时间:${stopwatch.elapsedMilliseconds}毫秒");
    toast = "android 返回的数据==$value";
    // print("value===$value");
    setState(() {
    });
  });
}

 _getString(){
   SGHostApi().getLocalizedText("I18N_COMMON_SERVICE_AGREEMENT_DESCRIPTION").then((value){
     print("android 给过来的数据==$value");
   });
  setState(() {

  });
}

Android的代码:

class SingleFlutterActivity : FlutterActivity(), EngineBindingsDelegate {

  private val engineBindings: EngineBindings by lazy {
    EngineBindings(activity = this, delegate = this, entrypoint = FlutterRouter.DEMO_ENTRY_POINTER, initialRoute = "${FlutterRouter.DEMO_ROUTER}?psId=1234")
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    engineBindings.attach()
    /**
     * flutter 调用原生的方法
     * */
    SGHostApiget.setUp(getFlutterEngine()!!.getDartExecutor().getBinaryMessenger(), MyApi())

    SGHostApi.setUp(getFlutterEngine()!!.getDartExecutor().getBinaryMessenger(),FlutterToNativeApi() )
  }

  override fun onDestroy() {
    super.onDestroy()
    engineBindings.detach()
  }

  override fun provideFlutterEngine(context: Context): FlutterEngine? {
    return engineBindings.engine
  }

  override fun onNext() {
    val flutterIntent = Intent(this, LoginActivity::class.java)
    startActivity(flutterIntent)
  }

  /**
   * flutter 调用原生的方法,原生写法
   * */
  override fun onNext(str: String?) {

    val flutterIntent = Intent(this, FlutterToAndroidActivity::class.java)
    flutterIntent.putExtra("extraString", str)
    startActivity(flutterIntent)
  }

  /**
   * flutter 调用原生的方法
   * */
  class MyApi : SGHostApiget {
    override fun getString(): String {
      println("测试===6666")
      return "测试===6666,这里是Android的方法"
    }
  }

  /**
   * flutter 调用原生的方法
   * */
  class FlutterToNativeApi() : SGHostApi {

    /**
     * flutter调用原生跳原生页面
     * */
    override fun pushNativePage(params: CommonParams) {
      //params.pageName 页面名称非类名,不可直接创建实例

      /*val flutterIntent = Intent(this, params::class.java)
      flutterIntent.putExtra("extraString", str)
      startActivity(flutterIntent)*/
    }

    /**
     * flutter调用原生跳原生页面
     * */
    override fun popPage(params: CommonParams?) {
      //flutter调用Android去退出页面
    }

    /**
     * flutter调用原生 获取国际化字符串
     * */
    override fun getLocalizedText(key: String?): String {
      return I18nUtil.getString(key);
    }

    /**
     * flutter调用原生 获取网络信息 异步
     * */
    override fun requestNativeApi(
      apiParams: ApiParams,
      callback: (Result<Map<Any, Any?>>) -> Unit,
    ) {
      /*val bitmap: Bitmap = BitmapFactory.decodeResource(BaseApplication.getApplication().resources, R.drawable.test_image)
      val outputStream: ByteArrayOutputStream = ByteArrayOutputStream()
      bitmap.compress(Bitmap.CompressFormat.PNG, 50, outputStream)
      val size: Int = outputStream.size()
      println("字节大小: $size")
      val result: Result<Map<Any, Any?>> = Result.success(
        mapOf(
          "name" to outputStream
        ),
      )
      callback(result)*/

       val map: MutableMap<String, Any> = HttpRequest.getCommonParamMap()
       HttpUtil.postWithPath("/v1/devService/getDevModelUpgradeConfigList", /*apiParams.arguments*/map, object : HttpGlobalHandlerCallback<UpdateRequestBean?>() {
        override fun onSuccess(jsonResult: JsonResults<UpdateRequestBean?>) {
          // 处理成功返回的数据
          val data = jsonResult.result_data // 获取数据
          if (data is UpdateRequestBean) {
            data as UpdateRequestBean
          }
          println("开始传输时间: ${System.currentTimeMillis()}")
            callback(Result.success(data!!.toMap())) // 通过callback返回结果
          // 打印字节大小
          val byteArray = data?.toString()?.toByteArray(Charsets.UTF_8)
          val size = byteArray?.size ?: 0
          println("字节大小: $size")
        }
        fun UpdateRequestBean.toMap(): Map<Any, Any?> {
          val map = mutableMapOf<Any, Any?>()
          map["code"] = code
          map["record"] = record
          map["totalFileSize"] = totalFileSize
          map["pageList"] = pageList?.map { fileInfo ->
            mapOf(
              "file_url" to fileInfo.file_url,
              "file_signature" to fileInfo.file_signature,
              "file_name" to fileInfo.file_name,
              "file_id" to fileInfo.file_id,
              "dev_model" to fileInfo.dev_model,
              "file_size" to fileInfo.file_size,
              "type" to fileInfo.type,
              "dev_type_id" to fileInfo.dev_type_id,
              "record_id" to fileInfo.record_id,
              "sn" to fileInfo.sn,
              "update_time_stamp" to fileInfo.update_time_stamp,
              "block_flag" to fileInfo.block_flag
            )
          }
          return map
        }

        override fun onError(type: ErrorNetType) {
          super.onError(type)
          // 处理错误情况
          // val result: Result<Map<Any, Any?>> = Result.success(
          //   mapOf(
          //     "name" to "John",
          //     "age" to 30,
          //     "email" to "john@example.com"
          //   )
          // )
          // callback(result)
          // 通过callback返回错误信息
          val failureResult: Result<Map<Any, Any?>> = Result.failure(
            Exception("An error occurred")
          )
          callback(failureResult)
        }
        override fun onFinish() {
          super.onFinish()
        }
      })
    }

    override fun disablePopRecognizer(disable: Boolean) {
    }
  }

}

在flutter中统一使用这个类来获取原生获取的网络数据:

/// 成功回调
typedef SuccessCallback = void Function(
    Map<String, dynamic>? resultData, String resultCode, String resultMsg);

/// 失败回调
typedef ErrorCallback = void Function();

class RequireNativeApi {
  static void postWithPathOversea(String? url, Map<String?, Object?>? arguments,
      SuccessCallback? successCallback, ErrorCallback? errorCallback,
      {bool showToast = true}) {
    postWithPath(url, arguments, successCallback, errorCallback,
        showToast: showToast, appType: AppType.oversea);
  }

  static void postWithPathDomestic(
      String? url,
      Map<String?, Object?>? arguments,
      SuccessCallback? successCallback,
      ErrorCallback? errorCallback,
      {bool showToast = true}) {
    postWithPath(url, arguments, successCallback, errorCallback,
        showToast: showToast, appType: AppType.domestic);
  }

  static void postWithPath(String? url, Map<String?, Object?>? arguments,
      SuccessCallback? successCallback, ErrorCallback? errorCallback,
      {bool showToast = true, AppType appType = AppType.oversea}) {
    ApiParams apiParams = ApiParams();
    apiParams.url = url;
    apiParams.arguments = arguments;

    sgApi.requestNativeApi(apiParams).then((data) {
      debugPrint("");
      debugPrint("======================================================");
      debugPrint("api: $url");
      debugPrint("业务入参: $arguments");
      debugPrint("出参:$data");
      debugPrint("======================================================");
      if (data == null || data.isEmpty) {
        /// 网络异常
        if (showToast) {
          sgApi.showToast(appType, SGi18n.key("I18N_COMMON_NETWORK_ERROR"));
        }
        if (errorCallback != null) {
          errorCallback();
        }
        return;
      }

      var jsonData = json.decode(data);
      if (successCallback != null) {
        // 返回接口返回数据,需要调用各个实体类的方法,所以需要各个业务层将Map转为对应的实体类
        successCallback(
          jsonData['result_data'] ?? {},
          jsonData['result_code'] ?? "",
          jsonData['result_msg'] ?? "",
        );
      }
    }).onError((error, stackTrace) {
      debugPrint("RequireNativeApi error: $error");
      debugPrint("RequireNativeApi error: $stackTrace");
      if (showToast) {
        sgApi.showToast(
            appType, SGi18n.key("I18N_COMMON_NETWORK_ERROR_MESSAGE"));
      }
      if (errorCallback != null) {
        errorCallback();
      }
    });
  }
}

flutter中要使用的地方直接拿到成功和失败的回调即可:

RequireNativeApi.postWithPathOversea(Api.getDeleteUserMessage, argument,
    getDeleteMessageSuccess, () => SGOSLoadingToast.dismissLoading());

我项目中用的是getx,大家可以看看我完整的代码:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:isolarcloud_flutter/api/api.dart';
import 'package:isolarcloud_flutter/api/require_native_api.dart';
import 'package:isolarcloud_flutter/bean/home_msg_entity.dart';
import 'package:isolarcloud_flutter/bean/update_msg_entity.dart';
import 'package:isolarcloud_flutter/modules/demo/constant/constant_state_id.dart';
import 'package:isolarcloud_flutter/utils/hud/sg_os_loading_toast.dart';
import 'package:isolarcloud_flutter/utils/json_cache_util.dart';

import 'sg_message_first_state.dart';

class SGMessageHomePageLogic extends GetxController {
  final SGMessageHomePageState state = SGMessageHomePageState();

  static const idMessageClearUpdate = "message_clear_update";
  static const idMessageList = "message_list";
  static const idLoadFail = "message_load_fail";

  void getList(String labelStr, {bool isFirst = false}) async {
    state.labelStr = labelStr;
    isFirst ? SGOSLoadingToast.showLoading() : null;
    Map<String, Object> argument = {};
    var cache = await JsonCacheManageUtils.getCacheData(
            JsonCacheManageUtils.HomeMessageResponse,
            labelId: labelStr.toString())
        .then((value) {
      if (value != null) {
        return HomeMsgEntity.fromJson(value as Map<String, dynamic>);
      }
    });
    state.hasCache = false;
    if (cache is HomeMsgEntity) {
      state.pageList = cache;
      state.hasCache = true;
      update([idMessageList + labelStr.toString()]);
    }

    RequireNativeApi.postWithPathOversea(
        Api.getHomeMessageList, argument, getHomeMessageSuccess, () {
      isFirst ? SGOSLoadingToast.dismissLoading() : null;
      update([idLoadFail]);
    });
  }

  void getHomeMessageSuccess(
      Map<String, dynamic>? resultData, String resultCode, String resultMsg) {
    if (resultCode == "1" && resultData != null && resultData.isNotEmpty) {
      JsonCacheManageUtils.saveCacheData(
          JsonCacheManageUtils.HomeMessageResponse,
          labelId: state.labelStr,
          resultData);
      state.pageList = HomeMsgEntity.fromJson(resultData);
    }
    SGOSLoadingToast.dismissLoading();
    if (!state.hasCache) {
      update(["$idMessageList${state.labelStr}"]);
    }
  }

  /// clear 消息 传null为全部
  void updateUserAllMessage(dynamic messageSceneCode) {
    SGOSLoadingToast.showLoading();
    Map<String, dynamic> argument = {
      "is_all": 1,
      "message_scene_code": messageSceneCode,
    };
    RequireNativeApi.postWithPathOversea(Api.getUpdateUserMessage, argument,
        getClearMessageSuccess, () => SGOSLoadingToast.dismissLoading());
  }

  void getClearMessageSuccess(
      Map<String, dynamic>? resultData, String resultCode, String resultMsg) {
    if (resultCode == "1" && resultData != null && resultData.isNotEmpty) {
      state.updateBean = UpdateMsgEntity.fromJson(resultData);
    }
    SGOSLoadingToast.dismissLoading();
    update([idMessageClearUpdate]);
  }

  //删除单个消息大类
  void deleteUserMessage(String? ids, String? messageSceneCode, int index) {
    SGOSLoadingToast.showLoading();
    Map<String, dynamic> argument = {
      "ids": ids,
      "message_scene_code": messageSceneCode,
    };
    state.removeIndex = index;
    RequireNativeApi.postWithPathOversea(Api.getDeleteUserMessage, argument,
        getDeleteMessageSuccess, () => SGOSLoadingToast.dismissLoading());
  }

  void getDeleteMessageSuccess(
      Map<String, dynamic>? resultData, String resultCode, String resultMsg) {
    debugPrint("resultData: $resultData");
    if (resultCode == "1" && resultData != null && resultData.isNotEmpty) {
      state.deleteBean = UpdateMsgEntity.fromJson(resultData);
    }
    SGOSLoadingToast.dismissLoading();
    update([StateListenerId.messageDelete]);
  }

  ///底部按钮删除全部选中的
  void deleteCheckMessage(List<HomeMsgPageList> messageHomeList) {
    SGOSLoadingToast.showLoading();
    StringBuffer messageSceneCodes = StringBuffer();
    for (int i = 0; i < messageHomeList.length; i++) {
      String? sceneCode = messageHomeList[i].messageSceneCode;
      messageSceneCodes.write(sceneCode);
      if (i != messageHomeList.length - 1) {
        messageSceneCodes.write(',');
      }
    }
    String result = messageSceneCodes.toString();
    if (result.isEmpty) {
      return;
    }

    Map<String, dynamic> argument = {
      "ids": null,
      "message_scene_code": result,
    };
    RequireNativeApi.postWithPathOversea(Api.getDeleteUserMessage, argument,
        getDeleteMessageSuccess, () => SGOSLoadingToast.dismissLoading());
  }
}

按理来说这个GetxController类是用来访问网络的类,这里用来和原生交互,就这样子。

最近工作忙,加班重,疏于思考,缺乏输出,望大家理解,有时间优化文章,敬请期待!