Android - app实现 A/B 升级

发布时间:2023年12月30日

官网介绍如下:

A/B 系统更新(也称为无缝更新)的目标是确保在无线下载 (OTA) 更新期间在磁盘上保留一个可正常启动和使用的系统。采用这种方式可以降低更新之后设备无法启动的可能性,这意味着用户需要将设备送到维修和保修中心进行更换和刷机的情况将会减少。

最开始只是知道这种升级模式,但是未具体了解,在 Android 4.4 和?Android 10 上沿用之前的 RecoverySystem.installPackage(mContext, OTA_PACKAGE); 方式是没出问题的,但是在 Android 12 貌似不支持这种升级方式了,于是开启检索之路

首先想到系统升级这块设置里肯定有,于是直接定位到

packages/apps/Settings/src_unisoc/com/android/settings/deviceinfo/LocalSystemUpdatePreferenceController.java

这个文件就是本地升级选项的 controller,简单介绍下

//点击选项处理的内容
    @Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        int messageId = SD_UDPATE_SUPPORTED ? R.string.recovery_update_message : R.string.recovery_update_message_internal_only;
        if (getPreferenceKey().equals(preference.getKey())) {
            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
            builder.setMessage(mContext.getResources().getString(messageId));
//点击确定的监听为 mDialogClickListener
            builder.setPositiveButton(android.R.string.ok, mDialogClickListener);
            builder.setNegativeButton(android.R.string.cancel, null);
            AlertDialog dialog = builder.create();
            dialog.setCancelable(false);
            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            dialog.show();
            return true;
        }   
        return super.handlePreferenceTreeClick(preference);
    }   

//点击确定的监听定义如下
    private DialogInterface.OnClickListener mDialogClickListener = new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface arg0, int arg1) {
            checkUpdatePackage();
        }
    };


//继续查找 checkUpdatePackage()方法
    public void checkUpdatePackage() {
//??检查升级包为什么要打印什么AB支持 mIsVirtualAbSupported
        Log.d(TAG, "mIsVirtualAbSupported:" + mIsVirtualAbSupported);
        mUpdateItems.clear();
        checkInteralStorage();
         // Add for bug1413281, support Non-A/B feature
        if (mIsVirtualAbSupported) {
            checkOtaPackage();
        }
        if (SD_UDPATE_SUPPORTED) {
            checkExternalStorage();
        }
        showOperateDialog();
    }

//看一下定义
    // Add for bug1413281, support Non-A/B feature
    private final boolean mIsVirtualAbSupported = SystemProperties.getBoolean("ro.build.ab_update", false);

//推测支持这种 A/B 升级的都可以通过 ro.build.ab_update 来判断
//C:\Users\lichang\Desktop>adb shell getprop ro.build.ab_update
//true

    public void showSingleChoiceDialog(String [] items) {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                mUserChoice = which;
            }
        });
        builder.setTitle(R.string.choice_dialog_title);
        builder.setPositiveButton(R.string.update_choice_dialog_ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                File file = mUpdateItems.get(mUserChoice);
//这里作了区分,A/B 和 normal 两种升级方式
                if (mIsVirtualAbSupported) {
                    startVirtualAbUpdateProgress(file);
                } else {
                    startNormalUpdateProgress(items[mUserChoice], file);
                }
            }
        });
        AlertDialog dialog = builder.create();
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }



经过自己的尝试,大概描述下升级过程。

1. 异步任务解析 A/B 升级zip文件
//开始A/B升级
    public void startVirtualAbUpdateProgress(File file) {
        if (mInstallationInProgress) {
            showInstallationInProgress();
        } else if (mUserChoice >= 0) {
            try {
//执行异步任务解析
                new UpdateVerifier().execute(file);
            } catch (Exception ex) {
                Log.e(TAG, ex.getMessage());
                Toast.makeText(mContext, R.string.recovery_update_package_verify_failed, Toast.LENGTH_SHORT).show();
            }
        }
    }


    //验证ota升级包的有效性
    private class UpdateVerifier extends AsyncTask<File, Void, UpdateParser.ParsedUpdate> {
        @Override
        protected UpdateParser.ParsedUpdate doInBackground(File... files) {
            Preconditions.checkArgument(files.length > 0, "No file specified");
            File file = files[0];
            try {
                return UpdateParser.parse(file);
            } catch (IOException e) {
                Log.e(TAG, String.format("For file %s", file), e);
                return null;
            }
        }

        @Override
        protected void onPostExecute(UpdateParser.ParsedUpdate result) {
            if (result == null) {
//验证失败
                Toast.makeText(mContext, R.string.recovery_update_package_verify_failed, Toast.LENGTH_SHORT).show();
                Log.e(TAG, String.format("Failed verification %s", result));
                return;
            }
            if (!result.isValid()) {
//无效
                Toast.makeText(mContext, R.string.recovery_update_package_verify_failed, Toast.LENGTH_SHORT).show();
                Log.e(TAG, String.format("Failed verification %s", result));
                return;
            }
            Log.d(TAG, "package verifier success");
            if (isLowBatteryLevel()) {
//低电量
                Toast.makeText(mContext, R.string.recovery_update_level, Toast.LENGTH_LONG).show();
            } else {
//成功,弹出dialog
                showConfirmInstallDialog(result);
            }
        }
2. 绑定监听,安装升级文件
//弹出确认是否升级的对话框
    public void showConfirmInstallDialog(final UpdateParser.ParsedUpdate parsedUpdate) {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        builder.setTitle(mContext.getResources().getString(R.string.recovery_update_package_install_title));
        builder.setMessage(mContext.getResources().getString(R.string.recovery_update_install_ready));
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
//show安装进度
                showInstallationInProgress();
                Settings.Global.putInt(mContext.getContentResolver(), DATABASE_KEY_INSTALL_IN_PROGRESS, 1);
//安装更新
                installUpdate(parsedUpdate);
            }
        });
        builder.setNegativeButton(android.R.string.cancel, null);
        AlertDialog dialog = builder.create();
        dialog.setCancelable(false);
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
        //UNISOC: ADD for bug1648653, change the title and message color.
        ((TextView)dialog.findViewById(android.R.id.message)).setTextColor(Color.BLUE);
        ((TextView)dialog.findViewById(R.id.alertTitle)).setTextColor(Color.BLUE);
    }


