一个Flutter中台组件的开发过程

背景问题

Flutter的优点是综合开发效率的提高,可是组件缺失大大限制了他的优点.android

举个栗子:ios

需求功能开发完成后,须要打点上报和回收数据. 使用原生开发,这些功能组件都是现成的,可是若是咱们用Flutter来作 发现要考虑的东西还很多.git

  1. 客户端 打点模块的设计. 上报策略,防错,防丢失,加密...
  2. 服务端 日志上报,入库...
  3. 数据端 日志自动化生成报表,日志格式校验,日志量监控...
  4. android端和iOS端的日志差别处理

因此放弃从头来写的想法,直接桥接Android和iOS的日志模块.
同理在Flutter组件开发的过程当中,尽可能避免重复造轮子的行为.例如网络,崩溃上报,一些工具类能复用的就复用,采用了Flutter技术就多想一想如今作的工做是否有必要,是否符合其技术目标(综合开发效率的提高).json

那么下面就以Flutter日志组件这个例子来展现如何作一个Flutter中台组件swift

Flutter层的准备工做

建立插件工程

选择kotlin和swift为开发语言api

实现消息通讯,Dart做为发起方 原生做为接收方(MethodChannel回调)

我定义两个方法, 一个是获取LogSDK里面存储的客户端参数,好比用户UID,系统版本信息,Appsflyer的第三方ID等. 第二个是打点信息按照产品既定的格式收集起来,转发给原生上报. 定义代码以下:xcode

class Logsdk {
  const MethodChannel _channel = const MethodChannel('logsdk');

  // 获取参数信息
  Future<BaseInfo> get baseInfo async {
    BaseInfo baseInfo = BaseInfo._fromMap(
        await _channel.invokeMapMethod<String, dynamic>('getBaseInfo'));
    return baseInfo;
  }
  
  // 打点
  act(int event,
      {String count, String act1, String act2, String act3, String act4, String
      act5}) {
    _channel.invokeMethod("act", {
      'event': event,
      'count': count,
      'act1': act1,
      'act2': act2,
      'act3': act3,
      'act4': act4,
      'act5': act5,
    });
复制代码

那么bash

  • 原生端怎么实现Dart发起的方法?
  • 原生端怎么把结果回调给Dart?

两个问题须要解决.服务器

实现消息通讯, Dart做为接收方 原生做为发起方(MethodChannel监听)

原生端怎么主动把消息发送给Dart.markdown

譬若有这样一个场景: 用户反馈App卡顿,运营联系上了用户拿到了他的UID,而后根据UID推送消息到用户手机,而推送模块在原生端,因而原生层发送消息给Dart层,Dart层收集必要信息借助原生能力发送到服务器. 从而实现了一个Log收集的链路. 消息接收:

class Logsdk {
  static const MethodChannel _channel = const MethodChannel('logsdk');

  static StreamController<int> _msgCodeController;

  static Stream<int> get msgCodeUpdated =>
      _msgCodeController.stream;

  init() {
    if (_msgCodeController == null) {
      _msgCodeController = new StreamController.broadcast();
    }
    _channel.setMethodCallHandler((call) {
      switch (call.method) {
        case "errorCode":
          _msgCodeController.add(call.arguments as int);
          break;
        case "msgCode":
          _msgCodeController.add(call.arguments);
          break;
        case "updateCode":
          _msgCodeController.add(new ConnectionResult.fromJSON(result));
        default:
          throw new ArgumentError('Unknown method ${call.method}');
      }
      return null;
    });
  }

复制代码

外部使用方式

Logsdk.msgCodeUpdated.listen((event) {
      if(event==1){
        // do it
      }
    });
复制代码

那么这里的问题是客户端怎么把信息发送给dart呢?

Android实现

打开建立插件工程自动帮咱们生成的LogsdkPlugin.kt文件 找到 onMethodCall. 这里有两个参数(@NonNull call: MethodCall, @NonNull result: Result). 参数call里面放的是dart层的方法名称以及方法携带过来的附加信息. 参数result用来把执行结果回调给dart层. 具体实现以下:

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
            "act" -> {
                val event: Int? = call.argument("event")
                event?.let {
                    val act1: String? = call.argument("act1")
                    val act2: String? = call.argument("act2")
                    val act3: String? = call.argument("act3")
                    val act4: String? = call.argument("act4")
                    val act5: String? = call.argument("act5")
                    val count: String? = call.argument("count")
                    if (count == null) {
                        StatisticLib.getInstance().onCountReal(event!!,
                                1,
                                act1 ?: "",
                                act2 ?: "",
                                act3 ?: "",
                                act4 ?: "",
                                act5 ?: "")
                    } else {
                        StatisticLib.getInstance().onCountReal(event!!, count.toLong(),
                                act1 ?: "",
                                act2 ?: "",
                                act3 ?: "",
                                act4 ?: "",
                                act5 ?: "")
                    }

                }

            }
            "getBaseInfo" -> {
                val build: MutableMap<String, Any> = StatisticUtil.uuParams
                build["idfa"] = AppsCache.get().sp().getString(AppSpConstants.GAID, "")
                build["afid"] = AppsFlyerLib.getInstance().getAppsFlyerUID(GlobalLib.getContext())
                build["isNew"] = StatisticUtil.isNew().toString()
                build["pkg"] = GlobalLib.getContext().packageName
                build["device"] = "android"
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    build["sys_lang"] = Locale.getDefault().toLanguageTag()
                } else {
                    build["sys_lang"] = ""
                }
                build["sdk_version"] = Build.VERSION.SDK_INT.toString()
                build["channel"] = ""

                result.success(build)
            }
            else -> {
                result.notImplemented()
            }
        }
    }
