【Android OTA】系统的更新升级

timg-3.jpeg

原文链接

上一篇说了Android应用的升级,这篇来介绍下关于整个系统的升级。公司的车载系统使用了MTK的板子,深度定制的Android系统,平时开发过程中的修改可以直接重新烧录固件,但设备量产投入市场之后的修改只能通过OTA的方式进行更新。

系统升级一般通过差分包的方式,因为整包更新需要的安装包有几百M,一般的小更新打个差分包的话也就几M十几M而已,能省不少流量。

设备检测更新的流程基本和上一篇文章一样,系统可以在设置界面上增加检测升级的入口。

系统升级流程

  1. 向服务器请求系统更新信息;
  2. 获取到返回信息后,判断是否有更新版本,若有更新,进入步骤3;
  3. 下载服务器对应的更新文件;
  4. 下载完成后验证更新文件;
  5. 系统重启进入recovery模式进行升级,升级完成后设备重启。

更新信息的请求和返回信息的处理过程与应用的更新升级一致。

更新文件的下载这次使用了系统自带的类 DownloadManager

DownloadManager 能自动管理系统的下载任务,在联网发生变化、系统开关机、断点续传等情况自行进行处理。

创建下载任务,设置指定下载目录

mDownloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDestinationInExternalPublicDir(OTAUtil.otaFolderName(), OTAUtil.otaFileName());
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setVisibleInDownloadsUi(false);
mDownloadId = mDownloadManager.enqueue(request);

DownloadManager 下载进度可以显示在通知栏中,也可以选择只在特定联网情况下(如wifi、移动信号)进行任务。

监听下载状态

class CompleteReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (completeDownloadId == mDownloadId) {
            // download successful
            String msg;
            switch (OTAUtil.getOTADownloadStatus(completeDownloadId, mDownloadManager)) {
                case DownloadManager.STATUS_FAILED:
                    msg = "Download failed!";
                    break;

                case DownloadManager.STATUS_PAUSED:
                    msg = "Download paused!";
                    break;

                case DownloadManager.STATUS_PENDING:
                    msg = "Download pending!";
                    break;

                case DownloadManager.STATUS_RUNNING:
                    msg = "Download in progress!";
                    break;

                case DownloadManager.STATUS_SUCCESSFUL:
                    break;

                default:
                    msg = "Download is nowhere in sight";
                    break;
            }

            Log.i(TAG, "CompleteReceiver onReceive...." + msg);
        }
    }
}

用来更新界面的进度条

class DownloadChangeObserver extends ContentObserver {

    public DownloadChangeObserver() {
        super(mHandler);
    }

    @Override
    public void onChange(boolean selfChange) {

        int progress = OTAUtil.getOTADownloadPro(mDownloadId, mDownloadManager);

        Message msg = mHandler.obtainMessage();
        msg.what = UPDATE_DOWNLOAD_PROGRESS;
        msg.arg1 = progress;
        mHandler.sendMessage(msg);
    }
}

其中getOTADownloadPro 方法通过id获取指定下载任务的进度值

public static int getOTADownloadPro(long id, DownloadManager downloadManager) {
    double progress = 0.0;

    DownloadManager.Query q = new DownloadManager.Query();
    q.setFilterById(id);

    Cursor c = downloadManager.query(q);
    if (c.moveToFirst()) {
        int sizeIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
        int downloadedIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
        long size = c.getInt(sizeIndex);
        long downloaded = c.getInt(downloadedIndex);

        if (size != -1) {
            progress = downloaded * 100.0 / size;
        }
    }
    c.close();

    return (int)progress;
}

这里有一个注意问题,正常情况下使用DownloadManager下载的文件只能保存在系统的外部存储中(系统级的DownloadManager有个私有方法能将文件保存在内部存储),而调用系统的接口更新升级时,recovery模式下系统文件路径会重定向,此时无法找到原来保存在外部存储的文件。所以为了保证能正常安装,下载好的更新文件要保存到内部存储中。

将更新文件从外部存储拷贝到/data/recovery/ota.zip路径下,这里使用了第三方库 RootTools,能够以命令行的方式,将文件从源目录拷贝到指定目录。

因为要对内部存储进行读写,所以记得加上对应的权限

<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>

