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

相关推荐
BD_Marathon3 小时前
【MySQL】函数
android·数据库·mysql
西西学代码4 小时前
安卓开发---耳机的按键设置的UI实例
android·ui
maki0778 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架9 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid12 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl13 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说14 小时前
Android Studio Narwhal 3 特性
android·ide·android studio
maki07720 小时前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux