# ovCompose-sample
**Repository Path**: ZXHHYJ/ovCompose-sample
## Basic Information
- **Project Name**: ovCompose-sample
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-08-08
- **Last Updated**: 2025-08-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[English](./README.md) | 简体中文
## 项目介绍
ovCompose(online-video-compose)是腾讯大前端领域Oteam中,腾讯视频团队基于 Compose Multiplatform
生态推出的跨平台开发框架,旨在弥补Jetbrains Compose
Multiplatform不支持鸿蒙平台的遗憾与解决iOS平台混排受限的问题,便于业务构建全跨端App。
## 仓库说明
[compose-multiplatform](https://github.com/Tencent-TDS/KuiklyBase-platform/tree/main/compose-multiplatform)
:跨端 Compose 编译器插件源码
[compose-multiplatform-core ](https://github.com/Tencent-TDS/ovCompose-multiplatform-core):跨端
Compose 源码
[ovCompose-sample ](https://github.com/Tencent-TDS/ovCompose-sample):跨端 Compose 示例项目
## 使用说明
### ArkUI 接入 Compose
1. 创建 Compose 跨端项目
使用 [Android Studio](https://developer.android.com/studio) 创建 Kotlin 跨端项目,并使用腾讯视频开源的支持鸿蒙平台的
Kotlin 版本构建项目。
`libs.version.toml` 参考如下
```toml
[versions]
agp = "8.5.2"
compose-plugin = "1.6.1-KBA-002"
kotlin = "2.0.21-KBA-005"
kotlinx-coroutines = "1.8.0-KBA-001"
[libraries]
# Compose multiplatform
compose-multiplatform-export = { module = "org.jetbrains.compose.export:export", version.ref = "compose-plugin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
```
`build.gradle.kts` 参考如下
其中链接 skia 库中的 `libskia.so` 从 `ovCompose-sample` 项目中获取。
```kotlin
plugins {
// 添加 kotlinMultiplatform jetbrainsCompose 和 composeCompiler 插件
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.composeCompiler)
}
kotlin {
ohosArm64 {
binaries.sharedLib {
// 指定二进制产物名称
baseName = "kn"
// 链接 skia 库
linkerOpts("-L${projectDir}/libs/", "-lskia")
// 导出 compose.export
export(libs.compose.multiplatform.export)
}
}
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.material)
implementation(compose.ui)
implementation(libs.kotlinx.coroutines.core)
}
val ohosArm64Main by getting {
dependencies {
// 使用 api 方式依赖 compose.multiplatform.export 库,用于导出 C 函数
api(libs.compose.multiplatform.export)
}
}
}
}
```
2. 编写跨端 Compose 代码
```kotlin
// 写在 commonMain 下,支持跨端复用
@Composable
internal fun Hello() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Hello Compose!")
}
}
// 写在 ohosArm64Main 下,创建接入鸿蒙 ArkUI 视图体系的 ArkUIViewController
@CName("createHelloArkUIViewController")
fun createHelloArkUIViewController(env: napi_env): napi_value {
initMainHandler(env)
return ComposeArkUIViewController(env) {
Hello()
}
}
```
3. 编译跨端 Compose 代码,生成接入鸿蒙平台的二进制产物
执行跨端模块的 `linkDebugSharedOhosArm64 ` 或 `linkReleaseSharedOhosArm64` 任务,
在该模块的 `build/bin/ohosArm64` 目录下将会生成 `libkn.so` 和 `libkn_api.h`
两个文件,这两个文件将被用于集成到鸿蒙项目中。
4. 创建鸿蒙 harmonyApp 项目
- 创建项目
在跨端项目下使用 [DevEco-Studio](https://developer.huawei.com/consumer/cn/deveco-studio/) 创建
harmonyApp 项目,在 Create Project 选择 "Native C++" 创建带有 Native 代码的项目。
- 添加 Compose 跨端二进制产物
将前步骤 3 中生成两个文件复制到 harmonyApp 项目下,其中:
- `libkn.so` 复制到 `entry/libs/arm64-v8a/` 目录下。
- `libkn_api.h` 复制到 `entry/src/main/cpp/include/` 目录下。
为了简化这个步骤,我们可以在跨端 Compose 项目中创建一个 Gradle Task 执行这个复制任务。这样只需执行
`publishDebugBinariesToHarmonyApp` 或者 `publishReleaseBinariesToHarmonyApp` 即可编译 Compose
跨端代码并复制产物到鸿蒙项目。
```kotlin
kotlin { /* */ }
arrayOf("debug", "release").forEach { type ->
tasks.register("publish${type.capitalizeUS()}BinariesToHarmonyApp") {
group = "harmony"
dependsOn("link${type.capitalizeUS()}SharedOhosArm64")
into(rootProject.file("harmonyApp"))
from("build/bin/ohosArm64/${type}Shared/libkn_api.h") {
into("entry/src/main/cpp/include/")
}
from(project.file("build/bin/ohosArm64/${type}Shared/libkn.so")) {
into("/entry/libs/arm64-v8a/")
}
}
}
```
- 添加 `skikobridge.har` 和 `compose.har` 依赖
- 将 `skikobridge.har` 复制到 `entry/libs/` 目录下,其中:`skikobridge.har` 可以从
`ovCompose-sample/harmonyApp` 项目下获取。
- 将 `compose.har` 复制到 `entry/libs` 目录下,其中 `compose.har` 是从
`compose-multiplatform-core/ui-arkui` 模块发布出来的,请参考文档中的编译发布板块里的第三部分内容。
5. 配置 harmonyApp 项目
配置依赖:`entry/oh-package.json`
```json5
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
"libentry.so": "file:./src/main/cpp/types/libentry",
// 添加 compose.har 依赖
"compose": "file:./libs/compose.har",
// 添加 skikobridge.har 依赖
"skikobridge": "file:./libs/skikobridge.har"
}
}
```
配置 CMake 编译:`entry/src/main/cpp/CMakeLists.txt`
```cmake
# the minimum version of CMake.
cmake_minimum_required(VERSION 3.5.0)
project(harmonyApp)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED PACKAGE_FIND_FILE)
include(${PACKAGE_FIND_FILE})
endif()
add_definitions(-std=c++17)
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 获取 skikobridge package
find_package(skikobridge)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so)
# 链接 libkn.so
target_link_libraries(entry PUBLIC ${NATIVERENDER_ROOT_PATH}/../../../libs/arm64-v8a/libkn.so)
# 链接 skikobridge 中的 skikobridge.so
target_link_libraries(entry PUBLIC skikobridge::skikobridge)
# 链接其他必要库
target_link_libraries(entry PUBLIC ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libc++_shared.so)
```
6. 将 Compose 接入 harmonyApp 项目
Compose ArkUI 初始化和 ArkUIViewController 的构建
```c++
// entry/src/main/cpp/napi_init.cpp
static napi_value CreateHelloArkUIViewController(napi_env env, napi_callback_info info) {
// 调用 Compose 跨端项目构造 ArkUIViewController 的函数
auto controller = createHelloArkUIViewController(env);
return reinterpret_cast(controller);
}
static napi_value Init(napi_env env, napi_value exports) {
// 初始化 compose arkui
androidx_compose_ui_arkui_init(env, exports);
// 使用 napi 注册一个 ArkTS 层的 createHelloArkUIViewController 函数
napi_property_descriptor desc[] = {
{"createHelloArkUIViewController", nullptr, CreateHelloArkUIViewController, nullptr, nullptr, nullptr, napi_default, nullptr}};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
```
添加 `createHelloArkUIViewController()` 函数声明:
```typescript
// entry/src/main/cpp/types/libentry/index.d.ets
import { ArkUIViewController } from 'compose';
// 声明 createHelloArkUIViewController 函数
export const createHelloArkUIViewController: () => ArkUIViewController
```
在 ArkUI 页面中接入 Compose
```typescript
import { common } from '@kit.AbilityKit';
import { ArkUIViewController, Compose } from 'compose';
import { createHelloArkUIViewController } from 'libentry.so';
@Entry
@Component
struct ComposePage {
private controller: ArkUIViewController = createHelloArkUIViewController()
// onPageShow 仅 @Entry 有效;页面每次显示时触发一次,包括路由过程、应用进入前台等场景
onPageShow(): void {
// 通知 controller onPageShow,用于处理生命周期
this.controller.onPageShow()
}
// onPageHide 仅 @Entry 有效;页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景
onPageHide(): void {
// 通知 controller onPageHide,用于处理生命周期
this.controller.onPageHide()
}
// onBackPress 仅 @Entry 有效;
onBackPress(): boolean | void {
// 转发返回事件给 controller,由 controller 优先消费
return this.controller.onBackPress()
}
build() {
Stack({ alignContent: Alignment.Center }) {
Compose({
controller: this.controller,
libraryName: "entry",
onBackPressed: () => {
// 未被 Compose 消费的返回事件,交由接入层兜底处理
(getContext() as common.UIAbilityContext).windowStage.loadContent("pages/Index")
return true
}
}).width('100%').height('100%')
}
.width('100%')
.height('100%')
}
}
```
在 ArkUI 导航节点中接入 Compose
```typescript
import { ArkUIViewController, Compose } from 'compose';
import { createHelloArkUIViewController } from 'libentry.so';
@Component
export struct ComposeDestination {
private controller: ArkUIViewController = createHelloArkUIViewController()
private navContext: NavDestinationContext | null = null;
build() {
NavDestination() {
Stack({ alignContent: Alignment.Center }) {
Compose({
controller: this.controller,
libraryName: "entry",
onBackPressed: () => {
// 未被 Compose 消费的返回事件,交由接入层兜底处理
this.navContext?.pathStack.pop()
return true
}
})
}
.width('100%')
.height('100%')
}
.onReady((navContext) => {
this.navContext = navContext
})
// 通知 controller onPageShow
.onShown(() => {
this.controller.onPageShow()
})
// 通知 controller onPageHide
.onHidden(() => {
this.controller.onPageHide()
})
// 转发返回事件给 controller,由 controller 优先消费
.onBackPressed(() => this.controller.onBackPress())
}
}
```
### 在Compose中使用ArkUI
ovCompose可与ArkUI框架混排。您可以将 Compose Multiplatform 嵌入到 ArkUI 应用中,也可以将原生 ArkUI
组件嵌入到 Compose Multiplatform 中。
此部分提供了在Compose Multiplatform UI 中嵌入 ArkUI 组件的示例。
#### 在 Compose Multiplatform 中使用 ArkUI-TS
要在 Compose Multiplatform 中使用 ArkUI-TS 元素, 请将要使用的 ArkUI-TS 元素添加到Compose
Multiplatform 中的`ArkUIView`中
1. 创建您的ArkUI-TS native组件及其“Builder”
```typescript
@Builder
export function buttonBuilder(args: ButtonArgs) {
button()
}
@Component
export struct button {
@Consume compose_args: ButtonArgs
build() {
Column() {
Button(this.compose_args.text).backgroundColor(this.compose_args.backgroundColor).width('100%').onClick(e => {
console.log(`Button Clicked: ${this.compose_args.text}`)
}).height('70%')
Text(this.compose_args.text).backgroundColor(this.compose_args.backgroundColor).width('100%').onClick(e => {
console.log(`Text Clicked: ${this.compose_args.text}`)
}).height('20%')
Stack().height('10%')
}
}
}
interface ButtonArgs {
text: string
backgroundColor: string
}
```
2. 使用`registerComposeInteropBuilder`注册ArkUI-TS 混排builder
```typescript
registerComposeInteropBuilder('button', buttonBuilder)
```
3. 请将要使用的 ArkUI-TS 元素添加到Compose Multiplatform 中的ArkUIView中
```kotlin
ArkUIView(
name = "button",
modifier = Modifier.width(250.dp).height(100.dp),
parameter = js {
"text"("ArkUI Button $index")
"backgroundColor"("#FF0000FF")
},
)
```
#### 在 Compose Multiplatform 中使用 ArkUI-C
要在 Compose Multiplatform 中使用 ArKUI-C 元素,请将要使用的 ArKUI-C 元素添加到Compose Multiplatform
中的`ArkUINodeHandle`中。
您可以纯用 Kotlin 编写此代码,也可以使用 `C/C++` 编写。
您可以使用 ArkUI-C 的API在 Compose Multiplatform 中实现Button视图。使用Compose Multiplatform
中的Modifier.size()或Modifier.fillMaxSize()函数为Button设置组件大小:
```kotlin
ArkUINodeHandle(
factory = {
ArkUI_NativeNodeAPI_1.createNode(ARKUI_NODE_BUTTON)
},
modifier = Modifier.size(300.dp)
)
```
### iOS 接入 Compose
Compose 项目公共配置部分可参考上述鸿蒙平台。
iOS 中提供了两种 Compose 容器方式接入。而每一种容器又分别可以选择 UIView 渲染模式或者 Skia 渲染模式
#### 使用 UIViewController 作为容器
```kotlin
fun MainViewController() = ComposeUIViewController(configure = {
renderBackend = RenderBackend.UIView
}) {
Box { ... }
}
```
我们提供了 `LocalLifecycleOwner`,可以在 Compose 函数内访问,并通过其感知页面的生命周期。
```kotlin
// 在 Compose 函数内可以使用 LocalLifecycleOwner 获取 LifecycleOwner,感知生命周期
val LocalLifecycleOwner = staticCompositionLocalOf { ... }
```
当 ComposeUIViewController 从视图栈中弹出后,内部会自动对 Compose 相关的数据结构进行 dispose,这可以在下一次 GC 到来的时候,释放相关内存。
#### 使用 UIView 作为 Compose 容器
UIView 作为容器是腾讯视频在官方基础上额外新增的一种方式。这更适合一些特殊的业务开发场景,比如 UIColletionViewCell 中嵌套 Compose 的 UIView。
```kotlin
fun MainComposeView(): UIView {
// UIKit 渲染模式,此外还可以选择使用 Skia 渲染模式
return ComposeUIView(configure = { renderBackend = RenderBackend.UIView }) {
Box { ... }
}
}
```
**注意点一**:由于 UIView 无法主动感知页面的 UIViewController 的生命周期。因此需要主动调用 ComposeUIView 的 `appear` 和 `disappear`,才能使驱动 Lifecycle 工作。
```kotlin
class ComposeUIView(
private val configuration: ComposeUIViewConfiguration,
private val content: @Composable () -> Unit,
) : UIView(...) {
// view 曝光的时候调用
fun appear() { ... }
// view 反曝光的时候调用
fun disappear() { ... }
// view 不再使用的时候需要调用,否则会出现内存泄漏
fun dispose() { ... }
}
```
**注意点二**:
函数返回类型为 UIView 或者 ComposeUIView 在导出的 OC 头文件中类型都是 UIView,因此是无法直接调用到`appear`、`disappear`、`dispose` 方法的。推荐使用自定义的类,包装 ComposeUIView。如下代码的`ComposeUIViewWrapper`
```kotlin
fun MainComposeView(): UIView { ... }
fun MainComposeView2(): ComposeUIView { ... }
class ComposeUIViewWrapper {
private val view by lazy {
ComposeUIView(configure = { renderBackend = RenderBackend.UIView }) {
Box { ... }
}
}
fun getUIView() : UIView = view
fun dispose() = view.dispose()
fun appear() = view.appear()
}
```
#### 有关说明
iOS应用程序提供了两种渲染模式:一种基于Skia,另一个基于UIKit。这两种模式可以在运行时同时存在。
UIKit渲染模式已在腾讯视频iOS中的许多核心页面中完全部署。
此模式旨在减少Skia模式引起的额外内存消耗,从而减轻使用多页面时低端iPhone的内存压力。
##### UIKit模式的跨平台一致性说明
| 维度 | 差异性 | 说明 |
|---------------|-----------------------------|----------------------------------------------------|
| Text | 文本渲染保持与Skia模式完全相同的效果 | 无差异性 |
| Image | 支持高斯模糊,着色等。目前仅部分支持其他特殊的过滤功能 | 和Skia存在一些差异 |
| GraphicsLayer | saveLayer操作尚不支持 | Marquee效果不支持.此功能在大多数业务开发方案中很少使用,并且可以使用UIKitView来解决 |
UIKit模式已在腾讯视频iOS中广泛采用,并且从业务反馈中没有报出UI不一致问题。如果您发现尚未支持重要功能,请随时提供反馈或提交拉动请求。
## License
ovCompose-sample 基于 Apache 2.0 协议发布,详见:[License](License.txt)
## 欢迎关注交流
欢迎扫码下方二维码关注最新动态或咨询交流。
