至于为何要在这里讲解android系统源码中的系统更新,我已经在上一篇《 CM android的CMUpdater分析(一)》中介绍了。在上一篇中,主要讲解了在eclipse中如何搭建系统应用的开发环境,现在我们就使用eclipse来分析CMUpdater源码。该系统更新是CM修改原生android的基础上实现的。通过分析android系统的应用源码,可以学到一些很好的思想和编程方法学。好了,废话少说,现在就开始我们的学习之旅。

首先,在开始介绍之前,我先把之前根据CMUpdater源码分析来的框图放到上来,大家先看框图,把系统更新的整体流程大体了解一下。

通过这个框图,看上去挺复杂,其实是挺简单的,就是Service检查更新,后台下载更新,包括全安装包或者是增量更新包,下载完成之后,安装更新,重启,安装完成。

现在我们从代码开始,进行分析。

首先,我们先来看一个app项目中,所有的activity和service,就是要查看AndroidManifest.xml文件。通过分析该代码,我们看到,在CMUpdater中的入口Activity是UpdatesSettings,同时在
AndroidManifest.xml文件中,还静态注册了四个receiver,其中包括:

  • UpdateCheckReceiver : 在该receiver中注册了两个action,其中包括BOOT_COMPLETED和CONNECTIVITY_CHANGE,该receiver用于在系统启动后或者是网络连接发生变化的时候,进行检查系统更新。
  • DownloadReceiver : 该receiver也注册了两个action,包括DOWNLOAD_COMPLETE和START_DOWNLOAD,当下载完成和开始下载广播发出之后,进行相应的操作。
  • NotificationClickReceiver,该广播接收器注册了一个DOWNLOAD_NOTIFICATION_CLICKED广播,当接收到下载提醒框被点击的时候,进行更新操作。
  • CheckFinishReceiver : 该广播接收器用于接收UPDATE_CHECK_FINISHED广播,当检查更新完成之后,刷新列表。

同时,在该xml文件中,也注册了几个Service:

  • CMDashClockExtension : 该服务用于检查时间,用户可以选择某个时间点,进行检查更新。
  • UpdateCheckService : 该服务的操作就是用于检查更新,检查更新完成之后,发出检查更新完成的广播。
  • DownloadService : 顾名思义,下载服务,也就是说该服务是用来下载系统更新的。
  • DownloadCompleteIntentService : 下载完成服务,当下载完成之后,发出下载完成广播。

好了,现在我们先进入入口Activity来看一下。

进入到UpdateSettings.java文件,可以看到,该类中注册了一个Receiver,该接收器的实现为:

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            //如果Action为ACTION_DOWNLOAD_STARTED,即为下载开始            if (DownloadReceiver.ACTION_DOWNLOAD_STARTED.equals(action)) {                mDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);                mUpdateHandler.post(mUpdateProgress);  //更新ProgressBar                //如果Action为ACTION_CHECK_FINISHED,即为检查更新完成            } else if (UpdateCheckService.ACTION_CHECK_FINISHED.equals(action)) {                if (mProgressDialog != null) {                    mProgressDialog.dismiss();                    mProgressDialog = null;                    //获取系统更新的条数                    int count = intent.getIntExtra(UpdateCheckService.EXTRA_NEW_UPDATE_COUNT, -1);                    if (count == 0) {  // 如果为0,提醒用户未找到                        Toast.makeText(UpdatesSettings.this, R.string.no_updates_found,                                Toast.LENGTH_SHORT).show();                    } else if (count < 0) {  //如果小于0,则表示检查更新失败                        Toast.makeText(UpdatesSettings.this, R.string.update_check_failed,                                Toast.LENGTH_LONG).show();                    }                }                //如果其他都不是,表示更新完成,并且有更新,则刷新UI                updateLayout();            }        }    };

