KMM 入门平台差异化实现
Posted 袁国正_yy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMM 入门平台差异化实现相关的知识,希望对你有一定的参考价值。
文章目录
平台差异化代码的使用场景
由于 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.kt
package com.coderyuan.myfirstkmm
import android.util.Log
actual 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.kt
package com.coderyuan.myfirstkmm
import platform.Foundation.NSLog
actual 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.kt
package com.coderyuan.myfirstkmm
import kotlin.native.concurrent.ThreadLocal
// 单例注意使用 @ThreadLocal 注解,以便给 var 变量赋值
@ThreadLocal
object PlatformSpecificMgr {
var ifuncImpl: IFunc? = null
var printLogBlockImpl: MyPrintLogBlock? = null
}
// 由于 iOS 端并不能为 object 生成类方法,所以需要单独写个 Top-Level 变量
val sharedInstance = PlatformSpecificMgr
interface 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.kt
package com.coderyuan.myfirstkmm.android
import android.app.Application
import android.util.Log
import com.coderyuan.myfirstkmm.IFunc
import com.coderyuan.myfirstkmm.PlatformSpecificMgr
class 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 Foundation
import UIKit
import shared
import os.log
class 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 log
PlatformSpecificMgr?.ifuncImpl?.printLogMethod("MyLog", "test")
// print a simple log
PlatformSpecificMgr?.ifuncImpl?.printLogMethod("MyLog", 1234567890 ,"test")
// print a log with types by block
PlatformSpecificMgr?.printLogBlockImpl?.invoke("LogBlock", 1, "testBlock")
iOS 调用示例:
// print a simple log
PlatformSpecificMgrKt.sharedInstance.ifuncImpl?.printLogMethod(tag: "AAA", content: "BBB")
// print a log with types by block
PlatformSpecificMgrKt.sharedInstance.printLogBlockImpl?("CCC", 1, "ZZZ")
在 iOS 模拟器中运行一下,可以看到打出的 Log
以上是关于KMM 入门平台差异化实现的主要内容,如果未能解决你的问题,请参考以下文章