使用DownloadManager下载更新Apk

需求:app本地检查更新,后台有新版apk则下载新版apk并安装。

业务逻辑流程图基本上是这样的 :

查阅的一些资料后基本上得到三种解决方案:

1,AsyncTask+Http下载;

2,DownloadManager;

3,Service+Http下载;

而DownloadManager(以下表示为DM)相对来说比较简单这里就选择第二种方案:

DM是一种处理长时间运行的HTTP下载的系统服务。客户端可以通过URI请求下载特定的目标文件。下载管理器将在后台进行下载,负责HTTP交互并在发生故障或连接更改和系统重新启动后重试下载;这是官方对DownloadManager的说明。

DM中主要包含两个静态内部类DownloadManager.Request(以下表示为DM.Request)和DownloadManager.Query(以下表示为DM.Query);DM.Request主要是用来配置请求信息,比如是否在系统通知栏显示下载,设置下的网络类型(wifi,移动网络)下载后文件的存储路径等;DM.Query主要是用来查询跟下载相关的信息;DM类中COLUMN_开头的静态常量都是可以通过DM.Query查询的如COLUMN_TOTAL_SIZE_BYTES查询下载文件的总字节数,COLUMN_BYTES_DOWNLOADED_SO_FAR查询当前已下载的字节数,COLUMN_STATUS查询下载状态;

下面贴出核心代码:

一,封装常用下载方法FileDownloadManager 工具


public class FileDownloadManager {

    private DownloadManager dManager;
    private Context context;
    private static FileDownloadManager instance;

    private FileDownloadManager(Context context) {
        dManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        this.context = context.getApplicationContext();
    }

    public static FileDownloadManager getInstance(Context context) {
        if (instance == null) {
            instance = new FileDownloadManager(context);
        }
        return instance;
    }

    /**
     * @param apkUri 文件下载路径 
     * @param title 通知标题
     * @param description 通知message
     * @return download id 每一个下载任务的唯一标识
     */
    public long startDownload(String apkUri, String title, String description, String appName) {

        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUri));
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        //设置文件的保存的位置[三种方式]
        //第一种
        //file:///storage/emulated/0/Android/data/your-package/files/Download/test/test.apk
        //request.setDestinationInExternalFilesDir(UpdateActivity.this,Environment.DIRECTORY_DOWNLOADS, "test"+ File.separator+"test.apk");
        //第二种
        //file:///storage/emulated/0/Download/test/test.apk
        //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "test"+ File.separator+"test.apk");
        //第三种 自定义文件路径
        //file:///storage/emulated/0/Android/data/your-package/files/Download/test/test.apk
        Uri filePath = Uri.parse("file://"+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator+"test"+ File.separator+"test.apk");
        request.setDestinationUri(filePath);

        request.setTitle(title);//通知栏标题
        request.setDescription(description);//通知栏内容
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setMimeType("application/vnd.android.package-archive");
        // 设置为可被媒体扫描器找到
        request.allowScanningByMediaScanner();
        // 设置为可见和可管理
        request.setVisibleInDownloadsUi(true);
        long downloadId = dManager.enqueue(request);
        // 把当前下载任务的downloadId保存起来
        SpUtil.putLong("downloadId",downloadId);//保存此次下载ID
        return downloadId;
    }

    /**
     * 获取文件保存的路径
     *
     * @param downloadId 下载任务id
     * @return file path
     * @see FileDownloadManager#getDownloadUri(long)
     */
    public String getDownloadPath(long downloadId) {
        DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
        Cursor c = dManager.query(query);
        if (c != null) {
            try {
                if (c.moveToFirst()) {
                    return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
                }
            } finally {
                c.close();
            }
        }
        return null;
    }

    /**
     * 获取保存文件的地址
     *
     * @param downloadId 下载任务id
     * @see FileDownloadManager#getDownloadPath(long)
     */
    public Uri getDownloadUri(long downloadId) {
        return dManager.getUriForDownloadedFile(downloadId);
    }

    public DownloadManager getDownloadManager() {
        return dManager;
    }

    /**
     * 获取下载状态
     *
     * @param downloadId 下载任务id
     * @return int
     * 以下是五种下载状态对应的状态码
     * @see DownloadManager#STATUS_PENDING
     * @see DownloadManager#STATUS_PAUSED
     * @see DownloadManager#STATUS_RUNNING
     * @see DownloadManager#STATUS_SUCCESSFUL
     * @see DownloadManager#STATUS_FAILED
     */
    public int getDownloadStatus(long downloadId) {
        DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
        Cursor c = dManager.query(query);
        if (c != null) {

            try {
                if (c.moveToFirst()) {
                    return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
                }
            } finally {
                c.close();
            }
        }
        return -1;
    }
   /**
     * @param downloadId 下载Id
     * @return 当前下载进度
     */
    public int queryProgress(long downloadId){
        int[] bytes = new int[] { -1, -1 };
        DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
        int ret = 0;
        Cursor c = null;
        try {
            c = dManager.query(query);
            if (c != null && c.moveToFirst()) {
                bytes[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//当前下载字节
                bytes[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));//文件总字节
                ret = bytes[0] * 100 / bytesAndStatus[1];
            }
        } finally {
            if (c != null) { c.close(); }
        }

        return ret;
    }

    /**
     * @param downloadId 下载任务Id
     * @return 返回取消的下载任务个数
     */
    public int remove(long downloadId){
        int removeCount = 0;
        if (downloadId != -1L) {
            int downloadStatus = FileDownloadManager.getInstance(App.getContext()).getDownloadStatus(downloadId);
            if (downloadStatus == DownloadManager.STATUS_RUNNING){
                removeCount = dManager.remove(downloadId);
                SpUtil.remove("downloadId");
            }
        }
        return removeCount;
    }
}

 二,启动下载

 与后台比较版本号比较简单就不多说了,在UpdateActivity中点击确认更新时的核心代码

 注意这里需要检查以下SD卡权限,权限通过后调用downloadApk()方法启动下载

 Timer主要用于查询当前下载进度,更新进度条对话框

