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

相关推荐
Java猿_1 天前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端
Jerry1 天前
Jetpack Compose Navigation
android
介一安全1 天前
【Frida Android】实战篇17:Frida检测与绕过——基于inline hook的攻防实战
android·网络安全·逆向·安全性测试·frida
龙之叶1 天前
Android如何通过adb命令push文件后在媒体库中显示
android·adb
Just_Paranoid1 天前
【Settings】Android 设备信息相关参数的获取
android·5g·wifi·ip·sn·network
StarShip1 天前
SystemServer类 与 system_server进程
android
画个太阳作晴天1 天前
Android App 跟随系统自动切换白天/黑夜模式:车机项目实战经验分享
android·android studo
成都大菠萝1 天前
2-2-2 快速掌握Kotlin-语言的接口默认实现
android
代码s贝多芬的音符1 天前
android webview 打开相机 相册 图片上传。
android·webview·webview打开相机相册
游戏开发爱好者81 天前
抓包工具有哪些?代理抓包、数据流抓包、拦截转发工具
android·ios·小程序·https·uni-app·iphone·webview