1. 方案选择
获取升级信息,有两种大方向可供选择:Push(服务端向服务器端推送更新)和Query(客户端主动查询)两种方案。Query实现比较简单,灵活;push需要长连接支持,以及全部设备id等信息,实现相对复杂。基于目前的需求和实际情况,决定采用客户端查询更新方式。
2. 整体框架
图1. 升级示意图
图2. 升级时序图
升级系统的示意图及时序图如上。主要流程上,大部分工作是集中在客户端的。
3. 客户端实现
3.1 客户端查询更新
客户端需要在一个合适的时机查询是否有新的升级包可用。基于目前的使用case,评估师基本会在眼镜使用完成之后,即关闭眼镜;需要再次使用的时候,再重新开机。眼睛上的app会随系统启动自启动。所以可以考虑在系统启动时(注册监听系统启动广播),或者是app启动时,向服务器查询是否有新的升级包。
另外,考虑到有可能眼镜连续长时间处于开机状态,在这种case下,可能没有机会触发查询更新包操作。所以可以考虑在后台service中运行一个定时任务,每隔固定间隔(比如24h)检查是否需要向服务端查询更新包信息。同时,鉴于眼镜在查询间隔周期内(比如24h)可能被开关多次,触发多次向服务器请求查询更新的操作。为避免过于冗余的请求,可在客户端进行标记,记录上次查询更新的时间,app启动时,首先检测在本次查询周期内(比如,与上次查询是否间隔超过24h),是否已经向服务器进行过查询。若在本周起内已经进行过查询,则本次启动不再请求服务器进行升级查询。
3.2 客户端下载更新包
下载可基于http实现,目前也比较成熟,相对基于tcp实现,开发效率会更高;可选支持断点续传(初期考虑app体积较小,简单期起见,也可以暂不支持)。
3.3 客户端安装升级包——静默安装
与普通手机上的app升级和安装不同,眼镜没有提供直接的UI交互,所以无法执行标准的app安装流程(期间需要任务干预,授权安装)。因为,目前眼镜上的app升级,需要采取静默升级方式。这种方式的实现需要满足一定条件:1)需要有root权限;2)系统级应用或者使用系统签名的应用。目前我们的眼镜和其上运行的app是满足这个条件的。满足以上条件的前提下,在代码中通过调用“pm install - xxx”命令来实现静默安装。
静默安装过程中,当前应用会退出,所以需要有自启动机制,确保升级完成之后,应用能够再次启动。这要借助于广播,监听PACKAGE_REPLACED广播。监听到该广播之后,执行启动app的操作。
以上功能需要在一个独立的service中,否则无法触发自启动。
3.3.1 静默升级实测结果
1)google加强了root权限的控制,只允许root和shell用户执行“su”命令,所以在app没有获取到系统签名,真正升级为系统应用的情况下,试验了多种方案,也不能在代码中成功升级root权限。关键代码:
process = Runtime.getRuntime().exec("su");
执行不成功,接下来尝试执行后续命令,会报:EPIPE: bad pipe错误。根本原因是当前进程没有权限执行su命令,没有能够成功获取root权限。
2)这样看,只能想办法获取看见系统的签名文件,对我们的应用进行系统签名,以获取系统权限。
3.4 客户端安装升级包——手机端app触发眼镜app更新(optional)
目前这只是一个想法,眼镜的升级可以通过手机端控制app来触发眼镜app更新,以及展示更新进度等。需要额外的开发工作进行眼镜和手机之间的通信,不过也能提供更好的用户体验。
3.5 客户端框图
图3.客户端框图
Service负责对更新的管理,执行,以及安装更新包之后,监听广播,重启app。其中的timer可选,用来在眼镜开机运行期间,每隔固定周期检测是否需要执行更新(应对眼镜长期开机,以及在一个周期之内已经检测或执行过升级的case)。
图4. 客户端时序图
3.6 升级监控应用打系统签名
首先,在应用程序manifest.xml文件根节点中加入属性:android:sharedUserId="android.uid.system"。
对apk进行系统签名,需要用到如下几个文件:platform.x509.pem、platform.pk8, 在Android系统源码的build/target/product/security/路径;signapk.jar,在系统源码的out/host/linux-x86/framework目录。
签名的命令如下:
java -jar signapk.jar platform.x509.pem platform.pk8 [source apk] [signed apk]
比如:
java -jar signapk.jar platform.x509.pem platform.pk8 otaservice-1.0.0.apk otaservice.apk
4. 服务端实现
4.1 查询升级包信息接口
方法 |
GET |
参数 |
无(目前看不需要) |
返回 |
{ "VersionCode": "12", // int "VersionName": "1.2.0", // string "size": "56384217", // long "md5": "38b8c2c1093dd0fec383a9d9ac940515", // string "url": "/ota_file/glasss.apk", //string “updateMessage”: “Fix bug” // string } |
4. 2 安装包部署
建议服务端提供安装包自动部署功能,提供交互和管理界面,管理员只需选择上传安装包,配置基本参数,程序自动检测安装包其它信息(size,md5等),并进行记录,数据库或其它存储方式,以方便提供给客户端查询API。
5. 其它
5.1 增量升级
增量升级比较适合安装包较大的案例,比如rom升级。目前我们的应用size很小,增量升级得不偿失。不过可以考虑未来对增量升级的支持。主要考虑:利用工具生成差分包(服务端,可考虑自动实现);从差分包合成安装包(客户端,可能需要进行native编码)。
5.2 统计升级情况
对眼镜的升级情况进行统计,以获取升级数量,版本,升级状态(成功,失败)等信息,供后续分析。
5.3 部分推送更新
新版本app发布之后,可以选择性的只允许按照一定算法规则随机筛选出的特定比例的终端设备可以检测到更新,这个比例可以是阶梯增长的,比如1%,10%, 25%, 50%, 100%。功能可以有效降低新版本中出现严重缺陷所影响的终端数量。在小范围发布版本中如果统计到严重缺陷,则可以及时撤回,修复并发布新版本。参考google play以及其它应用市场的功能。
对于眼镜上并不在应用市场发布的app,如果要支持该功能,需要server自己实现。首先需要所有终端设备的唯一标识,比如device id;其次要有算法确保每次能够随机筛选出不完全相同的指定指定比例的设备作为“实验品”,确保假定新版本有缺陷的情况下,不是每次都是同一批设备被坑,并且,只有筛选出的设备可以get到版本更新信息。
5.4 断点续传
考虑到我们app目前的规模,升级的断点续传应该也并不必要。如要支持断点续传,需要客户端以及服务端协同。首先需要服务端支持Ranges属性。其余工作主要在客户端完成。在下载终端的时候,需要记录当前传输的Range。在重启传输后,向服务器请求指定的range内容,服务器通过Content-Ranges返回指定range的内容。
图5. 断点续传流程图