当用户点击item(MENU_REFRESH)之后,手动进行更新检查,即为checkForUpdates()

    private void checkForUpdates() {        if (mProgressDialog != null) {            return;        }        // 未连接网络,提示用户并返回        if (!Utils.isOnline(this)) {            Toast.makeText(this, R.string.data_connection_required, Toast.LENGTH_SHORT).show();            return;        }        //弹出进度条,用户提示用户正在检查更新        mProgressDialog = new ProgressDialog(this);        mProgressDialog.setTitle(R.string.checking_for_updates);        mProgressDialog.setMessage(getString(R.string.checking_for_updates));        mProgressDialog.setIndeterminate(true);        mProgressDialog.setCancelable(true);        mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {            @Override            public void onCancel(DialogInterface dialog) {                //当用户点击取消后,提醒服务程序,取消检查更新操作                Intent cancelIntent = new Intent(UpdatesSettings.this, UpdateCheckService.class);                cancelIntent.setAction(UpdateCheckService.ACTION_CANCEL_CHECK);                startService(cancelIntent);                mProgressDialog = null;            }        });        //开启服务程序进行检查更新        Intent checkIntent = new Intent(UpdatesSettings.this, UpdateCheckService.class);        checkIntent.setAction(UpdateCheckService.ACTION_CHECK);        startService(checkIntent);        mProgressDialog.show();    }

现在我们进入检查更新服务程序,即UpdateCheckService。在服务程序的启动方法onStartCommand()检查用户的操作是取消检查还是检查更新,如果是取消检查,就将位于请求列表中的请求删除掉。

    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        //取消检查更新        if (TextUtils.equals(intent.getAction(), ACTION_CANCEL_CHECK)) {            ((UpdateApplication) getApplicationContext()).getQueue().cancelAll(TAG);            return START_NOT_STICKY;        }        return super.onStartCommand(intent, flags, startId);    }

接着,服务程序调用getAvailableUpdates()来检查可用更新,我们来看一下这个方法:

   private void getAvailableUpdates() {        // Get the type of update we should check for        int updateType = Utils.getUpdateType();        // Get the actual ROM Update Server URL        URI updateServerUri = getServerURI();Log.e(TAG, updateServerUri.toString());        UpdatesJsonObjectRequest request;        try {            request = new UpdatesJsonObjectRequest(updateServerUri.toASCIIString(),                    Utils.getUserAgentString(this), buildUpdateRequest(updateType), this, this);            // Set the tag for the request, reuse logging tag            request.setTag(TAG);        } catch (JSONException e) {            Log.e(TAG, "Could not build request", e);            return;        }        ((UpdateApplication) getApplicationContext()).getQueue().add(request);    }

这个代码很好理解,就是封装一下请求信息,封装为请求对象,将该请求对象添加到请求队列中,等待从服务器中获取信息。请求对象的封装是通过buildUpdateRequest()方法来实现的。

接着,服务程序通过回调函数onResponse()来获取相应的服务器反馈信息。

    @Override    public void onResponse(JSONObject jsonObject) {        int updateType = Utils.getUpdateType();        LinkedList<UpdateInfo> lastUpdates = State.loadState(this);        LinkedList<UpdateInfo> updates = parseJSON(jsonObject.toString(), updateType);        int newUpdates = 0, realUpdates = 0;        for (UpdateInfo ui : updates) {            if (!lastUpdates.contains(ui)) {                newUpdates++;            }            if (ui.isNewerThanInstalled()) {                realUpdates++;            }        }        Intent intent = new Intent(ACTION_CHECK_FINISHED);        intent.putExtra(EXTRA_UPDATE_COUNT, updates.size());        intent.putExtra(EXTRA_REAL_UPDATE_COUNT, realUpdates);        intent.putExtra(EXTRA_NEW_UPDATE_COUNT, newUpdates);        recordAvailableUpdates(updates, intent);        State.saveState(this, updates);    }

在该回调函数中,将更新信息放到更新列表中,同时通过调用recordAvailableUpdates()发送actionACTION_CHECK_FINISHED的广播给UpdateSettings中。接着UpdateSettings调用updateLayout()更新UI,同时调用refreshPreferences()接着调用回调函数onStartDownload()来通知DownloadReceiver进行下载。

我们先来看一下recordAvailableUpdates()函数的实现:

    private void recordAvailableUpdates(LinkedList<UpdateInfo> availableUpdates,            Intent finishedIntent) {        if (availableUpdates == null) {            sendBroadcast(finishedIntent);            return;        }        //保存上次更新时间,然后确保启动检查更新完成是正确的        Date d = new Date();        PreferenceManager.getDefaultSharedPreferences(UpdateCheckService.this).edit()                .putLong(Constants.LAST_UPDATE_CHECK_PREF, d.getTime())                .putBoolean(Constants.BOOT_CHECK_COMPLETED, true)                .apply();        int realUpdateCount = finishedIntent.getIntExtra(EXTRA_REAL_UPDATE_COUNT, 0);        UpdateApplication app = (UpdateApplication) getApplicationContext();        // Write to log        Log.i(TAG, "The update check successfully completed at " + d + " and found "                + availableUpdates.size() + " updates ("                + realUpdateCount + " newer than installed)");        //如果程序被关掉了,则在提醒框中显示更新信息        if (realUpdateCount != 0 && !app.isMainActivityActive()) {            // There are updates available            // The notification should launch the main app            Intent i = new Intent(this, UpdatesSettings.class);            i.putExtra(UpdatesSettings.EXTRA_UPDATE_LIST_UPDATED, true);            PendingIntent contentIntent = PendingIntent.getActivity(this, 0, i,                    PendingIntent.FLAG_ONE_SHOT);            Resources res = getResources();            String text = res.getQuantityString(R.plurals.not_new_updates_found_body,                    realUpdateCount, realUpdateCount);            // Get the notification ready            Notification.Builder builder = new Notification.Builder(this)                    .setSmallIcon(R.drawable.cm_updater)                    .setWhen(System.currentTimeMillis())                    .setTicker(res.getString(R.string.not_new_updates_found_ticker))                    .setContentTitle(res.getString(R.string.not_new_updates_found_title))                    .setContentText(text)                    .setContentIntent(contentIntent)                    .setAutoCancel(true);            LinkedList<UpdateInfo> realUpdates = new LinkedList<UpdateInfo>();            for (UpdateInfo ui : availableUpdates) {                if (ui.isNewerThanInstalled()) {                    realUpdates.add(ui);                }            }            Collections.sort(realUpdates, new Comparator<UpdateInfo>() {                @Override                public int compare(UpdateInfo lhs, UpdateInfo rhs) {                    /* sort by date descending */                    long lhsDate = lhs.getDate();                    long rhsDate = rhs.getDate();                    if (lhsDate == rhsDate) {                        return 0;                    }                    return lhsDate < rhsDate ? 1 : -1;                }            });            Notification.InboxStyle inbox = new Notification.InboxStyle(builder)                    .setBigContentTitle(text);            int added = 0, count = realUpdates.size();            for (UpdateInfo ui : realUpdates) {                if (added < EXPANDED_NOTIF_UPDATE_COUNT) {                    inbox.addLine(ui.getName());                    added++;                }            }            if (added != count) {                inbox.setSummaryText(res.getQuantityString(R.plurals.not_additional_count,                        count - added, count - added));            }            builder.setStyle(inbox);            builder.setNumber(availableUpdates.size());            //如果更新条数为1 ,则直接发送开始下载更新的广播            if (count == 1) {                i = new Intent(this, DownloadReceiver.class);                i.setAction(DownloadReceiver.ACTION_START_DOWNLOAD);                i.putExtra(DownloadReceiver.EXTRA_UPDATE_INFO, (Parcelable) realUpdates.getFirst());                PendingIntent downloadIntent = PendingIntent.getBroadcast(this, 0, i,                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);                builder.addAction(R.drawable.ic_tab_download,                        res.getString(R.string.not_action_download), downloadIntent);            }            // Trigger the notification            NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);            nm.notify(R.string.not_new_updates_found_title, builder.build());        }        //发送广播,提醒UpdateSettings类        sendBroadcast(finishedIntent);    }

updateLayout()函数中会更新UI,用户可以点击下载按钮进行更新下载。

当下载开始之后,会广播给`DownloadReceiver’接收器。

     @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        if (ACTION_START_DOWNLOAD.equals(action)) {            UpdateInfo ui = (UpdateInfo) intent.getParcelableExtra(EXTRA_UPDATE_INFO);            handleStartDownload(context, ui);        } else if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);            handleDownloadComplete(context, id);        } else if (ACTION_INSTALL_UPDATE.equals(action)) {            StatusBarManager sb = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);            sb.collapsePanels();            String fileName = intent.getStringExtra(EXTRA_FILENAME);            try {                Utils.triggerUpdate(context, fileName);            } catch (IOException e) {                Log.e(TAG, "Unable to reboot into recovery mode", e);                Toast.makeText(context, R.string.apply_unable_to_reboot_toast,                            Toast.LENGTH_SHORT).show();                Utils.cancelNotification(context);            }        }    }

在该接收器中,如果action为ACTION_START_DOWNLOAD,则进行handleStartDownload()操作。

    private void handleStartDownload(Context context, UpdateInfo ui) {        DownloadService.start(context, ui);    }

启动DownloadService开始操作。

    public static void start(Context context, UpdateInfo ui) {        Intent intent = new Intent(context, DownloadService.class);        intent.putExtra(EXTRA_UPDATE_INFO, (Parcelable) ui);        context.startService(intent);    }

静态方法,启动DownloadService服务。

    @Override    protected void onHandleIntent(Intent intent) {        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);        mInfo = intent.getParcelableExtra(EXTRA_UPDATE_INFO);        if (mInfo == null) {            Log.e(TAG, "Intent UpdateInfo extras were null");            return;        }        try {            getIncremental();        } catch (IOException e) {            downloadFullZip();        }    }

尝试下载增量更新包,如果增量更新包报出异常,则下载全更新包。

    private void downloadFullZip() {        Log.v(TAG, "Downloading full zip");        // Build the name of the file to download, adding .partial at the end.  It will get        // stripped off when the download completes        String fullFilePath = "file://" + getUpdateDirectory().getAbsolutePath() +                "/" + mInfo.getFileName() + ".partial";        long downloadId = enqueueDownload(mInfo.getDownloadUrl(), fullFilePath);        // Store in shared preferences        mPrefs.edit()                .putLong(Constants.DOWNLOAD_ID, downloadId)                .putString(Constants.DOWNLOAD_MD5, mInfo.getMD5Sum())                .apply();        Utils.cancelNotification(this);        Intent intent = new Intent(DownloadReceiver.ACTION_DOWNLOAD_STARTED);        intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);        sendBroadcast(intent);    }

调用enqueueDownload()函数来获取该下载在下载队列中的id:

    private long enqueueDownload(String downloadUrl, String localFilePath) {        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));        String userAgent = Utils.getUserAgentString(this);        if (userAgent != null) {            request.addRequestHeader("User-Agent", userAgent);        }        request.setTitle(getString(R.string.app_name));        request.setDestinationUri(Uri.parse(localFilePath));        request.setAllowedOverRoaming(false);        request.setVisibleInDownloadsUi(false);        // TODO: this could/should be made configurable        request.setAllowedOverMetered(true);        final DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);        return dm.enqueue(request);    }

该操作相当于封装请求服务器的信息,将该信息放到DownloadManager中,获取id。

接着,向DownloadReceiver发送ACTION_DOWNLOAD_STARTED广播。

当下载完成之后,会向DownloadReceiver发送广播ACTION_DOWNLOAD_COMPLET,当接收器接到该广播之后,则调用handleDownloadComplete(context, id)函数来处理下载完成的安装包。

    private void handleDownloadComplete(Context context, long id) {        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);        long enqueued = prefs.getLong(Constants.DOWNLOAD_ID, -1);        if (enqueued < 0 || id < 0 || id != enqueued) {            return;        }        String downloadedMD5 = prefs.getString(Constants.DOWNLOAD_MD5, "");        String incrementalFor = prefs.getString(Constants.DOWNLOAD_INCREMENTAL_FOR, null);        // Send off to DownloadCompleteIntentService        Intent intent = new Intent(context, DownloadCompleteIntentService.class);        intent.putExtra(Constants.DOWNLOAD_ID, id);        intent.putExtra(Constants.DOWNLOAD_MD5, downloadedMD5);        intent.putExtra(Constants.DOWNLOAD_INCREMENTAL_FOR, incrementalFor);        context.startService(intent);        // Clear the shared prefs        prefs.edit()                .remove(Constants.DOWNLOAD_MD5)                .remove(Constants.DOWNLOAD_ID)                .remove(Constants.DOWNLOAD_INCREMENTAL_FOR)                .apply();    }

通过md5检查完整性,接着启动DownloadCompleteIntentService服务来安装更新。

    @Override    protected void onHandleIntent(Intent intent) {        if (!intent.hasExtra(Constants.DOWNLOAD_ID) ||                !intent.hasExtra(Constants.DOWNLOAD_MD5)) {            return;        }        long id = intent.getLongExtra(Constants.DOWNLOAD_ID, -1);        String downloadedMD5 = intent.getStringExtra(Constants.DOWNLOAD_MD5);        String incrementalFor = intent.getStringExtra(Constants.DOWNLOAD_INCREMENTAL_FOR);        Intent updateIntent = new Intent(this, UpdatesSettings.class);        updateIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);        int status = fetchDownloadStatus(id);        if (status == DownloadManager.STATUS_SUCCESSFUL) {            // Get the full path name of the downloaded file and the MD5            // Strip off the .partial at the end to get the completed file            String partialFileFullPath = fetchDownloadPartialPath(id);            if (partialFileFullPath == null) {                displayErrorResult(updateIntent, R.string.unable_to_download_file);            }            String completedFileFullPath = partialFileFullPath.replace(".partial", "");            File partialFile = new File(partialFileFullPath);            File updateFile = new File(completedFileFullPath);            partialFile.renameTo(updateFile);            // Start the MD5 check of the downloaded file            if (MD5.checkMD5(downloadedMD5, updateFile)) {                // We passed. Bring the main app to the foreground and trigger download completed                updateIntent.putExtra(UpdatesSettings.EXTRA_FINISHED_DOWNLOAD_ID, id);                updateIntent.putExtra(UpdatesSettings.EXTRA_FINISHED_DOWNLOAD_PATH,                        completedFileFullPath);                updateIntent.putExtra(UpdatesSettings.EXTRA_FINISHED_DOWNLOAD_INCREMENTAL_FOR,                        incrementalFor);                displaySuccessResult(updateIntent, updateFile);            } else {                // We failed. Clear the file and reset everything                mDm.remove(id);                if (updateFile.exists()) {                    updateFile.delete();                }                displayErrorResult(updateIntent, R.string.md5_verification_failed);            }        } else if (status == DownloadManager.STATUS_FAILED) {            // The download failed, reset            mDm.remove(id);            displayErrorResult(updateIntent, R.string.unable_to_download_file);        }    }

该服务通过封装Intent来返回UpdateSettings中的onNewIntent()函数来进行处理。

    @Override    protected void onNewIntent(Intent intent) {        super.onNewIntent(intent);        // Check if we need to refresh the screen to show new updates        if (intent.getBooleanExtra(EXTRA_UPDATE_LIST_UPDATED, false)) {            updateLayout();        }        //检查下载完成        checkForDownloadCompleted(intent);    }

我们来看一下checkForDownloadCompleted()函数的实现:

    private void checkForDownloadCompleted(Intent intent) {        if (intent == null) {            return;        }        long downloadId = intent.getLongExtra(EXTRA_FINISHED_DOWNLOAD_ID, -1);        if (downloadId < 0) {            return;        }        String fullPathName = intent.getStringExtra(EXTRA_FINISHED_DOWNLOAD_PATH);        if (fullPathName == null) {            return;        }        String fileName = new File(fullPathName).getName();        // If this is an incremental, find matching target and mark it as downloaded.        String incrementalFor = intent.getStringExtra(EXTRA_FINISHED_DOWNLOAD_INCREMENTAL_FOR);        if (incrementalFor != null) {            UpdatePreference pref = (UpdatePreference) mUpdatesList.findPreference(incrementalFor);            if (pref != null) {                pref.setStyle(UpdatePreference.STYLE_DOWNLOADED);                pref.getUpdateInfo().setFileName(fileName);                //调用更新安装                onStartUpdate(pref);            }        } else {            // Find the matching preference so we can retrieve the UpdateInfo            UpdatePreference pref = (UpdatePreference) mUpdatesList.findPreference(fileName);            if (pref != null) {                pref.setStyle(UpdatePreference.STYLE_DOWNLOADED);                onStartUpdate(pref);            }        }        resetDownloadState();    }

该函数通过调用onStartUpdate(pref)来安装更新:

    @Override    public void onStartUpdate(UpdatePreference pref) {        final UpdateInfo updateInfo = pref.getUpdateInfo();        // Prevent the dialog from being triggered more than once        if (mStartUpdateVisible) {            return;        }        mStartUpdateVisible = true;        // Get the message body right        String dialogBody = getString(R.string.apply_update_dialog_text, updateInfo.getName());        // Display the dialog        new AlertDialog.Builder(this)                .setTitle(R.string.apply_update_dialog_title)                .setMessage(dialogBody)                .setPositiveButton(R.string.dialog_update, new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int which) {                        try {                            //提醒用户是否安装,用户点击确定之后,triggerUpdate()                            Utils.triggerUpdate(UpdatesSettings.this, updateInfo.getFileName());                        } catch (IOException e) {                            Log.e(TAG, "Unable to reboot into recovery mode", e);                            Toast.makeText(UpdatesSettings.this, R.string.apply_unable_to_reboot_toast,                                    Toast.LENGTH_SHORT).show();                        }                    }                })                .setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {                    @Override                    public void onClick(DialogInterface dialog, int which) {                        mStartUpdateVisible = false;                    }                })                .show();    }

提醒用户是否安装,用户点击安装之后,激发triggerUpdate()函数进行安装:

    public static void triggerUpdate(Context context, String updateFileName) throws IOException {        /*         * Should perform the following steps.         * 1.- mkdir -p /cache/recovery         * 2.- echo 'boot-recovery' > /cache/recovery/command         * 3.- if(mBackup) echo '--nandroid'  >> /cache/recovery/command         * 4.- echo '--update_package=SDCARD:update.zip' >> /cache/recovery/command         * 5.- reboot recovery         */        // Set the 'boot recovery' command        Process p = Runtime.getRuntime().exec("sh");        OutputStream os = p.getOutputStream();        os.write("mkdir -p /cache/recovery/\n".getBytes());        os.write("echo 'boot-recovery' >/cache/recovery/command\n".getBytes());        // See if backups are enabled and add the nandroid flag        /* TODO: add this back once we have a way of doing backups that is not recovery specific           if (mPrefs.getBoolean(Constants.BACKUP_PREF, true)) {           os.write("echo '--nandroid' >> /cache/recovery/command\n".getBytes());           }           */        // Add the update folder/file name        // Emulated external storage moved to user-specific paths in 4.2        String userPath = Environment.isExternalStorageEmulated() ? ("/" + UserHandle.myUserId()) : "";        String cmd = "echo '--update_package=" + getStorageMountpoint(context) + userPath            + "/" + Constants.UPDATES_FOLDER + "/" + updateFileName            + "' >> /cache/recovery/command\n";        os.write(cmd.getBytes());        os.flush();        // Trigger the reboot        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);        powerManager.reboot("recovery");    }

/*
* Should perform the following steps.
* 1.- mkdir -p /cache/recovery
* 2.- echo ‘boot-recovery’ > /cache/recovery/command
* 3.- if(mBackup) echo ‘–nandroid’ >> /cache/recovery/command
* 4.- echo ‘–update_package=SDCARD:update.zip’ >> /cache/recovery/command
* 5.- reboot recovery
*/

在注释中,说明了必须运行下面步骤:

  1. mkdir -p /cache/recovery //创建文件夹
  2. echo ‘boot-recovery’ > /cache/recovery/command
  3. if(mBackup) echo ‘–nandroid’ >> /cache/recovery/command
  4. echo ‘–update_package=SDCARD:update.zip’ >> /cache/recovery/command
  5. reboot recovery

这五个命令运行完成之后,便会重启安装系统更新了。

更多相关文章

  1. android 嵌套ViewPager + Fragment实现仿头条UI框架Demo
  2. WordPress for Android(安卓)已更新到2.0,网站管理也移动了
  3. Android广播机制详解
  4. 版本更新
  5. android 网络广播 类似QQ动态检查网络
  6. Android(安卓)sdk更新代理配置
  7. Android(安卓)UI开发专题(四) View自绘控件
  8. android的意图
  9. Android(安卓)Studio报错 (androidx.recyclerview.widget.Recycl

随机推荐

  1. Android缓存处理和清除数据、清除缓存、
  2. Android极光推送(Android(安卓)studio 3.
  3. 【Android开发学习17】Android OpenGL ES
  4. Android 当打开“开发者模式”中的“不保
  5. Android上 用Html5做界面,javascript调用
  6. Android智能硬件开发心得总结(二)
  7. Android之Zygote介绍
  8. Android Studio对真机和模拟器进行截图
  9. 关于Android唯一标识符
  10. 【Android】static静态变量的生命周期—