小工具知识网

当前位置: 首页 > 行业知识 > Android OTA 升级入门篇

行业知识

Android OTA 升级入门篇

2026-11-07 04:32:09 来源:互联网转载

简介

第一次接手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 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(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 ,把权限都加上.

上一篇:See风筝英语怎么读?

下一篇:战地五怎么组队进服务器?