混合应用大杂烩(flutter + uniapp)
初衷
一直以来很多项目中使用了不少跨平台开发方案,从react-native到weex再到flutter。使用下来感受最优的方案是 flutter,在短暂的发展过程中出现了一大批优秀的开源组件。由于项目开发初期选型 flutter 项目中 80%的功能使用 flutter 开发。后来项目功能不断增加,和项目的小程序端功能出现重复。flutter 中集成 uniapp 的需求就产生了。
功能
- flutter 拉起指定uniapp并可携带参数实现指定页面跳转
- uniapp 版本热更新
- uniapp 使用flutter编写的通用组件并可以透传数据给uniapp(避免uniapp引用部分无用的原生模块)
废话不多说上代码
Android
原生代码
// 小程序Event 对象
val event = EventChannel(messenger, "flutter_uni_stream")
var eventSink: EventSink? = null
event.setStreamHandler(
object : StreamHandler {
override fun onListen(arguments: Any?, events: EventSink) {
eventSink = events
Log.d("Android", "EventChannel onListen called")
}
override fun onCancel(arguments: Any?) {
Log.w("Android", "EventChannel onCancel called")
}
})
// 小程序实例
val unimpMap = mutableMapOf<String?, IUniMP?>();
var uniMpcallback:DCUniMPJSCallback? = null
// 小程序Channel 对象
val channel = MethodChannel(messenger, "flutter_uni")
channel.setMethodCallHandler { call, res ->
// 根据方法名,分发不同的处理
when (call.method) {
"initMP" -> {
try {
if (DCUniMPSDK.getInstance().isInitialize()) {
res.success(2)
} else {
// 初始化uniMPSDK
val config = DCSDKInitConfig.Builder()
.setCapsule(false)
.build()
DCUniMPSDK.getInstance().initialize(this, config)
//监听胶囊点击事件
DCUniMPSDK.getInstance()
.setCapsuleMenuButtonClickCallBack { argumentAppID ->
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("event", "capsuleaction")
}
eventSink?.success(backdata)
}
// 监听小程序关闭
DCUniMPSDK.getInstance().setUniMPOnCloseCallBack { argumentAppID ->
if (unimpMap.containsKey(argumentAppID)) {
unimpMap.remove(argumentAppID)
unimpMap[argumentAppID]?.closeUniMP();
}
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("event", "close")
}
eventSink?.success(backdata)
}
//监听小程序向原生发送事件回调方法
DCUniMPSDK.getInstance()
.setOnUniMPEventCallBack { argumentAppID, event, data, callback ->
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("event", event)
set("data", data)
}
eventSink?.success(backdata)
uniMpcallback = callback
}
res.success(1)
}
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 检查指定的 UniMP 小程序
* {
* "appid": ""
* }
*/
"checkMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
if (DCUniMPSDK.getInstance().isExistsApp(argumentAppID)) {
res.success(true)
} else {
res.success(false)
}
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 获取指定的 UniMP 小程序版本
* {
* "appid": ""
* }
*/
"versionMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
val result = DCUniMPSDK.getInstance().getAppVersionInfo(argumentAppID)
var versionStr: String? = null;
if(result != null) {
versionStr = result.toString()
}
res.success(versionStr)
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 安装 UniMP 小程序
* {
* "appid": "",
* "wgtPath": ""
* }
*/
"installMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
val wgtPath: String? = call.argument<String>("wgtPath")
val releaseConfig = UniMPReleaseConfiguration()
releaseConfig.wgtPath = wgtPath
DCUniMPSDK.getInstance().releaseWgtToRunPath(
argumentAppID,
releaseConfig
) { code, result ->
if (code == 1) {
res.success(true)
} else {
res.success(false)
}
}
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 打开指定的 UniMP 小程序
* {
* "appid": "",
* "isreload": true //重新打开
* "config": {
* "extraData": {}, //其他自定义参数JSON
* "path": "" //指定启动应用后直接打开的页面路径
* }
* }
*/
"openMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
if (unimpMap.containsKey(argumentAppID) == false) {
val argumentConfig: HashMap<String,Any>? = call.argument<HashMap<String,Any>>("config")
val uniMPOpenConfiguration = UniMPOpenConfiguration()
if (argumentConfig != null && argumentConfig.containsKey("extraData")) {
val jsonObject = org.json.JSONObject()
var extraData = argumentConfig.get("extraData") as HashMap<String,Any>
extraData.forEach { s, any -> jsonObject.put(s,any) }
jsonObject.put("path",argumentConfig.get("path") as String?)
uniMPOpenConfiguration.extraData = jsonObject
}
if (argumentConfig != null && argumentConfig.containsKey("path")) {
uniMPOpenConfiguration.path = argumentConfig.get("path") as String?
}
// 打开小程序
unimpMap[argumentAppID] = DCUniMPSDK.getInstance()
.openUniMP(applicationContext, argumentAppID, uniMPOpenConfiguration)
res.success(true)
} else {
val data = call.argument<Any>("config")
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("data", data)
}
unimpMap[argumentAppID]?.sendUniMPEvent("open_app", backdata)
unimpMap[argumentAppID]?.showUniMP();
res.success(true)
}
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 隐藏指定的 UniMP 小程序
* {
* "appid": "",
* }
*/
"hideMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
if (unimpMap.containsKey(argumentAppID)) {
unimpMap[argumentAppID]?.hideUniMP();
}
res.success(true)
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 关闭指定的 UniMP 小程序
* {
* "appid": "",
* }
*/
"closeMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID: String? = call.argument<String>("appid")
if (unimpMap.containsKey(argumentAppID)) {
unimpMap.remove(argumentAppID)
unimpMap[argumentAppID]?.closeUniMP();
}
res.success(true)
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/**发送数据到指定的UniMP小程序
* {
* "appid": "",
* "event": "",
* "data": {}
* }
*/
"sendMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID = call.argument<String>("appid")
val sendEvent = call.argument<String>("event")
val data = call.argument<Any>("data")
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("event", sendEvent)
set("data", data)
}
unimpMap[argumentAppID]?.sendUniMPEvent(sendEvent, backdata)
res.success(true)
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
/** 回调数据到到指定的UniMP小程序
* {
* "appid": "",
* "event": "",
* "data": {}
* }
*/
"callbackMP" -> {
try {
// 接收 Flutter 传入的参数
val argumentAppID = call.argument<String>("appid")
val sendEvent = call.argument<String>("event")
val data = call.argument<Any>("data")
val backdata = JSONObject().apply {
set("appid", argumentAppID)
set("event", sendEvent)
set("data", data)
}
uniMpcallback?.invoke(backdata)
res.success(true)
} catch (e: Exception) {
e.printStackTrace()
res.error("error_code", e.message, e.printStackTrace().toString())
}
}
else -> {
// 如果有未识别的方法名,通知执行失败
res.error("error_code", "error_message", null)
}
}
}
ios
原生代码
///小程序打开Map
var uniMpMap: [String: DCUniMPInstance] = [:]
///监听sink
var eventSink:FlutterEventSink?
///回调函数
var uniMpCallback:DCUniMPKeepAliveCallback?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
///是否初始化
var isInit = false
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "flutter_uni",binaryMessenger: controller.binaryMessenger)
let event = FlutterEventChannel(name: "flutter_uni_stream",binaryMessenger: controller.binaryMessenger)
event.setStreamHandler(self)
channel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
switch(call.method) {
case "initMP":
if isInit {
result(2)
} else {
let options = NSMutableDictionary.init(dictionary: launchOptions ?? [:])
options.setValue(NSNumber.init(value: true), forKey: "debug")
DCUniMPSDKEngine.setDelegate(self!)
DCUniMPSDKEngine.initSDKEnvironment(launchOptions: options as! [AnyHashable : Any])
DCUniMPSDKEngine.setCapsuleButtonHidden(true)
isInit = true
result(1)
}
break
case "checkMP":
if let arguments = call.arguments as? Dictionary<String, Any> {
let appid: String = arguments["appid"] as? String ?? ""
if DCUniMPSDKEngine.isExistsUniMP(appid) {
result(true)
} else {
do {
let wgtPath = Bundle.main.path(forResource: appid, ofType: "wgt") ?? ""
try DCUniMPSDKEngine.installUniMPResource(withAppid: appid, resourceFilePath: wgtPath, password: nil)
result(true)
} catch {
result(false)
}
}
}
break
case "versionMP":
if let arguments = call.arguments as? Dictionary<String, Any> {
let appid: String = arguments["appid"] as? String ?? ""
let versionInfo = DCUniMPSDKEngine.getUniMPVersionInfo(withAppid: appid)
result(versionInfo)
}
result(false)
break
case "installMP":
if let arguments = call.arguments as? Dictionary<String, Any> {
let appid: String = arguments["appid"] as? String ?? ""
let wgtPath: String = arguments["wgtPath"] as? String ?? ""
do {
try DCUniMPSDKEngine.installUniMPResource(withAppid: appid, resourceFilePath: wgtPath, password: nil)
result(true)
} catch {
result(false)
}
}
result(false)
break
case "openMP":
if let arguments = call.arguments as? Dictionary<String, Any> {
let appid: String = arguments["appid"] as? String ?? ""
if(self!.uniMpMap[appid] != nil) {
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["data"] = arguments["config"]
self!.uniMpMap[appid]?.sendUniMPEvent("open_app", data: backdata)
self!.uniMpMap[appid]?.show {(success, error) in
if success {
result(true)
} else {
result(false)
}
}
} else {
let data: [String: Any] = (arguments["config"] as? [String: Any])!
let configuration = DCUniMPConfiguration.init()
configuration.enableBackground = true
configuration.enableGestureClose = true
if let extraData = data["extraData"] as? [String: Any], let path = data["path"] {
var updatedExtraData = extraData
updatedExtraData["path"] = path
configuration.extraData = updatedExtraData
}
if let path = data["path"] {
configuration.path = path as? String
}
DCUniMPSDKEngine.openUniMP(appid, configuration: configuration) { instance, error in
if instance != nil {
self!.uniMpMap[appid] = instance;
result(true)
} else {
result(false)
}
}
}
}
break
case "hideMP":
if let arguments = call.arguments as? Dictionary<String,Any> {
let appid: String = arguments["appid"] as? String ?? ""
if(self!.uniMpMap[appid] != nil) {
self!.uniMpMap[appid]?.hide { (success, error) in
if success {
result(true)
} else {
result(false)
}
}
}
}
result(true)
break
case "closeMP":
if let arguments = call.arguments as? Dictionary<String,Any> {
let appid: String = arguments["appid"] as? String ?? ""
if(self!.uniMpMap[appid] != nil) {
self!.uniMpMap[appid]?.close { (success, error) in
if success {
self!.uniMpMap.removeValue(forKey: appid)
result(true)
} else {
result(false)
}
}
}
}
result(false)
break
case "sendMP":
if let arguments = call.arguments as? Dictionary<String,Any> {
let appid: String = arguments["appid"] as? String ?? ""
let event: String = arguments["event"] as? String ?? ""
let data: Any = arguments["data"] ?? [:]
if(self!.uniMpMap[appid] != nil) {
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["event"] = event
backdata["data"] = data
self!.uniMpMap[appid]?.sendUniMPEvent(event, data: backdata)
result(true)
}
}
result(false)
break
case "callbackMP":
if let arguments = call.arguments as? Dictionary<String,Any> {
let appid: String = arguments["appid"] as? String ?? ""
let event: String = arguments["event"] as? String ?? ""
let data: Any = arguments["data"] ?? [:]
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["event"] = event
backdata["data"] = data
if let callback = self?.uniMpCallback {
callback(backdata,true)
}
}
result(false)
break
default:
result(FlutterMethodNotImplemented)
break
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
///FlutterStreamHandler监听
func onListen(withArguments arguments: Any?,
eventSink event: @escaping FlutterEventSink) -> FlutterError? {
eventSink = event
return nil
}
///FlutterStreamHandler监听
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
///生命周期
override func applicationDidBecomeActive(_ application: UIApplication) {
DCUniMPSDKEngine.applicationDidBecomeActive(application)
}
override func applicationWillResignActive(_ application: UIApplication) {
DCUniMPSDKEngine.applicationWillResignActive(application)
}
override func applicationDidEnterBackground(_ application: UIApplication) {
DCUniMPSDKEngine.applicationDidEnterBackground(application)
}
override func applicationWillEnterForeground(_ application: UIApplication) {
DCUniMPSDKEngine.applicationWillEnterForeground(application)
}
override func applicationWillTerminate(_ application: UIApplication) {
DCUniMPSDKEngine.destory()
}
///监听小程序向原生发送事件回调方法
func onUniMPEventReceive(_ appid: String, event: String, data: Any, callback: @escaping DCUniMPKeepAliveCallback) {
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["event"] = event
backdata["data"] = data
eventSink?(backdata)
uniMpCallback = callback
}
///监听胶囊点击事件
func hookCapsuleMenuButtonClicked(_ appid: String) {
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["event"] = "capsuleaction"
eventSink?(backdata)
}
///监听小程序关闭
func hookCapsuleCloseButtonClicked(_ appid: String) {
var backdata: [String: Any] = [:]
backdata["appid"] = appid
backdata["event"] = "close"
eventSink?(backdata)
}
flutter
import 'dart:async';
import 'package:flutter/services.dart';
class UniMP {
static const MethodChannel _channel = MethodChannel('flutter_uni');
static const EventChannel _stream = EventChannel('flutter_uni_stream');
static StreamController? _controller;
static Stream<dynamic>? _streamInstance;
static Stream<dynamic>? get uniStream {
if (_streamInstance == null) {
_controller = StreamController.broadcast();
_streamInstance = _controller!.stream;
_stream.receiveBroadcastStream().listen((event) {
_controller!.add(event);
}, onError: (err) {
_controller!.addError(err);
});
}
return _streamInstance;
}
/// 初始化uniMPSDK
static Future<dynamic> init({void Function(dynamic)? receive}) async {
uniStream?.listen((event) async{
if(receive != null) {
receive(event);
}
}, onError: (err) {
print('Error occurred: $err');
});
final result = await _channel.invokeMethod('initMP');
return result;
}
/// 检查指定的 UniMP 小程序
static Future<dynamic> checkMP({required String appid}) async {
final result = await _channel.invokeMethod('checkMP', {'appid': appid});
return result;
}
/// 获取指定的 UniMP 小程序版本
static Future<dynamic> versionMP({required String appid}) async {
final result = await _channel.invokeMethod('versionMP', {'appid': appid});
return result;
}
/// 安装 UniMP 小程序
static Future<dynamic> installMP({required String appid,required String wgtPath}) async {
final result = await _channel.invokeMethod('installMP', {'appid': appid,"wgtPath": wgtPath});
return result;
}
/// 打开指定的 UniMP 小程序
static Future<dynamic> openMP({
required String appid,
Map<String, dynamic>? config,
}) async {
final result = await _channel.invokeMethod('openMP', {
'appid': appid,
'config': config ?? null,
});
return result;
}
/// 关闭指定的 UniMP 小程序
static Future<void> closeMP({required String appid}) async {
final result = await _channel.invokeMethod('closeMP', {'appid': appid});
return result;
}
/// 隐藏指定的 UniMP 小程序
static Future<void> hideMP({required String appid}) async {
final result = await _channel.invokeMethod('hideMP', {'appid': appid});
return result;
}
/// 发送数据到指定的UniMP小程序
static Future<void> sendMP({
required String appid,
required String event,
Map<String, dynamic>? data,
}) async {
final result = await _channel.invokeMethod('sendMP', {
'appid': appid,
'event': event,
'data': data ?? {},
});
return result;
}
/// 回调数据到到指定的UniMP小程序
static Future<void> callbackMP({
required String appid,
required String event,
Map<String, dynamic>? data,
}) async {
final result = await _channel.invokeMethod('callbackMP', {
'appid': appid,
'event': event,
'data': data ?? {},
});
return result;
}
}
接下来说咋使用
- flutter
//初始化小程序
await UniMP.init(receive: (event) async{
//文件选择
if(event['event'] == 'choose_file') {
UniMP.hideMP(appid: event['appid']);
Map data = event['data'];
// TODO编写的文件选择功能
List files = [];
UniMP.callbackMP(appid: event['appid'], event: event['event'],data: {"files": files});
UniMP.openMP(appid: event['appid']);
}
//TODO 想使用uniapp拉起flutter 自己定 event 即可
});
//检测更新安装
var versionResult = await UniMP.versionMP(appid: "__UNI__1946C50");
if(versionResult != null) {
Map version = jsonDecode(versionResult);
// TODO判断是否需要更新,下载保存 wgt 包。指定存储的wgtPath地址,执行安装更新(uniapp版本 code必须增加才能更新成功)
await UniMP.installMP(appid: "__UNI__888888", wgtPath: wgtPath);
}
//打开并携带参数指定路径
await UniMP.openMP(appid: "__UNI__888888",config: {"extraData": {},"path":"/pages/base/index"});
2.uniapp
//App.vue launch代码
async onLaunch(options) {
plus.nativeUI.toast = function (str) {
if (str == "再按一次退出应用") {
plus.runtime.quit()
} else {
uni.showToast({
title: str,
icon: "none"
})
}
}
uni.onNativeEventReceive((event, data) => {
if (event == "open_app") {
var pages = getCurrentPages()
var page = pages[pages.length - 1]
if (page.$getAppWebview()["__uniapp_route"] != data["data"]["path"]) {
uni.reLaunch({
url: data["data"]["path"]
})
}
}
})
}
//uniapp 拉起flutter 文件上传功能
uni.sendNativeEvent(
"choose_file",
{
type: "audio"
},
async function (ret) {
if (ret["data"]["files"].length > 0) {
//TODO执行上传动作
}
}
);
结尾
完整示例就不开源了,懒得梳理哈哈。现有项目依赖太多整理一个完整的太麻烦了。引入 uniapp sdk 可以查看官方文档,这里我也不在赘述了。如有啥问题欢迎留言交流。