Skip to main content

Kuikly 动态化 SDK 接入指引

一、隐私安全说明

Kuikly 动态化 SDK

版本:2.7.0

更新时间:2025年11月14日

SDK介绍:为 Kuikly 开发框架提供动态化能力,方便开发者在线更新或修复问题

服务提供方:深圳市腾讯计算机系统有限公司

接入指引:Kuikly 动态化 SDK 接入指引

隐私保护规则:《Shiply 容器动态化 SDK 隐私保护规则》

二、获取 SDK 和 License

Kuikly 动态化属于限制开放功能,有兴趣的用户请联系我们,以获取 SDK 和相关使用权限。

咨询二维码

三、运行时 SDK 接入

PS:在正式接入 Kuikly 动态化之前,请确保自己的项目已按照 Kuikly 开源版本的要求进行了接入和改造。

动态化版本相较于开源版本,在接入上的主要差异:

  1. SDK 发布源需要修改,以获得支持动态化的制品
  2. 平台侧少量接口参数有变化,以支持动态产物的查找和加载
  3. 跨端侧的模块配置需要修改,以支持 JS 产物的构建
  4. 同时需要引入新的动态化构建插件,以生成动态发布产物

3.1 Android 端接入及改造

1. 更换依赖的组件

修改项目的build.gradle.kts配置,添加新的 Maven 仓库和 Gradle 插件依赖

buildscript {
repositories {
// Kuikly Dynamic Repo
maven {
url = uri("https://maven.cnb.cool/tencent-tds/kuikly-dynamic/-/packages/")
credentials {
username = "分配的用户名"
password = "分配的Token"
}
}
}

dependencies {
classpath("com.tencent.kuikly-open:core-gradle-plugin:2.7.0-2.1.21")
}
}

allprojects {
repositories {
// Shiply Repo
maven { url = uri("https://maven.cnb.cool/tencent-tds/shiply-public/-/packages/") }
// Kuikly Dynamic Repo
maven {
url = uri("https://maven.cnb.cool/tencent-tds/kuikly-dynamic/-/packages/")
credentials {
username = "分配的用户名"
password = "分配的Token"
}
}
}
}

将开源的组件依赖更换为支持动态化的组件

dependencies {
implementation("com.tencent.kuikly-open:core:2.7.0-2.1.21")
implementation("com.tencent.kuikly-open:core-render-android:2.7.0-2.1.21")
}

2. 修改加载框架接入代码

为了支持动态化,我们修改了加载框架代码,并扩展了onAttach接口,在接口中增加了新的参数moduleName

模块作为动态产物下发的管理单位,一个模块可以有一个或多个页面组成,在打开页面时需要传入。

开发者可以根据自己项目的接入情况,适当修正自己的代码,以解决相关的编译告警。

以下是修改后的接口描述:

open class KuiklyRenderViewBaseDelegator {
/**
* 初始化KuiklyView
* @param moduleName 模块名字
* @param pageName 页面名字
* @param pageData 页面数据
* @param size 根View大小, 非必传
*/
fun onAttach(
moduleName: String,
pageName: String,
pageData: Map<String, Any>,
size: Size? = null
)
}

interface IKuiklyView {
/**
* 初始化KuiklyView
* @param moduleName 模块名字
* @param pageName 页面名字
* @param pageData 页面数据
* @param size 根View大小, 非必传
*/
fun onAttach(
moduleName: String,
pageName: String,
pageData: Map<String, Any>,
size: Size? = null
)
}

3. 接入并使用动态产物更新框架

KuiklyDynamic用于云端动态产物的检查、下载、更新管理。 在 App 启动后的合适时机(一般是在隐私合规弹窗通过后),可以初始化该模块。

初始化KuiklyDynamic

// 构造业务参数
val params = KuiklyDynamicParams(
this,
"c82963a5a2", // appId
"3c0286e3-12f1-4609-b620-ecd37d41d00f", // appKey
"123456", // 用户ID
"1.0.0", // 应用版本
"OPPO", // 厂商
"Y1031", // 机型
)
// 初始化Kuikly动态化组件
KuiklyDynamic.init(params)

通过checkUpdate()检查模块是否有更新:

KuiklyDynamic.checkUpdate("moduleName", object : ICheckUpdateCallback {
override fun onComplete(success: Boolean, installResult: InstallResult) {
// TODO:
}
})

PS: 开发者可以根据自己的需要,在合适的时机触发模块的更新检查。新的产物会自动下载到本地,并在下次打开页面时生效。

3.2 iOS 端接入及改造

1. 更换依赖的组件

