Android 从零开始撸一个应用内更新Demo

对于Android app来说,应用内更新几乎成了一个标配的功能了。原理其实不难,今天我们就从零开始撸一个自己的应用内更新的demo出来。

先看看最终实现的效果:

Picture

上图的效果,稍微将功能拆分一下,可以总结为以下几点。

1\. 检查更新;
2\. 最新apk下载;
3\. apk下载成功后应用内跳转安装;

1.检查更新

为了检验检查更新的效果,我们需要一个tomcat服务器。至于tomcat怎么搭建,这里就不花篇幅去讲了,网上资料还是很多的。
Tomcat部署完成后,在Tomcat ROOT目录上新建一个本次demo的目录,并且将新版的apk文件和一个保存了新版apk相关信息的json文件放在demo目录下。如下图所示:

Picture

各位应该已经想到检查更新的原理了,其实就是解析保存了新版apk信息的json文件,然后根据json中新版apk的版本信息来判断当前apk是否有可以更新。
我们这里模拟一个新版apk相关信息的json文件内容。

{"data":{"content":"更新内容如下:\n   1.xxxxxx;\n   2.xxxxxx;\n   3.xxxxxx;\n","id":"1","api_key":"android","version_code":"2","version_name":"1.0.2"},"msg":"获取成功","status":1}

可以看到,对于检查更新来说,最重要的几个信息都包含在data字段中,包括了更新内容,新版apk版本号,新版apk版本名称等。当然,根据实际需求,这个json可能会有所不同,具体项目中可以做一些修改,届时解析的时候稍作改动就好了。

