
Kotlin Multiplatform Mobile ( KMM ) 是一个 SDK,旨在简化跨平台移动应用程序的创建。在 KMM 的帮助下,您可以在 iOS 和 AndroID 应用程序之间共享通用代码,并仅在必要时编写特定于平台的代码。
KMM用纯Kotlin编写一次代码,即可在iOS和AndroID上运行,开发应用的公共业务逻辑只需要编写一次。KMM减少了为不同平台编写和维护相同代码所花费的时间。在Jenkins上一次构建可以产出aar、framework、klib,AndroID依赖aar,iOS依赖framework,性能与原生一致。当然可以使用KMM依赖klib开发AndroID、iOS应用。
二、KMM项目架构项目架构主要分为原生系统层、AndroID/iOS业务SDK层、KMM SDK层、KMM业务逻辑SDK层、iOS sdkframework层、AndroID/iOS App层。
原生系统层:这里提下原生系统层的目的是,有些平台特性需要分开实现,比如读取文件、打印日志、摄像头等。
AndroID/iOS业务SDK层:主要是包括一些现有的AndroID/iOS SDK,需要直接依赖现有SDK来开发KMM时,在commonMain expect声明接口,在androIDMain、iosMain actual分别依赖现有SDK实现。这样就可以使用已有的SDK,后续也可以保持接口不变,直接使用KMM实现SDK,如alog、PlatformMMKV。
KMM SDK层:如alog、PlatformMMKV写成一个SDK可以供其他KMM模块(business)使用。
KMM业务逻辑SDK层:具体业务的逻辑模块,比如登录逻辑、获取首页列表逻辑、查看首页列表数据详情等。
iOS sdkframework层:Kotlin/Native构建一个framework时,产物是二进制,也包含了Kotlin/Native的基础库、Runtime,会使包大小增加1M+左右,而且多个Kotlin/Native构建的framework不会共享基础库导致每一个framework都会增加1M+,为了避免包过大,统一构建一个framework。
App层:AndroID的依赖无变化,依赖aar或者jar;iOS依赖sdkframework,这样iOS包大小只增加1M+。当然如果依赖了一些库如ktor网络库,包也会变大,避免这个问题也可以不用依赖ktor,直接依赖现有的网络库来实现一个KMM SDK。
三、使用expect/actual编写平台特定的代码以打印日志为例,打造一个alog日志SDK
在commonMain定义IALog接口,声明fun v函数,其他函数忽略。并定义expect ALogImpl类来实现平台特性打印日志
interface IALog { fun v(tag: String,message: String) ...}expect class ALogImpl(): IALog在androIDMain实现ALogImpl
import androID.util.Logactual class ALogImpl actual constructor() : IALog { overrIDe fun v(tag: String,message: String) { Log.v(tag,message) } ...}在iosMain实现ALogImpl
import platform.Foundation.NSLoginternal actual class ALogImpl actual constructor(): IALog { overrIDe fun v(tag: String,message: String) { NSLog("[$tag] $message") } ...}到此,我们已经使用KMM实现了一个alog日志SDK。
四、依赖现有的AndroID/iOS SDK开发KMM SDKalog的实现过于简单,使用了androID.util.Log、platform.Foundation.NSLog。如果使用现有的AndroID/iOS SDK,如何实现呢?比如AndroID使用mars-xlog、iOS使用CocoaLumberjack
AndroID的实现没什么变化,依赖mars-xlog即可
implementation("com.tencent.mars:mars-xlog:1.2.6")import com.tencent.mars.xlog.Logactual class ALogImpl actual constructor() : IALog { overrIDe fun v(tag: String,message) } ...}在ios实现依赖CocoaLumberjack,需要用到native.cocoapods插件
plugins { kotlin("multiplatform") kotlin("native.cocoapods") ID("com.androID.library")}cocoapods { ... frameworkname = "alog" pod("CocoaLumberjack")}通过cinterop一些gradle Task会自动生成头文件给iosMain使用,比如生成alog-cinterop-CocoaLumberjack.klib包含1_CocoaLumberjack.knm。
import cocoapods.CocoaLumberjack.*internal actual class ALogImpl actual constructor(): IALog { private val dLog = DDLog overrIDe fun v(tag: String,message: String) { dLog.log(asynchronousLog,toMessage(tag,"[$tag] $message",DDLogLevelVerbose,DDLogFlagVerbose)) } private fun toMessage(tag: String,message: String,level: DDLogLevel,flag: DDLogFlag): DDLogMessage { return DDLogMessage(message,level,flag,"",null,tag,null) } ...}为了方便AndroID/iOS App使用,添加一个ALog.kt类
/** * AndroID App使用 ALog.i(tag,message) */val ALog: IALog by lazy { ALogImpl() }/** * iOS App使用ALogKt.i(tag,message) */fun d(tag: String,message: String) = ALog.d(tag,message)到此,alog就完成了依赖现有的AndroID/iOS SDK(mars-xlog、CocoaLumberjack)开发alog KMM SDK。
五、声明AndroID/iOS公共接口以及独有接口用expect修饰commonMain中声明公共的接口
expect interface IALog { fun v(tag: String,message: String) ...}在iosMain中用actual修饰来实现真正的接口
actual interface IALog { actual fun v(tag: String,message: String) ...}在androIDMain中用actual修饰来实现真正的接口,带actual修饰的方法为AndroID/iOS公共方法,不带actual修饰的方法为AndroID独有(AndroID有这个接口iOS没有这个接口)
actual interface IALog { actual fun v(tag: String,message: String) ... fun v(tag: String,format: String,vararg args: Any?)}这样AndroID就可以使用fun v(tag: String,vararg args: Any?)函数,而iOS没有这个函数。好处是通常一些SDK在commonMain中会定义一套公共接口,有时候AndroID或iOS有一些独有接口,就可以用这种方式声明。同理data class也是可以这样使用。
六、为iOS统一构建成一个framework为了避免Kotlin/Native构建framework时包过大,统一构建一个framework,下面把包名称为sdkframework。这里提一下几个值得注意的问题。有2种方式构建:1、本地构建,写一个sdkframework项目依赖其他模块的klib包,来构建sdkframework。2、构建系统上构建依赖其他模块的klib包构建,业务直接pod sdkframework即可。第1种方案比较灵活,版本号可以写脚本控制,但是要求开发人员使用的电脑都要配置KMM开发环境。第2种方案业务接入更加简单,跟iOS原生开发的SDK一样,无需KMM环境,主要问题是各个业务依赖klib的版本不一致,导致构建sdkframework多个版本,这时需要用不同分支构建不同业务的sdkframework,版本号加后缀来区别 1.0.0-love、1.0.0-like。
6.1 sdkframework模块的iosMain需要有一个kotlin文件如果iosMain没有kotlin文件,将无法生成 iOS framework,为其添加一个文件即可,如SDKTest.kt
// 加个类,避免Framework没生成class SDKTest { fun test() { }}6.2 生成头文件sdkframework.h时,把注释也带上生成头文件sdkframework.h时,如果需要把注释也带上,那需要在gradle中添加Task
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> { compilations.get("main").kotlinoptions.freeCompilerArgs += "-Xexport-kdoc"}6.3 依赖的模块需要使用export来导出到sdkframework.h头文件中sdkframework依赖了utils、alog、PlatformMMKV、business,需要添加export,把这几个模块的类和方法导出到sdkframework.h头文件中,这样iosApp才可以使用这几个模块的类和方法。
val iosX64 = iosX64()val iosArm64 = iosArm64()targets { configure(listof(iosX64,iosArm64)) { binarIEs.withType(org.jetbrains.kotlin.gradle.plugin.mpp.Framework::class.java) { export(project(":utils")) export(project(":alog")) export(project(":PlatformMMKV")) export(project(":business")) } }}6.4 sdkframework本地依赖的模块使用了pod,sdkframework也要pod,以klib依赖可避免该问题sdkframework依赖utils、alog、PlatformMMKV、business模块源码构建framework时,模块使用了pod的,那sdkframework也要pod。如PlatformMMKV pod("MMKV","1.2.8"),那sdkframework也要pod("MMKV","1.2.8")。那如何避免这个问题,可以先把utils、alog、PlatformMMKV、business模块在构建系统上构建成klib,sdkframework依赖各个模块的klib即可。
6.5 use_frameworks! 和 use_modular_headers!上面说到的第1点本地构建,在iosApp本地依赖构建sdkframework时,要将依赖项正确导入 Kotlin/Native 模块,Podfile必须包含use_modular_headers! 或 use_frameworks! 指令,查看文档链接。当然,如果是第2点构建系统上构建则不需要使用这2个指令。
源码地址:https://github.com/libill/kmmApp
七、参考链接:1、本文地址:https://www.cnblogs.com/liqw/p/15416758.html
2、kmm-getting-started
3、Multiplatform programming
4、KMM 求生日记二:Kotlin/Native 被踩中的坑
5、KNDemo
总结以上是内存溢出为你收集整理的Kotlin/Native KMM项目架构全部内容,希望文章能够帮你解决Kotlin/Native KMM项目架构所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)