使用 Kuikly 同时开发 Android、iOS、鸿蒙 App 的最佳实践指南

39 阅读5分钟

一、背景:为什么选择 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
JDK17编译环境(注意:AS ≥ 2024.2.1 默认 JDK 21,需手动切换)
Kotlin2.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 中依次搜索并安装:

  1. Kotlin
  2. Kotlin MultiPlatform
  3. Kuikly(≥ 1.1.0 版本,支持生成鸿蒙工程)

四、创建 Kuikly 跨平台工程

4.1 使用插件一键创建

安装 Kuikly 插件后,在 Android Studio 中:

plaintext

File → NewNew 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 布局、业务逻辑commonMain90%+ 代码放这里
平台 API 调用androidMain/iosMain/ohosArm64Main通过 expect/actual 隔离
原生能力扩展各平台壳工程封装为 Module 供 Kotlin 侧调用

10.2 性能优化要点

  1. 列表优化:超长列表使用 vforLazy 替代 vfor,避免全量渲染
  2. 内存控制:iOS Extension 等内存敏感场景,适时调用 GC.collect()
  3. 首屏优化:避免在 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 配置是否一致

十一、参考资源

总结:Kuikly 通过 Kotlin Multiplatform 技术,让开发者用一套 Kotlin 代码同时覆盖 Android、iOS、鸿蒙三端,编译为各平台原生二进制产物,实现原生级性能。相比 Flutter/RN,Kuikly 无 JS 桥接开销,与现有原生工程无缝共存,是目前国内支持鸿蒙最完善的跨平台方案之一。