Kotlin 与 ArkTS 交互性能与效率优化实践

B站影视 韩国电影 2025-09-29 17:35 1

摘要:ByteKMP是字节内部基于 KMP(Kotlin Multiplatform) 建设的客户端跨平台方案,希望通过 KMP 技术实现 Android、鸿蒙、iOS 三端的代码复用,以此降低开发成本、提高逻辑与 UI 的多端一致性。

本文编写:@吴霖鹏

合作伙伴:@王明哲、@刘潇

背景

ByteKMP是字节内部基于 KMP(Kotlin Multiplatform) 建设的客户端跨平台方案,希望通过 KMP 技术实现 Android、鸿蒙、iOS 三端的代码复用,以此降低开发成本、提高逻辑与 UI 的多端一致性。

由于抖音鸿蒙版已经基于 ArkTS 完成了大部分基础能力和部分业务开发,接入 KMP 后需要在充分复用现有 ArkTS 能力的同时,支持业务侧在 ArkTS 场景下调用 KMP 代码。因此,我们需要建设 Kotlin 与 ArkTS 之间的跨语言交互能力,为开发者提供便捷、高效的跨语言交互体验,助力 ByteKMP 在业务顺利落地。

名词解释

KN:Kotlin/Native,ByteKMP 在鸿蒙上采用 Kotlin/Native 技术执行 Kotlin 代码

ArkTS:鸿蒙官方开发语言

主模块:KN 在鸿蒙中以 so 形式集成,因此在 KN 项目中需要一个处于顶层的模块将依赖的 KMP 代码打包为目标平台二进制产物,这个模块称为主模块

Kotlin 调用 ArkTS

在鸿蒙开发中,系统提供了 NAPI实现 ArkTS 与C/C++ 模块之间的交互。而 Kotlin/Native 本身就具备与 C/C++ 互操作的能力(基于 cinterop),因此理论上 Kotlin/Native 也能够通过NAPI实现与 ArkTS 互操作。

如何基于 NAPI 调用 ArkTS 代码

ArkTS 对象在 native 侧均以 napi_value类型表示,包括ArkTS 模块、类、实例以及方法等。NAPI 提供了一系列方法用于操作napi_value 对象,比如获取模块、获取模块导出类、调用 ArkTS 方法等,同时也支持在 native 与 ArkTS 之间进行基础类型数据转换。

下面以一个ArkTS 模块 @douyin/logger导出的logger对象为例,演示 KN 如何基于 NAPI 调用logger 来打印日志,ArkTS 代码如下

// ArkTSLogger.ets
export class ArkTSLogger {
d(tag: string, msg: string) {
console.log(`[${tag}] ${msg}`)
}
}

export const logger = new ArkTSLogger

// Index.ets
export { logger } form './src/main/ets/ArkTSLogger'

在 KN 侧主要通过以下流程调用 logger 的 log 方法

通过 napi_load_module_with_info获取模块@douyin/logger

通过napi_get_named_property获取模块导出的logger 对象以及方法 d

通过napi_create_string_utf8构造 string 类型的参数 tag 和 msg

通过napi_call_function调用d 方法并传递参数

// 1. 获取 @douyin/logger 模块
val module = nativeHeap.alloc
napi_load_module_with_info(globalEnv, "@douyin/logger", bundleName, module.ptr)

// 2. 获取 @douyin/logger 模块导出的 logger 对象
val log = nativeHeap.alloc
napi_get_named_property(globalEnv, module.value, "logger", log.ptr)

// 3. 获取 logger 对象的 d 方法
val dFunc = nativeHeap.alloc
napi_get_named_property(globalEnv, log.value, "d", dFunc.ptr)

// 4. 构造参数 tag、msg,将 Kotlin String 转换为 ArkTS string
val tag = "KmpTag"
val msg = "KmpMsg"
val tagVar = nativeHeap.alloc
val msgVar = nativeHeap.alloc
napi_create_string_utf8(globalEnv, value, strlen(tag), tagVar.ptr)
napi_create_string_utf8(globalEnv, value, strlen(msg), msgVar.ptr)

// 5. 构造参数数组
val argsValue = nativeHeap.allocArray
argsValue[0] = tagVar
argsValue[1] = msgVar

// 6. 调用 d 方法
val result = nativeHeap.alloc
napi_call_function(globalEnv, log.value, dFunc.value, 2, argsValue, result.ptr)

封装 NAPI

