Core Rules
- Kuikly Thread Constraints: All Kuikly UI operations (View, Attr, Event, observable, Module methods, setTimeout, etc.)can only be called on the Kuikly thread, and Kuikly UI-related classes are not thread-safe.
- After an asynchronous task completes, you must return to the Kuikly threadbefore you can update the UI or access reactive properties.
- Dynamic scenariokotlinx coroutines are not supported (the js target platform does not support multithreading); you can only use the Module mechanism or Kuikly built-in coroutines.
Comparison of Coroutine Implementation Approaches
| Feature | Callback (no coroutines) | Kuikly built-in coroutines | kotlinx coroutines |
|---|---|---|---|
| Dynamic support | ✅ Supported | ✅ Supported | ❌ Not supported |
| Dependency package size increase | None | None | kotlinx coroutine library |
| Thread safety | Not involved | Automatically ensured | Needs consideration |
Three coroutine APIs
1. Kuikly built-in coroutines (recommended for simple async)
Built into the framework, always executed on the Kuikly thread, with no thread-switching overhead,supports dynamic。
API entry points:
GlobalScope.launch { ... }— global scopelifecycleScope.launch { ... }— bound to the Pager lifecycle (recommended)async { ... }/await()— concurrently obtain results
import:
import com.tencent.kuikly.core.coroutines.GlobalScope
import com.tencent.kuikly.core.coroutines.launch
import com.tencent.kuikly.core.coroutines.async
import com.tencent.kuikly.core.coroutines.delay
Note: The Kuikly built-in coroutine API itself is not thread-safe and cannot be called outside the Kuikly thread.
2. kotlinx.coroutines library
Kotlin's official coroutine library, supports multithreaded dispatchers (Dispatchers.Default/IO/Main, etc.),does not support dynamic。
Integration method:
// iOS & Android
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLINX_COROUTINES_VERSION")
// 鸿蒙
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLINX_COROUTINES_OHOS_VERSION")
3. kuiklyx.coroutines library (switch to the Kuikly thread)
Provides Dispatchers.Kuikly dispatcher and KuiklyContextScheduler callback API, used to switch back to the Kuikly thread from a non-Kuikly thread.
Integration method:
// iOS & Android
implementation("com.tencent.kuiklyx:coroutines:$KUIKLYX_COROUTINES_VERSION")
// 鸿蒙
implementation("com.tencent.kuiklyx:coroutines:$KUIKLYX_COROUTINES_OHOS_VERSION")
Coroutine-style API:
// 启动协程,在 Kuikly 线程执行
GlobalScope.launch(Dispatchers.Kuikly[ctx]) { ... }
// 在协程中切换到 Kuikly 线程
withContext(Dispatchers.Kuikly[ctx]) { ... }
Callback-style API:
KuiklyContextScheduler.runOnKuiklyThread(pagerId) { cancel ->
if (cancel) return // pager 已销毁
// 在 Kuikly 线程执行
}
Scenario Selection Decision Tree
Choose the appropriate asynchronous programming approach according to your needs:
需要异步编程?
├── 只需简化回调,无多线程诉求?
│ └── → 方式1:Kuikly 内建协程
├── 需要执行耗时任务 + 支持动态化?
│ └── → 方式2:Module 机制(可配合内建协程)
├── 需要多线程 + 不需要动态化?
│ └── → 方式3:kotlinx 协程 + kuiklyx 协程
└── Compose DSL 页面?
└── → 方式4:LaunchedEffect + withContext
⚠️ Consistency principle: When choosing a solution, you should also pay attention to the existing asynchronous programming approach in the current project or module, and should prioritize keeping it consistent, avoiding mixing multiple asynchronous solutions in the same module.
Detailed scenario examples and code: See SCENARIOS.md
Coroutines in Compose DSL
Compose DSL pages (inheriting ComposeContainer) use the kotlinx coroutine system:
@Composable
fun MyContent() {
var data by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
// 默认在 Kuikly 线程执行(ComposeDispatcher)
val result = withContext(Dispatchers.IO) {
// 在 IO 线程执行耗时任务
fetchData()
}
// 自动回到 Kuikly 线程
data = result
}
Text(text = data)
}
Key points:
LaunchedEffectruns on the Kuikly thread by default (viaComposeDispatcherdispatch)- Use
withContext(Dispatchers.IO)to switch to the IO thread to execute time-consuming tasks withContextautomatically returns to the Kuikly thread after returningviewModelScopealso bound to the Kuikly thread, automatically cancelled when the ViewModel is destroyed
Dispatchers.IO cross-platform definition:
// commonMain 中声明 expect
internal expect val Dispatchers.IO: CoroutineDispatcher
// androidMain / appleMain / ohosArm64Main
internal actual val Dispatchers.IO: CoroutineDispatcher
get() = Dispatchers.IO // 使用平台原生 IO 调度器
// jsMain(不支持多线程)
internal actual val Dispatchers.IO: CoroutineDispatcher
get() = Dispatchers.Default
Thread-safe
Thread-safety rules
- Kuikly UI-related classes (View, Attr, Event, ObservableProperties, GlobalFunctions, Module methods, Layout, etc.)Not thread-safe, can only be accessed on the Kuikly thread
Thread-safety verification mechanism
Detailed usage and examples: see THREAD_SAFETY.md
override fun willInit() {
super.willInit()
Pager.VERIFY_THREAD = true // 开启线程校验
Pager.VERIFY_REACTIVE_OBSERVER = true // 开启 observable 校验
Pager.verifyFailed { exception -> // 自定义验证失败处理
println("线程安全验证失败: ${exception.message}")
throw exception
}
}
Common pitfalls and correct practices
| ❌ Incorrect practice | ✅ Correct practice |
|---|---|
| Directly updating observable on a non-Kuikly thread | via Dispatchers.Kuikly or KuiklyContextScheduler switch back to the Kuikly thread |
| Calling Module methods on a non-Kuikly thread | Module methods such as acquireModule / toNative must be called on the Kuikly thread |
| Using kotlinx coroutines in dynamic scenarios | Use Kuikly built-in coroutines or the Module mechanism |
| Calling built-in coroutine APIs outside the Kuikly thread | Built-in coroutine APIs can only be called on the Kuikly thread |
| Forgetting to handle callbacks after Pager destruction | KuiklyContextScheduler callback checks the cancel parameter |
in verifyFailed operate UI in the callback |
Verification failures usually occur on a non-Kuikly thread, and UI APIs cannot be called |
| Updating State on an IO thread in Compose | Use withContext return to the default dispatcher before updating State |