安装更新的函数具体实现

public static boolean installOtaPackageAuto(final Context context, File file) {

    String internalPath = "/data/recovery/ota.zip";
    String cmd = "cat " + file.getAbsolutePath() + " > " + internalPath;

    String res = Tools.shell(cmd, false);
    Log.i(TAG, "shell: " + res);

    File otaPackageFile = new File(internalPath);

    try {
        RecoverySystem.verifyPackage(otaPackageFile , null , null);
    }
    catch ( IOException e ) {
        ((HDUpdateActivity)context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, "RecoverySystem verifyPackage failed, file doesn't exist", Toast.LENGTH_LONG).show();
            }
        });
        e.printStackTrace( );
        return false;
    }
    catch ( GeneralSecurityException e ) {
        ((HDUpdateActivity)context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, "RecoverySystem verifyPackage failed, invalid package", Toast.LENGTH_LONG).show();
            }
        });
        e.printStackTrace( );
        return false;
    }

    try {
        RecoverySystem.installPackage( context , otaPackageFile );
    }
    catch ( IOException e ) {
        ((HDUpdateActivity)context).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, "RecoverySystem installPackage error, failed to install", Toast.LENGTH_LONG).show();
            }
        });
        e.printStackTrace( );
        return false;
    }
    return true;
}

传入的参数 fileDownloadManager已经下载到外部存储的OTA更新文件,通过执行cat命令,将更新文件保存到内部存储/data/recovery/ota.zip

String internalPath = "/data/recovery/ota.zip";
String cmd = "cat " + file.getAbsolutePath() + " > " + internalPath;

String res = Tools.shell(cmd, false);

调用了系统方法verifyPackage验证更新包是否合法,若验证失败则安装过程终止

RecoverySystem.verifyPackage(otaPackageFile , null , null);

接下来直接调用系统安装更新的方法

RecoverySystem.installPackage( context , otaPackageFile );

若成功则设备重启,并进入recovery模式更新升级,升级完成后设备重启。

制作差分包

MTK系统的目录

1.jpg

可以看到每次通过make命令编译后生成的image文件既是 SP Flash Tool 烧录需要用到的固件。

ota打包

make otapackage

打包完成后命令行打印的结果

2.jpg

这里有两个文件需要注意的,一个在out/target/product/CM01B目录下,一个在out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目录下(CM01B为产品编号,每个项目不一样),虽然文件名一样,但前者有400多M,后者800多M,和image固件大小差不多。

out/target/product/CM01B目录下的文件为完全更新的包,可以用来给系统进行完全升级。

out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目录下的文件主要用来生成差分包(具体能不能直接用来升级没尝试过),通过两个这种包可以生成差分包文件。

系统生成差分包的工具在这个位置,官方标准工具,里面包含了制作差分包的脚本

3.jpg

这里需要注意,执行差分包命令时必须在根目录下执行,因为脚本里面写定了相对路径的引用文件。

制作差分包需要准备两个不同版本的文件(out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目录下的),假设分别为a.zipb.zip

将两个文件拷贝到根目录下,然后运行命令

./build/tools/releasetools/ota_from_target_files -i a.zip b.zip ota.zip

最终生成的差分包文件为ota.zip,即b相对于a修改了的内容,只能用在a上将a升级成b。ota.zip差分包的升级使用要求当前系统必须是a,否则无法进行升级。

系统升级

系统通过差分包升级,安装时进入刷机界面,完成后自动重启,升级完成

4.jpg

如果差分包有不正确或者没保存到内部存储中会报错

5.jpg

以上

源码和设备相关联的,这次就不给了。完。:)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 原文链接 Android应用经常会内置检测版本更新的功能,在有版本更新的时候,通过下载更新文件进行本地的升级。本文...
    msq3阅读 10,602评论 0 19
  • 【作者】唐志燕 【指导老师】刘艳老师 【内容解说】这是金库老师今天给家长上的V导师课程笔记。 主要讲了两个板块的内...
    静心和阅读 181评论 0 0
  • 春来花香 雨来草长 晨来鸟唱 花香四溢只春一季 草长莺飞皆因润雨 鸟声欢啼以慰晨意 我一心一念独为等你 你来我喜 ...
    笨笨的平凡生活阅读 166评论 0 0