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

相关推荐
枯骨成佛6 小时前
MTK Android 14 通过属性控制系统设置显示双栏或者单栏
android
雨白6 小时前
Android 自定义 View:范围裁切和几何变换
android
jiushiapwojdap7 小时前
Flutter上手记:为什么我的按钮能同时在iOS和Android上跳舞?[特殊字符][特殊字符]
android·其他·flutter·ios
limuyang29 小时前
Android RenderScript-toolkit库,替换老式的脚本方式(常用于高斯模糊)
android
柿蒂10 小时前
产品需求驱动下的技术演进:动态缩放View的不同方案
android·kotlin·android jetpack
Andy_GF12 小时前
鸿蒙Next在蒲公英平台分发测试包
android·ios·harmonyos
恋猫de小郭13 小时前
iOS 26 正式版即将发布,Flutter 完成全新 devicectl + lldb 的 Debug JIT 运行支持
android·前端·flutter
幻雨様14 小时前
UE5多人MOBA+GAS 54、用户登录和会话创建请求
android·ue5
Just_Paranoid14 小时前
【SystemUI】锁屏来通知默认亮屏Wake模式
android·framework·systemui·keyguard·aod