android的开发有时候让人很蛋疼,比如说app的更新,像ios的更新都被app store包办了方便快捷,但是国内的安卓市场却五花八门,途径各种各样。不要太期望像苹果那样把更新全权交个某夹或者某0,app内都要自己继承一个自动更新的功能。那我们就要自己实现一个。

效果图如下:

android app自己更新并启动 安卓app自动更新功能开发_ide

android app自己更新并启动 安卓app自动更新功能开发_android app自己更新并启动_02

更新分成2种,一种是普通更新(如上图1)用户可以选择不更新,一种是强制更新(如上图2),新版本已经不再向下兼容,更新时会锁定屏幕,用户要么更新要么退出。

这里我把代码分享一下,更新功能都集成到了一个library中方便调用,下面先说下使用方法:

使用方法:

首先下载更新功能库library-version,导入工程后关联到自己项目:
library-version下载链接

然后在自己项目中需要检查更新的地方添加如下代码:

UpdateHelper updateHelper = new UpdateHelper.Builder(this)
        .checkUrl(url)
        .isAutoInstall(false) //设置为false需在下载完手动点击安装,默认值为true,下载后自动安装。
        .build();
    updateHelper.check();

如果你有复杂的需求,需要在更新过程中加入自己的处理逻辑,那么可以使用下面的接口:

UpdateHelper updateHelper = new UpdateHelper.Builder(this)
        .checkUrl(url)
        .isAutoInstall(false) //设置为false需在下载完手动点击安装,默认值为true,下载后自动安装。
        .build();
    updateHelper.check(new OnUpdateListener() {
            @Override
            public void onStartCheck() {
            }
            @Override
            public void onFinishCheck(UpdateInfo info) {
            }
            @Override
            public void onStartDownload() {
            }
            @Override
            public void onInstallApk() {
            }
            @Override
            public void onFinshDownload() {
            }
            @Override
            public void onDownloading(int progress) {
            }
        });

有几点需要注意:
第一点:其中isAutoInstall(false)只适用于普通更新,强制更新一律自动安装。
第二点:其中url是你的apk的下载接口,你需要提供一个如下的接口:

{
    "status":1,
    "isForce":1,
    "apkUrl":"http://dl.m.duoku.com/game/49000/49554/20150206145821_DuoKu.apk",
    "versionCode":14,
    "updateTips":"update1.1.7.2",
    "versionName":"1.1.7.2",
    "changeLog":"1.新增了用户信息的采集<br>2.修复了路径规划节点超过1000个异常崩溃的bug",
    "size":"25M"
}

参数说明:

参数名

类型

说明

是否必选

status

int

请求是否成功(1成功其他否)


isForce

int

是否强制更新(1是0否)


apkUrl

String

下载url


versionCode

int

版本号


updateTips

String

更新标题


versionName

String

版本名


changeLog

String

更新说明


size

String

包大小



其中版本号versionCode是判断是否需要更新的关键,它被设置在你的app的AndroidManifest.xml文件中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ccrv.cber"
    android:versionCode="13"
    android:versionName="1.1.7.1" >

而版本名versionName只是用于给用户作展示用,它并不参与到逻辑中。这是android的版本管理规范。

提供了以上的接口功能集成就完成了,很方便吧。

下面解释下主要代码:不看实现原理的可以跳过了

先看下目录结构:

android app自己更新并启动 安卓app自动更新功能开发_自动更新_03

解释下每个包中的类是用来干嘛的:

  • UpdateHelper:更新功能的主流程,程序的入口
  • VersionDialog:复用的几种的弹出对话
  • OnUpdateListener:更新过程的回调接口,如果有回调需要则需要自己实现
  • UpdateInfo:版本信息的实体类
  • com.shelwee.update.utils包下则都是些网络相关的工具类

library的主要处理流程都集中在UpdateHelper类中,在检查更新的时候首先调用了check方法:

public void check(OnUpdateListener listener) {
        initButtonReceiver();
        if (listener != null) {
            this.updateListener = listener;
        }
        if (mContext == null) {
            Log.e("NullPointerException", "The context must not be null.");
            return;
        }
        AsyncCheck asyncCheck = new AsyncCheck();
        asyncCheck.execute(checkUrl);
    }

在check方法内部启动异步任务AsyncCheck去我们提供的接口获取服务器目前app信息,
通过JSONHandler类来获取返回信息并封装成UpdateInfo实体对象:

