华为ArkUI-X跨平台技术探索
一、背景
随着业务的发展以及多个移动终端平台的出现,市场上逐渐形成了以Android、iOS、Harmony三个平台的移动终端,产品业务支持与开发同时需要至少三个端的开发,工作量3倍。跨平台技术的出现,可以基于一套核心代码部署到多个移动平台,节约开发成本1倍+人力,同时可以做更多的业务。
关于ArkUI-X跨平台实现的核心技术架构如下图所示。
二、ArkUI-X 开发环境搭建
开发环境的搭建,需要按照官方配置文档,这里不在细述,不在本文档的重点内容。
三、创建跨平台工程
创建跨平台工程有两种形式,基于ACE TOOLS工具命令行方式,也可使用开发工具Dev Studio创建。
3.1 创建工程
一、创建跨平台项目
直接在该项目内进行跨平台相关业务功能开发,不需要额外的工作。 语法:
ace create [project]
将在你的工作目录创建跨平台ArkUI-X项目"project"。
二、创建跨平台library
创建跨平台库支持,将该库集成到已有项目。
示例:以Andorid项目为例,可以将该library库以aar制品或module形式依赖集成至主工程。 语法:
ace create [project] -t library // 创建library项目
ace build aar // 构建aar
或者通过Dev studio开发工具创建:
总结:具体以哪种方式创建工程,取决于实际需要。
3.2 工程结构
案例:开发图片浏览功能,支持列表滑动预览等,该项目支持鸿蒙OS、Android、iOS 平台。
ace create ArkUiXImagePreview -t library
项目结构如下图 ArkUi-X 项目结构所示:
项目结构概要说明,ArkUiXImagePreview 目录下:
- .arkui-x隐藏文件夹,包含跨平台android、ios端端工程代码,可基于Android studio、Xcode 开发工具编译运行或者打包制品。
- entry及其他该文件或目录是HarmonyOS平台项目文件,可基于 Dev studio开发工具打开ArkUiXImagePreview工程。
四、图片浏览功能案例
我们先看下跨端的显示效果,由于小编未储备iOS,只熟悉鸿蒙&Android平台,只展示这两个端的效果。
项目地址: ArkUiXImagePreview (说明:该项目集成开源图片预览三方库 @xwf/image_preview(V1.0.1) 。)
一、鸿蒙OS效果
由于鸿蒙模拟器无法录屏,截取图片展示。
二、Android端效果
从以上两个效果,可以看到一套ArkUI代码,确实可以部署到多个平台,节约开发成本。
五、ArkUI-X跨端技术
要开发上述功能,涉及到ArkUI-X的相关技术,相关文档参考官方指导 How to 系列。这里介绍几个重要的点,其他的可以自行根据How to 系列学习。
5.1 通信桥brigde
负责ArkUI和Android、iOS平台侧的通信交互,例如函数调用,回调函数等。
案例:ArkUI业务调用Android 侧Log 或请求数据。
一、ArkUI侧实现
可以在UIAbility或者你的业务page里,定义通信桥。
private bridgeImpl = bridge.createBridge('NaviPageBridge'); // 参数标记唯一性对应java
logD(tag: string, msg: string): void { // 定义日志函数,基于callMethod能力
this.bridgeImpl.callMethod('logD', tag, msg);
}
requestData(index: number): Promise<string> { // 定义请求数据函数
return this.bridgeImpl.callMethod('requestData', index) as Promise<string>;
}
定义好上述桥接函数后,ArkUI的Page页面UI交互或业务逻辑,既可以调用该logD、requestData函数打印日志或者请求数据。注意:创建通信桥的参数“NaviPageBridge”一定要对应Android/iOS侧的名字。
二、Android侧实现
定义一个桥接通信类,并实现ArkUI-X的sdk桥接插件,示例代码如下:
public class NaviPageBridge extends BridgePlugin implements IMessageListener {
public NaviPageBridge(Context context, String bridgeName, BridgeManager bridgeManager) {
super(context, bridgeName, bridgeManager);
setMessageListener(this);
}
@Override
public Object onMessage(Object o) { return null; }
@Override
public void onMessageResponse(Object o) { }
public void logD(String tag, String msg) {
Log.d(tag, msg);
}
public String requestData(int index) {
return "I'm from Android OS: " + index;
}
}
并将该业务类注册到ArkUI-X的StageActivity容器:
public class ArkUIActivity extends StageActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 建立与ArkUI侧同名的平台桥接,即可用于消息传递
new NaviPageBridge(this, "NaviPageBridge", getBridgeManager());
setInstanceName("com.example.arkuiximagepreview:entry:EntryAbility:");
super.onCreate(savedInstanceState);
}
}
这里比较重要的setInstanceName参数设置,一定要对应ArkUI侧的 EntryAbility包名路径。同时注册的桥接名 “NaviPageBridge” 对应于ArkUI侧的bridge获取参数。
5.2 Activity启动ArkUI Ability
老生常谈的问题,安卓原生肯定会启动跨端界面,就如上述视频,安卓原生一级界面《跳转ARKUI-X》按钮启动传递参数给ArkUI界面,该参数可以是Class对象,也可以是List<String>等,本示例传递的是字符串列表List<String>。
一、参数格式
根据官方文档的参数传递规范,我们要定义一套格式:
看到这个格式(参数格式),感觉很不友善,根据和华为沟通,后续可能会做优化(待后面再看)。
二、定义Android参数ArkUIParam
@Keep
public class ArkUIParam {
List<Entity> params;
public List<Entity> getParams() {
return params;
}
public void setParams(List<Entity> params) {
this.params = params;
}
public static class Entity {
public String key;
public int type;
public String value;
}
}
public class ImageParam {
public List<String> imageUrlList;
}
三、启动Ability
Activity中点击按钮跳转,启动ArkUI界面,传递图片集合。
void startBusiness() {
Intent intent = new Intent(this, ArkUIActivity.class);
ArkUIParam arkUIParam = new ArkUIParam();
List<ArkUIParam.Entity> entityList = new ArrayList<>();
ImageParam imageParam = new ImageParam();
imageParam.setImageUrlList(ImageData.imageUrlList);
ArkUIParam.Entity entity = new ArkUIParam.Entity();
entity.setKey(INTENT_KEY_ARKUI_PARMA);
entity.setValue(GsonUtils.getObjectString(imageParam));
entity.setType(10);
entityList.add(entity);
arkUIParam.setParams(entityList);
intent.putExtra(INTENT_KEY_ARKUI_PARMA, GsonUtils.getObjectString(arkUIParam));
startActivity(intent);
}
传递参数的key: String INTENT_KEY_ARKUI_PARMA= "params"。
注意:intent设置的key必须固定不变,entity设置的key随意。
5.3 UIAbility启动接受参数
在启动时onCreate时机,根据want获取数据。 示例代码:
provideRouteImages(want: Want, launchParam: AbilityConstant.LaunchParam): Array<string> {
if (want?.parameters?.params) {
let params: ImageParms = JSON.parse(want.parameters.params as string);
LogUtil.d(ArkUiXNaviPageBridge.TAG, 'provideRouteImages: ' + JSON.stringify(params.imageUrlList))
return params.imageUrlList;
}
return []
}
这里注意,want.parameters 这个是固定参数,后面的 params设置的参数,将该json字符串数据解析为数据对象。
export class ImageParms {
imageUrlList: Array<string> = []
}
至此,完成整个参数传递链路。注意:ArkUI-X跨端开发目前只支持 Activity < -- > UIAblility页面级别业务能力,不支持纯数据业务的跨端能力。如果有纯数据业务逻辑跨端,还要考虑其他跨端能力。(ps: 期望官方未来能够完善支持)
5.4 Navigation路由Page跳转
当前版本,ArkUI-X还不支持route_map.json配置动态路由,根据沟通后续会优化支持。(待后续跟踪)官方给出的建议,使用navigation静态路由import page方式支持。示例:Index页面跳转ImagePreviewPage页面。主要点在Index内:
@Entry({ storage: localStorage })
@Component
struct Index {
@Provide('pathStack') pathStack: NavPathStack = new NavPathStack()
@Builder
PageMap(name: string) { // 静态路由
if (name === "ImagePreviewPage") {
ImagePreviewPage()
}
}
build() {
Navigation(this.pathStack) {
Column() {
// ...
// click to push imagePreview.
this.pushImagePreview({ images: this.viewModel.ImageUriList, index: index })
}
}.navDestination(this.PageMap) // 静态路由
}
pushImagePreview(option: ImagePreviewOption) {
this.pathStack.pushPath({ name: 'ImagePreviewPage', param: option })// 静态路由
}
}
核心点:定义静态路由builder函数古剑指定页面,Navigation设置导航目标bulder函数。
六、面临问题
ArkUI-X跨端固然一套代码,但是项目工程代码必须集成相关sdk、so等,要充分考虑sdk及so的包体积、崩溃、Anr、帧率性能监控等,需要保证整个生态的完整性。
6.1 so包体积过大
根据官方说法, 后续会优化icu库、arkui最小化包集成等策略,可以大幅减小so库等大小。但是,虽然会压缩,对于Android已有项目来说仍然包体积预估会有10M+或者20M+以上。对于直接集成双架构明显成本较高。对比打包release.apk查看大小,单架构so也有近20M,双架构更多。
好在ArkUI-X提供了一套动态化方案。
6.1.2 动态化方案
业务方可以根据需要,在App启动后,动态下载so库并动态加载,支持api:
appDelegate = new StageApplicationDelegate();
appDelegate.initApplication(this)
该函数所做的工作,初始化加载“/data/data/应用/files/arkui-x”下的库“/libs/arm64-v8a”以及将assets拷贝到该目录下等工作。如果没有初始化成功,可以下次再初始化。当然,目前版本还存在一些问题,sdk初始化标记不准确(发生error捕获场景),sdk未提供初始化完成状态、so加载和asset资源拷贝没有分开等。
示例做了一下简单兼容处理:
private boolean initSdk() {
try {
if (this.appDelegate == null) {
this.appDelegate = new StageApplicationDelegate();
}
this.appDelegate.initApplication(mApplication);
} catch (Throwable throwable) {
Log.e(LOG_TAG, Objects.requireNonNull(throwable.getMessage()));
// 反射强制修改StageApplicationDelegate类的静态变量 isInitialized 为false
try {
Class<?> clazz = Class.forName("ohos.stage.ability.adapter.StageApplicationDelegate");
java.lang.reflect.Field field = clazz.getDeclaredField("isInitialized");
field.setAccessible(true);
field.set(null, false);
} catch (Exception e) {
Log.e(LOG_TAG, "initSdk强制修改标记失败: " + e.getMessage());
}
return false;
}
return true;
}
关于依赖的OH其他so库,如libbridge.so,也可以采用下载机制,拷贝到目标沙盒目录。这些平台so的加载是在ark方舟编译器运行时加载。
6.1.3 so动态化版本
基于so动态化加载,那么,需要从后端下载相应版本的so, 所以需要根据对应版本的sdk、so维护后端配置,App业务根据App版本号去请求对应版本sdk的so进行动态化加载。
6.2 稳定性问题
稳定性主要包括 :崩溃、Anr发生及其监控,能够有效的抓取到现场堆栈,并能够通过堆栈发现问题。但是目前的版本,对Crash、Anr的日志堆栈搜集不够明确,基本都是native层的数据信息,无法直接定位到ArkTs代码堆栈,目前华为ArkUI-X还在支持优化补充相关工组。对于现有App来说,这里面是存在很大风险点,通过Bugly搜集点堆栈不能有效定位问题,就不能有效解决。
风险:监控无法提供有效堆栈定位问题,crash、anr 问题定义解决难度较大。
解决方案:有待华为ArkUI-X开发团队的持续优化。
6.3 性能问题
App的流畅性、帧率、首桢加载时间等。老版本4.x性能差,新版本5.x已做优化,性能提升较多。更多性能的相关方面,需要结合项目去检测分析。
七、总结
ArkUI-X跨平台方案,从技术角度来说整体是可行的,重点风险在于如下几点:
- 稳定性及其堆栈回溯问题
- 性能:内存、流畅性等方面需要验证及其官方的说明。
- 包体积较大(影响动态化加载速度)
ArkUI-X图片预览 示例项目地址。
《追梦旅途》
前路漫漫,探索无限,
星辰作伴,梦随心愿。
山高水远,步履坚定,
心怀希望,勇敢前行。