Android系统定制的完整升级流程

Android系统升级分为:全量升级和增量升级。全量升级一般是首次安装,需要打全量OTA升级包;而增量升级,可以打增量OTA升级包,也可以通过静默安装来实现升级。这里主要介绍增量升级的全流程:获取升级包—>验证升级包—>版本对比—>压缩包解压—>升级—>进度监听—>重启系统。

一、获取升级包

获取升级包一般有两种方式:从网络下载和SD卡读取。这里介绍的是从SD卡读取升级包,当监听到SD卡插入时,等待SD卡扫描完成,然后遍历SD卡目录,判断有没升级包。

    //注册广播监听SD卡插拔
    private void registerBroadcast(){
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_REMOVED);
        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
        intentFilter.addDataScheme("file");
        cardReceiver = new SDCardReceiver();
        registerReceiver(cardReceiver, intentFilter);
    }
    //SD卡插拔广播接收并处理
    private class SDCardReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent == null || intent.getAction() == null)
                return;
            switch (intent.getAction()){
                case Intent.ACTION_MEDIA_MOUNTED://SD卡插入
                    break;
                case Intent.ACTION_MEDIA_REMOVED://SD卡移除
                case Intent.ACTION_MEDIA_UNMOUNTED:
                    break;
                case Intent.ACTION_MEDIA_SCANNER_FINISHED://SD卡扫描完成
                    //获取升级包路径
                    String updateFileName = FileUtil.getUpdateFile();
                    if (TextUtils.isEmpty(updateFileName))
                        return;
                    //比较版本号
                    int checkVersion = VersionUtil.parseVersion(updateFileName);
                    int result = VersionUtil.isNewVersion(checkVersion);
                    if (result == 0){
                        Log.i(TAG, "the same version, no need upgrade...");
                        return;
                    }
                    String updateMsg = result > 0 ? getString(R.string.update_msg_new)
                            : getString(R.string.update_msg_old);
                    showConfirmDialog(getString(R.string.update_tip), updateMsg);
                    break;
                default:
                    break;
            }
        }
    }

二、验证升级包

如果是OTA升级包,一般采用系统签名,然后系统升级前,会进行系统签名的校验,防止不合法或者不完整的升级包。如果是静默安装升级包,可以使用MD5校验。

三、版本对比

版本对比过程是,从升级包获取待升级版本,与当前系统版本进行对比。

    /**
     * 对比当前版本与待升级版本
     * @param checkVersion 待升级版本
     * @return 1:新版本;0:相同版本;-1:旧版本
     */
    public static int isNewVersion(float checkVersion){
        float currentVersion = SystemUtil.getAppVersion();
        HxLogUtil.LogError(TAG, "currentAppVersion="+currentVersion);
        if (checkVersion > currentVersion){
            return 1;
        }else if (checkVersion < currentVersion){
            return -1;
        }else {
            return 0;
        }
    }

系统版本,我们可以使用系统Settings进行储存与读取:

public static void setAppVersion(float appVersion){
        Settings.System.putFloat(UpdateApplication.getInstance().getContentResolver(),
                "appVersion", appVersion);
    }

    public static float getAppVersion(){
        return Settings.System.getFloat(UpdateApplication.getInstance().getContentResolver(),
                "appVersion", 1.0f);
    }

四、解压升级包

解压发生在版本对比后,如果判断为新版本,那么我们才从SD卡拷贝升级包到内存并解压。

    /**
     * 解压zip到指定的路径
     * @param zipFile  ZIP文件路径
     * @param outPath  待解压缩路径
     * @throws Exception Exception
     */
    public static void unZipFile(String zipFile, String outPath) throws Exception {
        if (TextUtils.isEmpty(zipFile) || TextUtils.isEmpty(outPath))
            return;
        ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFile));
        ZipEntry zipEntry;
        String  szName;
        while ((zipEntry = inZip.getNextEntry()) != null) {
            szName = zipEntry.getName();
            if (zipEntry.isDirectory()) {
                //获取部件的文件夹名
                szName = szName.substring(0, szName.length() - 1);
                File folder = new File(outPath + File.separator + szName);
                if(!folder.mkdirs())
                    break;
            } else {
                HxLogUtil.LogError("FileUtil","unzip=" + outPath + File.separator + szName);
                File file = new File(outPath + File.separator + szName);
                if (!file.exists()){
                    if(!file.createNewFile()) {
                        HxLogUtil.LogError("FileUtil","createNewFile error");
                        continue;
                    }
                }
                // 获取文件的输出流
                FileOutputStream out = new FileOutputStream(file);
                int len;
                byte[] buffer = new byte[1024];
                // 读取(字节)字节到缓冲区
                while ((len = inZip.read(buffer)) != -1) {
                    // 从缓冲区(0)位置写入(字节)字节
                    out.write(buffer, 0, len);
                    out.flush();
                }
                out.close();
            }
        }
        inZip.close();
    }

五、升级

解压完升级包,得到apk文件后,我们就可以开始升级了。这里使用的是静默安装,反射调用系统的API:

    //遍历所有APK文件进行升级
    public void update(String updateFilePath){
        if (TextUtils.isEmpty(updateFilePath))
            return;
        File updateFile = new File(updateFilePath);
        if (updateFile.exists()){
            File[] apkList = updateFile.listFiles();
            if (apkList == null || apkList.length == 0){
                HxLogUtil.LogError(TAG, "no any apk in the package...");
                return;
            }
            apkTotal = apkList.length;
            for (File apk : apkList){
                installSilent(UpdateApplication.getInstance(), apk.getAbsolutePath());
            }
        }
    }

