前言:cmp使用了大半年了,说一下感受
架构:kmp,kotlin是2.1.21
ui:compose,版本是1.8.2
网络:ktor
数据存储:datastore/room,(目前除了js,都支持了)
平台是android/ios/desktop(macos、win10)/鸿蒙
关于android
这个没什么好讲的,原本cmp就是基于kotlin开发的,为了支持android开发, 只是最近接触了kotlin/native开发so,感觉这个东西挺可以的,侧重讲一下步骤 1.在kmp的项目(自己在as新建kmp项目就好了)基础上),新建kmp lib子模块 配置一下依赖
kotlin
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.android.kotlin.multiplatform.library)
依赖加入
scss
implementation(libs.kotlin.stdlib)
配置native,因为android平台在native已经存在定义,配置好就可以开发了
kotlin
listOf(
androidNativeArm64(),
androidNativeArm32(),
androidNativeX64(),
androidNativeX86(),
).forEach {
it.compilations.getByName("main") {
cinterops {
val myCppLib by creating {
// 指定头文件目录
// definitionFile.set(
// project.file("src/nativeInterop/cinterop/jni.def")
// )
packageName("myCppLib")
headers(
project.fileTree("src/nativeInterop/cinterop/headers")
)
includeDirs(
"src/nativeInterop/cinterop/headers"
)
// compilerOpts(
// "-I${project.file("src/nativeInterop/cinterop/cpp")}"
//
// )
}
}
}
it.binaries {
sharedLib { // 编译为 .so
baseName = "native_lib" // 输出 libnative-lib.so
linkerOpts += listOf(
// "-llog",
// "-lc++",
)
val path= if(it.name.contains("androidNativeArm64")){
"arm64-v8a"
}else if(it.name.contains("androidNativeArm32")){
"armeabi-v7a"
}else if(it.name.contains("androidNativeX64")){
"x86_64"
}else {
"x86"
}
outputDirectory=file("${rootProject.projectDir}/composeApp/src/androidMain/libs/${path}")
// transitiveExport=true
val sopath="${rootProject.projectDir}/composeApp/src/androidMain/libs/${path}"
println("so path=$sopath")
linkerOpts("-L${sopath}","-lcplus_lib")
}
}
}
这里面也实现了引入外部so的参与编译的写法,这里要注意so的路径拷贝写法

NativeLib.kt实现如下
动态注册方法
ini
@CName(externName="JNI_OnLoad")
fun JNI_OnLoad(vm: CPointer<JavaVMVar>, preserved: COpaquePointer): jint {
return memScoped {
val envStorage = alloc<CPointerVar<JNIEnvVar>>()
val vmValue = vm.pointed.pointed!!
val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)
if (result == JNI_OK) {
val env = envStorage.pointed!!.pointed!!
val jclass = env.FindClass!!(envStorage.value, "com/example/native/NativeAndroidLib".cstr.ptr)
val jniMethod = allocArray<JNINativeMethod>(2)
jniMethod[0].fnPtr = staticCFunction(::getNativeMessageDy)
jniMethod[0].name = "getNativeMessageDy".cstr.ptr
jniMethod[0].signature = "()V".cstr.ptr
jniMethod[1].fnPtr = staticCFunction(::cpp_add)
jniMethod[1].name = "cppAdd".cstr.ptr
jniMethod[1].signature = "(IZ)V".cstr.ptr
env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 2)
}else{
return@memScoped JNI_ERR
}
JNI_VERSION_1_6
}
}
kotlin
fun getNativeMessageDy() {
myCppLib.helloOutSo()
__android_log_print(ANDROID_LOG_ERROR.toInt(), "Kotlin", "Hello %s", "Kotlin Native!")
}
fun cpp_add(
env: CPointer<JNIEnvVar>,
thiz: jobject,
a: jint,
b: jboolean
) {
// 将 JNI 类型转换为 Kotlin 类型
val kotlinA: Int = a.toInt()
val kotlinB: Boolean = b.toByte() != 0.toByte() // jboolean 是 0 或 1
// 调用业务逻辑
println("Received values: a=$kotlinA, b=$kotlinB")
}
@CName(externName="JNI_OnLoad")
fun JNI_OnLoad(vm: CPointer<JavaVMVar>, preserved: COpaquePointer): jint {
return memScoped {
val envStorage = alloc<CPointerVar<JNIEnvVar>>()
val vmValue = vm.pointed.pointed!!
val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)
if (result == JNI_OK) {
val env = envStorage.pointed!!.pointed!!
val jclass = env.FindClass!!(envStorage.value, "com/example/native/NativeAndroidLib".cstr.ptr)
val jniMethod = allocArray<JNINativeMethod>(2)
jniMethod[0].fnPtr = staticCFunction(::getNativeMessageDy)
jniMethod[0].name = "getNativeMessageDy".cstr.ptr
jniMethod[0].signature = "()V".cstr.ptr
jniMethod[1].fnPtr = staticCFunction(::cpp_add)
jniMethod[1].name = "cppAdd".cstr.ptr
jniMethod[1].signature = "(IZ)V".cstr.ptr
env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 2)
}else{
return@memScoped JNI_ERR
}
JNI_VERSION_1_6
}
}
因为kotlin内置了platform.android.*,所以基本所有android的c语法都可以用
静态注册
kotlin
@CName(externName="Java_com_example_native_NativeAndroidLib_getNativeMessage")
fun getNativeMessage(env: CPointer<JNIEnvVar>, thiz: jobject): jstring {
memScoped {
return env.pointed.pointed!!.NewStringUTF!!.invoke(
env, "Hello from Kotlin/Native!".cstr.ptr
)!!
}
}
在androidMain编写kotlin方法声明
kotlin
package com.example.native
object NativeAndroidLib {
init {
System.loadLibrary("native_lib")
}
external fun getNativeMessage():String
external fun getNativeMessageDy()
external fun cppAdd(a:Int,b: Boolean)
}
然后在主模块的androidMain加入模块依赖
markdown
androidMain.dependencies {
implementation(projects.androidNative)
}
编写拷贝脚本,在主模块的kts自己写执行任务
scss
//依赖android原生库,先生成和拷贝
tasks.named("preBuild").configure {
dependsOn(":android_native:linkReleaseSharedAndroidNativeArm64")
dependsOn(":android_native:linkReleaseSharedAndroidNativeArm32")
dependsOn(":android_native:linkReleaseSharedAndroidNativeX64")
dependsOn(":android_native:linkReleaseSharedAndroidNativeX86")
}

参与这个大神说的,很细节,讲的很好,juejin.cn/post/747271...
然后在主模块正常调用就好了
关于感受
这种kotlin写c++的感觉,很奇妙,感觉熟悉了写法,会很爽,不用专门写c++的语法,而且生成的h文件也可以给别的语法使用