直接调用 NAPI 的方式既繁琐,又要求使用者具备一定的 NAPI 知识,同时还伴随着大量模板化代码。为降低使用成本,我们有必要在 NAPI 之上进行一层封装。

考虑到 ArkTS 对象在 native 侧统一表示为 napi_value,且所有操作都必须基于 napi_value 展开,我们可以将 ArkTS 对象抽象为一个 Kotlin 对象:该对象内部持有napi_value,并通过封装相应的方法,对外提供更友好的操作接口。

以 ArkTs 实例为例,我们可以进行如下封装

class ArkInstance(private val napiValue: napi_valueVar) {
fun getProperty(name: String): ArkInstance {
val propertyNapiValue = ...
// 省略 napi 操作
retun ArkInstance(propertyNapiValue)
}

fun getFunction(name: String): ArkFunction {
// 省略 napi 操作
}
}

class ArkFunction(private val receiver: napi_valueVar, private val napiValue: napi_valueVar) {
fun call(args: Array
// 省略 napi 操作
}
}

除了实例对象外还有模块、类、方法、数组、基础类型等对象,由于所有对象都需要napi_value,我们可以定义一个基类ArkObject 来持有napi_value,其他对象均继承自ArkObject并提供特定的能力。

不过需要注意的是,napi_value只在一次主线程方法执行期间有效,当本次调用结束后就会失效。因此需要通过napi_ref 来延长它的生命周期,并且在 Kotlin 对象被回收后主动释放引用避免内存泄漏。ArkObject代码实现如下

open class ArkObject(var value: napi_value) {
// 创建 napi_value 引用
private var napiRef = value.createRef

// 通过 ref 获取 napi_value
var napiValue: napi_value = value
get = napiRef.getRefValue

// 当前对象回收后主动解除 napiRef 的绑定
@Suppress("unused")
private val cleaner = createCleaner(napiRef) {
GlobalScope.launch(Dispatchers.Main) {
it.deleteRef
}
}
}

下面展示了基于封装后的 logger调用实现。与原始的 NAPI 调用方式相比,这种写法不仅更加简洁,也显著提升了代码的可读性。

val ezLogModule = ArkModule("@douyin/logger") // 获取模块
val logger = ezLogModule.getExportInstance("logger") // 获取导出对象 log
val dFunc = logger.getFunction("d") // 获取方法 d
dFunc.call(arrayOf(arkString("KmpLogger"), arkString("kmp msg"))) // 调用 d

Kotlin 代码导出至 ArkTS

如何基于 NAPI 导出 C++ 代码

NAPI同样支持将native代码导出为TS 声明(.d.ts)供 ArkTS 使用。在鸿蒙中,系统会在 native 模块初始化时注入一个exports 对象,我们可以通过 NAPI 将属性、方法或类信息注册到该对象中,并在 ArkTS 侧提供对应的 .d.ts 声明。当 ArkTS 调用这些代码时,NAPI 会将调用请求转发至 native 侧的桥接代码,从而实现 ArkTS 对 native 能力的访问。

由于 exports 对象是在鸿蒙 C++ 模块的 Init 方法中传入,我们就用 C++ 演示如何基于 NAPI 导出代码到 ArkTS。首先在 C++ 代码中添加一个包含日志打印逻辑的方法testLog

static void testLog(int value) {
OH_LOG_Print(LOG_APP, LOG_INFO, 0, "KmpLogger", "log from c++: %{public}d", value);
}

根据 NAPI 规范,我们需要实现一个桥接方法,用于将 ArkTS 的调用请求转发至 native 侧的具体实现。同时,还需在exports对象中将 testLog 方法注册到对应的桥接方法上

// 桥接方法,napi 固定签名
static napi_value bridgeMethod(napi_env env, napi_callback_info info) {
// 获取 ArkTS 侧传递的参数
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int value;
// 将参数转换为 int
napi_get_value_int32(env, args[0], &value);
// 调用 testlog
testLog(value);
return nullptr;
}

// 注册 bridgeMethod
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
napi_property_descriptor desc = {
{ "testLog", nullptr, bridgeMethod, nullptr, nullptr, nullptr, napi_default, nullptr }
};
// 向 exports 中注册桥接方法
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END

最后在 index.d.ts 中定义testLog 方法的签名即可

// Index.d.ts
export const testLog: (value: number) => void

这样在 ArkTS 模块中引用并调用 testLog即可触发 C++ 侧的 testLog 方法执行。