iOS 端需要将 Kuikly 开源组件 OpenKuiklyIOSRender 替换成 KuiklyDynamic

具体步骤是:

  • 删除 Podfile 中的 OpenKuiklyIOSRender

  • 在 Podfile 中引入 KuiklyDynamic.xcframework

  • 在 Podfile 中引入 dynamic_core.xcframework

系统要求:

  • iOS版本:iOS 14.1 及以上
  • Xcode版本:Xcode 12.0 及以上
  • 开发语言:Objective-C / Swift
  • 架构支持:真机(arm64)、模拟器(arm64、x86_64)

当前推荐版本:

  • KuiklyDynamic:2.7.0
  • dynamic_core:2.7.0-2.1.212.7.0-2.0.21(根据你的 Kotlin 版本选择)

完整接入文档: iOS-KuiklyDynamic-Integration-Guide.md

iOS Demo: Demo 仓库

CocoaPods 集成(推荐)

在你的 Podfile 中添加以下依赖:

# iOS 最低版本要求
platform :ios, '14.1'

target 'YourApp' do
# KuiklyDynamic 主要SDK
pod 'KuiklyDynamic', :git => 'https://cnb.cool/tencent-tds/KuiklyDynamic.git', :tag => '2.4.2-beta1'

# dynamic_core 核心模块
pod 'dynamic_core', :git => 'https://cnb.cool/tencent-tds/KuiklyDynamic.git', :tag => '2.4.2-beta1'

end

接入并使用动态产物更新框架

KuiklyDynamic用于云端动态产物的检查、下载、更新管理。 在 App 启动后的合适时机(一般是在隐私合规弹窗通过后),可以初始化该模块。

初始化KuiklyDynamic

// 在 AppDelegate 或合适的时机初始化
- (void)viewDidLoad {
[super viewDidLoad];

// 创建初始化参数
KuiklyDynamicParams *params = [[KuiklyDynamicParams alloc]
initWithContext:self // 上下文对象
appId:@"c82963a5a2" // 应用ID
appKey:@"3c0286e3-12f1-4609-b620-ecd37d41d00f" // 应用密钥
userId:@"123456" // 用户唯一标识
appVersion:@"1.0.0" // 应用版本
manufacturer:@"Apple" // 设备制造商
model:@"iPhone" // 设备型号
env:@"online" // 环境标识
customParams:@{}]; // 自定义参数

// 初始化 KuiklyDynamic
[[KuiklyDynamic shared] initializeWithParams:params
logCallback:self];
}

// 实现 KuiklyDynamicLogProtocol 协议,将动态化日志打印到你的应用日志中
- (void)onLogMessage:(NSString *)fullMessage logLevel:(KuiklyDynamicLogLevel)logLevel {
NSLog(@"%@", fullMessage);
}

增加动态化模块更新检查

KuiklyDynamicModule.h 文件

#import <Foundation/Foundation.h>
#import <KuiklyDynamic/KRBaseModule.h>
#import <KuiklyDynamic/KuiklyDynamic.h>

NS_ASSUME_NONNULL_BEGIN

@interface KuiklyDynamicModule : KRBaseModule <KuiklyDynamicCheckUpdateDelegate>

// 检查是否已初始化
- (NSString *)isInitialized:(NSDictionary *)args;

// 检查更新
- (void)checkUpdate:(NSDictionary *)args;

@end

KuiklyDynamicModule.m 文件

#import "KuiklyDynamicModule.h"

@interface KuiklyDynamicModule()

@property (nonatomic, copy) KuiklyRenderCallback currentCallback;

@end

@implementation KuiklyDynamicModule

+ (NSString *_Nonnull)moduleName {
return @"KuiklyDynamicModule";
}

- (NSString *)isInitialized:(NSDictionary *)args {
BOOL initialized = [[KuiklyDynamic shared] isInitialized];
return initialized ? @"true" : @"false";
}

- (void)checkUpdate:(NSDictionary *)args {
NSString *moduleName = args[KR_PARAM_KEY] ?: @"";
KuiklyRenderCallback callback = args[KR_CALLBACK_KEY];

// 保存回调供后续使用
self.currentCallback = callback;

[[KuiklyDynamic shared] checkUpdateForModule:moduleName delegate:self];
}

- (void)onCheckUpdateComplete:(BOOL)success installResult:(KuiklyDynamicInstallResult *)installResult {
if (self.currentCallback) {
NSDictionary *result = @{
@"success": @(success),
@"installResult": installResult.resultName ?: @""
};
self.currentCallback(result);

// 清理回调,避免内存泄漏
self.currentCallback = nil;
}
}

