快应诞生背景
微信的小程序使得很多原来需要调动APP的场景不复存在,正式由于微信小程序的冲击,3月20日,华为联手九大手机厂商,共同举办了“快应用”标准启动发布会。“快应用”是几家手机厂商基于硬件平台共同推出的新型应用生态,用户不必下载安装,即点即用,能够享受到原生应用的性能体验。“快应用”使用前端技术栈开发与原生渲染,兼具H5页面和原生应用的双重优点。
快应用使用场景
进入小米应用商店,搜索“饿了么”:
点击“秒开”就可以使用快应用了
在华为应用市场搜索“快应用”:
点击查看更多:
可以看到一些快应用app已经上线了,使用起来体验也不错,体积也相当小
微信小程序VS快应用
微信小程序推出后,尽管前期受到了不少质疑,但却一直发展非常稳健。腾讯3月21日刚刚公布的2017年全年财报中披露,自2017年1月推出小程序以来,截至2018年1月已推出58万个小程序,日活跃账户超过1.7亿个。张小龙也曾经表示,未来两年内,小程序将取代80%的应用市场。如果该目标达成,这意味着微信小程序建立起一个强大的超级生态,极大挤压了国产手机厂商应用分发和数字广告业务的成长空间。因此,国产手机厂商的“快应用”主要针对微信小程序,与后者争抢用户和流量。
那么快应用要和微信小程序去竞争,它们各自又有什么优缺点,谁又会更占优势呢?我个人总结之后列出了如下几点:
微信小程序
优点:
- 微信小程序已经在市场上取得了一定的规模效益,抢占了市场先机
- 微信自带用户流量,用户黏度较高,通过微信打开小程序非常方便
- 微信小程序支持iOS和安卓两大操作系统,覆盖了所有的用户群体,不同操作系统用户之间数据共享方便,比如iOS用户可以通过一个微信链接响安卓用户发送一个微信小程序链接
缺点:
- 微信小程序基于系统上层进行的封装,在性能上和原生app差距较大
快应用
优点:
- 基于系统层开发,将脚本转化为原生组件,运行效率更接近原声app,用户体验会更加好
缺点:
- 只支持安卓系统,在用户群体上受到限制,也无法在安卓和iOS系统之间做到数据共享
- 几大厂商合作,可能会产生分歧,如何去协调各大厂商,以及后期的利益分配等是一个大问题
- 缺乏用户粘性和使用场景,如果每次打开其他应用都要去应用商店搜索快应用app的话显然不如微信小程序方便
总结:
从快应用的诞生,我们能够看到国内手机硬件厂商开始反思自己在安卓生态中的地位,寻求转型和突破以争取更大的话语权和利益。这个方向显然是对的,但是对比微信小程序,较好的性能几乎成了快应用的唯一优势,但是随着现在手机性能不断增强以及微信小程序的不断优化,这个问题将变得忽略不计,况且现在很多微信小程序用户流畅度意境做的相当不错了,而在用户群体、使用方便性等几大方面,微信小程序占据着绝对优势,而且快应用由于自生的缺陷无法弥补这几方面的劣势,所以说快应用几乎无法撼动微信小程序的地位,更不用说打败小程序,不过如果快应用能够在应用分发市场上对微信形成一定威胁并且从中分得一杯羹的话,那也能够证明快应用取得了成功。
环境配置
6.0版本以上NodeJS,官方推荐 v6.11.3 LTS
安装hap-toolkit:
npm install -g hap-toolkit(帮助开发者通过命令行工具来完成工程的创建等工作),在命令行中执行hap -V
会输出版本信息表示hap-toolkit
安装成功,如下命令所示:
hap -V
创建项目
建好环境后,开发者就可以利用全局hap
命令创建一个项目模板,如下所示,其中<ProjectName>
为自定义的项目名称
hap init <ProjectName>
命令执行后,会在当前目录下创建<ProjectName>
文件夹,并作为项目根目录
这个项目已经包含了项目配置与简单页面的初始代码,项目根目录结构如下:
├── node_modules
├── sign rpk包签名模块
│ └── debug 调试环境
│ ├── certificate.pem 证书文件
│ └── private.pem 私钥文件
├── src
│ ├── Common 公用的资源文件和组件文件
│ │ └── logo.png manifest.json中配置的icon
│ ├── Demo 页面目录
│ | └── index.ux 页面文件,文件名不必与父文件夹相同
│ ├── app.ux APP文件(用于包括公用资源)
│ └── manifest.json 项目配置文件(如:应用描述、接口申明、页面路由等)
└── package.json 定义项目需要的各种模块及配置信息,npm install根据这个配置文件,自动下载所需的运行和开发环境
目录的简要说明如下:
- src:项目源文件夹
- node_modules:项目的依赖类库
- sign:签名模块,当前仅有
debug
签名,如果内测上线,请添加release
文件夹,增加线上签名;签名生成方法请参考文档:编译工具
的openssl
编译项目
安装npm依赖
在项目根目录下,运行如下命令安装依赖包(webpack,babel等)
npm install
编译项目
在项目的根目录下,运行如下命令进行编译打包,生成rpk包
npm run build
编译打包成功后,项目根目录下会生成文件夹:build、dist
- build:临时产出,包含编译后的页面js,图片等
- dist:最终产出,包含rpk文件。其实是将build目录下的资源打包压缩为一个文件,后缀名为
rpk
,这个rpk
文件就是项目编译后的最终产出
自动重新编译
如果希望每次修改源代码文件后,都自动重新编译项目,请使用如下命令:
npm run watch
手机安装调试器
调试器APK是一个Android应用程序,请从站点地址下载
说明如下:
- 扫码安装:配置HTTP服务器地址,下载rpk包,并唤起平台运行rpk包
- 本地安装:选择手机文件系统中的rpk包,并唤起平台运行rpk包
- 在线更新:重新发送HTTP请求,更新rpk包,并唤起平台运行rpk包
- 开始调试:唤起平台运行rpk包,并启动远程调试工具
注意:若无法正常使用调试器,请升级手机系统到最新版本或安装平台预览版
手机安装平台预览版
较新的系统版本中内置平台正式版,即真实的运行环境。然而,更新平台正式版的时间周期较长,开发调试平台新功能可使用平台预览版
平台预览版存在以下优缺点:
- 优点:迭代速度快,可立即体验平台新功能
- 缺点:实现与真实的运行环境存在差异,对厂商服务和第三方服务的支持存在缺陷
平台预览版APK是一个Android应用程序,请从站点地址下载
下载安装成功后,在调试器中点击切换运行平台至...mockup
即可在平台预览版上运行rpk包
在平台上运行rpk包
在调试器中唤起平台打开rpk包有多种途径,以下两者选其一即可,推荐第一种途径:
- HTTP请求:开发者启动HTTP服务器,打开调试器,点击
扫码安装
配置HTTP服务器地址,下载rpk包,并唤起平台运行rpk包 - 本地安装:开发者将rpk包拷贝到手机文件系统,打开调试器,点击
本地安装
选择rpk包,并唤起平台运行rpk包
1. HTTP请求
启动HTTP服务器
在终端中新建一个窗口,进入项目的根目录运行如下命令,启动本地服务器(默认端口为12306)
npm run server
自定义端口(如:8080)
npm run server -- --port 8080
在手机上预览运行效果
配置HTTP服务器地址有两种方式,以下两者选其一即可:
-
打开调试器 --> 点击"扫码安装"
,扫描终端窗口中的二维码即可完成配置(若扫描不成功,可在浏览器中打开页面:http://localhost:<your port>
,扫描页面中的二维码) -
打开调试器 --> 点击右上角menu --> 设置
,输入终端窗口中提示的HTTP服务器地址
配置完成后,若没有自动唤起平台运行rpk包,点击在线更新
唤起平台运行rpk包
若提示安装失败
,请检查执行npm run server的终端窗口是否正常运行
2. 本地安装
复制rpk包到手机中
将<ProjectName>/dist
目录下编译产出的rpk
包通过USB数据线或其他方式,复制到手机文件系统中
本地安装rpk包
打开调试器 --> 点击"本地安装"
,选择手机文件系统中的rpk包,并自动唤起平台运行rpk包,查看效果
配置应用基本信息
每个应用都要有专属的名称,图标等,这些信息都需要在manifest.json
文件中配置;详细信息请参考文档:manifest文件
应用包名(package)
应用包名,是区别于其他应用的唯一标识
推荐采用com.company.module的格式,示例如下:
{
"package": "com.example.demo"
}
应用名称(name)
应用名称,6个汉字以内,与应用商店保存的名称一致;框架提供保存到桌面的功能,桌面上显示的应用名即为此属性
示例如下:
{
"name": "发票小助手"
}
应用图标(icon)
规则为正方形(不能是圆角),且务必无白边
{
"icon": "/Common/logo.png"
}
注意:
请使用绝对路径,其中/
对应于路径<ProjectName>/src/
应用版本名称、版本号(versionName、versionCode)
应用版本名称、版本号为开发者的应用包维护的版本信息
应用版本名称为主版本.次版本
格式
应用版本号为整数,从1
开始,每次更新上架请自增1
示例如下:
{
"versionName": "1.0",
"versionCode": 1
}
支持的最小平台版本号(minPlatformVersion)
支持的最小平台版本号为必填项,默认值为1000,标识开发者的rpk包兼容支持的最小运行平台版本
当使用了1000以上的平台版本新增特性时,就必须确保minPlatformVersion
最低为该平台版本号,避免上线后在更低版本平台上运行出错
示例如下:
{
"minPlatformVersion": "1000"
}
配置接口列表(features)
在使用接口时,需要先在manifest中声明接口。在每个接口文档的顶部,都附有声明接口的配置代码
以fetch网络请求为例,示例如下:
{
"features": [
{ "name": "system.fetch" }
]
}
配置页面路由(router)
路由,用于定义页面的实际地址、跳转地址。如果ux页面没有配置路由,则不参与项目编译。一个目录下最多只能存在一个主页面文件(不包括组件文件)
首页名称(router.entry)
首页,即应用平台启动时默认打开的页面。首页需配置为应用中某页面的名称,即在<ProjectName>/src
目录下,页面目录的相对路径
示例如下:
假设工程根目录如下所示
└── src
└── Demo 页面目录,存放各自页面私有的资源文件和组件文件
└── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)
假设首页为Demo目录下的index.ux文件,则首页对应的页面名称为Demo
{
"router": {
"entry": "Demo"
}
}
页面路由对象(router.pages)
页面路由对象,key为页面名称(<ProjectName>/src
目录下,页面目录的相对路径),value为页面具体路由配置,key不要重复
页面具体路由配置(router.pages的value)包括以下属性:
- component:页面对应的ux文件名
- path:页面路径,不填则默认为页面名称(
<ProjectName>/src
目录下,页面目录的相对路径)
示例如下:
假设工程根目录如下所示
└── src
|── Demo 页面目录,存放各自页面私有的资源文件和组件文件
| └── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)
└── Doc
└── Layout 页面目录,存放各自页面私有的资源文件和组件文件
└── index.ux 页面文件,文件名不必与父文件夹相同(推荐index.ux)
当页面名称(router.pages的key)为Demo
时,对应的页面配置(router.pages的value)包括:
- component:页面对应的ux文件名
index
- path:页面路径,默认为页面名称
Demo
{
"router": {
"pages": {
"Demo": {
"component": "index"
},
"Doc/Layout": {
"component": "index"
}
}
}
}
现在,开发者就可以通过/Demo
访问到Demo目录下的index.ux页面了
配置页面UI显示(display)
UI显示,用于定义与UI显示相关的配置。支持定义:页面公用的默认UI显示、页面私有的UI显示
页面公用的默认UI显示
页面公用的默认UI显示,即被所有页面共享
以标题栏文字的配置为例:
{
"display": {
"titleBarText": "页面公用的默认标题"
}
}
未配置私有标题的页面,标题栏文字均将显示为页面公用的默认标题
页面私有的UI显示
页面私有的UI显示,在display.pages
对象下配置:key为页面名称(与路由中的页面名称保持一致),value为页面私有的UI显示
以标题栏文字的配置为例:
{
"display": {
"pages": {
"Demo": {
"titleBarText": "Demo页面的标题"
}
}
}
}
manifest文件
manifest.json文件中包含了应用描述、接口声明、页面路由信息
manifest
属性
|
类型
|
默认值
|
必填
|
描述
|
---|---|---|---|---|
package | String | - | 是 | 应用包名,确认与原生应用的包名不一致,推荐采用com.company.module的格式,如:com.example.demo |
name | String | - | 是 | 应用名称,6个汉字以内,与应用商店保存的名称一致,用于在桌面图标、弹窗等处显示应用名称 |
icon | String | - | 是 | 应用图标,提供192x192大小的即可 |
versionName | String | - | 否 | 应用版本名称,如:"1.0"
|
versionCode | Integer | - | 是 | 应用版本号,从1 自增,推荐每次重新上传包时versionCode +1 |
minPlatformVersion | Integer | 1000 | 是 | 支持的最小平台版本号,原理同Android API Level,兼容性检查,避免上线后在低版本平台运行并导致不兼容 |
features | Array | - | 否 | 接口列表,绝大部分接口都需要在这里声明,否则不能调用,详见每个接口的文档说明 |
config | Object | - | 是 | 系统配置信息,详见下面说明 |
router | Object | - | 是 | 路由信息,详见下面说明 |
display | Object | - | 否 | UI显示相关配置,详见下面说明 |
config
用于定义系统配置和全局数据。
属性
|
类型
|
默认值
|
描述
|
---|---|---|---|
logLevel | String | log | 打印日志等级,分为off,error,warn,info,log,debug |
designWidth | Integer | 750 | 页面设计基准宽度,根据实际设备宽度来缩放元素大小 |
data | Object | - | 全局数据对象,属性名不能以$或_开头,在页面中可通过this进行访问;如果全局数据属性与页面中data属性重名,则页面初始化时,全局数据会覆盖页面中对应的属性值 |
router
用于定义页面的组成和相关配置信息,如果页面没有配置路由信息,则在编译打包时跳过。
属性
|
类型
|
默认值
|
描述
|
---|---|---|---|
entry | String | - | 首页名称 |
pages | Object | - | 页面配置列表,key值为页面名称(对应页面目录名,例如Hello对应'Hello'目录),value为页面详细配置page,详见下面说明 |
router.page
用于定义单个页面路由信息。
属性
|
类型
|
默认值
|
必填
|
描述
|
---|---|---|---|---|
component | String | - | 是 | 页面对应的组件名,与ux文件名保持一致,例如'hello' 对应 'hello.ux' |
path | String | /<页面名称> | 否 | 页面路径,例如“/user”,不填则默认为/<页面名称>。 path必须唯一,不能和其他page的path相同。 下面page的path因为缺失,会被设置为“/Index”: "Index": {"component": "index"}
|
filter | Object | - | 否 | 声明页面可以处理某种请求 |
router.page.filter
声明页面可以处理某种请求,页面可以从$page获取打开页面的参数,参见script脚本。filter的结构如下:
"filter": {
"<action>": {
"uri": "<pattern>"
}
}
属性
|
类型
|
默认值
|
必填
|
描述
|
---|---|---|---|---|
action | String | - | 是 | 请求的动作,目前仅支持view这一种 |
uri | Pattern | - | 是 | 请求的数据的匹配规则。必须是正则表达式。如https?://.* 可以匹配所有http和https类型的网址 |
可以处理所有http和https请求的filter定义如下:
"filter": {
"view": {
"uri": "https?://.*"
}
}
display
用于定义与UI显示相关的配置。
属性
|
类型
|
默认值
|
描述
|
---|---|---|---|
backgroundColor | String | #ffffff | 窗口背景颜色 |
fullScreen | Boolean | false | 是否是全屏模式,默认不会同时作用于titleBar,titleBar需要继续通过titleBar控制 |
titleBar | Boolean | true | 是否显示titleBar |
titleBarBackgroundColor | String | - | 标题栏背景色 |
titleBarTextColor | String | - | 标题栏文字颜色 |
titleBarText | String | - | 标题栏文字(也可通过页面跳转传递参数(titleBarText)设置) |
menu | Boolean | false | 是否显示标题栏右上角菜单按钮 |
pages | Object | - | 各个页面的显示样式,key为页面名(与路由中的页面名保持一致),value为窗口显示样式,页面样式覆盖default样式。 |
示例:
{
"package": "com.company.unit",
"name": "appName",
"icon": "/Common/icon.png",
"versionName": "1.0",
"versionCode": 1,
"minPlatformVersion": 1000,
"features": [
{ "name": "system.network" }
],
"permissions": [
{ "origin": "*" }
],
"config": {
"logLevel": "off"
},
"router": {
"entry": "Hello",
"pages": {
"Hello": {
"component": "hello",
"path": "/",
"filter": {
"view": {
"uri": "https?://.*"
}
}
}
}
},
"display": {
"backgroundColor": "#ffffff",
"fullScreen": false,
"titleBar": true,
"titleBarBackgroundColor": "#000000",
"titleBarTextColor": "#fffff",
"pages": {
"Hello": {
"backgroundColor": "#eeeeee",
"fullScreen": true,
"titleBarBackgroundColor": "#0000ff",
"titleBarText": "Hello"
}
}
}
}
源码文件
APP,页面和自定义组件均通过ux文件编写,ux文件由template模板、style样式和script脚本3个部分组成
app.ux
当前app.ux
编译后会包含manifest配置信息
(可以在npm run build
之后查看文件内容),所以请不要删除/**manifest**/
的注释内容标识。
您可以在<script>
中引入一些公共的脚本,并暴露在当前app的对象上,如下所示,然后就可以在页面ux文件的ViewModel中,通过this.$app.util
访问
<script> import util from './util.js'
module.exports = { /**manifest**/, util: util }
</script>
页面路由
导入模块 import router from '@system.router' 或 var router = require("@system.router")
接口定义
router.push(OBJECT)
跳转到应用内的某个页面
参数:
参数
|
类型
|
必填
|
说明
|
---|---|---|---|
uri | String | 是 | 要跳转到的uri,可以是下面的格式:
支持包含schema的完整uri。对于带有schema的uri,处理流程如下:
默认策略的处理逻辑:
|
params | Object | 否 | 跳转时需要传递的数据,参数可以在页面中通过this.param1 的方式使用,param1为json中的参数名,param1对应的值会统一转换为String类型 |
示例:
// launch phone app
router.push({
uri: 'tel:10086'
});
// open page by path
router.push({
uri: '/about',
params: {testId:'1'}
});
// open page by name
router.push({
uri: 'About',
params: {testId:'1'}
});
// open web page
router.push({
uri: 'http://www.example.com'
});
// install apk
router.push({
uri: 'internal://cache/example.apk'
});
router.replace(OBJECT)
跳转到应用内的某个页面,当前页面无法返回
参数:
参数
|
类型
|
必填
|
说明
|
---|---|---|---|
uri | String | 是 | 要跳转到的uri,可以是下面的格式:
|
params | Object | 否 | 跳转时需要传递的数据,参数可以在页面中通过this.param1 的方式使用,param1为json中的参数名,param1对应的值会统一转换为String类型 |
示例:
router.replace({
uri: '/test'
params: {testId:'1'}
})
router.back()
返回上一页面
参数:
无
示例:
// A页面
router.push({
uri: 'B'
})
// B页面
router.push({
uri: 'C'
})
// C页面通过back,将返回B页面
router.back();
// B页面通过back,将返回A页面
router.back();
router.clear()
清空所有历史页面记录,仅保留当前页面
参数:
无
示例:
router.clear()
router.getLength()
获取当前页面栈的页面数量
返回值:
类型
|
说明
|
---|---|
Number | 页面数量 |
示例:
var length= router.getLength()
console.log("pages' length = "length);
router.getState()
获取当前页面状态
返回参数:
参数名
|
类型
|
说明
|
---|---|---|
index | Number | 当前页面在页面栈中的位置 |
name | String | 当前页面的名称 |
path | String | 当前页面的路径 |
示例:
var page = router.getState()
console.log("page index = "+page.index);
console.log("page name = "+page.name);
console.log("page path = "+page.path);
快应用技术架构
快应用通过脚本来编写组件,安卓内部嵌入一个脚本解析引擎,将脚本转化为原生控件,通过编译生成rpk文件,应用调试器联系起脚本及安卓系统进行调试工作,具体流程图如下:
参考资料
- 开应用开发文档:https://doc.quickapp.cn