//show安装进度
    private void showInstallationInProgress() {
        mInstallationInProgress = true;
        showStatus(R.string.recovery_update_package_install_title, R.string.recovery_update_package_download_in_progress);
//bind方法很重要,便于自定义操作及查看进度
        mUpdateEngine.bind(mOtaUpdateEngineCallback, new Handler(mContext.getMainLooper()));
    }


//将ota包数据发送到updateEngine进行升级
    private void installUpdate(UpdateParser.ParsedUpdate parsedUpdate) {
        Log.d(TAG, "mUrl:" + parsedUpdate.mUrl + ",mOffset:" + parsedUpdate.mOffset
                + ",mSize:" + parsedUpdate.mSize + ",mProps:" + parsedUpdate.mProps);

        try {
            if(mContext.getSystemService(KeyguardManager.class).isDeviceSecure()){
                Log.d(TAG, "prepareForUnattendedUpdate");
                RecoverySystem.prepareForUnattendedUpdate(mContext, TAG, null);
            }
            if (mWakeLock != null && !mWakeLock.isHeld()) mWakeLock.acquire();
//主要执行这行命令,applyPayload
            mUpdateEngine.applyPayload(
                    parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps);
        } catch (Exception ex) {
            mInstallationInProgress = false;
            if (mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release();
            Settings.Global.putInt(mContext.getContentResolver(), DATABASE_KEY_INSTALL_IN_PROGRESS, 0);
            //Modify for bug1420894, no need to show toast if update succeed
            String message = ex.getMessage();
            Log.e(TAG, message);
            if (!ERROR_NEED_REBOOT.equals(message)) {
                Toast.makeText(mContext, R.string.recovery_update_package_install_failed, Toast.LENGTH_SHORT).show();
            }
        }
    }
3. 重启
//处理来自UpdateEngine的事件
    public class OtaUpdateEngineCallback extends UpdateEngineCallback {
        @Override
        public void onStatusUpdate(int status, float percent) {
            switch (status) {
                case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT:
//升级基本完成,只需重启即可,自定义逻辑
                    break;
                case UpdateEngine.UpdateStatusConstants.DOWNLOADING:
//downloading
                    Log.d(TAG, "downloading progress:" + ((int) (percent * 100) + "%"));
                    break;
                default:
                    // do nothing
            }
        }

        @Override
        public void onPayloadApplicationComplete(int errorCode) {
            Log.w(TAG, String.format("onPayloadApplicationComplete %d", errorCode));
            int messageId = (errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS) ? R.string.recovery_update_package_install_success : R.string.recovery_update_package_install_failed;
            Toast.makeText(mContext, messageId, Toast.LENGTH_SHORT).show();
//升级完成(不论成功或是失败)
        }
    }

升级过程中的几个状态如下

    public static final class UpdateStatusConstants {
        public static final int IDLE = 0;
        public static final int CHECKING_FOR_UPDATE = 1;
        public static final int UPDATE_AVAILABLE = 2;
        public static final int DOWNLOADING = 3;
        public static final int VERIFYING = 4;
        public static final int FINALIZING = 5;
        public static final int UPDATED_NEED_REBOOT = 6;
        public static final int REPORTING_ERROR_EVENT = 7;
        public static final int ATTEMPTING_ROLLBACK = 8;
        public static final int DISABLED = 9;

        public UpdateStatusConstants() {
        }
    }

找到了源码中的 demo。路径为packages/apps/Car/SystemUpdater

简单看了下,代码下共有 5 个文件,如有需要可私信我

DeviceListFragment.java? ? ? ? ? ? ? ? //显示文件和目录的列表。

SystemUpdaterActivity.java? ? ? ? ? ? ? ? //在内部或外部存储上使用ota包应用系统更新

UpdateLayoutFragment.java? ? ? ? ? ? ? ? //显示更新状态和进度

UpdateParser.java? ? ? ? ? ? ? ? //解析A/B升级zip文件

UpFragment.java? ? ? ? ? ? ? ? //允许一个片段处理up动作

最后,仿照?LocalSystemUpdatePreferenceController.java 的实现流程即可实现 A/B 升级。

文章来源:https://blog.csdn.net/qq_38909786/article/details/135250601
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。