赞
踩
第一次接手FOTA ,记录下app 端的实现过程,和一些基本概念,不对的地方,还望指正.
Android FOTA 广义是Android 实现设备系统无线升级的全部过程和手段。如果一个设备无线升级的系统是完备的话,则一个FOTA过程主要包含以下工作:
1. 制作FOTA升级包:升级包一种可供设备实现系统更新升级的一个压缩包文件,解压缩后本质是各种img文件、被执行文件和一些配置信息等。Android升级时
会有对应的程序解析这个文件。
2. 测试FOTA升级包:在发布这个版本之后,需要测试设备是否可以进行无线升级,是否会出现问题。
3. 发布升级包:将FOTA升级包发给客户或者上传客户服务器
其实和app 升级是一个道理,下载-安装-重启生效
google 已经为我们集成了升级方式,AB 升级和Recovery 升级,相对来说recovery 会进入系统recovery mode ,影响用户使用,而AB 升级是无感升级,两个slot 重启切换,不影响用户使用,体验更好.
目前android 11 默认是开启 virtural AB,对于上层来说其实没啥区别,都是调用updata_engine就可以了,参考了一部分文章,两者主要是super 分区的差异
bootloader_a |
bootloader_b |
boot_a |
boot_b |
vendor_boot_a |
vendor_boot_b |
dtbo_a |
dtbo_b |
vbmeta_a |
vbmeta_b |
super |
virtualAB 通过快照方式写入 super 分区,只保留一份
system | product | vendor |
AB 传统分区,两个slot
system_a | product_a | vendor_a |
system_b | product_b | vendor_b |
第一次接触的,可以参考packages/apps/Car/SystemUpdater android 系统为我们提供的demo
好了直接上代码,我把安装流程集成到service 里面.actvity 等要是想要进度条,实现 ResultReceiver,传递过来就可以了
-
- /**
- 安装软件的服务
- */
- public class InstallService extends IntentService {
-
- private static final String TAG = "InstallService";
- private final UpdateEngine mUpdateEngine = new UpdateEngine();
- private final SimUpdateEngineCallback mSimUpdateEngineCallback = new SimUpdateEngineCallback();
- public static final String EXTRA_PARAM_RESULT_RECEIVER = "result-receiver";
-
-
- public InstallService() {
- super("InstallService");
- }
-
- WeakReference<ResultReceiver> resultCallbackWeak;
- @Override
- protected void onHandleIntent(Intent intent) {
- Log.d(TAG, "On handle intent is called");
- spec = new PayloadSpec();
- ResultReceiver resultCallback = (ResultReceiver)intent.getParcelableExtra(EXTRA_PARAM_RESULT_RECEIVER);
- this.resultCallbackWeak = new WeakReference<ResultReceiver>(resultCallback);
- Log.d(TAG, "resultCallback = "+resultCallback);
- File otaFile = new File(CommonUtils.INSTALL_PATH);
- if(otaFile.exists()){
- execute(otaFile);
- }
- }
-
- PayloadSpec spec;
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
- }
-
-
-
- @Override
- public android.os.IBinder onBind(Intent intent) {
- return null;
- }
-
-
- android.os.Handler mHandler = new android.os.Handler() {
- @Override
- public void handleMessage(android.os.Message msg) {
-
- }
- };
-
- /**
- * Starts InstallService.
- *
- * @param context application context
- * @param resultCallback callback that will be called when the update is ready to be installed
- */
- public static void startService(Context context,
- ResultReceiver resultCallback) {
- Log.d(TAG, "Starting InstallService");
- Intent intent = new Intent(context, InstallService.class);
- intent.putExtra(EXTRA_PARAM_RESULT_RECEIVER, resultCallback);
- context.startService(intent);
- }
-
-
- private void execute(File file){
- // Preconditions.checkArgument(files.length > 0, "No file specified");
- try {
- UpdateParser.ParsedUpdate result = UpdateParser.parse(file);
- if (result == null) {
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "Failed verification";
- Log.e(TAG, String.format("Failed verification"));
- updateResult(spec);
- return;
- }
- if (!result.isValid()) {
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "file invalid";
- Log.e(TAG, String.format("Failed verification %s", result));
- updateResult(spec);
- return;
- }
- if (Log.isLoggable(TAG, Log.INFO)) {
- Log.i(TAG, result.toString());
- }
- mUpdateEngine.bind(mSimUpdateEngineCallback, handler);
- mUpdateEngine.applyPayload(
- result.mUrl, result.mOffset, result.mSize, result.mProps);
-
- } catch (IOException e) {
- Log.e(TAG, String.format("For file %s", file), e);
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "IOException";
- updateResult(spec);
- }
-
-
- }
-
- /** Handles events from the UpdateEngine. */
- public class SimUpdateEngineCallback extends UpdateEngineCallback {
-
- @Override
- public void onStatusUpdate(int status, float percent) {
- Log.d(TAG, String.format("onStatusUpdate %d, Percent %.2f", status, percent));
- switch (status) {
- case UpdateEngine.UpdateStatusConstants.IDLE:
- //todo ,Update status code: update engine is in idle state.
- break;
- case UpdateEngine.UpdateStatusConstants.CHECKING_FOR_UPDATE:
- //todo ,Update status code: update engine is checking for update.
- break;
- case 11:
- //todo ,i do not know why
- break;
- case UpdateEngine.UpdateStatusConstants.UPDATE_AVAILABLE:
- //todo ,Update status code: an update is available.
- break;
- case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
- Log.d(TAG, "UpdateEngine.UpdateStatusConstants.DOWNLOADING progress = "+percent);
- int progress = (int) (percent * 100);
- spec.resultCode = MessageContant.FOTA_UPDATE_PROGRESS;
- spec.progress = progress;
- updateResult(spec);
- break;
- case UpdateEngine.UpdateStatusConstants.VERIFYING:
- //todo ,Update status code: update engine is verifying an update.
- break;
- case UpdateEngine.UpdateStatusConstants.FINALIZING :
- //todo Update status code: update engine is finalizing an update.
- break;
- case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
- spec.resultCode = MessageContant.FOTA_FINISH_REBOOT;
- spec.description = "更新成功";
- updateResult(spec);
- break;
- case UpdateEngine.UpdateStatusConstants.REPORTING_ERROR_EVENT:
- //出错
- // spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- // spec.description = "系统更新失败";
- // updateResult(spec);
- break;
- case UpdateEngine.UpdateStatusConstants.ATTEMPTING_ROLLBACK:
- //系统回滚
- // spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- // spec.description = "系统更新失败";
- // updateResult(spec);
- break;
- case UpdateEngine.UpdateStatusConstants.DISABLED:
- //update engine disable
- break;
-
- default:
- // spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- // spec.description = "系统更新失败";
- // updateResult(spec);
- break;
- // noop
- }
- }
-
-
- @Override
- public void onPayloadApplicationComplete(int errorCode) {
- Log.w(TAG, String.format("ErrorCodeConstants onPayloadApplicationComplete %d", errorCode));
- mUpdateEngine.unbind();
- switch(errorCode){
- case UpdateEngine.ErrorCodeConstants.SUCCESS:
- //todo 升级成功
- break;
- case UpdateEngine.ErrorCodeConstants.ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "ERROR";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.NOT_ENOUGH_SPACE:
- spec.resultCode = MessageContant.FOTA_NOT_ENOUGH_SPACE;
- spec.description = "存储空间不足";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "时间戳不对";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "hash值不对";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "大小不对";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "签名不对";
- updateResult(spec);
- break;
- case UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR:
- spec.resultCode = MessageContant.FOTA_FILE_INVAlID;
- spec.description = "类型不匹配";
- updateResult(spec);
- break;
-
- default:
- //升级失败
- spec.resultCode = MessageContant.FOTA_UPGRADE_FAIL;
- spec.description = "系统更新失败";
- updateResult(spec);
- break;
-
- }
- }
- }
-
- private Handler handler=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- // switch (msg.what){
- // case MessageContant.FOTA_START_UPDATE:
- // Log.e(TAG,"FOTA_START_UPDATE");
- // break;
- // case MessageContant.FOTA_UPDATE_PROGRESS:
- // Log.e(TAG,"FOTA_UPDATE_PROGRESS");
- // //通过 msg.obj 获取 ProgressBar 的进度,然后显示进度值
- // int process = (int) msg.obj;
- // break;
- // }
- super.handleMessage(msg);
- }
- };
-
- Bundle resultData = new Bundle();
-
- /**
- return to UI
- */
- private void updateResult(PayloadSpec spec){
- //非法安装包,则统一删除
- if(spec.description != null){
- Log.e(TAG, "spec.description = "+spec.description);
- }
- if(spec.resultCode == MessageContant.FOTA_FILE_INVAlID){
- Log.e(TAG, "parse file error");
- UpdateUtil.showUpdateResult(getApplicationContext());
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- SharedPreferences.Editor pEdits = sp.edit();
- pEdits.putInt ("INSTALL",0);
- pEdits.putInt ("DOWNLOADING",0);
- pEdits.commit();
- }
- if(null != resultCallbackWeak.get()){
- Log.i(TAG,"updateResult");
- resultData.putSerializable("result",spec);
- resultCallbackWeak.get().send(0, resultData);
- }
- }
-
-
- }
PayloadSpec.java
- public class PayloadSpec implements Serializable {
-
- private static final long serialVersionUID = 41043L;
-
- /**
- 返回结果码
- */
- public int resultCode;
- /**
- 描述
- */
- public String description;
- /**
- 进度条
- */
- public int progress;
-
- }
只需调用
/**
mResultReceiver 为ResultReceiver 实例
*/
InstallService.startService(mContext,mResultReceiver);
我们主要关注几个状态码
case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
升级成功,提示您是否重启
public void onPayloadApplicationComplete(int errorCode)
中的
case UpdateEngine.ErrorCodeConstants.SUCCESS:
提示升级完成,
升级失败的,主要监听这里面的,这个是最终状态.
make otapackage
out/target/product/(device)/(device)ota-eng.xss.zip
编译出整包,优点是只保留最新版本就可以了,缺点是下载包太大.
差分包顾名思义,就是两个版本的差异,通过以下命令.
./build/make/tools/releasetools/ota_from_target_files.py -v -p ./out/host/linux-x86/ -i old-target-files.zip new-target-files.zip fota.zip
old-target-files.zip
new-target-files.zip 这两个target 包,整编后位置在out/disk 下面
或者out/target/product/(device)/obj/PACKAGING/target_files_intermediates
差分包升级缺点是每次都得备份target 包,优点是差分包比较小,节约流量;
为了验证差分包是否ok, 可以先通过命令升级,看看是否ok.
制作完差分包后 adb shell ,输入下面命令
update_engine_client --payload=file:///sdcard/payload.bin --update --headers="
FILE_HASH=osa/esLUKk6WPwoZkIehvz4JB/PVO7j15DEBaHKhr/s=
FILE_SIZE=192242848
METADATA_HASH=pdqbMcHlZpKSPll7XRMUz8wN/demqmgRy3fOIiPpQ50=
METADATA_SIZE=95795"
注意换行,标注红色部分的,需要手动修改对应差分包里面的信息,解压差分包得到下面信息
打开payload_properties.txt 就能看到上面需要的参数信息了,对应填上就可以了,
注意把payload.bin push 到设置升级的位置,我这边选择的是file:///sdcard/payload.bin
1,针对http 下载的文件,一定要主要文件权限问题,我这边下载到/data/ota_package/ 后发现文件权限变成,-rw------- 1 system system ,这样升级就会遇到权限问题,其他分组的就没有读写权限.
修改方式:修改下载文件的权限,注意一点要下载之前,就修改,下载后修改就不行了.
- public static void changemode(File destFile){
- try{
- if(!destFile.exists()){
- destFile.createNewFile();
- }
- /* file.createNewFile();*/
- destFile.setWritable(Boolean.TRUE);
- destFile.setReadable (Boolean.TRUE);
- String command = "chmod 666 " + destFile.getAbsolutePath();
- Log.i(TAG, "command = " + command);
- Runtime runtime = Runtime.getRuntime();
- Process proc = runtime.exec(command);
- } catch (IOException e) {
- Log.i(TAG,"chmod fail!!!!");
- e.printStackTrace();
- }
- }
2,刚开始升级会抛出状态码
onStatusUpdate
2 11 3
其中11 ,我查了值是大小不匹配,我以为是异常情况,认为安装包不对,直接删除了,导致后面更新进行不下去了,莫名其妙
所以onStatusUpdate 中的状态码,我们主要关注reboot 的,即可,
onPayloadApplicationComplete 中返回的,才是真正是否升级成功状态.
参考system/update_engine/common/error_code.h 中状态码
1,android 默认升级文件是放在/data/ota_package/,建议保留,但是data 分区随着时间的推移,空间可能不够了,所以,也可以创建一个单独的分区放升级文件 eg:/fota/
2,测试的时候一定要把selinux 关掉,adb shell setenforce 0,可以专注升级流程是否ok,最后通过dmesg ,把权限都加上.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。