private void downloadApk() {
        //查看是否已经下载过apk,如果已经下载过则校验版本,和服务器版本一致则直接安装,否则删除apk并重新下载
        // 获取存储的下载ID
        long downloadId = SpUtil.getLong("downloadId", -1L);
        Log.e(TAG, "获取存储的下载ID ====downloadId==" + downloadId);
        if (downloadId != -1L) {
            FileDownloadManager fdm = FileDownloadManager.getInstance(App.getContext());
            int status = fdm.getDownloadStatus(downloadId);
            Log.e(TAG, "===downloadApk: =由id获取状态=status=="+status);
            if (status == DownloadManager.STATUS_SUCCESSFUL) {
                final Uri uri = fdm.getDownloadUri(downloadId);
                if (uri != null) {
                    //比较本地已存在的安装包版本和后台apk版本及已安装版本和本地包的版本,以确认是否需要重新下载
                    if (compare(getApkInfo(UpdateActivity.this, uri.getPath()), UpdateActivity.this)) {
                        new AlertFragmentDialog.Builder(UpdateActivity.this)
                                .setCancel(true).setContent("检测到您已下载最新安装包,点击确定安装!")
                                .setTitle("安装apk")
                                .setLeftBtnText("取消")
                                .setRightBtnText("确定")
                                .setRightCallBack(new AlertFragmentDialog.RightClickCallBack() {
                                    @Override
                                    public void dialogRightBtnClick() {
                                        startInstall(UpdateActivity.this, uri);
                                    }
                                }).build();
                    } else {
                        //移除本地过时apk,重新下载
                        fdm.remove(downloadId);
                        start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
                    }
                } else {//文件路径不存在,启动下载
                    start(UpdateActivity.this, apkUrl, "title", App.getAPPName());
                }

            } else if (status == DownloadManager.STATUS_FAILED) {//下载失败,重新下载
                start(UpdateActivity.this, apkUrl, "title", App.getAPPName());

            } else {//其他状态
                Log.d(TAG, "apk is already downloading");
            }
        } else {
            start(UpdateActivity.this, apkUrl, "title", App.getAPPName());

        }

    }


    /**
     * 获取apk程序信息[packageName,versionName...]
     *
     * @param context Context
     * @param path    apk path
     */
    private PackageInfo getApkInfo(Context context, String path) {
        PackageManager pm = context.getPackageManager();
        PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
        if (info != null) {
            return info;
        }
        return null;
    }


    /**
     * 下载的apk和当前程序版本比较
     *
     * @param apkInfo apk file's packageInfo
     * @param context Context
     * @return 如果当前应用版本小于apk的版本则返回true
     */
    private boolean compare(PackageInfo apkInfo, Context context) {
        if (apkInfo == null) {
            return false;
        }
        String localPackage = context.getPackageName();
        if (apkInfo.packageName.equals(localPackage)) {
            try {
                PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);
                Log.e(TAG, "compare: apkInfo.versionCode=" + apkInfo.versionCode + ",packageInfo.versionCode=" + packageInfo.versionCode);
               
                //判断逻辑是已下载的apk版本号大于已安装的app版本号,而且已下载的apk版本号和服务器的版本号一致,才确定已下载的apk是最新的apk
                if (apkInfo.versionCode > packageInfo.versionCode && apkInfo.versionCode == 10073) {
                    return true;
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private void start(Context context, String url, String title, String appName) {
        long id = FileDownloadManager.getInstance(context).startDownload(url,
                title, "下载完成后点击打开", appName);
        Log.e(TAG, "==apk start download==" + id);
        startProgressTimer();
    }

    private void startInstall(Context context, Uri uri) {
        Intent install = new Intent(Intent.ACTION_VIEW);
        install.setDataAndType(uri, "application/vnd.android.package-archive");
        install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(install);
    }


    /**********************定时查询下载进度***************************/
    private static final int TIMER_WHAT = 2;
    private Timer timer;
    private TimerTask timerTask;
    private UpdateDialog updateDialog;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case TIMER_WHAT:
                    if (msg.arg1 < 100) {
                        updateDialog.setProgress(msg.arg1);
                    } else {
                        timer.cancel();
                        timerTask.cancel();
                        updateDialog.setProgress(msg.arg1);
                        updateDialog.dismissAllowingStateLoss();
                    }
                    break;
            }
        }
    };

    private void startProgressTimer() {
        timer = new Timer();
        timerTask = new TimerTask() {
            @Override
            public void run() {
                int cProgress = FileDownloadManager.getInstance(App.getContext()).getProgress(SpUtil.getLong("downloadId", -1L));
                Message message = mHandler.obtainMessage(TIMER_WHAT);
                message.arg1 = cProgress;
                mHandler.sendMessage(message);
            }
        };
        //200毫秒查询一次进度
        timer.schedule(timerTask, 0, 200);
        updateDialog = new UpdateDialog.Builder(UpdateActivity.this).setCancelDownloadListener(new UpdateDialog.CancelDownloadListener() {
            @Override
            public void onCancelDownloadListener() {
                ToastUtil.showToast("取消!");
                long dId = SpUtil.getLong("downloadId", -1L);
                FileDownloadManager.getInstance(App.getContext()).remove(dId);
                timer.cancel();
                timerTask.cancel();
            }
        }).build();
    }