接下来完成检查更新相关代码:

    /**
     * 检查更新
     */
    public void checkUpdate(OnCheckUpdateListener onCheckUpdateListener) {
        mOnCheckUpdateListener = onCheckUpdateListener;
        HttpUtils.sendOkHttpRequest(mUpdateHelper.getNewestApkVersionInfoUrl(), new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                ToastUtils.showToast("check update failed.");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String strJson = response.body().string();
                Log.e("onResponse", "response.body().string() = " + strJson);
                if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
                    sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
                } else {
                    sendMessage(MSG_ON_NEWEST, null);
                }
            }
        });
    }

    /**
     * 解析json数据
     *
     * @param jsonData json数据
     *                 {
     *                 "data": {
     *                 "content": "更新内容如下:1.xxxxxx;/n 2.xxxxxx;/n 3.xxxxxx;/n",
     *                 "id": "1",
     *                 "api_key": "update test",
     *                 "version_code": "2"
     *                 },
     *                 "msg": "获取成功",
     *                 "status": 1
     *                 }
     * @return dataBean
     */
    private DataBean parseJson(String jsonData) {
        DataBean dataBean = new DataBean();

        try {
            JSONObject jsonObject = new JSONObject(jsonData);
            JSONObject dataObject = jsonObject.getJSONObject("data");
            dataBean.setContent(dataObject.getString("content"));
            dataBean.setId(dataObject.getInt("id"));
            dataBean.setApiKey(dataObject.getString("api_key"));
            dataBean.setVersionCode(dataObject.getInt("version_code"));
            dataBean.setVersionName(dataObject.getString("version_name"));
            //            Log.e("parseJson", "content " + dataObject.getString("content"));
            //            Log.e("parseJson", "id " + dataObject.getInt("id"));
            //            Log.e("parseJson", "api_key " + dataObject.getString("api_key"));
            //            Log.e("parseJson", "version_code " + dataObject.getInt("version_code"));
            //            Log.e("parseJson", "version_name " + dataObject.getString
            // ("version_name"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataBean;
    }

2.最新apk文件下载

如何检查更新的代码搞定了,接下来是稍微麻烦一点的,就是下载apk文件。
由于下载文件可以作为一个单独的功能,所以将下载文件这一块单独独立出来作为一个子模块来编写。其实谷歌官方有提供专门的DownloadManager来下载文件,但是很多国内的rom把DownloadManager阉割了。所以这里我们自己来撸一个DownloadManager,具体代码如下,注释还是比较清晰的,就不多做解释了。

DownloadManager.java:

public class DownloadManager {
    private static volatile DownloadManager manager = null;

    private DownloadTask downloadTask;

    private String mFileName;

    private String mFileParentPath;

    private DownloadManager() {
        mFileParentPath = DownloadHelper.getDownloadParentFilePath();
    }

    public static DownloadManager getInstance() {
        if (manager == null) {
            synchronized (DownloadManager.class) {
                if (manager == null) {
                    manager = new DownloadManager();
                }
            }
        }
        return manager;
    }

    /**
     * 开启下载任务
     *
     * @param url                下载链接
     * @param onDownloadListener onDownloadListener
     */
    public void startDownload(String url, OnDownloadListener onDownloadListener) {
        startDownload(url, DownloadHelper.getDownloadParentFilePath(), DownloadHelper
                        .getUrlFileName(url)
                , onDownloadListener);
    }

    /**
     * 开启下载任务
     *
     * @param url                下载链接
     * @param fileName           指定下载文件名
     * @param onDownloadListener onDownloadListener
     */
    public void startDownload(String url, @NonNull String fileName, OnDownloadListener
            onDownloadListener) {
        startDownload(url, DownloadHelper.getDownloadParentFilePath(), fileName,
                onDownloadListener);
    }

    /**
     * 开启下载任务
     *
     * @param url                下载链接
     * @param fileParentPath     指定下载文件目录
     * @param fileName           指定下载文件名
     * @param onDownloadListener onDownloadListener
     */
    public void startDownload(String url, @NonNull String fileParentPath, @NonNull String fileName,
                              OnDownloadListener onDownloadListener) {
        if (StringUtils.isEmpty(fileParentPath))
            fileParentPath = DownloadHelper.getDownloadParentFilePath();

        if (StringUtils.isEmpty(fileName))
            fileName = DownloadHelper.getUrlFileName(url);

        mFileParentPath = fileParentPath;
        mFileName = fileName;

        if (downloadTask == null) {
            downloadTask = new DownloadTask(onDownloadListener);
            downloadTask.execute(url, fileParentPath, fileName);
            downloadTask.setOnDownloadTaskFinshedListener(new DownloadTask
                    .OnDownloadTaskFinshedListener() {
                @Override
                public void onFinished() {
                    downloadTask = null;
                }

                @Override
                public void onCanceled() {
                    //下载任务取消,删除已下载的文件
                    clearCacheFile(getDownloadFilePath());
                }

                @Override
                public void onException() {
                    //下载任务异常,删除已下载的文件
                    clearCacheFile(getDownloadFilePath());
                }
            });
        }
    }

    /**
     * 暂停下载任务
     */
    public void pauseDownload() {
        if (downloadTask != null) {
            downloadTask.pauseDownload();
        }
    }

    /**
     * 取消下载任务
     */
    public void cancelDownload() {
        if (downloadTask != null) {
            downloadTask.cancelDownload();
        }
    }

    /**
     * 清除下载的全部cache文件
     */
    public void clearAllCacheFile() {
        if (StringUtils.isEmpty(mFileParentPath))
            return;

        FileUtils.delAllFile(mFileParentPath);
    }

    /**
     * 清除下载的cache文件
     *
     * @param filePath 要删除文件的绝对路径
     */
    public void clearCacheFile(String filePath) {
        if (StringUtils.isEmpty(filePath))
            return;

        FileUtils.delFile(filePath);
    }

    /**
     * 获取下载文件的全路径
     *
     * @return 下载文件的全路径
     */
    public String getDownloadFilePath() {
        if (StringUtils.isEmpty(mFileParentPath) || StringUtils.isEmpty(mFileName))
            return null;

        return mFileParentPath + "/" + mFileName;
    }
}

DownloadTask.java

public class DownloadTask extends AsyncTask<String, Integer, Integer> {

    private static final int TYPE_SUCCESS = 0;
    private static final int TYPE_FAILED = 1;
    private static final int TYPE_PAUSED = 2;
    private static final int TYPE_CANCELED = 3;

    private OnDownloadListener mOnDownloadListener;
    private OnDownloadTaskFinshedListener mOnDownloadTaskFinshedListener;

    private boolean isCanceled = false;

    private boolean isPaused = false;

    private int lastProgress;

    private File mDownloadFile = null;

    private long mContentLength; // 记录url下载文件的长度

    public DownloadTask(OnDownloadListener onDownloadListener) {
        mOnDownloadListener = onDownloadListener;
    }

    public void setOnDownloadTaskFinshedListener(OnDownloadTaskFinshedListener
                                                         onDownloadTaskFinshedListener) {
        mOnDownloadTaskFinshedListener = onDownloadTaskFinshedListener;
    }

    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        try {

            long downloadLength = 0; // 记录已下载的文件长度
            String downloadUrl = params[0];
            String fileParentPath = params[1];
            String fileName = params[2];
            mDownloadFile = new File(fileParentPath, fileName);
            if (mDownloadFile.exists()) {
                downloadLength = mDownloadFile.length();
            }

            mContentLength = getContentLength(downloadUrl);
            if (mContentLength == 0) {
                return TYPE_FAILED;
            } else if (mContentLength == downloadLength) {
                // 已下载字节和文件总字节相等,说明已经下载完成了
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    // 断点下载,指定从哪个字节开始下载
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            if (response != null) {
                is = response.body().byteStream();
                savedFile = new RandomAccessFile(mDownloadFile, "rw");
                savedFile.seek(downloadLength); // 跳过已下载的字节
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        // 计算已下载的百分比
                        int progress = (int) ((total + downloadLength) * 100 / mContentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled &amp;&amp; mDownloadFile != null) {
                    mDownloadFile.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            mOnDownloadListener.onProgress(progress);
            lastProgress = progress;
        }
    }

    @Override
    protected void onPostExecute(Integer status) {
        switch (status) {
            case TYPE_SUCCESS:
                if (mContentLength != mDownloadFile.length()) {
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onException();

                    //下载数据异常,告知downManager下载任务已失败
                    if (mOnDownloadTaskFinshedListener != null)
                        mOnDownloadTaskFinshedListener.onException();
                } else {
                    if (mOnDownloadListener != null)
                        mOnDownloadListener.onSuccess();
                }
                break;
            case TYPE_FAILED:
                if (mOnDownloadListener != null)
                    mOnDownloadListener.onFailed();
                break;
            case TYPE_PAUSED:
                if (mOnDownloadListener != null)
                    mOnDownloadListener.onPaused();
                break;
            case TYPE_CANCELED:
                if (mOnDownloadListener != null)
                    mOnDownloadListener.onCanceled();

                if (mOnDownloadTaskFinshedListener != null)
                    mOnDownloadTaskFinshedListener.onCanceled();
            default:
                break;
        }

        if (mOnDownloadTaskFinshedListener != null)
            mOnDownloadTaskFinshedListener.onFinished();
    }

    /**
     * 暂停下载任务
     */
    public void pauseDownload() {
        isPaused = true;
    }

    /**
     * 取消下载任务
     */
    public void cancelDownload() {
        isCanceled = true;
    }

    /**
     * 获取下载文件长度
     * @param downloadUrl 下载文件url
     * @return 下载文件长度
     * @throws IOException IOException
     */
    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null &amp;&amp; response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.close();
            return contentLength;
        }
        return 0;
    }

    public interface OnDownloadTaskFinshedListener {
        /**
         * 下载任务已结束
         */
        void onFinished();

        /**
         * 下载任务已取消
         */
        void onCanceled();

        /**
         * 下载文件异常,不是完整的文件或者文件包异常
         */
        void onException();
    }
}

3.apk下载成功后应用内跳转安装

在7.0以前,通过apk的uri跳转安装就可以了。7.0以后由于StrictMode API 政策,需要通过FileProvider来安装apk文件,使用步骤也比较简单。

第一步:在AndroidManifest.xml清单文件中注册provider
  • exported:必须为false,true会报安全异常。

  • grantUriPermissions:true,授予 URI 临时访问权限。

  • authorities 组件标识

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>
第二步:指定共享的目录

在res资源目录下新建一个xml目录,并新建一个名字和上一步指定的resource文件相同名字的xml文件,这里是:file_paths。 file_path.xml的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="" name="updateDemo" />
</paths>

< files-path />代表根目录: Context.getFilesDir()
< external-path />代表根目录: Environment.getExternalStorageDirectory()
< cache-path />代表根目录: getCacheDir()
external-path path=""代表根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。

第三步:使用FileProvider
    /**
     * 安装 apk
     *
     * @param apkPath apk全路径
     */
    public void installApk(String apkPath) {
        if (StringUtils.isEmpty(apkPath)) {
            Log.e("tag", "apkPath is null.");
            return;
        }

        File file = new File(apkPath);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        // 由于没有在Activity环境下启动Activity,设置下面的标签
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >= 24) { //判断版本是否在7.0以上
            //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致  参数3 共享的文件
            Uri apkUri = FileProvider.getUriForFile(AppUtils.getContext(), BuildConfig
                    .APPLICATION_ID + ".fileProvider", file);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        AppUtils.getContext().startActivity(intent);
    }

接下来完成UpdateManager的全部代码:

package com.zyw.horrarndoo.updatedemo.update;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.FileProvider;
import android.util.Log;

import com.zyw.horrarndoo.updatedemo.BuildConfig;
import com.zyw.horrarndoo.updatedemo.bean.DataBean;
import com.zyw.horrarndoo.updatedemo.download.DownloadHelper;
import com.zyw.horrarndoo.updatedemo.download.DownloadManager;
import com.zyw.horrarndoo.updatedemo.download.OnDownloadListener;
import com.zyw.horrarndoo.updatedemo.net.HttpUtils;
import com.zyw.horrarndoo.updatedemo.utils.AppUtils;
import com.zyw.horrarndoo.updatedemo.utils.SpUtils;
import com.zyw.horrarndoo.updatedemo.utils.StringUtils;
import com.zyw.horrarndoo.updatedemo.utils.ToastUtils;

import org.json.JSONObject;

import java.io.File;
import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

import static com.zyw.horrarndoo.updatedemo.constant.Constant.SP_KEY_CACHE_APK_VERSION_CODE;
import static com.zyw.horrarndoo.updatedemo.constant.Constant.SP_KEY_CACHE_VALID_TIME;

/**
 * Created by Horrarndoo on 2018/2/1.
 * <p>
 */

public class UpdateManager {
    private static volatile UpdateManager manager = null;

    private static final int MSG_ON_START = 1;
    private static final int MSG_ON_PROGRESS = 2;
    private static final int MSG_ON_DOWNLOAD_FINISH = 3;
    private static final int MSG_ON_FAILED = 4;
    private static final int MSG_ON_CANCLE = 5;
    private static final int MSG_ON_FIND_NEW_VERSION = 6;
    private static final int MSG_ON_NEWEST = 7;
    private static final int MSG_ON_UPDATE_EXCEPTION = 8;

    private DownloadManager mDownloadManager;

    private OnUpdateListener mOnUpdateListener;
    private OnCheckUpdateListener mOnCheckUpdateListener;

    private int mNewestVersionCode;
    private String mNewestVersionName;
    private String mNewVersionContent;

    /**
     * 最后一次保存cache的时间
     */
    private long mLastCacheSaveTime = 0;

    private UpdateManager() {
        mDownloadManager = DownloadManager.getInstance();
    }

    /**
     * 获取updateManager实例
     *
     * @return updateManager实例
     */
    public static UpdateManager getInstance() {
        if (manager == null) {
            synchronized (UpdateManager.class) {
                if (manager == null) {
                    manager = new UpdateManager();
                }
            }
        }
        return manager;
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_ON_START:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onStartUpdate();
                    break;

                case MSG_ON_PROGRESS:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onProgress((Integer) msg.obj);
                    break;

                case MSG_ON_DOWNLOAD_FINISH:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onApkDownloadFinish((String) msg.obj);
                    installApk((String) msg.obj);
                    break;

                case MSG_ON_FAILED:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onUpdateFailed();
                    break;

                case MSG_ON_CANCLE:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onUpdateCanceled();
                    break;

                case MSG_ON_UPDATE_EXCEPTION:
                    if (mOnUpdateListener != null)
                        mOnUpdateListener.onUpdateException();
                    break;

                case MSG_ON_FIND_NEW_VERSION:
                    DataBean dataBean = (DataBean) msg.obj;

                    mNewestVersionCode = dataBean.getVersionCode();
                    mNewestVersionName = dataBean.getVersionName();
                    mNewVersionContent = dataBean.getContent();

                    if (mOnCheckUpdateListener != null)
                        mOnCheckUpdateListener.onFindNewVersion(mNewestVersionName,
                                mNewVersionContent);
                    break;

                case MSG_ON_NEWEST:
                    if (mOnCheckUpdateListener != null)
                        mOnCheckUpdateListener.onNewest();
                    break;
            }
        }
    };

    /**
     * 检查更新
     *
     * @param apkInfoUrl            服务器端保存新版apk相关信息json的url
     * @param onCheckUpdateListener onCheckUpdateListener
     */
    public void checkUpdate(String apkInfoUrl, OnCheckUpdateListener onCheckUpdateListener) {
        mOnCheckUpdateListener = onCheckUpdateListener;
        HttpUtils.sendOkHttpRequest(apkInfoUrl, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                ToastUtils.showToast("check update failed.");
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String strJson = response.body().string();
                Log.e("onResponse", "response.body().string() = " + strJson);
                if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
                    //最后一次缓存的时间超过缓存文件有效期,或者最后一次缓存的apk不是最新版本的apk,删除缓存apk
                    if ((System.currentTimeMillis() - mLastCacheSaveTime > getCacheSaveValidTime())
                            || (getCacheApkVersionCode() != parseJson(strJson).getVersionCode())) {
                        clearCacheApkFile();
                        setCacheApkVersionCode(parseJson(strJson).getVersionCode());
                    }
                    sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
                } else {
                    sendMessage(MSG_ON_NEWEST, null);
                    //当前已经是最新版本APK,清除本地已经缓存的apk安装包
                    clearCacheApkFile();
                }
            }
        });
    }

    /**
     * 开始更新App
     * <p>
     * 此时开始正式下载更新Apk
     *
     * @param apkUrl           服务端最新apk文件url
     * @param onUpdateListener onUpdateListener
     */
    public void startToUpdate(String apkUrl, OnUpdateListener onUpdateListener) {
        mOnUpdateListener = onUpdateListener;

        if (StringUtils.isEmpty(mNewestVersionName) || mNewestVersionCode == 0)
            return;

        downloadNewestApkFile(apkUrl, mNewestVersionCode, mNewestVersionName);
    }

    /**
     * 设置缓存文件有效时间,单位:秒
     * <p>
     * 默认缓存有效期为7天
     *
     * @param cacheValidTime 缓存文件有效时间
     */
    public void setCacheSaveValidTime(long cacheValidTime) {
        SpUtils.putLong(SP_KEY_CACHE_VALID_TIME, cacheValidTime);
    }

    /**
     * 获取缓存文件有效时间,单位:秒
     * <p>
     * 默认缓存有效期为7天
     *
     * @return 缓存文件有效时间
     */
    public long getCacheSaveValidTime() {
        return SpUtils.getLong(SP_KEY_CACHE_VALID_TIME, 60 * 60 * 24 * 7);
    }

    /**
     * 设置缓存文件版本号
     *
     * @param versionCode cacheApk版本号
     */
    public void setCacheApkVersionCode(int versionCode) {
        SpUtils.putInt(SP_KEY_CACHE_APK_VERSION_CODE, versionCode);
    }

    /**
     * 获取缓存文件版本号
     *
     * @return 缓存文件版本号
     */
    public int getCacheApkVersionCode() {
        return SpUtils.getInt(SP_KEY_CACHE_APK_VERSION_CODE, 0);
    }

    /**
     * 取消更新
     */
    public void cancleUpdate() {
        //保留下载已完成的部分apk cache文件,cache文件最多保留7天
        mDownloadManager.pauseDownload();
    }

    /**
     * 清除已下载的APK缓存
     */
    public void clearCacheApkFile() {
        Log.e("tag", "清除所有的apk文件");
        mDownloadManager.clearAllCacheFile();
    }

    /**
     * 下载最新版本的APK文件
     *
     * @param url               服务端最新apk文件url
     * @param newestVersionCode 最新版本APK版本号
     * @param newestVersionName 最新版本APK版本名称
     */
    private void downloadNewestApkFile(String url, int newestVersionCode, String
            newestVersionName) {
        String apkFileName = getApkNameWithVersionName(DownloadHelper.getUrlFileName(url),
                newestVersionName);

        sendMessage(MSG_ON_START, null);

        mDownloadManager.startDownload(url, apkFileName, new
                OnDownloadListener() {
                    @Override
                    public void onException() {
                        sendMessage(MSG_ON_UPDATE_EXCEPTION, null);
                    }

                    @Override
                    public void onProgress(int progress) {
                        sendMessage(MSG_ON_PROGRESS, progress);
                    }

                    @Override
                    public void onSuccess() {
                        mLastCacheSaveTime = System.currentTimeMillis();
                        sendMessage(MSG_ON_DOWNLOAD_FINISH, mDownloadManager.getDownloadFilePath());
                    }

                    @Override
                    public void onFailed() {
                        mLastCacheSaveTime = System.currentTimeMillis();
                        sendMessage(MSG_ON_FAILED, null);
                    }

                    @Override
                    public void onPaused() {
                        mLastCacheSaveTime = System.currentTimeMillis();
                        //取消升级时,调用download pause,保留已下载的部分apk文件
                        sendMessage(MSG_ON_CANCLE, null);
                    }

                    @Override
                    public void onCanceled() {
                        //为了保证断点续传,升级时,调用download pause,不使用cancle,onCancle不会被调用
                        mLastCacheSaveTime = System.currentTimeMillis();
                        sendMessage(MSG_ON_CANCLE, null);
                    }
                });
    }

    private void sendMessage(int msgWhat, Object o) {
        Message msg = Message.obtain();
        msg.what = msgWhat;
        msg.obj = o;
        mHandler.sendMessage(msg);
    }

    /**
     * 解析json数据
     *
     * @param jsonData json数据
     * @return dataBean
     */
    private DataBean parseJson(String jsonData) {
        ...
        return dataBean;
    }

    /**
     * 获取带版本名称的apk文件名
     *
     * @param apkName apk原名
     * @return 带版本名称的apk文件名
     */
    private String getApkNameWithVersionName(String apkName, String versionName) {
        if (StringUtils.isEmpty(apkName))
            return apkName;

        apkName = apkName.substring(apkName.lastIndexOf("/") + 1, apkName.indexOf("" +
                ".apk"));
        Log.e("tag", "newApkName = " + apkName + "_v" + versionName + ".apk");
        return apkName + "_v" + versionName + ".apk";
    }

    /**
     * 安装 apk
     *
     * @param apkPath apk全路径
     */
    public void installApk(String apkPath) {
        ...
    }
}

