一、什么是KMM?

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.Log
actual class ALogImpl actual constructor() : IALog {
override fun v(tag: String, message: String) {
Log.v(tag, message)
}
...
}

在iosMain实现ALogImpl

import platform.Foundation.NSLog
internal actual class ALogImpl actual constructor(): IALog {
override fun v(tag: String, message: String) {
NSLog("[$tag] $message")
}
...
}

到此,我们已经使用KMM实现了一个alog日志SDK。

四、依赖现有的Android/iOS SDK开发KMM SDK

alog的实现过于简单,使用了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.Log
actual class ALogImpl actual constructor() : IALog {
override fun v(tag: String, message: String) {
Log.v(tag, 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, 0, "", null, 0, tag, 0, 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, format: 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项目架构的更多相关文章

  1. 简单测试 Kotlin native 性能

    准备 一直使用kotlin JVM平台开发服务器的应用,最近想试试看 Kotlin native的性能. 我使用的是 kotlin native 1.3.21,要使用他非常的简单,下载最新的 IDEA ...

  2. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入

    上次实现了依赖注入,但是web项目必须要引用业务逻辑层和数据存储层的实现,项目解耦并不完全:另一方面,要同时注入业务逻辑层和数据访问层,注入的服务直接写在Startup中显得非常臃肿.理想的方式是,w ...

  3. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

  4. UWP开发之Mvvmlight实践九:基于MVVM的项目架构分享

    在前几章介绍了不少MVVM以及Mvvmlight实例,那实际企业开发中将以那种架构开发比较好?怎样分层开发才能节省成本? 本文特别分享实际企业项目开发中使用过的项目架构,欢迎参照使用!有不好的地方欢迎 ...

  5. c#项目架构搭建经验

    读过.Net项目中感觉代码写的不错(备注1)有:bbsMax(可惜唧唧喳喳鸟像消失了一样),Umbraco(国外开源的cms项目),Kooboo(国内做开源cms).本人狭隘,读的代码不多,范围也不广 ...

  6. Mvc项目架构分享之项目扩展

    Mvc项目架构分享之项目扩展 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4.项目架构各部分解析 5.项目 ...

  7. mvc项目架构搭建之UI层的搭建

    项目架构搭建之UI层的搭建 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4.项目架构各部分解析 5.项目创 ...

  8. mvc项目架构分享系列之架构搭建之Repository和Service

    项目架构搭建之Repository和Service的搭建 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4. ...

  9. mvc项目架构分享系列之架构搭建之Infrastructure

    项目架构搭建之Infrastructure的搭建 Contents 系列一[架构概览] 0.项目简介 1.项目解决方案分层方案 2.所用到的技术 3.项目引用关系 系列二[架构搭建初步] 4.项目架构 ...

随机推荐

  1. Mysql时间戳转Java时间戳

    MySQL 时间戳和Java返回的时间戳是不一样的 例如: 当前时间是 2014-08-04 10:42:55.204000 使用mysql时间戳函数UNIX_TIMESTAMP 返回的结果为: 14 ...

  2. 浅谈可持久化Trie与线段树的原理以及实现(带图)

    浅谈可持久化Trie与线段树的原理以及实现 引言 当我们需要保存一个数据结构不同时间的每个版本,最朴素的方法就是每个时间都创建一个独立的数据结构,单独储存. 但是这种方法不仅每次复制新的数据结构需要时 ...

  3. 稚晖君-最小linux服务器运行 nginx + netcore

    华为天才少年, B站科技大神,稚晖君(自称野生钢铁侠),多少科技爱好者拜服在他的全方位技术栈 今天我们就去入手一个他的量产产品 号称最小linux电脑 的"夸克" 到手之后,我们马 ...

  4. noip模拟35

    A. 玩游戏 考场做法用双指针向两侧更新,当左段点左移一位时,如果右端点不满足条件,则跳回肯定满足的位置.复杂度玄学 题解做法是类似最长子段和,如果有一个区间和为负,则维护的指针跳过去即可 B. 排列 ...

  5. MongoDB(14)- 查询 null 字段或缺少某个字段的文档

    插入测试数据 db.inventory.insertMany([ { _id: 1, item: null }, { _id: 2 } ]) 后面的栗子都会用到这里的测试数据 查询匹配包含值为 nul ...

  6. JS010. 三元运算符扩展运用(多层判断语句 / 多条表达式)

    MDN - 三元运算符 语法 Condition ? exprIfTrue : exprIfFalse 用例: function getFee(isMember) { return(isMember ...

  7. 密码学系列之:海绵函数sponge function

    目录 简介 海绵函数的结构 海绵函数的应用 简介 海绵函数sponge function是密码学中使用的一种函数,它接收一定长度的输入,然后输出一定长度的输出,中间包含了有限个内部状态. 因为海绵函数 ...

  8. Spring Boot学习(一)——Spring Boot介绍

    Spring Boot介绍 Spring Boot简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式 ...

  9. webgl 图像处理 加速计算

    webgl 图像处理 webgl 不仅仅可以用来进行图形可视化, 它还能进行图像处理 图像处理1---数据传输 webgl 进行图形处理的第一步: 传输数据到 GPU 下图为传输点数据到 GPU 并进 ...

  10. 第一节:《线程安全和锁Synchronized概念》

    第一节:线程安全和锁Synchronized概念 一.进程与线程的概念 (1)在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位都是进程. 在未配置 OS 的系统中,程序的执行方 ...