Android运维--app更新升级

整个流程分为三步:
1.版本检测
2.新apk文件下载
3.覆盖安装
基本流程就是这样的了, 区别在于怎么安排, 用什么策略更新. 网上的思路基本上是, 在主页里检测apk版本, 需要更新就弹一个对话框给用户, 提醒其更新. 用户确定更新, 就在主页中或者开一个服务, 利用downloadmanager进行文件下载广播注册, 下载完了在广播中进行自动安装(downloadmanager下载完了会发出一条广播).
在实际项目中, 考虑到版本维护的成本, 更新策略选择强制用户更新. 具体做法有点类似与应用商店的静默安装, 有更新就先后台下载, 等到下次打开弹出一个对话框进行安装.

一. 版本检测

思路
一般最新的版本信息会以json文件的形式放在服务器上, 我们要做的就是利用OKhttp3下载这个json文件, 解析取出对应字段, 和本地versionName比较, 不同就进入下一步.
OKhttp3
Android OkHttp完全解析 是时候来了解OkHttp了
Android 一个改善的okHttp封装库
Android Https相关完全解析 当OkHttp遇到Https
本地版本信息获取

PackageManager packageManager = getPackageManager();
#0 means all the flags are turned off
PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);
String version = packageInfo.versionName;
二. 新apk文件下载

思路
这里使用Google推荐的DownloadManager来进行异步下载
DownloadManager
如果你不想纠结什么断点续传, 网络环境啥的, 那么你就使用DownloadManager吧

//首先, 构建一个下载请求, uri 是你的下载地址,可以使用Uri.parse("http://")包装成Uri对象
DownloadManager.Request req = new DownloadManager.Request(uri);

//通过setAllowedNetworkTypes方法可以设置允许在何种网络下下载
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);

// 此方法表示在下载过程中通知栏会一直显示该下载,在下载完成后仍然会显示,
// 直到用户点击该通知或者消除该通知。还有其他参数可供选择
//req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//我希望静悄悄地下载
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);

// 设置下载文件存放的路径,同样你可以选择以下方法存放在你想要的位置。
// setDestinationUri
// setDestinationInExternalPublicDir
req.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, title);

// 设置一些基本显示信息
//req.setTitle("Android.apk");
//req.setDescription("下载完后请点击打开");
req.setMimeType("application/vnd.android.package-archive");

// 构建完下载任务, 传入downloadmanager进行下载, 是不是很像我们使用迅雷下载电影
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = dm.enqueue(req);

下载的基本配置就是这样, 还是挺简单的, 并且性能较高.
在实际使用中我们更应该关心的是下载前的准备工作和下载后的善后工作.
下载前的准备工作
首先, 我的需求是发现有新版本, 先下载, 下载完后等到用户再次打开软件进行更新操作. 所以这里就有一个判断最新软件包是否下载完的逻辑判断, 没有就下载, 如果已经就直接进入到安装环节.

 /**
     * 下载APK文件
     *
     * @param context
     * @param url
     * @param info
     * @param appName
     */
    public void downloadApk(MainActivity mainActivity, final Context context, String url, String info, final String appName) {
        //获取存储的下载ID
        long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
        if (downloadId != -1) {
            //获取当前状态
            int status = getDownloadStatus(downloadId);
            //状态为下载成功
            if (DownloadManager.STATUS_SUCCESSFUL == status) {
                //获取下载路径URI
                final Uri downloadUri = getDownloadUri(downloadId);
                if (downloadUri != null) {
                    //存在下载的APK,如果两个APK相同,启动更新界面。否之则删除,重新下载。
                    //安装逻辑......
                        return;
                    } else {
                        //删除下载任务以及文件
                        mDownloadManager.remove(downloadId);
                    }
                }
                start(context, url, info, appName);
            } else if (DownloadManager.STATUS_FAILED == status) {
                //下载失败,重新下载
                start(context, url, info, appName);
            } else {
                Log.d(context.getPackageName(), "apk is already downloading");
            }
        } else {
            //不存在downloadId,没有下载过APK
            start(context, url, info, appName);
        }
    }

上面代码中涉及获取下载文件以及判断下载状态的操作, 均与downloadmanager有关:

//获取下载文件
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null) {
    if (c.moveToFirst()) {
        String fileUri = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
        // TODO 你可以在这里处理你的文件
    }
    c.close();
}

//获取下载状态
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null && c.moveToFirst()) {
    int status = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
    switch (status) {
        case DownloadManager.STATUS_PENDING:
            break;
        case DownloadManager.STATUS_PAUSED:
            break;
        case DownloadManager.STATUS_RUNNING:
            break;
        case DownloadManager.STATUS_SUCCESSFUL:
            break;
        case DownloadManager.STATUS_FAILED:
            break;
}
if (c != null) {
    c.close();
}

当然, 下载前需要先判断一下能不能下载, WiFi有没有打开, 没有打开跳转到手机设置页

    public boolean canDownload() {
        try {
            int state = mContext.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
            if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                    || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
                    || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public void skipToDownloadManager() {
        String packageName = "com.android.providers.downloads";
        Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.parse("package:" + packageName));
        mContext.startActivity(intent);
    }

下载后的善后工作
下载完后, downloadmanager会发送一条DownloadManager.ACTION_DOWNLOAD_COMPLETE广播,并传递downloadId作为参数, 我们可以自定义一个广播接收器接收这条广播完成自动安装. 但这里我们时重启APP后安装, 所以用不到这个广播功能.
但是这里我们要做的工作是安装完后删除apk文件, 虽然很多手机能够自动安装后删除, 但为了以防万一嘛.
这个删除判断放在下载文件前, 逻辑是: 如果存在apk文件并且已经安装过了就删除.

    public void removeFile(Context context) {
        //获取存储的下载ID
        long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
        if (downloadId != -1) {
            //或者使用mDownloadManager.remove(downloadId)直接删除文件;
            Uri filePath = getDownloadUri(downloadId);
            if (filePath != null) {
                //删除之前先判断用户是否已经安装了,安装了才删除, 判断逻辑还是检测versionCode
                if (!compare(getApkInfo(context, filePath.getPath()), context)) {
                    File downloadFile = new File(filePath.getPath());
                    if (null != downloadFile && downloadFile.exists()) {
                        downloadFile.delete();
                    }
                }
            }
        }
    }
三. 覆盖安装

思路
最后就是覆盖安装了, 看你选择什么策略了, 我这里是直接弹出一个用户不能拒绝的对话框, 让他更新, 以节省后期维护成本

    if (compare(getApkInfo(context, downloadUri.getPath()), context)) {
        AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
        builder.setTitle("检测到有新版本!");
        builder.setMessage(info);
        builder.setCancelable(false);
        builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                startInstall(context, downloadUri);
            }
        });
        mAlertDialog = builder.create();
        handler.sendEmptyMessageDelayed(0, 1000);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • 一、前言 提到 APK 更新,大家可能会想到友盟(umeng)更新,市场上已有数万款应用在使用友盟自动更新的服务。...
    文淑阅读 6,976评论 2 40
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 公司开发时候,应该最常用的就是APP升级功能,倒不是说的是热修复等技术,而是普通的检测到服务器版本比本地手机版本高...
    青蛙要fly阅读 4,516评论 12 86
  • 峻岭青松越峰巅, 白桦丛林世外园; 潺潺溪水闻声乐, 恋景欣怡不归还。
    月夜秋荷阅读 182评论 0 1