三,自动安装

DM中有三个Action常量分别是:

//两个广播 Action
/**
  * 下载完成时发出的广播的Action,注意这里的下载完成包括下载成功和取消下载
  */
public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
/**
  * 这个Action 是用户点击了通知栏里正在下载的任务是发出的广播所持有的Action
  */
public final static String ACTION_NOTIFICATION_CLICKED =
            "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
//一个Activity Action
/**
  * 显示所有下载任务Activity的Action
  */
public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";

这样我们就可以通过注册 ACTION_DOWNLOAD_COMPLETE 广播来监听下载完成(也可以在上面获取进度为100时判断下载完成);广播的实现如下

public class DownLoadBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        long completeId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        long downloadId = SpUtil.getLong("downloadId");
        //下载完成广播(包括下载成功,取消下载(取消下载intent携带的downloadId会置0这样就可以区分是下载成功还是取消下载))
        if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
            if (downloadId==completeId){
                DownloadManager  manager =
                        (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
                Intent installIntent=new Intent(Intent.ACTION_VIEW);
                Uri downloadFileUri = manager
                        .getUriForDownloadedFile(completeId);
                installIntent.setDataAndType(downloadFileUri,
                        "application/vnd.android.package-archive");
                installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(installIntent);
            }
        }else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)){//点击正在进行的下载启动系统下载Activity
            Intent intentDllActivity = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
            intentDllActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intentDllActivity);
        }

    }
}

记得注册广播

<receiver android:name=".receiver.DownLoadBroadcastReceiver">
    <intent-filter >
        <!--下载完成,取消下载-->
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
        <!--点击正在下载的通知栏-->
        <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>

    </intent-filter>
</receiver>

致谢:

参考博文https://blog.csdn.net/u013278099/article/details/52692008#t2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值