PS: 开发者可以根据自己的需要,在合适的时机触发模块的更新检查。新的产物会自动下载到本地,并在下次打开页面时生效。

KuiklyRenderViewController 支持动态产物 ModuleName

#import <KuiklyDynamic/KuiklyDynamic.h>
#import <KuiklyDynamic/KuiklyRenderViewControllerDelegator.h>
#import <KuiklyDynamic/KuiklyRenderContextProtocol.h>

@interface KuiklyRenderViewController : UIViewController
/*
* @brief 创建实例对应的初始化方法(支持动态产物模块名).
* @param moduleName 动态产物模块名,用于检查是否存在对应的动态产物
* @param pageName 页面名 (对应的值为kotlin侧页面注解 @Page("xxxx")中的xxx名)
* @param params 页面对应的参数(kotlin侧可通过pageData.params获取)
* @return 返回KuiklyRenderViewController实例
*/
- (instancetype)initWithModuleName:(NSString * _Nullable)moduleName
pageName:(NSString *)pageName
pageData:(NSDictionary *)pageData;
@end


@implementation KuiklyRenderViewController {
NSDictionary *_pageData;
NSString *_moduleName;
}

- (instancetype)initWithPageName:(NSString *)pageName pageData:(NSDictionary *)pageData {
if (self = [super init]) {
pageData = [self p_mergeExtParamsWithOriditalParam:pageData];
_pageData = pageData;
_delegator = [[KuiklyRenderViewControllerBaseDelegator alloc] initWithPageName:pageName pageData:pageData];
_delegator.delegate = self;
}
return self;
}
@end

头文件替换

// ❌
#import <OpenKuiklyIOSRender/KuiklyRenderBridge.h>
// ✅
#import <KuiklyDynamic/KuiklyRenderBridge.h>

// ❌
#import "KRRouterModule.h"
// ✅
#import <KuiklyDynamic/KRRouterModule.h>


// ❌
#import <OpenKuiklyIOSRender/KRBaseModule.h>
// ✅
#import <KuiklyDynamic/KRBaseModule.h>

3.3 鸿蒙端接入及改造

待补充

3.4 跨端层接入及改造

1. 修改 KMP 配置

kotlin {
// 1. 添加生成 js 目标产物
js(IR) {
moduleName = "nativevue2"
browser {
webpackTask {
outputFileName = "${moduleName}.js"
}
commonWebpackConfig {
output?.library = null
}
}
binaries.executable()
}

// 2. 将开源的组件依赖更换为支持动态化的组件
val commonMain by sourceSets.getting {
dependencies {
implementation("com.tencent.kuikly-open:core:2.7.0-2.1.21")
api("com.tencent.kuikly-open:core-annotations:2.7.0-2.1.21")
}
}

// 3. 添加 js 目标产物的依赖
val jsMain by sourceSets.getting {
dependsOn(commonMain)
}
}

2. 修改 KSP 配置

dependencies {
// 1. 将开源的组件依赖更换为支持动态化的组件
compileOnly("com.tencent.kuikly-open:core-ksp:2.7.0-2.1.21") {
// 2. 添加 js 目标产物的支持
add("kspJs", this)
}
}

3. 引入动态化构建插件

plugins {
// 1. 添加 Kuikly 动态化插件
id("com.tencent.kuikly-open.kuikly")
}

// 2. 配置 Kuikly 动态化插件
configure<KuiklyConfig> {
js {
outputName("nativevue2")
// 需要构建的动态化页面,每次构建按照实际需求填写
addSplitPages(listOf("image_demo", "mask_demo", "example"))
}
dynamicApk {
shellProjectName = "apk-builder"
// 需要构建的动态化页面,每次构建按照实际需求填写
addSplitPages(listOf("image_demo", "mask_demo", "example"))
}
shiply {
// 平台颁发的 License
license = project.file("kuikly_license").absolutePath
}
}

// 3. 配置 Kuikly 的 KSP
ksp {
// 保障KSP编译时,能够获取到分包的pageName参数
arg("pageName", project.properties["pageName"] as? String ?: "")
}

动态化构建插件支持两种类型的动态产物,apk 产物提供给 Android 平台使用,js 产物提供给 iOS 平台使用。

Android 平台的动态产物构建,需要依赖一个壳儿工程,开发者需要自行在项目中创建,并将该壳儿工程的名字填写到shellProjectName 字段。

PS:该壳儿工程建议直接使用 KuiklyDynamicDemoapk-builder模块, 并修改apk-builder模块中的跨端层模块依赖,修改为业务实际的跨端层模块。