基于 KSP 封装模板代码

与 C++ 类似,在 Kotlin 中每个需要导出到 ArkTS 的代码(类、方法、属性)都必须经过以下步骤:

定义桥接方法,在方法内处理对象获取、参数解析、调用 native 实现、数据返回及类型转换

将桥接方法注册到 exports

index.d.ts 中添加对应声明

这一整套流程同样既繁琐又充斥着大量模板代码,为降低开发成本,我们考虑通过代码生成来简化该流程,其整体设计如下

桥接代码代码生成

桥接代码生成基于 KSP + 注解的方案,使用方通过注解标注需要导出的属性、方法和类,KSP 插件在编译期自动生成对应的桥接代码。以一个简单的方法 test 为例,使用方只需要在方法上标注@ArkTsExportFunction注解

@ArkTsExportFunction
fun test: Int {
return 1
}

KSP 插件将会生成如下桥接代码

// 桥接代码
private fun methodBridge(env: napi_env?, info: napi_callback_info?): napi_value? {
// 调用 Kotlin 代码
val result = test
// 处理类型转换并返回结果
return createInt(result).value
}

// 注册代码
fun defineFunctionFor_test(env: napi_env, exports: napi_value) {
val descArray = nativeHeap.allocArray
descArray[0].name = arkString("test").napiValue.value
descArray[0].method = staticCFunction(::methodBridge)
descArray[0].attributes = napi_default

napi_define_properties(env, exports, 1u, descArray)
}

最终我们会在主模块收集项目中全部模块生成的注册代码,生成一个整体的注册代码,代码示例如下

fun init(env: napi_env, exports: napi_value, bundle: String, soName: String) {
defineExports(env, exports)
}

private fun defineExports(env: napi_env, exports: napi_value) {
// 项目中 ksp 生成的全部桥接代码
com.bytedance.kmp.demo.js_bind_function.defineFunctionFor_test1(env, exports)
com.bytedance.kmp.demo.js_bind_function.defineFunctionFor_test2(env, exports)
com.bytedance.kmp.demo.js_bind_function.defineFunctionFor_test3(env, exports)
com.bytedance.kmp.demo.js_bind_function.defineFunctionFor_test4(env, exports)
// ...
}

然而,KSP 并不具备跨模块访问能力,这意味着主模块在处理时仅能看到本模块的代码,从而导致子模块生成的桥接代码无法被识别。为了解决这一问题,我们采用了 MetaInfo 机制:在每个子模块中生成一份固定包名的 MetaInfo 代码,并将桥接信息以字符串形式保存在注解中。主模块通过 getDeclarationsFromPackage获取指定包下的所有声明,再解析注解提取子模块的桥接信息,从而生成完整的注册代码。

完整的处理流程如下图所示

导出 Kotlin Class

导出 Kotlin 顶层方法、属性只需要像上面的代码一样提供桥接方法即可,而导出类则会复杂一些,因为需要支持非基础类型(Class)在 Kotlin 与 ArkTS 之间的相互转换。

核心思路是:利用 NAPI 提供的napi_wrap 将 ArkTS 对象与 Kotlin 对象进行绑定;在 ArkTS 调用 Kotlin 时,通过napi_unwrap获取当前 ArkTS 对象绑定的 Kotlin 实例,并将调用转发至该对象。大致流程为:

实现构造函数桥接方法:该方法会在 ArkTS 侧尝试创建导出类时调用

创建 Kotlin 对象

通过 napi_wrap将 Kotlin 对象与 ArkTS 对象绑定

通过napi_unwrap获取当前 ArkTS 对象绑定的 Kotlin 实例

调用 Kotlin 对象的对应方法

将结果返回给 ArkTS,并处理必要的类型转换。

以 KotlinClass 为例,生成的桥接代码如下所示

class KotlinClass {
fun test {
}
}

// 定义导出类的构造桥接方法
fun constructor(env: napi_env?, info: napi_callback_info?): napi_value? {
// 获取当前对象的 this
val thisArg = info!!.thisArg
// 创建 KN 对象
val kmpClassInstance = KotlinClass
val stableRef = StableRef.create(kmpClassInstance).asCPointer
// 将 Kotlin 对象与 ArkTS 对象绑定
napi_wrap(env, thisArg, stableRef, null, null, null)
// 返回构造参数
return thisArg
}

