
在移动开发领域,同时维护 Android、iOS、鸿蒙三套代码是一件极其耗费人力的事情。开发者面临的核心痛点是:
Kuikly 是腾讯推出的基于 Kotlin Multiplatform (KMP) 的企业级跨平台框架,已在 QQ、QQ音乐、QQ浏览器等产品中服务 5亿+ 日活用户,覆盖 20+ 业务线。
特性 | 说明 |
|---|---|
一套代码,多端运行 | 90%+ 代码跨 Android/iOS/HarmonyOS 共享 |
原生级性能 | 直接编译为原生二进制(.aar/.framework/.so),无 JS 桥接 |
声明式 UI | 支持 Kuikly DSL 和 Compose DSL 两种范式 |
动态化能力 | 支持页面级热更新(iOS/Android/鸿蒙均支持) |
渐进式接入 | 可与现有原生工程共存,按页面粒度逐步迁移 |
Kuikly 采用「Kotlin 多平台 + 平台原生渲染」的混合架构:
plaintext
┌─────────────────────────────────────────────┐
│ 业务代码层(commonMain) │
│ Kotlin 编写,跨平台共享 UI 逻辑 │
├──────────────┬──────────────┬───────────────┤
│ androidMain │ iosMain │ ohosArm64Main │
│ 平台差异实现 │ 平台差异实现 │ 平台差异实现 │
├──────────────┼──────────────┼───────────────┤
│ core-render │ core-render │ core-render │
│ -android │ -ios │ -ohos │
│ 原生渲染器 │ 原生渲染器 │ 原生渲染器 │
├──────────────┼──────────────┼───────────────┤
│ Android App │ iOS App │ HarmonyOS │
│ 壳工程 │ 壳工程 │ 壳工程 │
└──────────────┴──────────────┴───────────────┘各平台编译产物:
.aar.xcframework.so工具 | 版本要求 | 用途 |
|---|---|---|
Android Studio | ≥ 2024.2.1 | 主开发 IDE |
JDK | 17 | 编译环境(注意:AS ≥ 2024.2.1 默认 JDK 21,需手动切换) |
Kotlin | 2.0.21(推荐) | 跨平台编译 |
⚠️ 重要:Android Studio ≥ 2024.2.1 时,需手动切换 Gradle JDK:
Settings → Build,Execution,Deployment → Build Tools → Gradle → Gradle JDK → 选择 JDK 17
bash
# 安装 CocoaPods
sudo gem install cocoapods
# 要求:Xcode ≥ 12,iOS ≥ 14.1.npmrc 中配置 npm 源:plaintext
registry=https://registry.npmjs.org/在 Settings → Plugins → Marketplace 中依次搜索并安装:
安装 Kuikly 插件后,在 Android Studio 中:
plaintext
File → New → New Project → Kuikly Project Template填写项目信息,选择:
创建完成后,工程结构如下:
plaintext
MyKuiklyApp/
├── shared/ # 跨平台业务逻辑模块(核心)
│ └── src/
│ ├── commonMain/ # 跨平台共享代码(UI + 逻辑)
│ ├── androidMain/ # Android 平台差异实现
│ ├── iosMain/ # iOS 平台差异实现
│ └── ohosArm64Main/ # 鸿蒙平台差异实现
├── androidApp/ # Android 壳工程
├── iosApp/ # iOS 壳工程
└── ohosApp/ # 鸿蒙壳工程在 shared/build.gradle.kts 中配置 KSP 插件(用于编译时自动生成路由表):
kotlin
plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp") version "2.0.21-1.0.28"
}
ksp {
arg("moduleId", "shared") // 模块标识
}
dependencies {
add("kspCommonMainMetadata", "com.tencent.kuikly:core-ksp:x.x.x")
}Kuikly 2.5.0+ 需额外添加 Maven 源: maven("https://mirrors.tencent.com/nexus/repository/maven-tencent/") >
在 shared/src/commonMain/ 中创建页面文件:
kotlin
// HomePage.kt(commonMain)
import com.tencent.kuikly.compose.foundation.layout.Box
import com.tencent.kuikly.compose.foundation.layout.fillMaxSize
import com.tencent.kuikly.compose.material3.Text
import com.tencent.kuikly.compose.ui.Alignment
import com.tencent.kuikly.compose.ui.Modifier
import com.tencent.kuikly.core.annotations.Page
@Page(name = "HomePage")
internal class HomePage : BaseComposePage() {
@Composable
override fun PageContent(pageData: JSONObject) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Hello, Kuikly!",
fontSize = 24.sp
)
}
}
}@Page 注解的作用:KSP 在编译时自动扫描该注解,生成路由注册代码 KuiklyCoreEntry,无需手动注册。
当需要调用平台特有能力时,使用 Kotlin 的 expect/actual 机制:
kotlin
// commonMain
expect fun getPlatformName(): String
// androidMain
actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.RELEASE}"
// iosMain
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName()
// ohosArm64Main
actual fun getPlatformName(): String = "HarmonyOS"在 androidApp/build.gradle.kts 中:
kotlin
dependencies {
implementation("com.tencent.kuikly:core-render-android:x.x.x")
// 引入 shared 模块产物
implementation(project(":shared"))
}Kuikly 需要业务侧实现以下适配器:
kotlin
// 图片加载适配器
class KRImageAdapter(val context: Context) : IKRImageAdapter {
override fun fetchDrawable(
imageLoadOption: HRImageLoadOption,
callback: (drawable: Drawable?) -> Unit
) {
// 使用 Glide/Coil 等加载图片
Glide.with(context).load(imageLoadOption.url).into(object : CustomTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
callback(resource)
}
override fun onLoadCleared(placeholder: Drawable?) = callback(null)
})
}
}
// 路由适配器
class KRRouterAdapter : IKRRouterAdapter {
override fun openPage(context: Context, pageName: String, pageData: JSONObject) {
KuiklyRenderActivity.start(context, pageName, pageData)
}
override fun closePage(context: Context) {
(context as? Activity)?.finish()
}
}
// 日志适配器
object KRLogAdapter : IKRLogAdapter {
override val asyncLogEnable: Boolean = false
override fun i(tag: String, msg: String) { Log.i(tag, msg) }
override fun d(tag: String, msg: String) { Log.d(tag, msg) }
override fun e(tag: String, msg: String) { Log.e(tag, msg) }
}在 Application.onCreate() 中初始化:
kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 注册适配器
KuiklyRenderAdapterManager.setImageAdapter(KRImageAdapter(this))
KuiklyRenderAdapterManager.setRouterAdapter(KRRouterAdapter())
KuiklyRenderAdapterManager.setLogAdapter(KRLogAdapter)
// 触发页面路由注册(KSP 自动生成)
KuiklyCoreEntry().triggerRegisterPages()
}
}kotlin
class KuiklyRenderActivity : AppCompatActivity() {
private lateinit var kuiklyRenderViewDelegator: KuiklyRenderViewBaseDelegator
private lateinit var contextCodeHandler: ContextCodeHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kuikly)
val pageName = intent.getStringExtra("pageName") ?: return
contextCodeHandler = ContextCodeHandler(this, pageName)
kuiklyRenderViewDelegator = contextCodeHandler.initContextHandler()
val container = findViewById<ViewGroup>(R.id.hr_container)
contextCodeHandler.openPage(container, pageName, createPageData())
}
override fun onResume() {
super.onResume()
kuiklyRenderViewDelegator.onResume() // 触发 pageDidAppear
}
override fun onPause() {
super.onPause()
kuiklyRenderViewDelegator.onPause() // 触发 pageDidDisappear
}
override fun onDestroy() {
super.onDestroy()
kuiklyRenderViewDelegator.onDestroy()
}
companion object {
fun start(context: Context, pageName: String, pageData: JSONObject) {
context.startActivity(
Intent(context, KuiklyRenderActivity::class.java).apply {
putExtra("pageName", pageName)
putExtra("pageData", pageData.toString())
}
)
}
}
}方式一:CocoaPods(推荐传统工程)
在 iosApp/Podfile 中:
ruby
source 'https://cdn.cocoapods.org/'
platform :ios, '14.1'
target 'iosApp' do
inhibit_all_warnings!
pod 'OpenKuiklyIOSRender', 'x.x.x'
end执行:
bash
pod install --repo-update方式二:SPM(推荐新工程)
在 Xcode → Package Dependencies 中添加:
plaintext
https://github.com/Tencent-TDS/KuiklyUI.git编译 shared 模块后会生成 .xcframework,通过 SPM 集成:
swift
// Package.swift
let package = Package(
name: "shared",
products: [.library(name: "shared", targets: ["shared"])],
targets: [
.binaryTarget(
name: "shared",
path: "./shared.xcframework"
)
]
)创建 KuiklyRenderViewController.m:
objc
#import <KuiklyRender/KuiklyRender.h>
@interface KuiklyRenderViewController : UIViewController
- (instancetype)initWithPageName:(NSString *)pageName
pageData:(NSDictionary *)pageData;
@end
@implementation KuiklyRenderViewController {
KuiklyRenderViewControllerBaseDelegator *_delegator;
}
- (instancetype)initWithPageName:(NSString *)pageName
pageData:(NSDictionary *)pageData {
if (self = [super init]) {
_delegator = [[KuiklyRenderViewControllerBaseDelegator alloc]
initWithViewController:self
pageName:pageName
pageData:pageData];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[_delegator viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[_delegator viewWillAppear]; // 触发 pageDidAppear
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_delegator viewDidDisappear]; // 触发 pageDidDisappear
}
// 返回 Framework 名称(非动态化模式)
- (void)fetchContextCodeWithPageName:(NSString *)pageName
resultCallback:(KuiklyContextCodeCallback)callback {
callback(@"shared", nil); // shared 为 KMP 模块名
}
// 资源目录
- (NSURL *)resourceFolderUrlForKuikly:(NSString *)pageName {
return [[NSBundle mainBundle] URLForResource:@"common" withExtension:nil];
}
@endNoNo在 ohosApp/hvigor/hvigor-config.json5 中:
json
{
"dependencies": {
"kuikly-ohos-compile-plugin": "latest"
}
}在 ohosApp/entry/oh-package.json5 中添加依赖:
json
{
"dependencies": {
"@kuiklyopen/render": "x.x.x"
}
}typescript
// EntryAbility.ets
import { Kuikly } from '@kuiklyopen/render'
@Entry
@Component
struct KuiklyPage {
@State pageName: string = 'HomePage'
@State pageData: object = {}
private kuiklyViewDelegate: KuiklyViewDelegate = new KuiklyViewDelegate()
build() {
Column() {
Kuikly({
pageName: this.pageName,
pageData: this.pageData,
delegate: this.kuiklyViewDelegate,
})
.width('100%')
.height('100%')
}
}
onPageShow() {
this.kuiklyViewDelegate.pageDidAppear() // 触发 pageDidAppear
}
onPageHide() {
this.kuiklyViewDelegate.pageDidDisappear() // 触发 pageDidDisappear
}
}鸿蒙 KN 编译耗时较长,以下是生产实践中的优化方案:
kotlin
// build.gradle.kts - 自测包关闭 LTO,可减少约 80% 编译耗时
kotlin {
ohosArm64 {
compilations.all {
compilerOptions.options.apply {
freeCompilerArgs.add("-opt=${enableLinkOpt()}")
}
}
}
}
fun enableLinkOpt(): Boolean {
return project.property("BUILD_TYPE") != "OnlyPackage"
}properties
# gradle.properties - 调大内存(鸿蒙编译峰值可达 50G+)
org.gradle.jvmargs=-Xmx64G -Xms12G -Xss3072K -XX:NewRatio=8 -XX:+UseG1GC
代码类型 | 放置位置 | 说明 |
|---|---|---|
UI 布局、业务逻辑 | commonMain | 90%+ 代码放这里 |
平台 API 调用 | androidMain/iosMain/ohosArm64Main | 通过 expect/actual 隔离 |
原生能力扩展 | 各平台壳工程 | 封装为 Module 供 Kotlin 侧调用 |
vforLazy 替代 vfor,避免全量渲染GC.collect()onCreatePager 中执行耗时同步操作,使用 addNextTickTask 异步处理Kuikly 支持页面级动态化,无需发版即可更新页面:
⚠️ 注意:动态化能力需通过 Shiply 发布平台接入使用,Shiply 平台地址:https://shiply.tds.qq.com。
问题 | 解决方案 |
|---|---|
Gradle Sync 失败 | 执行 Build → Clean Project,检查 JDK 版本 |
Page not registered | 检查 @Page 注解,确认 KSP 已执行 |
鸿蒙找不到平台实现 | 切换为鸿蒙专用 Gradle 配置(cp settings.ohos.gradle.kts settings.gradle.kts) |
pageDidAppear 不触发 | 检查各端生命周期回调是否正确对接 |
iOS 资源加载失败 | 检查 resourceFolderUrlForKuikly 返回路径与 Bundle 配置是否一致 |
总结:Kuikly 通过 Kotlin Multiplatform 技术,让开发者用一套 Kotlin 代码同时覆盖 Android、iOS、鸿蒙三端,编译为各平台原生二进制产物,实现原生级性能。相比 Flutter/RN,Kuikly 无 JS 桥接开销,与现有原生工程无缝共存,是目前国内支持鸿蒙最完善的跨平台方案之一。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。