最后在Activity中调用UpdateManager就可以简单的检查更新我们的apk啦,不过有一点稍微要注意下,就是运行时权限的获取,如果6.0以上的手机没有获取sd卡权限的话,我们的程序是无法正常运行的,所以如果用户没有给予权限的话,就退出程序并给出提示。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private UpdateManager mUpdateManager;

    private ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init() {
        ...

        initPermission();

        mUpdateManager = UpdateManager.getInstance();
    }

    private void initProgressDialog() {
        ...
    }

    private void initPermission() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission
                .WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission
                    .WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_check_update:
                mUpdateManager.checkUpdate(Constant.VERSION_INFO_URL, new OnCheckUpdateListener() {
                    @Override
                    public void onFindNewVersion(String versionName, String newVersionContent) {
                        String content = "最新版: V" + versionName + "\n" + newVersionContent;
                        buildNewVersionDialog(content);
                    }

                    @Override
                    public void onNewest() {
                        showToast("app is newest version.");
                        dismissProgressDialog();
                    }
                });

                break;
            case R.id.btn_clear_apk:
                mUpdateManager.clearCacheApkFile();
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 &amp;&amp; grantResults[0] != PackageManager
                        .PERMISSION_GRANTED) {
                    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }

    private void dismissProgressDialog() {
        mProgressDialog.setProgress(0);
        if (mProgressDialog.isShowing())
            mProgressDialog.dismiss();
    }

    /**
     * 创建发现新版本apk alert dialog
     *
     * @param message dialog显示消息
     */
    private void buildNewVersionDialog(String message) {
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle("发现新版本")
                .setIcon(R.mipmap.ic_launcher)
                .setMessage(message)
                .setPositiveButton("更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        mUpdateManager.startToUpdate(Constant.APK_URL, mOnUpdateListener);
                    }
                })
                .setNegativeButton("取消", null)
                .create();
        dialog.show();
    }

    private OnUpdateListener mOnUpdateListener = new OnUpdateListener() {
        @Override
        public void onStartUpdate() {
            mProgressDialog.show();
        }

        @Override
        public void onProgress(int progress) {
            mProgressDialog.setProgress(progress);
        }

        @Override
        public void onApkDownloadFinish(String apkPath) {
            showToast("newest apk download finish. apkPath: " + apkPath);
            Log.e("tag", "newest apk download finish. apkPath: " + apkPath);
            dismissProgressDialog();
            //所有的更新全部在updateManager中完成,Activity在这里只是做一些界面上的处理
        }

        @Override
        public void onUpdateFailed() {
            showToast("update failed.");
            dismissProgressDialog();
        }

        @Override
        public void onUpdateCanceled() {
            showToast("update cancled.");
            dismissProgressDialog();
        }

        @Override
        public void onUpdateException() {
            showToast("update exception.");
            dismissProgressDialog();
        }
    };
}

至此已经基本完成此次的应用内更新模块了,用起来还是很简单的,UpdateManager只做更新相关判断处理,DownloadManager则处理下载文件、缓存文件续传及相关状态处理,并且UpdateManager和DownloadManager都是单独可以作为一个独立模块在实际项目中使用的。

最后附上源码:https://github.com/Horrarndoo/UpdateDemo

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

推荐阅读更多精彩内容