kmp的实际使用1,开发android项目和native转kotlin开发

前言: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文件也可以给别的语法使用

相关推荐
darling_user2 小时前
Android14 耳机按键拍照
android
Mryan20054 小时前
Android 应用多语言与系统语言偏好设置指南
android·java·国际化·android-studio·多语言
刘大浪5 小时前
uniapp 实战新闻页面(一)
android·uni-app
CYRUS_STUDIO5 小时前
破解 VMP+OLLVM 混淆:通过 Hook jstring 快速定位加密算法入口
android·算法·逆向
Renounce6 小时前
【Android】四大组件Service
android
没有了遇见6 小时前
Activity 启动模式总结
android
wangjialelele6 小时前
二叉树基本学习
android
雨白9 小时前
Android 音视频播放:MediaPlayer 与 VideoView
android
Harry技术9 小时前
Fragment 和 AppCompatActivity 两个核心组件设计的目的和使用场景对比
android·android studio