复制代码

那么怎么把消息发送给dart呢?

lateinit var channel: MethodChannel

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "logsdk")
        channel.setMethodCallHandler(this);
    }
    fun sendMsg(code: Int) {
        channel.invokeMethod("errorCode", code);
    }
复制代码

仍是利用MethodChannel这个对象 channel.invokeMethod("errorCode", code)方法的第一个参数是发送给dart的方法名称,第二个参数能够为任意类型. 若是是对象传递,看函数的注释文档,建议使用Map/Json来实现.

/** * Arguments for the call. * * <p>Consider using {@link #arguments()} for cases where a particular run-time type is expected. * Consider using {@link #argument(String)} when that run-time type is {@link Map} or {@link * JSONObject}. */
复制代码

例如

// 在Android原生上发送
    JSONObject item = new JSONObject();
                            item.put("connected", false);
                            channel.invokeMethod("connection-updated", item.toString());
                            
// dart接收
    Map<String, dynamic> result = jsonDecode(call.arguments);
          _purchaseController.add(new PurchasedItem.fromJSON(result));
复制代码

也就是说上面的三个问题的解决办法都是利用方法通道了.具体总结起来就是

  1. 原生端怎么实现Dart发起的方法?

利用onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) 的第一个参数

  1. 原生端怎么把结果回调给Dart?

利用onMethodCall(@NonNull call: MethodCall, @NonNull result: Result)的第二个参数

  1. 原生端怎么主动把消息发送给Dart.

利用MethodChannel.invokeMethod()方法

iOS实现

iOS的实现方法和Android大同小异. xCode上打开自动生成的classSwiftLogsdkPlugin 而后编辑自动生成的方法public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {. FlutterMethodCall里面放的是dart传过来的方法名称和参数信息. FlutterResult用来作回调,传给dart层

// 不管成功与否原生必定要执行result()方法 不然flutter端会一直等待,由于flutter是单线程模型致使卡死.
    // 另外这段代码是在Flutter的UI线程执行,若是是耗时操做记得切换线程
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "getPlatformVersion":
            result("iOS \(UIDevice.current.systemVersion)")
            result(nil)
        case "uu":
            // 上报UU
            handleReportUU(call, result)
        case "act":
            handleLogAct(call, result)
        case "report":
            handleReportUU(call, result)
        case "getBaseInfo":
            handleGetBaseInfo(call, result)
        default:
            result(FlutterMethodNotImplemented)
        }
    }
    
    private func handleLogAct(_ call:FlutterMethodCall,_ result: FlutterResult){
        let arguments = call.arguments as? NSDictionary
        if let _args = arguments{
            let event = _args["event"] as! Int
            let act1 = _args["act1"] as? String
            let act2 = _args["act2"] as? String
            let act3 = _args["act3"] as? String
            let act4 = _args["act4"] as? String
            let act5 = _args["act5"] as? String
            let count = _args["count"] as? String
            if let count = count {
                if let countValue = Int64(count) {
                    // 统计时长上报
                    EventLogger.shared.logEventCount(EventCombine(code: event), act1 ?? "",act2 ?? "" , act3 ?? "", act4 ?? "",act5 ?? "",count: countValue)
                } else{
                    // 普通上报
                    EventLogger.shared.logEventCount(EventCombine(code: event), act1 ?? "",act2 ?? "" , act3 ?? "", act4 ?? "",act5 ?? "")
                }
            }
            result(nil)
        }
    }
    
    private func handleGetBaseInfo(_ call:FlutterMethodCall,_ result: FlutterResult){
        var infos:Dictionary<String,String> = Dictionary()
        if let config = UULogger.shared.config{
            infos["vendor_id"] = config.vendor_id
            // android系统是由google提供的uid 对应iOS?
            infos["idfa"] = config.idfa
            // appsflyers提供的uid
            infos["afid"] = config.afid
            infos["device"] = "ios";
            // 系统语言
            infos["sys_lang"] = UIDevice.current.accessibilityLanguage;
            // 系统版本
            infos["sdk_version"] = UIDevice.current.systemVersion;
            // 渠道
            infos["channel"] = config.referrer;
            // 是不是新用户
            infos["isNew"] =  SwiftLogsdkPlugin.isNew ? "1":"0"
            // 包名
            infos["pkg"] = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? ""
        }
        result(infos)
    }
}
复制代码