// 获取 ArkTS 对象绑定的 KotlinClass 对象
fun napi_value.getKotlinClass: KotlinClass? {
// 获取 Kotlin 对象
val instance = unwrap?.asStableRef
return instance
}

// test 方法的桥接代码
private fun bridgeMethodFortest(env: napi_env?, info: napi_callback_info?): napi_value? {
// 通过 this 获取当前绑定的 Kotlin 对象
val obj = info!!.thisArg.getKotlinClass
// 调用 Kotlin 对象的 test 方法
obj?.test
return null
}

// 注册代码
fun defineClassForKotlinClass(env: napi_env, exports: napi_value) {
val descArray = nativeHeap.allocArray

// 定义 Function
descArray[0].name = createString("test")
descArray[0].method = staticCFunction { env: napi_env?, info: napi_callback_info? ->
bridgeMethodFortest(env, info)
}
descArray[0].attributes = napi_default

// 定义 Class
val result = nativeHeap.alloc
napi_define_class(env, "KotlinClass", strlen("KotlinClass"), staticCFunction { env: napi_env?, info: napi_callback_info? ->
constructor(env, info)
}, null, 1u, descArray, result.ptr)
napi_set_named_property(env, exports, "KotlinClass", result.value)
}

导出 Kotlin Interface

Kotlin 导出代码供 ArkTS 调用的场景中,经常会涉及 回调ArkTS 能力注入。以接口回调为例,Kotlin 侧可能会设计出如下代码

interface Callback {
fun onSuccess(result: String)
}

fun requestNetwork(callback: Callback) {
// 忽略请求代码
callback.onSuccess("success")
}

requestNetwork方法导出至 ArkTS ,callback 由 ArkTS 实现并在调用时传入,这样在后续请求结束时 Kotlin 侧可以将结果回调至 ArkTS。

这种场景下接口回调本质上是一次 Kotlin 调用 ArkTS 的过程,我们可以通过前面设计的能力来实现,代码如下

fun requestNetwork(callback: napi_value) {
// 忽略请求代码
val result = arkString("success")
ArkInstance(callback).getFunction("onSuccess")?.call(arrayOf(result))
}

不过这种实现方式存在一个明显的问题:缺少强制约束。ArkTS 与 Kotlin 之间的通信完全依赖双方的“约定”,一旦任意一方修改了接口定义或编写代码时出现错误,往往难以及时发现问题,排查成本也会大幅提升。为了支持这种「Kotlin 侧定义接口,由 ArkTS 实现」的场景,我们需要实现 Kotlin 接口的导出能力。

核心实现思路是:

基于 KSP 为接口自动生成一个实现类,在该类中持有 napi_value

根据方法签名信息,将 Kotlin 方法的调用转发至 napi_value 对应的 ArkTS 方法上

将接口定义导出到 ArkTS,确保导出的代码具有明确定义和约束

根据这个思路,编译期将为 Callback 接口生成如下实现

// 接口实现类,为 ArkTS 侧实现的 Callback 对象做一层代理
class JsImportInterfaceBinding_Callback(val instance: ArkInstance) : Callback {
override fun onSuccess(result:String): Unit {
// 将 onSuccess 方法转发到 napi_value 的 onSuccess 方法上
val function = instance.getFunction("onSuccess")
val params = arrayOf(createString(result))
function.getNapiValue.call(instance.getNapiValue, params)
}
}

// 将 ArkTS 对象转换为 Callback
fun napi_value.getCallback: Callback? {
val instance = ArkInstance(this)
return JsImportInterfaceBinding_Callback(instance)
}

并在 requestNetwork的桥接方法中将 ArkTS 传入的对象转换为JsImportInterfaceBinding_Callback

private fun methodBridge(env: napi_env?, info: napi_callback_info?): napi_value? {
val params = info!!.params(1)
// 将 ArkTS 对象转换为 Callback
val arg0 = params[0]!!.getCallback!!
// 调用 requestNetwork
requestNetwork(arg0)
}

最终导出到 ArkTS 的接口定义如下

// Callback.d.ts
export interface Callback {
onSuccess: (result: string) => void;
}

// index.d.ts
import { Callback } from './Callback'
export { Callback }
export const requestNetwork: (callback: Callback) => void;

接入使用

导出代码

导出顶层属性

使用@ArkTsExportProperty注解标注对应的属性来导出到 ArkTS,支持val/var,代码示例如下所示

