Android OTA 升级入门篇

本文详细阐述了在Android应用中实现FOTA升级的过程,涉及FOTA包制作、测试、使用UpdateEngineAPI,以及代码片段,包括IntentService和处理升级状态的回调方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

第一次接手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

app 实现

第一次接触的,可以参考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:
提示升级完成,

升级失败的,主要监听这里面的,这个是最终状态.             

OTA 包

整包升级

  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 ,把权限都加上.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值