那么主动发消息给dart利用FlutterMethodChannel来实现.

public static var channel:FlutterMethodChannel? = nil
    
    public static func register(with registrar: FlutterPluginRegistrar) {
        channel = FlutterMethodChannel(name: "logsdk", binaryMessenger: registrar.messenger())
        let instance = SwiftLogsdkPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel!)
    }
    
    public static func sendMsg(_ errorCode:Int){
        channel?.invokeMethod("errorCode", arguments: errorCode)
    }
复制代码

组件工程化

dart层和原生之间的主从关系

从业务上来看flutter是android 和 iOS的承载,可是看下代码依赖就会发现原生才是flutter的宿主,同时也是各类组件的宿主. 只是这些组件须要遵照一些协议来支持flutter组件. 而其中的MethodChannel就是一个基础的通讯协议.

以Android工程为例.

  1. 打开Android的宿主gradle文件. 看到apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"这个就是把flutter依赖进来.
  2. 打开setting.gradle看到
    这里就是把Flutter的组件引入依赖到宿主.

Android怎么配置依赖

  1. 打开原生开发环境.

明白了原生是Flutter组件的宿主后,就能够按照传统的Android/iOS工程打开项目 开发原生代码.

Android: AS直接打开,flutter组件文件夹下面的example

这就是一个Android工程,直接在下面开发

固然也能够把项目总体当作Android工程打开,可是这样会致使项目代码和依赖超级多,影响导入和编译的速度,从而影响效率.

因此组件开发推荐使用example工程打开,这样还能够在example中写下组件的文档和使用范例.

  1. 依赖现有模块

在Log上报的代码看到,并无去实现,而是直接使用了StatisticLib.getInstance().onCountReal, StatisticLib其实就是我现有的上传代码,打开组件的Android文件夹下面的build.gradle文件直接添加现有依赖

  1. 组件发布

打开pubspec.yaml,如今组件的依赖方式是基于本地文件路径.

 logsdk:
 path: ../flutter-plugins/packages/plugin_logsdk
复制代码

修改成

组件开发的时候使用本地路径,组件集成的时候使用git依赖, 这样对开发和集成方来讲都相对友好.

iOS怎么配置依赖

  1. 打开原生开发环境

执行 flutter pub get

在example/ios 执行 pod install

打开 Runner.xcworkspace

  1. 依赖现有模块 打开logsdk.podspec

添加EventLog依赖

而后在主项目的Podfile中添加

以及各类Pod依赖

pod 'Flutter', :path => 'Flutter'
  pod 'KeychainAccess'
  pod 'FBSDKCoreKit', '~> 5.6.0'
  pod 'AppsFlyerFramework', '5.2.0'
  pod 'EventLog', '~>1.0'
  pod 'SwiftyUserDefaults'
复制代码

这样就实现了插件工程对EventLog原生模块的依赖引入.

  1. 组件发布

打开pubspec.yaml,如今组件的依赖方式是基于本地文件路径.

 logsdk:
 path: ../flutter-plugins/packages/plugin_logsdk
复制代码

修改成

组件开发的时候使用本地路径,组件集成方使用git依赖.

组件调试

  1. Android和Flutter可使用Attach调试. 不用重新编译

  2. iOS上重新执行,使用Debug调试 使用po+对象内容 的方式来打印

断点调试不要多端同时进行.

总结

  1. 使用Flutter的目标是去提高客户端综合开发效率
  2. 以前没有接触过iOS开发,可是在使用xcode和swift的时候. 大部分问题,都能在androidstudio和kotlin里面找到对应的解决模式.
相关文章
相关标签/搜索