dependencies {
implementation(project(":shared"))
}

四、动态产物构建

按照以上流程完成 Kuikly 动态化 SDK 的接入及项目改造后,项目应该跟接入前一样,能正常的构建启动,并加载内置页面。

同时,我们的跨端模块下会新增加一系列 Gradle Task,用于触发页面的动态产物构建和打包行为,如下图所示:

Kuikly Dynamic Gradle Tasks

4.1 构建 Android 平台动态产物

修改dynamicApk下的addSplitPages字段,将本次构建的模块所涉及的动态页面填入其中。

kuikly {
dynamicApk {
// 需要构建的动态化页面,每次构建按照实际需求填写
addSplitPages(listOf("image_demo", "mask_demo", "example"))
}
}

执行./gradlew packSplitApkRelease,触发动态化产物的构建。

构建成功后, 可在跨端模块的build/outputs/kuikly/apk/release/split/final/kuikly_dynamic.zip处找到构建产物, 该产物作为最终的发布产物,需要上传到发布平台后,经由网络下发到客户端,才可被动态加载。

4.2 构建 iOS 平台动态产物

修改js下的addSplitPages字段,将本次构建的模块所涉及的动态页面填入其中。

kuikly {
js {
// 需要构建的动态化页面,每次构建按照实际需求填写
addSplitPages(listOf("image_demo", "mask_demo", "example"))
}
}

执行./gradlew packSplitJSBundleRelease,触发动态化产物的构建。

构建成功后, 可在跨端模块的build/outputs/kuikly/js/release/split/nativevue2.zip处找到构建产物, 该产物作为最终的发布产物,需要上传到发布平台后,经由网络下发到客户端,才可被动态加载。

4.2 动态产物下发

生成的动态产物需要上传到发布平台后,经由网络下发到客户端,才可被动态加载。

如何在发布平台进行动态产物下发,请查看:发布平台使用指引

五、测试体验 Demo

对于想快速体验的开发者,也可以直接使用我们的测试 Demo 进行体验。 在正式接入时,该 Demo 也是一个很好的参考样例,可以提升开发者的接入速度。

Demo 地址:KuiklyDynamicDemo

六、业务动态化改造注意事项

6.1 Kuikly 动态化原理

Kuikly动态化框架

Kuikly 通过将页面及相关代码单独编译成各个平台使用的独立动态产物,通过 Shiply 进行分发。 在打开页面时,Render 优先检查本地是否存在动态产物,存在则优先从动态产物加载页面,否则在原生内置中查找。

由于不同平台的合规性要求,各平台采用的动态产物也不尽相同。Android 端采用的是动态 dex 的模式,iOS、鸿蒙则采用 js 的模式。

6.2 业务代码实现的约束

1. 代码实现隔离

由于动态化部分需要独立的构建编译和加载,因此代码需要从结构上做到隔离,即:动态化页面对Native组件的调用必须通过 Moudle 机制来实现。 这里提到的Native组件可能是 KMP 组件,也可能是业务自己的业务组件。

通过这样的隔离机制,动态化的部分与原生没有了直接的代码调用,统一通过 Moudle 机制来实现相互的数据交互,我们就可以随意的替换 UI 层的实现,最终达到页面动态化的目的。

2. 线程和协程

因为部分平台采用的是 js 的模式,而 js 又是单线程的模型,无法支持多线程,因此需要动态化的部分不能直接创建线程。 对于确实有多线程需求的场景,可以考虑把多线程的调用转到 Native 层。

另外,kotlin 原生的协程不支持 js,所以在动态化场景不能直接使用。Kuikly 提供了“内建协程”,用于解决“并行执行”和“回调地狱”问题,包括 GlobalScopePager.lifecycleScope。 该协程库并不是真正意义上的协程,它运行在 Kuikly 的线程中,因此也不存在线程安全问题。

GlobalScope.launch {
val data = fetchData()
...
}

关于 Kuikly 的协程和线程问题,也可以查看官方文档:协程和多线程编程指引

七、动态化常见问题

7.1 动态化产物的 Assets 资源访问失败

动态产物构建时,默认会将assets目录下的页面同名目录common目录打包到产物中, 如遇动态化产物无法访问 Assets 资源的问题,请检查项目的资源组织方式。

# image_demo 页面的动态产物解压
├── assets
│ ├── common
│ │ └── penguin.png
│ └── image_demo
│ └── panda.png
├── config.json
└── image_demo.js
这篇文档对您有帮助吗?