一、背景:为什么选择 Kuikly?
在移动开发领域,同时维护 Android、iOS、鸿蒙三套代码是一件极其耗费人力的事情。开发者面临的核心痛点是:
- 三端代码割裂:同一功能需要用 Kotlin/Java、Swift/ObjC、ArkTS 分别实现
- 性能与体验妥协:传统跨端方案(如 Flutter、React Native)存在 JS 桥接开销或渲染不一致问题
- 动态化能力缺失:原生开发难以实现热更新,发版周期长
Kuikly 是腾讯推出的基于 Kotlin Multiplatform (KMP) 的企业级跨平台框架,已在 QQ、QQ音乐、QQ浏览器等产品中服务 5亿+ 日活用户,覆盖 20+ 业务线。
Kuikly 的核心优势
| 特性 | 说明 |
|---|---|
| 一套代码,多端运行 | 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 │
│ 壳工程 │ 壳工程 │ 壳工程 │
└──────────────┴──────────────┴───────────────┘
各平台编译产物:
- Android → .aar
- iOS → .xcframework
- HarmonyOS → .so
三、环境准备
3.1 通用工具
| 工具 | 版本要求 | 用途 |
|---|---|---|
| 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
3.2 iOS 开发环境
bash
# 安装 CocoaPods
sudo gem install cocoapods
# 要求:Xcode ≥ 12,iOS ≥ 14.1
3.3 鸿蒙开发环境
- 下载安装 DevEco Studio ≥ 5.1.0,API Version ≥ 18
- 在 .npmrc 中配置 npm 源:
plaintext
registry=https://registry.npmjs.org/
3.4 安装 Android Studio 插件
在 Settings → Plugins → Marketplace 中依次搜索并安装:
- Kotlin
- Kotlin MultiPlatform
- Kuikly(≥ 1.1.0 版本,支持生成鸿蒙工程)
四、创建 Kuikly 跨平台工程
4.1 使用插件一键创建
安装 Kuikly 插件后,在 Android Studio 中:
plaintext
File → New → New Project → Kuikly Project Template
填写项目信息,选择:
- DSL 类型:Compose DSL(推荐)或 Kuikly DSL
- 支持平台:勾选 Android、iOS、HarmonyOS
4.2 工程目录结构
创建完成后,工程结构如下:
plaintext
MyKuiklyApp/
├── shared/ # 跨平台业务逻辑模块(核心)
│ └── src/
│ ├── commonMain/ # 跨平台共享代码(UI + 逻辑)
│ ├── androidMain/ # Android 平台差异实现
│ ├── iosMain/ # iOS 平台差异实现
│ └── ohosArm64Main/ # 鸿蒙平台差异实现
├── androidApp/ # Android 壳工程
├── iosApp/ # iOS 壳工程
└── ohosApp/ # 鸿蒙壳工程
4.3 Gradle 基础配置
在 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("mirrors.tencent.com/nexus/repos…") >
五、编写第一个跨平台页面
5.1 使用 Compose DSL 创建页面
在 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,无需手动注册。
5.2 平台差异实现(expect/actual 机制)
当需要调用平台特有能力时,使用 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"
六、Android 端接入
6.1 添加渲染器依赖
在 androidApp/build.gradle.kts 中:
kotlin
dependencies {
implementation("com.tencent.kuikly:core-render-android:x.x.x")
// 引入 shared 模块产物
implementation(project(":shared"))
}
6.2 实现必备适配器
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) }
}
6.3 初始化 SDK
在 Application.onCreate() 中初始化:
kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 注册适配器
KuiklyRenderAdapterManager.setImageAdapter(KRImageAdapter(this))
KuiklyRenderAdapterManager.setRouterAdapter(KRRouterAdapter())
KuiklyRenderAdapterManager.setLogAdapter(KRLogAdapter)
// 触发页面路由注册(KSP 自动生成)
KuiklyCoreEntry().triggerRegisterPages()
}
}
6.4 创建页面承载 Activity
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())
}
)
}
}
}
七、iOS 端接入
7.1 添加渲染器依赖
方式一: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
7.2 集成业务 Framework
编译 shared 模块后会生成 .xcframework,通过 SPM 集成:
swift
// Package.swift
let package = Package(
name: "shared",
products: [.library(name: "shared", targets: ["shared"])],
targets: [
.binaryTarget(
name: "shared",
path: "./shared.xcframework"
)
]
)
7.3 实现 Kuikly 承载容器
创建 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];
}
@end
7.4 注意事项
- Enable Bitcode 需设置为 No
- User Script Sandboxing 需设置为 No
- 所有 Kuikly 相关方法必须在主线程调用
八、鸿蒙端接入
8.1 配置 Hvigor 插件
在 ohosApp/hvigor/hvigor-config.json5 中:
json
{
"dependencies": {
"kuikly-ohos-compile-plugin": "latest"
}
}
8.2 配置 oh-package.json5
在 ohosApp/entry/oh-package.json5 中添加依赖:
json
{
"dependencies": {
"@kuiklyopen/render": "x.x.x"
}
}
8.3 在 Page 中集成 Kuikly 组件
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
}
}
8.4 鸿蒙编译优化建议
鸿蒙 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
九、完整接入流程总览
十、最佳实践总结
10.1 代码组织原则
| 代码类型 | 放置位置 | 说明 |
|---|---|---|
| UI 布局、业务逻辑 | commonMain | 90%+ 代码放这里 |
| 平台 API 调用 | androidMain/iosMain/ohosArm64Main | 通过 expect/actual 隔离 |
| 原生能力扩展 | 各平台壳工程 | 封装为 Module 供 Kotlin 侧调用 |
10.2 性能优化要点
- 列表优化:超长列表使用 vforLazy 替代 vfor,避免全量渲染
- 内存控制:iOS Extension 等内存敏感场景,适时调用 GC.collect()
- 首屏优化:避免在 onCreatePager 中执行耗时同步操作,使用 addNextTickTask 异步处理
10.3 动态化能力
Kuikly 支持页面级动态化,无需发版即可更新页面:
- Android/iOS:通过 JS 产物热更新
- 鸿蒙:同样支持 JS 产物动态下发
- 非动态化页面(Native 模式)与动态化页面可混合共存
⚠️ 注意:动态化能力需通过 Shiply 发布平台接入使用,Shiply 平台地址:shiply.tds.qq.com。
10.4 常见问题速查
| 问题 | 解决方案 |
|---|---|
| Gradle Sync 失败 | 执行 Build → Clean Project,检查 JDK 版本 |
| Page not registered | 检查 @Page 注解,确认 KSP 已执行 |
| 鸿蒙找不到平台实现 | 切换为鸿蒙专用 Gradle 配置(cp settings.ohos.gradle.kts settings.gradle.kts) |
| pageDidAppear 不触发 | 检查各端生命周期回调是否正确对接 |
| iOS 资源加载失败 | 检查 resourceFolderUrlForKuikly 返回路径与 Bundle 配置是否一致 |
十一、参考资源
- 开源仓库:github.com/Tencent-TDS…
- 官方文档:kuikly.tds.qq.com
- Android 接入指南:kuikly.tds.qq.com/QuickStart/…
- iOS 接入指南:kuikly.tds.qq.com/QuickStart/…
- 鸿蒙接入指南:kuikly.tds.qq.com/QuickStart/…
总结:Kuikly 通过 Kotlin Multiplatform 技术,让开发者用一套 Kotlin 代码同时覆盖 Android、iOS、鸿蒙三端,编译为各平台原生二进制产物,实现原生级性能。相比 Flutter/RN,Kuikly 无 JS 桥接开销,与现有原生工程无缝共存,是目前国内支持鸿蒙最完善的跨平台方案之一。