Flutter的优点是综合开发效率的提高,可是组件缺失大大限制了他的优点.android
举个栗子:ios
需求功能开发完成后,须要打点上报和回收数据. 使用原生开发,这些功能组件都是现成的,可是若是咱们用Flutter来作 发现要考虑的东西还很多.git
因此放弃从头来写的想法,直接桥接Android和iOS的日志模块.
同理在Flutter组件开发的过程当中,尽可能避免重复造轮子的行为.例如网络,崩溃上报,一些工具类能复用的就复用,采用了Flutter技术就多想一想如今作的工做是否有必要,是否符合其技术目标(综合开发效率的提高).json
那么下面就以Flutter日志组件这个例子来展现如何作一个Flutter中台组件swift
选择kotlin和swift为开发语言api
我定义两个方法, 一个是获取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.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呢?
打开建立插件工程自动帮咱们生成的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));
复制代码
也就是说上面的三个问题的解决办法都是利用方法通道了.具体总结起来就是
利用
onMethodCall(@NonNull call: MethodCall, @NonNull result: Result)
的第一个参数
利用
onMethodCall(@NonNull call: MethodCall, @NonNull result: Result)
的第二个参数
利用
MethodChannel.invokeMethod()
方法
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)
}
复制代码
从业务上来看flutter是android 和 iOS的承载,可是看下代码依赖就会发现原生才是flutter的宿主,同时也是各类组件的宿主. 只是这些组件须要遵照一些协议来支持flutter组件. 而其中的MethodChannel
就是一个基础的通讯协议.
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
这个就是把flutter依赖进来.明白了原生是Flutter组件的宿主后,就能够按照传统的Android/iOS工程打开项目 开发原生代码.
Android: AS直接打开,flutter组件文件夹下面的example
在Log上报的代码看到,并无去实现,而是直接使用了StatisticLib.getInstance().onCountReal
, StatisticLib其实就是我现有的上传代码,打开组件的Android文件夹下面的build.gradle文件直接添加现有依赖
打开pubspec.yaml,如今组件的依赖方式是基于本地文件路径.
logsdk:
path: ../flutter-plugins/packages/plugin_logsdk
复制代码
修改成
执行 flutter pub get
在example/ios 执行 pod install
打开 Runner.xcworkspace
logsdk.podspec
pod 'Flutter', :path => 'Flutter'
pod 'KeychainAccess'
pod 'FBSDKCoreKit', '~> 5.6.0'
pod 'AppsFlyerFramework', '5.2.0'
pod 'EventLog', '~>1.0'
pod 'SwiftyUserDefaults'
复制代码
这样就实现了插件工程对EventLog原生模块的依赖引入.
打开pubspec.yaml,如今组件的依赖方式是基于本地文件路径.
logsdk:
path: ../flutter-plugins/packages/plugin_logsdk
复制代码
修改成
Android和Flutter可使用Attach调试. 不用重新编译
iOS上重新执行,使用Debug调试 使用po+对象内容 的方式来打印
断点调试不要多端同时进行.