作者:苎麻
“ 如题目所述,咱们将继续更新《优酷鸿蒙开发实际》系列文章。本文为系列首篇技术文章,后续文章包含:鸿蒙 /Android 混合打包技术实际,多屏互动技术实际等,欢送继续关注【阿里巴巴挪动技术】。”
背景
随着华为 Harmony OS2.0 的公布,各大厂商纷纷领先与华为开展单干。优酷作为国内当先的长视频在线视听平台,与华为公司长期以来放弃严密的单干,独特为消费者带来优质的影音娱乐体验。因而,优酷技术团队也在第一工夫投入对鸿蒙零碎以及鸿蒙开发者 SDK 的钻研。优酷技术团队通过多轮的头脑风暴,利用鸿蒙的某些新个性开展鸿蒙利用开发的尝试。
鸿蒙 OS 反对利用以 Ability 为单位进行部署。Ability 分为两种类型:FA(Feature Ability)和 PA(Particle Ability)。FA/PA 是利用的根本组成单元,可能实现特定的业务性能。FA 有 UI 界面,而 PA 无 UI 界面。
每种类型为开发者提供了不同的模板,以便实现不同的业务性能。
鸿蒙 OS 的应用软件包以 APP Pack(Application Package)模式公布,它是由一个或多个 HAP(HarmonyOS Ability Package)以及形容每个 HAP 属性的 pack.info 组成。HAP 是 Ability 的部署包,HarmonyOS 利用代码围绕 Ability 组件开展。
鸿蒙工程通过鸿蒙打包工具链打包后,其产物格局即为 HAP。
以后,蕴含有鸿蒙 FA/PA 的优酷鸿蒙版曾经在华为鸿蒙利用市场上架,鸿蒙混合包在利用市场上会显示为“含 HarmonyOS 服务”。如果 App 是 100% Pure 鸿蒙 App,其 Icon 右下角会有 HMOS 字样。
在手机桌面上的优酷 Icon 微微上滑, 会弹出一个鸿蒙卡片,向用户举荐最近的热剧,点击卡片能疾速拉起半屏落地页显示更多信息,点击落地页则跳转到优酷客户端的相应落页面。
点击卡片上的图钉按钮,还能够将这个 FA 卡片固定在桌面上。
这个 FA 是 100% 利用鸿蒙 API 编写的,能够脱离优酷主客独立运行。因为 FA 卡片有极其严格的体积限度,而应用 native 的库体积则会过大。最终,咱们的 Widget 通过一个 Webview,加载 JS 版本的前端网络库去申请优酷外部的网络接口,获取到数据后再应用鸿蒙的 Native 图形图像 API 去绘制 Native 界面。
这个桌面 Widget 与 iOS 桌面 Widget 的区别在于,它不依赖于优酷主客即可运作。即便优酷主客不被启动,卡片的数据也可能更新。
鸿蒙卡片的开发模式
在鸿蒙零碎上,触摸优酷主客的利用图标向上滑动,能够唤起优酷的鸿蒙卡片。实现这一点须要卡片的实现代码与优酷主客做混合打包,一起提交到利用市场。
而如果要实现服务中心免装置应用,则须要卡片的独立包总大小要小于 10M。这一体积限度使得很多 Native 库都无奈引入,否则无奈将体积管制在红线之内。
最终,优酷鸿蒙卡片的代码放在一个工程中,不便跟优酷主客进行混合打包。同时,优酷鸿蒙卡片的代码仅依赖极少数的二、三方库(例如 JSON 解析、图片缓存等),以减小体积。
卡片款式
鸿蒙零碎提供 4 中大小不同的卡片,依据占用桌面图标数量的不同,别离是: 1×2、2×2、2×4、4×4。优酷卡片实现了其中两种: 2×2 和 2 ×4,其中 2 ×2 的卡片是必选项。
下图显示了两种不同款式的卡片,以及不同的呈现场景。
桌面 | 服务中心 | 发现 |
---|---|---|
申明卡片
跟 Android 的利用微件相似,鸿蒙的卡片也须要在一个配置文件中申明。在一个鸿蒙利用中,每个模块都有本人的配置文件,位于该模块的代码 main 目录下,名字为 config.json。
在配置文件中,每个模块有一个 abilities 属性,其值是一个数组,数组的每一个对象都定义了一个 Ability。卡片就定义在其中一个 Ability 中:
{
...
"formsEnabled": true,
"forms": [
{
"landscapeLayouts": [
"$layout:youku_widget_2_2",
"$layout:youku_widget_2_4"
],
"isDefault": true,
"defaultDimension": "2*2",
"name": "youku_widget",
"description": "$string:yk_widget_description",
"colorMode": "auto",
"type": "Java",
"supportDimensions": [
"2*2",
"2*4"
],
"portraitLayouts": [
"$layout:youku_widget_2_2",
"$layout:youku_widget_2_4"
],
"updateEnabled": true,
"updateDuration": 1
}
],
...
}
鸿蒙零碎中,卡片用 Form 来示意。上述申明中,formsEnabled 用于批示这个 Ability 是用于定义卡片的。forms 数组用来定义一系列的卡片。通常多个卡片能够定义在一个数组元素中。其中 landscapeLayouts、portraitLayouts、supportDimensions 用于定义卡片的布局文件和大小,updateEnabled、updateDuration 用于管制卡片的数据更新,updateDuration 的单位是半小时。
生命周期
在鸿蒙零碎上,卡片的生命周期比一般的 Page Ability 要简略很多,只有三个相干的回调:
/**
* 创立卡片时的回调。* 在 intent 中,存有创立卡片的一些重要参数,能够通过 Intent.getXXXParam()办法获取。* AbilitySlice.PARAM_FORM_IDENTITY_KEY: long 类型,用于惟一标识一个卡片
* AbilitySlice.PARAM_FORM_NAME_KEY: String 类型,卡片名称,即在 config.json 中定义的 name 属性
* AbilitySlice.PARAM_FORM_DIMENSION_KEY: int 类型,卡片大小标识,* 取值范畴是 1 -4,别离示意 1x2、2x2、2x4、4x4
*/
protected ProviderFormInfo onCreateForm(Intent intent)
/**
* 更新卡片时的回调。* 这里的 formId 就是 onCreateForm 中的 AbilitySlice.PARAM_FORM_IDENTITY_KEY 参数。*/
protected void onUpdateForm(long formId)
/**
* 删除卡片时的回调。* 这里的 formId 就是 onCreateForm 中的 AbilitySlice.PARAM_FORM_IDENTITY_KEY 参数。*/
protected void onDeleteForm(long formId)
传输卡片内容
卡片的创立和显示通常由桌面(或者服务中心、搜寻)发动,而决定显示内容的是优酷卡片这个模块,内容提供方和显示方不在同一个过程,甚至由不同开发者开发。在 Android 上也是一样的状况。
在这种状况下,个别都是内容提供方通过近程 View 的形式将内容渲染到内容显示方的,鸿蒙零碎上这个跨过程的数据传输行为是由 ComponentProvider 来实现的。
创立 ComponentProvider 有两种形式:
// 第一种: 在 onCreateForm()时,先创立一个卡片对应的 ProviderFormInfo 实例。// 再通过 ProviderFormInfo 的实例拿到向它传输数据的 ComponentProvider。ProviderFormInfo form = new ProviderFormInfo(layoutId, context);
ComponentProvider cp = form.getComponentProvider();
// 第二种: 在 onUpdateForm()时,间接创立出一个 ComponentProvider。ComponentProvider cp = new ComponentProvider(layoutId, context);
须要留神的问题 1
其中设置 IntentAgent 时须要留神,通常一个布局中会有多个 View 来笼罩根布局的矩形区域。如果设置了 IntentAgent 的 View 没有笼罩满根布局,则未笼罩区域被点击时,零碎也会响应点击,默认调起这个卡片所属的 Ability,传入的 Intent 只蕴含 formId。
针对这个默认调起的 Ability,个别有两种形式解决:一是确保设置了 IntentAgent 的 View 笼罩满根布局;二是 Ability 提供兜底计划,例如页面做成通明,并且主动退出。
须要留神的问题 2
在创立 IntentAgent 时,须要提供一个 IntentAgentInfo 实例。这个 IntentAgentInfo 创立时的第一个参数是一个 int 类型的申请代码,这个代码必须放弃各个卡片的不同点击区都不一样。否则后设置的 IntentAgent 会笼罩先前设置的同一申请代码的 IntentAgent。
须要留神的问题 3
如果是跟优酷主客混合打包,卡片的布局文件中,View 的 id 必须跟主客中所有的 id 不同,否则零碎会无奈正确更新布局文件中对应的 View。
关上直达页
因为零碎的限度,点击卡片关上的页面必须是纯鸿蒙利用中的页面,无奈间接关上 Android 利用页面。优酷卡片的点击,目标是关上优酷主客的播放页。
在这里咱们做了分类:
- 当用户未装置优酷主客时,显示一个直达页,提供下载按钮供用户跳转到华为利用市场去下载优酷主客,当用户装置完优酷主客回来时,下载按钮变成全集列表,对单集视频则变成播放按钮;
- 当用户已装置优酷主客时,直达页主动关上优酷主客的播放页,并退出。
数据申请
在优酷主客中,网络数据的申请都是通过对立的网络库拜访的。因为优酷鸿蒙卡片并未集成网络库,优酷鸿蒙卡片必须应用其余形式申请网络接口。
要实现在鸿蒙上发动数据申请,有两个计划:
- 一是针对每个数据申请接口,封装一个新的 HTTP Open API 接口,客户端能够通过 HTTP(S)间接拜访;
- 二是客户端通过 H5 页面里的 JS 版 Network 库发动数据申请。
思考到未来在鸿蒙零碎上有可能实现更多其余的需要,且第一种计划有安全性的危险,所以最终咱们采纳了第二种计划。
前端业务应用的 JS 版本的网络库,其应用形式是通过 JS 中的 Promise 机制来实现异步回调,然而这种形式在 Java 中并不好实现对应的调用构造。所以这里须要有一层封装,将网络申请后果通过简略回调来告诉申请方。相应的在 Java 侧须要对 WebView 注册全局的 JS 对象,实现 JS 对象的回调办法,买通 JS -> Java 的调用通路。
这个计划在纸面上看着还不错,然而在理论应用中会发现有重大的性能瓶颈。WebView 自身是一个很重的控件,在过程中首次创立的时候会比拟耗时,有很多的 so 加载、初始化等工作。加载 HTML 是一个网络申请,耗时在百毫秒级,而加载并解析完 HTML 当前,还要再加载 JS 版本的网络库,又是一次网络拜访。等 JS 网络库加载并解释执行后,才能够失常服务调用方。
要在这个过程中进行优化,这里有主动权的中央就是加载 HTML 和 JS 网络库这两个文件。在鸿蒙零碎中,WebView 能够通过设置 WebAgent 来实现特定 URL 的劫持,将其转化为读取本地资源中的 HTML 和 JS 文件。
public class LoadAgent extends WebAgent {
// ...
@Override
public ResourceResponse processResourceRequest(WebView webView, ResourceRequest request) {
// mInterceptor 用于辨认 HTML 和 JS 网络库的 URL,并返回本地资源中的 HTML 和 JS。ResourceResponse response = mInterceptor.intercept(request);
if (response != null) {return response;}
return super.processResourceRequest(webView, request);
}
}
这能够将两个百毫秒级的串行操作缩减为毫秒级,大大减少 JS 版本的网络库的初始化工夫。
数据缓存
从网络申请返回的卡片数据,除了用于即时渲染卡片之外,还会被保留一份到本地存储中。如果下一次发动网络申请的时候,无奈失常拜访网络(例如手机重启后一时连不上网络),则能够应用缓存中的卡片数据先渲染一下,使用户不至于齐全看不到内容。这就须要有一套卡片数据的缓存治理能力。
针对卡片数据的特点,咱们应用了两个数据库表来存储卡片的缓存数据。依据卡片大小不同,申请中会提供不同的参数给服务端。反过来说,同样大小的卡片发出请求的参数是雷同的,也就是说同样大小的卡片申请失去的数据是雷同的。所以有一个表用来存储不同大小的卡片数据,每个卡片大小对应一条记录,包含惟一标识、卡片大小、申请返回的数据、工夫戳等。
零碎不限度用户向桌面增加卡片的数量,同时在服务中心也能够有曾经增加到桌面的卡片。所以同样的卡片数据是能够被显示在多个卡片上的。数据库须要有一个表来记录每一个卡片的信息,包含卡片的惟一标识、卡片大小、数据表中对应的记录等。
如果在 Android 中实现过 ContentProvider,个别都会比拟相熟 SQLite 数据库。通常 ContentProvider 须要治理大量、不同类型且相互有关联的数据,这种需要用 SQLite 来实现最合适了。这里治理卡片数据的缓存也具备同样的特色,并且鸿蒙零碎也提供了 SQLite 数据库的应用接口。典型的数据库初始化操作如下:
// StoreConfig 最常见的作用是配置数据库名字。也能够配置存储模式、加密等高级需要。StoreConfig config = StoreConfig.newDefaultConfig(DB_NAME_FORM_STORE);
// RdbOpenCallback 用于定义创立数据库、降级数据库构造版本等机会的回调。RdbOpenCallback callback = new FormStoreOpenCallback(context);
DatabaseHelper helper = new DatabaseHelper(context);
// RdbStore 是数据库的封装类,最终的增删改查操作都通过它来进行。RdbStore store = helper.getRdbStore(config, CURRENT_VERSION, callback);
具体的增删改查操作就不一一列举了。
数据更新
后面申明卡片一节提到了 config.json 中,updateEnabled、updateDuration 定义了卡片的数据更新机制。
其中 updateEnabled 用于指定是否通过零碎来自动更新卡片数据,如果心愿由利用本身触发数据更新,这个能够设置为 false。优酷卡片的场景是心愿零碎可能自动更新卡片数据的,所以设为了 true。
在 updateEnabled 设为 true 的状况下,updateDuration 才有意义。updateDuration 用于指定更新的工夫距离。鸿蒙零碎还反对固定工夫更新,通过指定 scheduledUpateTime 来设置更新工夫。updateDuration 和 scheduledUpateTime 只能抉择其中一种形式。
优酷卡片抉择了用 updateDuration 指定更新距离。为了防止未来应用卡片的用户多了,对服务端产生过大的压力,更新距离被管制在 4 小时,这样用户在上午、下午、早晨等不同时段去看卡片时,内容都会有更新。
然而有些状况下,优酷卡片本身的逻辑也会更新卡片数据,所以为了防止两种更新策略抵触而导致更密集的更新,或者长时间不更新,updateDuration 被指定为 1,即每半小时零碎就会调用一次 onUpdateForm()。在 onUpdateForm()中,会判断上一次理论产生更新的工夫,使更新距离放弃在 4 小时左右。
容错解决
思考到一些极其状况,例如用户装置优酷后,在没有网络的状况下就增加了桌面卡片。此时卡片的数据申请是没有返回的,同时因为刚装置,也没有缓存数据,所以卡片展现不出任何数据,只有灰色的打底图作为背景。此时如果点击卡片,也没有任何视频信息,也就无奈跳转到某个特定视频的播放页,只能显示一个加载失败的提醒,等用户网络复原后,通过刷新失去无效数据。
空白卡片 | 点击卡片后的空白页面 |
---|---|
瞻望
当初优酷鸿蒙版的桌面卡片曾经随着鸿蒙零碎的公布,正式上线了。在鸿蒙零碎的手机上,从华为利用市场装置的优酷主客就曾经附带了优酷卡片的能力。
因为这是一个全新的开发技术栈,晚期公布的利用必定会有一些改良空间。从当初看来次要有以下一些方面:
- 性能 \
因为数据申请和埋点用到了 JS 库,并且在 WebView 中运行,这使得运行时效率比 Java 要低,还要解决 WebView 与外界的交互,对性能有较大影响。尽管曾经有了一些措施来缩小这方面的影响,然而后续还是须要持续挖掘潜力 - 监控 \
后续还须要补足 JS 侧解体等信息收集的能力。 - 线上配置能力 \
优酷主客能够通过各种近程配置平台下发各种配置信息。而鸿蒙上因为体积限度无奈自带相干的库。今后须要思考应用其余形式实现近程配置能力。
最初,10 月 20 日将上线《优酷鸿蒙开发实际》系列技术文章第二篇,为大家介绍如何实现 Android/ 鸿蒙混合打包的流程。感激关注,咱们下篇技术实际再见。
关注咱们,每周 3 篇挪动技术实际 & 干货给你思考!