使用 Kanzi Android Service 进行开发¶
Kanzi Android Service 使您能够在设备上从多个应用程序使用同一个 Kanzi Android framework (droidfw) 实例。实现方式是:在一个应用程序的 Android 服务中托管 Kanzi Android framework (droidfw),然后所有客户端应用程序通过 Android IPC 机制使用它。
这带来以下能力:
Shared libraries。通过在客户端应用程序之间复用同一套 Kanzi 库来优化静态内存使用。
Shared runtime and resources。通过在客户端应用程序之间共享相同的 Kanzi Engine 和资源来优化运行时内存和 GPU 使用。
Improved application startup time。通过在后台预加载 Kanzi Engine 和资源来缩短启动时间。
Cross-application transition animations。使用共享的 Kanzi 资源,在不同应用程序之间实现无缝的迁移动画。
有关如何使用 Kanzi Android Service 的示例,请参阅 Android 服务示例。
备注
Kanzi Android Service 是一项实验性功能,您的反馈对我们非常重要。要提交反馈,请使用此 反馈表单。
架构详解¶
服务宿主应用程序¶
此应用程序通过 kzservice 库托管 Kanzi Android Service。该服务负责初始化 Kanzi Android framework (droidfw),并通过它初始化 Kanzi 运行时。宿主应用程序用于打包您的所有 Kanzi 资产、Kanzi 原生库和 Kanzi Engine 插件。
客户端应用程序¶
这些是同一系统上使用服务应用程序所提供 Kanzi 功能的任意数量的应用程序。它们使用 kzclient 库中的视图和可组合项类型访问 Kanzi。
跨应用程序迁移动画¶
客户端可以请求服务使用共享的 Kanzi 资源,在不同应用程序之间执行无缝的迁移动画。其工作原理是:服务创建一个显示在所有其他应用程序之上的叠加窗口,并在其中渲染迁移动画。动画结束后,该叠加窗口会被隐藏。
使用入门¶
要在 Kanzi Studio 中创建 Kanzi Android Service 工程:
在 Quick Start 窗口的 New 选项卡中,选择 Android Service application 工程模板。
单击 Create。
Kanzi 会创建一个 Kanzi Studio 工程和两个 Android Studio 应用程序工程:
位于
<MyProject>Application目录中的服务宿主应用程序。位于
<MyProject>Client目录中的客户端应用程序。
使用 Client API¶
这是您在客户端应用程序中用于配置服务应用程序中的 Kanzi Engine 实例并与之通信的 API。
View 和 Composable 类型¶
您可以将 Kanzi 内容添加到 XML 布局或 Composable 中。
在 XML 布局中,对于基于 SurfaceView 的渲染,添加
<com.rightware.kanzi.service.KanziServiceSurfaceView
android:id="@+id/clientred"
app:clientId="clientred"
app:kzbPathList="clientred.kzb"
app:startupPrefabUrl="kzb://clientred/StartupPrefab"
app:serviceHostPackage="com.example.host"
app:serviceIntentAction="com.example.host.action.LAUNCH_SERVICE"
/>
对于基于 TextureView 的渲染,添加
<com.rightware.kanzi.service.KanziServiceTextureView
android:id="@+id/clientred"
app:clientId="clientred"
app:kzbPathList="clientred.kzb"
app:startupPrefabUrl="kzb://clientred/StartupPrefab"
app:serviceHostPackage="com.example.host"
app:serviceIntentAction="com.example.host.action.LAUNCH_SERVICE"
/>
KanziServiceTextureView 不支持跨应用程序过渡。请参阅 设置跨应用程序过渡动画。
要了解基于 SurfaceView 的视图与基于 TextureView 的视图之间的区别,请参阅 添加 Kanzi 视图。
或者在您的 Composable 中添加:
KanziServiceComposable(
modifier = Modifier,
request = Request(
clientId = "clientblue",
kzbPathList = "clientblue.kzb",
startupPrefabUrl = "kzb://clientblue/StartupPrefab",
),
serviceHostPackage = "com.example.host",
serviceIntentAction = "com.example.host.action.LAUNCH_SERVICE"
)
KanziServiceComposable 基于 KanziServiceSurfaceView,并且还接受以下可选参数:transparency、focusableInTouchMode 和 listener。
View 和 Composable 类型接受以下 XML 特性:
特性 (Attribute) |
格式 (Format) |
描述 (Description) |
|---|---|---|
|
string |
客户端所显示的 Kanzi 内容的标识符。仅当要在两个客户端应用程序之间进行跨应用程序过渡时,才在这两个客户端应用程序中使用相同的客户端 ID。请参阅 设置跨应用程序过渡动画。 |
|
string |
要为此客户端加载的 .kzb 或 .kzb.cfg 文件的逗号分隔列表。 |
|
string |
客户端加载时实例化到客户端表面上的预设件的 URL。 |
|
string |
托管 Kanzi Android Service 的应用程序的 Android 包名。 |
|
string |
Kanzi Android Service 用于将客户端应用程序与服务绑定的 Intent 动作。 |
|
颜色 |
用于清除表面的颜色。默认值: |
|
BOOLEAN |
启用清除表面。默认值: |
|
BOOLEAN |
启用内置的旋钮输入处理。设置后,Kanzi 会处理旋钮输入事件并将其转换为 Kanzi 按键事件。默认值: |
|
BOOLEAN |
启用透明度支持。默认值: |
确保 serviceHostPackage 和 serviceIntentAction 与 Service 宿主应用程序清单文件中声明的 applicationId 和 Intent 动作完全一致。
客户端视图生命周期¶
客户端视图在附加到窗口时连接到 Kanzi Android Service,在从窗口分离时断开连接。这适用于 KanziServiceSurfaceView、KanziServiceTextureView 和 KanziServiceComposable。视图会反复附加和分离,例如在使用 fragment、ViewPager、RecyclerView 和 LazyColumn 时。当您重新添加已移除的客户端视图时,它会再次连接并显示其内容。
当客户端视图从其窗口分离时,Kanzi Android Service 宿主会调用 KanziServiceViewAdapterListener.onDetachedFromWindow。当另一个具有相同 clientId 的客户端视图仍在显示该内容时,或者在跨应用程序过渡进行期间,Kanzi Android Service 宿主不会调用该方法。当 Kanzi Android Service 宿主释放已终止的客户端应用程序的内容时,也会调用 onDetachedFromWindow。请参阅 客户端应用程序终止。
客户端会丢弃您在 onServiceConnected 之前以及客户端视图从其窗口分离之后进行的调用。Kanzi Android Service 宿主会保留您在客户端视图分离之前推送的数据。在 onServiceConnected 中,推送在客户端视图分离期间发生更改的数据。
KanziServiceClientListener¶
KanziServiceClientListener 是用于接收客户端生命周期事件通知的接口。所有回调都有默认的空实现,因此您只需重写您关注的回调。
回调 |
描述 (Description) |
|---|---|
|
在 Kanzi Android Service 已连接且客户端可供使用时调用。当客户端视图重新附加到窗口后 Kanzi Android Service 再次连接时,以及 Kanzi Android Service 宿主重新启动时,会再次调用。请参阅 服务宿主重新启动。 |
|
在客户端视图附加到窗口时调用。 |
|
在客户端视图从其窗口分离时调用。 |
|
在渲染表面已创建且可用时调用。 |
|
在渲染表面即将被销毁时调用。 |
|
在渲染表面尺寸发生变化时调用。 |
要在视图上设置监听器:
val kanziView = findViewById<KanziServiceSurfaceView>(R.id.clientred)
kanziView.listener = object : KanziServiceClientListener {
override fun onServiceConnected(client: KanziServiceClient) {
// Service is ready, interact with Kanzi
}
}
备注
请在视图附加到窗口之前设置监听器。如果在服务已连接后才设置监听器,会错过回调。
数据源 setter¶
在基于视图的应用程序中,您可以按如下方式获取 KanziServiceClient 引用:
val clientX = findViewById(R.id.clientred) as KanziServiceClient
在基于 Compose 的应用程序中,按如下方式获取:
var kanziServiceClient: KanziServiceClient? by remember { mutableStateOf(null) }
KanziServiceComposable(
...
listener = object : KanziServiceClientListener {
override fun onServiceConnected(client: KanziServiceClient) {
kanziServiceClient = client
}
},
)
通过该引用,您可以访问 setter pushDataInt、pushDataString、pushDataReal 和 pushDataBoolean。例如:
kanziServiceClient?.pushDataInt(
"kzb://launcher/Data Sources/Scene Data source",
"MainMenuScene",
MainMenuScenes.HOME.value
)
您需要提供数据源的 KZB URL、数据对象的路径以及要推送的值。客户端会丢弃您在 onServiceConnected 之前以及客户端视图从其窗口分离之后进行的调用。
手动分离客户端¶
默认情况下,当 activity 停止时,客户端应用程序不会显式地从服务分离。因此,服务会继续运行关联的 Kanzi Engine 实例,并保留为该客户端分配的资源。
要显式分离,请在 activity 的生命周期方法中对 KanziServiceClient 实例调用 handleClientAttached 和 handleClientDetached。
override fun onStop() {
super.onStop()
kanziServiceClient?.handleClientDetached()
}
override fun onStart() {
super.onStart()
kanziServiceClient?.handleClientAttached()
}
备注
手动分离与无缝迁移功能不兼容。如果您使用跨应用程序迁移,请勿调用这些方法。
客户端应用程序终止¶
客户端应用程序可能会在未从 Kanzi Android Service 分离的情况下终止。例如,Android 为释放内存而停止该应用程序,或者您强制停止该应用程序。当客户端应用程序的进程在其客户端视图位于屏幕上时终止,Kanzi Android Service 会释放该视图的图面。如果使用该客户端 ID 的跨应用程序过渡中的另一个客户端应用程序仍在运行,Kanzi Android Service 会为该客户端应用程序保留内容。否则,Kanzi Android Service 还会释放该客户端 ID 的内容,并调用 KanziServiceViewAdapterListener.onDetachedFromWindow。当具有该客户端 ID 的客户端应用程序再次连接时,Kanzi Android Service 会加载该客户端应用程序的启动预设件,而不是您预加载的预设件。
When a client application terminates in the background, after it lost its surface, the Kanzi Android Service keeps its content. The client application then shows the content immediately when it starts again. If the client application that last showed the content terminated on the screen, the Kanzi Android Service does not keep the content. It releases the content when the last client application with that client ID terminates, also in the background.
备注
当客户端应用程序在跨应用程序过渡期间终止时,Kanzi Android Service 不会释放其图面或内容。请参阅 已知问题。
处理方向更改¶
当设备方向更改时,通知 Kanzi Android Service:
kanziServiceClient?.handleOrientationChange(newOrientation)
将 newOrientation 设置为来自 Android Configuration 的新设备方向值。
该事件不会旋转 Kanzi 内容。要设置 Kanzi 内容的方向,请使用 屏幕节点 的 Orientation 属性。
处理可见性更改¶
当客户端视图的可见性更改时,通知 Kanzi Android Service,以便它优化渲染:
kanziServiceClient?.handleVisibilityChange(isVisible)
当视图可见时,将 isVisible 设置为 true,否则设置为 false。当客户端没有内容时,Kanzi Android Service 会忽略该调用。从客户端视图附加到窗口或 Kanzi Android Service 宿主预加载其预设件的那一刻起,客户端即拥有内容。该内容一直存在,直到客户端视图从窗口分离。
客户端隐藏的内容会保持隐藏状态,直到具有另一个连接的客户端为同一客户端 ID 提供图面。然后 Kanzi Android Service 会显示该内容。例如,当 Android 重新创建活动(这会为活动提供新的连接)时,或者当客户端接管 Kanzi Android Service 宿主已预加载并隐藏的内容时,就会出现这种情况。
当多个具有相同客户端 ID 的客户端处于连接状态时,Kanzi Android Service 会将可见性更改归属于最后连接的客户端。如果较早的客户端隐藏了内容,那么当最新的客户端再次提供其图面时,该内容仍保持隐藏。
服务宿主重新启动¶
当 Kanzi Android Service 宿主进程终止时,Android 会重新启动该进程并再次连接客户端:
在 Kanzi Android Service 宿主停止运行期间,客户端会丢弃您进行的调用,例如
pushDataInt。当客户端再次连接时,会再次触发
onServiceConnected。重新启动后的 Kanzi Android Service 宿主会从
startupPrefabUrl再次加载客户端内容。您在重新启动之前推送的数据会丢失。请在onServiceConnected中再次推送这些数据。
使用 Service API¶
这是您在服务宿主应用程序中通过 Kanzi Java API 初始化和配置 Kanzi Android framework (droidfw) 时使用的 API:
KanziServiceRuntime 是一个引用计数的单例,您可以通过调用 KanziServiceRuntime.acquire(context) 获取它。将返回的 Reference 作为成员存储在您的 Application 子类中,以便在服务启动之前保持运行时存活。当最后一个 Reference 关闭时,运行时会关闭。
要注册 Kanzi Engine Java 插件,请获取
KanziServiceRuntime,并将其domain与 Kanzi Java API 一起使用。例如:private lateinit var runtimeRef: KanziServiceRuntime.Reference override fun onCreate() { super.onCreate() runtimeRef = KanziServiceRuntime.acquire(this) val domain = requireNotNull(runtimeRef.get().domain) { "KanziServiceRuntime.domain must not be null after acquire()" } domain.registerPlugin(MyJavaPlugin()) }
您可以实现
KanziServiceViewAdapterListener,以在服务宿主应用程序中监听与客户端应用程序关联的KanziViewAdapter的状态更改。例如,使用
KanziServiceViewAdapterListener.onPrefabLoaded回调来访问和修改您配置的启动预设件实例化的节点树。override fun onCreate() { super.onCreate() KanziServiceRuntime.addViewAdapterListener(clientId, object : KanziServiceViewAdapterListener { override fun onPrefabLoaded( clientId: String, viewAdapter: KanziViewAdapter, domain: Domain ) { // Look up a child node starting from the root. val text = viewAdapter.root.lookupNode<TextBox2D>("#Text") // Set properties on a node. text.setProperty(TextBox2D.TextProperty, "Hello world!") } }) }
要从服务宿主应用程序显示或隐藏客户端的内容,请使用客户端 ID 调用
KanziServiceRuntime.handleVisibilityChange。例如,要在客户端应用程序接管预加载的内容之前使其保持隐藏:runtimeRef.get().handleVisibilityChange("clientred", false)
该调用遵循与客户端可见性更改相同的规则。Kanzi Android Service 将其归属于最后连接的客户端,并且具有另一个连接的客户端在提供其图面时会再次显示该内容。请参阅 处理可见性更改。
预加载客户端预设件¶
您可以在服务宿主应用程序中预加载客户端预设件,这样当客户端应用程序启动时,Kanzi 内容会立即可用,无需等待预设件加载。
要预加载客户端预设件,请在您的 Application 子类的 onCreate 方法中获取 KanziServiceRuntime,并调用 preloadClientPrefab:
private lateinit var runtimeRef: KanziServiceRuntime.Reference
override fun onCreate() {
super.onCreate()
runtimeRef = KanziServiceRuntime.acquire(this)
runtimeRef.get().preloadClientPrefab(
"clientred",
arrayOf("clientred.kzb"),
"kzb://clientred/Prefabs/ClientRedPrefab"
)
}
preloadClientPrefab 为给定的 clientId 创建 KanziViewAdapter,加载 kzb 文件,并实例化预设件。如果预设件 URL 为空,preloadClientPrefab 不加载任何预设件,客户端保留其注册的启动预设件。
请在第一个客户端应用程序连接之前,或在某个客户端应用程序拥有图面时调用 preloadClientPrefab。在加载预设件期间,预加载的内容会在没有窗口的情况下驱动 Kanzi 主循环。在以下情况下,Kanzi Android Service 宿主的调试版本会在加载预设件期间终止:
您在所有客户端应用程序都失去其图面之后调用
preloadClientPrefab。拥有图面的最后一个客户端应用程序在预设件加载完成之前失去了图面。
从 Application.onCreate 调用 preloadClientPrefab 可避免第一种情况。
无论是否预加载预设件,客户端应用程序的配置都相同。预加载仅影响客户端的启动时间。
备注
预加载的内容会一直保留在内存中,直到真正的客户端以相同的 clientId 连接并取得其所有权。如果从未有客户端以给定的 clientId 连接,其预加载的资源会在服务的整个生命周期内保持分配状态。当 Kanzi Android Service 因客户端应用程序终止而释放某个客户端 ID 的内容时,不会再次预加载该内容。请参阅 客户端应用程序终止。
在没有活动客户端视图时提交任务¶
服务主机应用程序仅在至少一个客户端视图处于活动状态时运行 Kanzi 主循环。客户端视图在拥有表面且可见时处于活动状态。预加载的客户端视图在其预设件加载期间处于活动状态。当没有客户端视图处于活动状态时,例如所有客户端应用程序都在后台时,Kanzi 不运行任何帧。
在没有客户端视图处于活动状态期间,服务主机应用程序提交给 TaskDispatcher 的任务会处于等待状态。Kanzi 会在客户端视图再次变为活动状态后的第一帧中执行所有这些任务。如果服务主机应用程序持续以高频率提交任务,例如每次数据源更新时都提交任务,这些任务就会堆积。它们会使客户端视图的第一帧延迟,延迟时长为执行所有这些任务所需的时间。
在没有客户端视图处于活动状态期间,不要为每次更新都提交任务。而应保留每个数据项的最新值,并且仅在没有等待中的应用这些值的任务时才提交任务。请参阅 在没有视图处于活动状态时提交任务。
客户端 API 的数据源 setter(例如 pushDataInt)不使用任务调度程序。它们在服务主机应用程序的主线程上设置数据对象的值,因此客户端应用程序推送的值不会堆积。
设置跨应用程序迁移动画¶
借助跨应用程序迁移动画,您可以在 Kanzi 工程中的不同 Android 应用程序之间切换时创建无缝的视觉迁移。
要设置跨应用程序迁移:
将源应用程序和目标应用程序中的 Kanzi 服务视图配置为:
使用相同的 Kanzi Android Service 实例和相同的
clientId。在两个客户端应用程序中都使用
KanziServiceSurfaceView或KanziServiceComposable。备注
KanziServiceTextureView不支持跨应用程序迁移,因为TextureView没有SurfaceControl。其startSeamlessTransition会抛出UnsupportedOperationException。在 activity 中为全屏视图。
满足 Kanzi Android framework (droidfw) 的透明度要求。请参阅 创建透明视图:
在 Kanzi 服务视图中,设置:
将
transparency设置为true将
clearEnabled设置为true将
clearColor设置为@android:color/transparent
编辑
application.cfg以要求具有 alpha 位的表面。请参阅 颜色格式。SurfaceBitsAlpha = 8
在 Kanzi Studio 中选择显示透明内容的节点,并在 Properties 中将 Background Brush 属性设置为 < No Brush >。
使用以下参数调用
startSeamlessTransitionAPI:clientId- 在 Android 应用程序之间移动的客户端 ID。transitionDuration- Kanzi 内容在叠加层中保留的持续时间。kanziTransition- 更新 Kanzi 状态以驱动视觉过渡的 lambda 函数。androidTransition- 启动 Android 端应用程序过渡的 lambda 函数。leftMargin- 可选的左边距(以像素为单位),用于在叠加层中定位 Kanzi 内容。默认值:0。topMargin- 可选的上边距(以像素为单位),用于在叠加层中定位 Kanzi 内容。默认值:0。rightMargin- 可选的右边距(以像素为单位),用于在叠加层中定位 Kanzi 内容。默认值:0。bottomMargin- 可选的下边距(以像素为单位),用于在叠加层中定位 Kanzi 内容。默认值:0。
// Start a seamless transition between applications. kanziServiceView?.startSeamlessTransition( clientId = "coverflow", transitionDuration = 2.seconds, kanziTransition = { // Update Kanzi state to drive the visual transition. kanziServiceView?.pushDataInt( "kzb://service/Data Sources/Data source", "State", 1 ) }, androidTransition = { // Launch the target application. val intent = Intent().apply { setClassName( "com.example.clientred", "com.example.clientred.MainActivity" ) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } startActivity(intent) } )
调用此 API 时,Kanzi Android Service 会创建一个叠加窗口,在应用程序迁移期间显示动画。如果目标应用程序不显示其 Kanzi 服务视图,该叠加窗口会在屏幕上保留最多 10 秒,然后迁移才会结束。
向服务宿主应用程序授予 显示在其他应用的上层 权限。服务宿主负责渲染叠加层,因此只有该应用程序需要此权限。
要授予此权限:
在设备上安装服务宿主应用程序。
打开设备设置。
导航至 应用 > <HostApplicationName> > 权限。
启用 Display over other applications 权限。
备注
在 Android Automotive 设备上,您无法通过 Android UI 为新安装的应用程序授予 Display over other applications 权限。
使用以下替代方法之一:
使用平台密钥对 host APK 签名,以自动授予此权限。
在 AOSP 级别为 host 应用程序授予此权限。
使用 adb 授予此权限:
adb shell appops set <host_package_name> SYSTEM_ALERT_WINDOW allow
例如,对于 Android 服务示例 示例中的 host 应用程序:
adb shell appops set com.rightware.kanzi.transition.host SYSTEM_ALERT_WINDOW allow
已知问题¶
目前,即使没有任何客户端使用动画功能,服务 host 应用程序也需要 Display over other applications 权限。
当客户端应用程序在跨应用程序迁移期间终止时,Kanzi Android Service 不会释放其表面或内容。
另请参阅¶
使用 Kanzi Android framework (droidfw) 进行开发时
使用 Kanzi application framework (appfw) 开发 Android 应用程序。