// Kotlin
@ArkTsExportProperty
val name: String = "123"
@ArkTsExportProperty
var age: String = "123"
// 自动生成 ArkTs 侧代码声明 index.d.ts
export const name: string
export var age: string

导出顶层方法

使用@ArkTsExportFunction注解标注对应的方法来导出到 ArkTS,对于 suspend类型的方法在 ArkTS 侧会生成对应的 Promise 方法,代码示例如下所示

// Kotlin
@ArkTsExportFunction
fun test: String {
...
}

@ArkTsExportFunction
suspend fun testSuspend: String {
// ...
}// 自动生成 ArkTs 侧代码声明 index.d.ts
export const test: => string
export const testSuspend: => Promise

导出类

使用@ArkTsExportClass注解标注对应的类来导出到 ArkTS,默认情况下框架会使用无参数构造函数来构造 KN 对象,如果想指定有参构造函数可以搭配@ArkTsExportClassGenerator来使用

// Kotlin
@ArkTsExportClass
class A {

}
@ArkTsExportClass
class B {
@ArkTsExportClassGenerator
constructor(name: String)
}
// 自动生成 ArkTs 侧代码声明 index.d.ts
export class A {}

export class B {
constructor(name: string)
}

默认情况下不会导出类中的属性和方法,如果需要导出需要在对应的属性和方法上标注@ArkTsExport

// Kotlin
@ArkTsExportClass
class A {
val name: String = ""
fun test {}
}
@ArkTsExportClass
class B {
@ArkTsExport

@ArkTsExport
fun test {}
}
// 自动生成 ArkTs 侧代码声明 index.d.ts
export class A {}

export class B {
readonly name: string
test: => void
}

导出枚举

使用@ArkTsExportEnum注解标注对应的枚举类来导出到 ArkTS

// Kotlin
@ArkTsExportEnum
enum class UserType {
A, B, C
}
// 自动生成 ArkTs 侧代码声明 index.d.ts
export enum UserType {
A, B, C
}

导出接口

通过@ArkTsExportInterface导出需要 ArkTS 实现的接口,不支持导出接口属性,且默认导出所有方法不需要使用@ArkTsExport标注

// Kotlin
@ArkTsExportInterface
interface ArkTsService {
fun getUserName: String
}

@ArkTsExportFunction
fun injectArkTsService(service: ArkTsService)
// 自动生成 ArkTs 侧代码声明 index.d.ts
export interface ArkTsService {
getUserName: => string
}

export injectArkTsService: (service: ArkTsService) => void

// ArkTs侧实现接口并传递给 Kotlin
injectArkTsService({
getUserName: => {
return ...
}
})

线程安全

由于在 KN 侧调用@ArkTsExportInterface方法涉及到 NAPI 操作, 而 NAPI 调用需要保证在主线程执行,否则会发生崩溃。为了避免业务侧过多的关注线程切换逻辑,框架提供了以下两种方式实现自动线程切换以保证线程安全

1. @ArkTsThreadSafe

对于非 suspend 方法,如果业务想在调用时不去关注线程切换问题,可以为该方法标注@ArkTsThreadSafe,框架会在方法实现内使用 runBlocking切换到主线程调用并阻塞当前线程直到结果返回

// Kotlin
@ArkTsExportInterface
interface ArkTsService {
@ArkTsThreadSafe
fun getUserName: String = ""
}

2. safeSuspend

同时框架也提供了接口对象的扩展方法safeSuspend,用于获取该接口的 suspend 包装类,该类提供了当前接口所有方法的 suspend版本,业务可以在协程中安全使用

val service: ArkTsService = xxx
GlobalScope.launch {
// 调用生成的 suspend 方法
val userName = service.safeSuspend.getUserName
}

支持类型

框架对导出代码的类型有一定限制,包括属性类型、方法参数类型和方法返回值类型,具体支持的类型如下表所示

类型Kotlin 类型ArkTS 类型基础类型IntnumberLongnumberDoublenumberFloatnumberStringstring容器MapMapArrayArrayListListByteArrayArrayBuffer自定义类型@ArkTsExportClass class@ArkTsExportInterfaceInterface导出枚举类型@ArkTsExportEnumenumArkTS对象napi_valueEsObject协程suspend functionPromise

性能优化

Kotlin Native 基于 LLVM 编译器基础设施构建,它将 K2 中间表示(IR)转换为 LLVM IR,其语言后端与 C/C++ 保持一致。这种设计使得 Kotlin Native 的理论性能有潜力接近 C 语言的水平。然而,为了维护 Kotlin 语言的内存安全特性和垃圾回收(GC)机制,Kotlin Native 引入了一套相对厚重的运行时系统。这套运行时在提供便利性的同时,也带来了不可忽视的开销。