@Override
        protected UpdateInfo doInBackground(String... params) {
                ...
            updateInfo = JSONHandler.toUpdateInfo(HttpRequest.get(url));
                ...
            return updateInfo;
        }

在onPostExecute回调方法中,比较服务器最新版本和本地版本号,如果不是最新,则调用showUpdateUI(updateInfo)方法:

protected void onPostExecute(UpdateInfo updateInfo) {
    if (mContext != null && updateInfo != null) {
        if (Integer.parseInt(updateInfo.getVersionCode()) > getPackageInfo().versionCode) {
            showUpdateUI(updateInfo);
            ...
        } else {
            Toast.makeText(mContext, "当前已是最新版", Toast.LENGTH_LONG).show();
            ...
        }
    }
}

在showUpdateUI方法中判断是否是强制更新来弹出不同的窗口和处理事件,并在点击事件中判断用户当前是否使用流量,如果是则调用showNetDialog(updateInfo)提示,如果不是则启动下载异步任务asyncDownLoad.execute(updateInfo):

/**
     * 弹出提示更新窗口
     */
    private void showUpdateUI(final UpdateInfo updateInfo) {
    if (updateInfo.getIsForce()==0) {
            //普通更新
            final VersionDialog dialog = new VersionDialog(...,STYLE_UPDATE_NOMAL);
            dialog.setOnPositiveListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    dialog.dismiss();
                    NetWorkUtils netWorkUtils = new NetWorkUtils(mContext);
                    int type = netWorkUtils.getNetType();
                    if (type != 1) {
                        showNetDialog(updateInfo);
                    }else {
                        AsyncDownLoad asyncDownLoad = new AsyncDownLoad();
                        asyncDownLoad.execute(updateInfo);
                    }
                }
            });
            dialog.show();
        }else {
            //强制更新
            final VersionDialog dialog = new VersionDialog(...,STYLE_UPDATE_FORCE);
            ...
            dialog.show();
        }
    }

下载任务中,每5%调用handler发送一次UPDATE_NOTIFICATION_PROGRESS消息来更新UI中的进度条;
并在下载完成的回调函数onPostExecute中给handler发送COMPLETE_DOWNLOAD_APK来通知安装:

@Override
        protected Boolean doInBackground(UpdateInfo... params) {
            ...
            FileOutputStream fos = new FileOutputStream(apkFile);
            while ((length = inputStream.read(buf)) != -1) {
                ...
                int progress = (int) ((count / (float) total) * 100);
                if (progress % 5 == 0) {
                    handler.obtainMessage(UPDATE_NOTIFICATION_PROGRESS,progress, -1, params[0]).sendToTarget();
                }
                ...
            }
            inputStream.close();
            fos.close();
            ...
        }

        @Override
        protected void onPostExecute(Boolean flag) {
            ...
            handler.obtainMessage(COMPLETE_DOWNLOAD_APK).sendToTarget();
            ...
        }

在handler的处理函数中,UPDATE_NOTIFICATION_PROGRESS消息调用showDownloadNotificationUI()来更新UI;
COMPLETE_DOWNLOAD_APK消息中判断是否自动安装来调用installApk()自动安装:

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case UPDATE_NOTIFICATION_PROGRESS:
                showDownloadNotificationUI((UpdateInfo) msg.obj, msg.arg1);
                break;
            case COMPLETE_DOWNLOAD_APK:
                if (UpdateHelper.this.isAutoInstall) {
                    ...
                    //自动安装
                    installApk(Uri.parse("file://" + cache.get(APK_PATH)));
                } else {
                    //点击通知栏消息进行手动安装
                    ...
                }
            }
            ...
        }
    }

showDownloadNotificationUI判断是否是强制更新来决定是通知栏提示进度或者对话框提示进度:

private void showDownloadNotificationUI(UpdateInfo updateInfo,final int progress) {
        if (mContext != null) {
            if (updateInfo.getIsForce()==0) {
                // 普通更新,通知栏提示
                ...
                notificationManager.notify(DOWNLOAD_NOTIFICATION_ID,ntfBuilder.build());
            }else {
                // 强制更新,对话框提示
                ...
                pd.show();
            }
        }
    }

下面是安装apk的方法:

private void installApk(Uri data) {
        ...
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setDataAndType(data, "application/vnd.android.package-archive");
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.startActivity(i);
        ...
    }

以上插入的代码都省略过,为了方便讲解只贴出了重点代码行,具体代码请查看源码。