KMM 入门(三)平台差异化实现
文章目录
- 平台差异化代码的使用场景
-
- 差异化代码的基本实现
- Demo 及注意点
-
- expect & actual 实现方式
-
- 在 Common 中建立一个 expect 类或 Top-Level 方法
- 完成 actual 实现
-
- Android 示例:
- iOS 示例:
- 注入式实现
-
- 定义
- 注入实现
-
- Android 示例:
- iOS 示例:
平台差异化代码的使用场景
由于 KMM 运行在各平台时,实际上是翻译成了各平台专用的库,如:Android 上就会将共享模块编译成 Dalvik Bytecode 然后打包成 AAR 文件,而 iOS 上会打包成 Apple Framework,所以,一些平台相关的、不可共享的具体实现代码,就必须利用各平台的 API 来实现
举个简单的例子,公共模块有一个统一的业务逻辑——获取手机型号,控制逻辑可以在 KMM 的 common 代码库中实现,且它并不关系具体的实现逻辑,而实际需要获取手机型号字符串的方法,Android 需要调用 android.os.Build.MODEL
获取,而 iOS 需要通过 UIDevice.current.model
来获取
类似的平台强相关功能,就需要在 KMM 中利用平台差异化代码实现
差异化代码的基本实现
这里需要再次引用 Kotlin 官方的一张图
如图所示,在 KMM 中,Common 库中可以将需要差异化实现的 class
或 fun
使用 expect
关键字修饰,expect
字面意思是:期望,这里代表需要各平台具体实现,之后在 Android、iOS 对应的库中,再使用 actual
关键字来创建对应的 class
及 fun
,即可完成差异化代码的构建,在 App 中调用 Common 中使用 expect
修饰的内容时,将自动执行不同平台的 actual
内容
Demo 及注意点
expect & actual 实现方式
这种方式是官方推荐的一种平台差异化功能实现方式,实际操作时也比较简单
在 Common 中建立一个 expect 类或 Top-Level 方法
如图所示,创建 expect
类或方法,有点类似 Kotlin 和 Java 中的 interface
在完成基本的代码以后,此时可以看到 IDE 已经报错了,提示没有找到给 Android 使用的 JVM 实现,以及给 iOS 使用的 Native 实现
完成 actual 实现
此时不要使用 Option + Enter(Windows:Alt + Enter) 来靠 IDE 自动创建, 需要分别在 androidMain 和 iosMain 目录中,创建与 expect
内容包名、类名、方法签名(包括方法名、参数类型及名称)完全一致的 actual
内容,否则会报错
Android 示例:
// AndroidImpl.ktpackage com.coderyuan.myfirstkmmimport android.util.Logactual class PlatformSpecific { actual fun method1(): String { return "Android-Method1" } actual fun method2() { println("Android-Method2") }}actual fun platformTestFunc(v1: Int, v2: String) { Log.i("Android-Func", "v1: $v1, v2: $v2")}
iOS 示例:
// iOSImpl.ktpackage com.coderyuan.myfirstkmmimport platform.Foundation.NSLogactual class PlatformSpecific { actual fun method1(): String { return "iOS-Method1" } actual fun method2() { println("iOS-Method2") }}actual fun platformTestFunc(v1: Int, v2: String) { NSLog("iOS-Func: v1: %d, v2: %s", v1, v2)}
完成上面的双端功能实现以后,如果在 Android 或 iOS 主 App 中调用 Common 中的方法,将会执行各自实现的 actual 方法
注入式实现
除了官方的 expect & actual 实现模式,还可以使用类似 IoC 的注入式实现,我们可以利用 Interface
或 Block 的形式来让双端实现需要的差异化功能
如果使用过 Spring Framework 或 Dagger,此实现则不难理解
定义
首先,在 Common 中定义好需要注入的方法,可以使用接口或闭包,并使用单例来承载,如下代码所示:
// PlatformSpecificMgr.ktpackage com.coderyuan.myfirstkmmimport kotlin.native.concurrent.ThreadLocal// 单例注意使用 @ThreadLocal 注解,以便给 var 变量赋值@ThreadLocalobject PlatformSpecificMgr { var ifuncImpl: IFunc? = null var printLogBlockImpl: MyPrintLogBlock? = null}// 由于 iOS 端并不能为 object 生成类方法,所以需要单独写个 Top-Level 变量val sharedInstance = PlatformSpecificMgrinterface IFunc { fun printLogMethod(tag: String, content: String) fun printLogMethod(tag: String, ts: Long, content: String)}typealias MyPrintLogBlock = ((tag: String, type: Int, content: String) -> Unit)
注入实现
此时,我们需要在 Android 及 iOS App 主工程的代码中注入具体的实现
根据需要,选择合适的生命周期,也可以使用懒加载形式的注入,比如,在 Android App 的 Application 类中注入,在 iOS App 的 AppDelegate 类中注入
Android 示例:
// Android App 主工程中的 MyApplication.ktpackage com.coderyuan.myfirstkmm.androidimport android.app.Applicationimport android.util.Logimport com.coderyuan.myfirstkmm.IFuncimport com.coderyuan.myfirstkmm.PlatformSpecificMgrclass MyApplication: Application() { override fun onCreate() { super.onCreate() PlatformSpecificMgr.ifuncImpl = object : IFunc { override fun printLogMethod(tag: String, content: String) { Log.i(tag, content) } override fun printLogMethod(tag: String, ts: Long, content: String) { Log.i(tag, "time: ${ ts}, $content") } } PlatformSpecificMgr.printLogBlockImpl = { tag: String, type: Int, content: String -> Log.i(tag, "type: ${ type}, $content") } }}
iOS 示例:
//// AppDelegate.swift// iosApp//// Created by yuanguozheng on 2021/5/28.// Copyright © 2021 orgName. All rights reserved.//import Foundationimport UIKitimport sharedimport os.logclass MyAppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { let mgr = PlatformSpecificMgr.init() mgr.ifuncImpl = LogImpl.init() mgr.printLogBlockImpl = { (tag: String, type: KotlinInt, content: String) -> Void in let log = String(format: "%@: %@, type: %d", tag, content, type.intValue) print(log) } return true }}class LogImpl : IFunc { func printLogMethod(tag: String, ts: Int64, content: String) { let log = String(format: "%@: %@, time: %l", tag, content, ts) print(log) } func printLogMethod(tag: String, content: String) { let log = String(format: "%@: %@", tag, content) print(log) }}
那么,在使用时,可以按照如下的代码进行调用
// Android 代码 或 Common 中调用// print a simple logPlatformSpecificMgr?.ifuncImpl?.printLogMethod("MyLog", "test")// print a simple logPlatformSpecificMgr?.ifuncImpl?.printLogMethod("MyLog", 1234567890 ,"test")// print a log with types by blockPlatformSpecificMgr?.printLogBlockImpl?.invoke("LogBlock", 1, "testBlock")
iOS 调用示例:
// print a simple logPlatformSpecificMgrKt.sharedInstance.ifuncImpl?.printLogMethod(tag: "AAA", content: "BBB")// print a log with types by blockPlatformSpecificMgrKt.sharedInstance.printLogBlockImpl?("CCC", 1, "ZZZ")
在 iOS 模拟器中运行一下,可以看到打出的 Log
更多相关文章
- 浅谈Java中Collections.sort对List排序的两种方法
- python list.sort()根据多个关键字排序的方法实现
- android auto 能微信_5分钟搞定Flutter与Android(安卓)的交互(内
- Android(安卓)高工面试必考题(二):Android的事件分发机制设计与实现
- 【Android(安卓)UI设计与开发】第06期:底部菜单栏(一)使用TabActivi
- android 连接远程数据库
- Android高手进阶教程(二十五)之---Android(安卓)中的AIDL!!!
- 安卓(android)开发应该怎么学?需要哪些基础知识?
- Android(安卓)ROM研究---制作Nexus S上的ROM