1、静默升级的方法来自PackageManager的installPackage方法,这里需要传入context和filePath参数:

        private void installSilent(Context context, String filePath) {
        try {
            PackageManager packageManager = context.getPackageManager();
            Method method = packageManager.getClass().getDeclaredMethod("installPackage",
                    Uri.class, IPackageInstallObserver.class, int.class, String.class);
            method.setAccessible(true);
            File apkFile = new File(filePath);
            Uri apkUri = Uri.fromFile(apkFile);

            method.invoke(packageManager, apkUri, new IPackageInstallObserver.Stub() {
                @Override
                public void packageInstalled(String pkgName, int resultCode) throws RemoteException {

                    apkCount ++;

                    if (onUpdateListener != null){
                        onUpdateListener.onUpdate(apkCount*100/apkTotal);
                    }

                }
            }, 2, "com.example.update");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2、除了反射调用,我们也可以使用pm install -r apkPath指令来静默安装:

    //使用pm指令进行静默安装
    private static boolean doUpdate(String apkPath){
        if (TextUtils.isEmpty(apkPath))
            return false;
        String cmd = "pm install -r " + apkPath + " \n";
        try {
            Process process = Runtime.getRuntime().exec(cmd);
            process.waitFor();

            InputStream inputStream = process.getInputStream();
            byte[] data = new byte[1024];
            int size = inputStream.read(data);
            if (size > 0){
                String result = new String(data, 0, size);
            }
            inputStream.close();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

3、OTA升级是调用RecoverySystem的installPackage方法,需要Recovery权限:

    public static void installPackage(Context context, File packageFile, boolean processed)
            throws IOException {
        synchronized (sRequestLock) {
            LOG_FILE.delete();
            // Must delete the file in case it was created by system server.
            UNCRYPT_PACKAGE_FILE.delete();

            String filename = packageFile.getCanonicalPath();
            Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");

            // If the package name ends with "_s.zip", it's a security update.
            boolean securityUpdate = filename.endsWith("_s.zip");

            // If the package is on the /data partition, the package needs to
            // be processed (i.e. uncrypt'd). The caller specifies if that has
            // been done in 'processed' parameter.
            if (filename.startsWith("/data/")) {
                if (processed) {
                    if (!BLOCK_MAP_FILE.exists()) {
                        Log.e(TAG, "Package claimed to have been processed but failed to find "
                                + "the block map file.");
                        throw new IOException("Failed to find block map file");
                    }
                } else {
                    FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
                    try {
                        uncryptFile.write(filename + "\n");
                    } finally {
                        uncryptFile.close();
                    }
                    // UNCRYPT_PACKAGE_FILE needs to be readable and writable
                    // by system server.
                    if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
                            || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
                        Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
                    }

                    BLOCK_MAP_FILE.delete();
                }

                // If the package is on the /data partition, use the block map
                // file as the package name instead.
                filename = "@/cache/recovery/block.map";
            }

            final String filenameArg = "--update_package=" + filename + "\n";
            final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
            final String securityArg = "--security\n";

            String command = filenameArg + localeArg;
            if (securityUpdate) {
                command += securityArg;
            }

            RecoverySystem rs = (RecoverySystem) context.getSystemService(
                    Context.RECOVERY_SERVICE);
            if (!rs.setupBcb(command)) {
                throw new IOException("Setup BCB failed");
            }

            // Having set up the BCB (bootloader control block), go ahead and reboot
            // 如果已经启动BCB,执行系统重启
            PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            String reason = PowerManager.REBOOT_RECOVERY_UPDATE;

            // On TV, reboot quiescently if the screen is off
            //在TV中,如果屏幕处于熄灭状态,那么静默重启
            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
                WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
                    reason += ",quiescent";
                }
            }
            pm.reboot(reason);

            throw new IOException("Reboot failed (no permissions?)");
        }
    }

六、进度监听

我们可以使用当前已安装app数与总app数计算百分比,然后回调升级进度:

   apkCount ++;
   if (onUpdateListener != null){
       onUpdateListener.onUpdate(apkCount*100/apkTotal);
   }

监听升级进度,更新UI界面:

    @Override
    public void onUpdate(int progress) {
        //使用handler通知主线程更新升级进度
        mHandler.obtainMessage(MSG_PROGRESS, progress).sendToTarget();
        if (progress == 100){
            //保存更新的版本号
            if (checkVersion > 0){
                SystemUtil.setAppVersion(checkVersion);
            }
            //删除内存升级包
            boolean deleteResult = FileUtil.deleteFolder(outputPath);
            HxLogUtil.LogInfo(TAG, "delete result=" + deleteResult);
            //通知升级完成
            mHandler.sendEmptyMessage(MSG_UPDATE_FINISH);
        }
    }

七、系统重启

在升级完成后,我们一般调用PowerManager重启系统,保证升级后的app重新启动生效:

    public static void reboot(Context context){
        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (powerManager != null) {
            powerManager.reboot("");
        }
    }

至此,整个系统升级流程已完成。。。

发布了63 篇原创文章 · 获赞 179 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/u011686167/article/details/84347614
今日推荐