当 Kotlin 通过 cinterop 调用 NAPI 时,这种双重内存管理模型的开销会进一步放大,在字符串转换场景尤为明显。一个 C 语言版本的实现可以直接在栈上或原生堆上分配内存,过程直接高效。

static napi_value c_string_params(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

size_t length;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &length);
char* buf = new char[length + 1]; // 在原生堆上分配
napi_get_value_string_utf8(env, args[0], buf, length + 1, nullptr);

// ... 使用 buf ...
delete buf;
return nullptr;
}

相比之下,Kotlin 版本的实现则面临双重内存管理的开销

fun napi_value.asString: String {
// 在原生堆上为长度变量分配空间,状态切换
val length = nativeHeap.alloc
napi_get_value_string_utf8(OhosFFIManager.globalEnv, this, null, 0.toULong, length.ptr)

// 在原生堆上为字符串内容分配空间,UTF16转为UTF8,字符串拷贝赋值,状态切换
val buffer = nativeHeap.allocArray
napi_get_value_string_utf8(OhosFFIManager.globalEnv, this, buffer, (length.value.toInt + 1).toULong, null)

// 在 Kotlin 托管堆上的第三次分配,UTF8转为UTF16,字符串拷贝赋值
return buffer.toKString
}

在一次简单的字符串转换过程中,Kotlin 与 C/C++ 层之间存在 2 次状态切换、 2 次字符串拷贝以及2 次编码转换,字符串跨语言传输已成为业务的性能瓶颈。

针对这种问题我们参考Kotiln内部实现引入了@GCUnsafeCall,它的作用是向编译器声明:与此函数链接的 C++ 实现是“可信的”,它完全理解并遵循 Kotlin 的 GC 规则和调用约定。因此,编译器无需为其生成常规的、带有性能开销的包装代码。

借助@GCUnsafeCall,我们针对字符串场景做了优化,实现机制如下:

1. 声明内存安全接口:暂时性地、非安全地获取内部内存的写权限,完成数据填充后,再将其作为一个合法的、不可变的对象交还给 Kotlin 。

@GCUnsafeCall("Kotlin_napi_get_kotlin_string_utf16")
@Escapes.Nothing
public external fun napi_get_kotlin_string_utf16(env: NativePtr, value: NativePtr): String

2. 消除中间抽象:直接在 C++ 侧处理 `napi_value` 等 Native 句柄和裸指针,彻底抛弃为指针、缓冲区等创建的 Kotlin 包装器,免除 2 次状态切换与 1 次拷贝。

OBJ_GETTER(Kotlin_napi_get_kotlin_string_utf16, KNativePtr env, KNativePtr value) {
// 获取字符串长度
size_t str_size;
napi_get_value_string_utf16((napi_env)env, (napi_value)value, NULL, 0, &str_size);

// 复制字符串内容
size_t str_size_read;
ArrayHeader* result = AllocArrayInstance(theStringTypeInfo, (int32_t)str_size, OBJ_RESULT)->array;
napi_get_value_string_utf16((napi_env)env, (napi_value)value, (char16_t *)CharArrayAddressOfElementAt(result, 0), str_size, &str_size_read);

RETURN_OBJ(result->obj);
}

优化前后的对比如下,最终长字符串转换耗时能够降低 90%

对比维度优化前优化后指针/句柄处理创建 Kotlin 对象封装C++侧直接使用原始值内存分配2+ 次 (包装器, C缓冲, Kotlin对象)1 次 (仅最终 Kotlin 对象)数据拷贝2 次1 次编码转换2 次 (UTF-16 -> UTF-8 -> UTF-16)0 次长字符串转换耗时25.4 ms2.4 ms (-90.5%

未来规划

实现 ArkTS 与 KMP 之间的字符串共享,避免拷贝操作,彻底解决字符串传输性能问题

解决多线程限制问题,抹平平台(Android、Harmony)差异,提供一致的调用体验

我们是「抖音客户端架构」团队,以「打造极致的研发效能,助力业务高效发展」为使命,「成为行业卓越的架构师团队」为愿景,为抖音客户端高效高质量交付保驾护航。

客户端跨端技术专家

抖音客户端架构师

来源:字节跳动技术团队

相关推荐