Android通过DownloadManager实现App的版本更新功能

1.DownloadManager介绍

 

DownloadManger是android 2.3(api level 9)开始 提供的用于优化处理长时间的下载操作。DownloadManager 处理Http/Https连接并监控连接中的状态变化及系统重启来确保每一个下载任务顺利完成。大多数涉及到下载的情况中使用DownloadManager都是很好的选择,尤其是后台继续下载,下载状态回调,断点续传,下载环境设置,下载文件的操作等方面,支持的很好。

DownloadManager是系统开放给第三方应用使用的类,包含两个静态内部类DownloadManager.Query和DownloadManager.Request。DownloadManager.Request用来请求一个下载,DownloadManager.Query用来查询下载信息,具体接口信息可参看最后的api说明。

2.DownloadManager使用

 

DownloadManager主要对外提供了以下接口:

  • public long enqueue(Request request)执行下载,返回downloadId,downloadId可用于后面查询下载信息。若网络不满足条件、Sdcard挂载中、超过最大并发数等异常会等待下载,正常则直接下载。

  • int remove(long… ids) 删除下载,若下载中取消下载。会同时删除下载文件和记录。

  • Cursor query(Query query) 查询下载信息。

  • getMaxBytesOverMobile(Context context) 返回移动网络下载的最大值

  • rename(Context context, long id, String displayName) 重命名已下载项的名字

  • getRecommendedMaxBytesOverMobile(Context context) 获取建议的移动网络下载的大小

  • 其它:通过查看代码我们可以发现还有个CursorTranslator私有静态内部类。这个类主要对Query做了一层代理。将DownloadProvider和DownloadManager之间做个映射。将DownloadProvider中的十几种状态对应到了DownloadManager中的五种状态,DownloadProvider中的失败、暂停原因转换为了DownloadManager的原因。

  • 3.DownloadManager实现下载代码     

3.1下载需要的权限  

<uses-permission android:name="android.permission.INTERNET" />;

< uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;

 

  3.2 自定义下载线程

 

package com.tt.travel_and.Util;

import android.app.DownloadManager;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Process;

/**
 * Created by Summer on 2017/9/8.
 */

public class DownLoadRunnable implements Runnable {
    private String url;
    private Handler handler;
    private Context mContext;
    private long requestId;

    public DownLoadRunnable(Context context, String url, Handler handler) {
        this.mContext = context;
        this.url = url;
        this.handler = handler;
    }

    @Override
    public void run() {
        //设置线程优先级为后台,这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        startDownload();
    }

    private long startDownload() {
        //获得DownloadManager对象
        DownloadManager downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
        //获得下载id,这是下载任务生成时的唯一id,可通过此id获得下载信息
        requestId = downloadManager.enqueue(CreateRequest(url));
        //查询下载信息方法
        queryDownloadProgress(requestId, downloadManager);
        return requestId;
    }

    private void queryDownloadProgress(long requestId, DownloadManager downloadManager) {


        DownloadManager.Query query = new DownloadManager.Query();
        //根据任务编号id查询下载任务信息
        query.setFilterById(requestId);
        try {
            boolean isGoging = true;
            while (isGoging) {
                Cursor cursor = downloadManager.query(query);
                if (cursor != null && cursor.moveToFirst()) {

                    //获得下载状态
                    int state = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch (state) {
                        case DownloadManager.STATUS_SUCCESSFUL://下载成功
                            isGoging = false;
                            Bundle bundle = new Bundle();
                            bundle.putString("path", downloadManager.getUriForDownloadedFile(requestId).toString());
                            Message msg = handler.obtainMessage(downloadManager.STATUS_SUCCESSFUL);
                            msg.setData(bundle);
                            msg.sendToTarget();//发送到主线程,更新ui
                            break;
                        case DownloadManager.STATUS_FAILED://下载失败
                            isGoging = false;
                            handler.obtainMessage(downloadManager.STATUS_FAILED).sendToTarget();//发送到主线程,更新ui
                            break;

                        case DownloadManager.STATUS_RUNNING://下载中
                            /**
                             * 计算下载下载率;
                             */
                            int totalSize = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
                            int currentSize = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
                            int progress = (int) (((float) currentSize) / ((float) totalSize) * 100);
                            handler.obtainMessage(downloadManager.STATUS_RUNNING, progress).sendToTarget();//发送到主线程,更新ui
                            break;

                        case DownloadManager.STATUS_PAUSED://下载停止
                            handler.obtainMessage(DownloadManager.STATUS_PAUSED).sendToTarget();
                            break;

                        case DownloadManager.STATUS_PENDING://准备下载
                            handler.obtainMessage(DownloadManager.STATUS_PENDING).sendToTarget();
                            break;
                    }
                }
                if (cursor != null) {
                    cursor.close();
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private DownloadManager.Request CreateRequest(String url) {
        if(url==null || url.equals("")){
            return null;
        }
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);// 隐藏notification

        request.setAllowedNetworkTypes(request.NETWORK_WIFI);//设置下载网络环境为wifi
        request.setTitle("天津出行版本更新");
        request.setDescription("正在下载");
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "/tjcx/tianjintrek.apk");//指定apk缓存路径,默认是在SD卡中的Download文件夹

        return request;
    }

}

 

3.3Activity中实现下载功能
     

    Handler handler = new Handler() {
        @Override
        public void handleMessage(final Message msg) {
            switch (msg.what) {
                case DownloadManager.STATUS_SUCCESSFUL://下载成功
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "下载任务已经完成!", Toast.LENGTH_SHORT).show();
                            DownloadFinished();//下载完成安装apk
                        }
                    });
                    break;

                case DownloadManager.STATUS_RUNNING://下载中
                    final int progress = (int) msg.obj;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            upload_progress.setProgress(progress);
                            tv_currentprogress.setText(progress + "%");
                            if(progress==100){
                                dialog_update.dismiss();
                            }
                        }
                    });
                    break;

                case DownloadManager.STATUS_FAILED://下载失败
//                    canceledDialog();
                    break;

                case DownloadManager.STATUS_PENDING://准备下载
//                    showDialog();
                    break;
            }
        }
    };

    /**
     * 下载完成安装apk
     */
    private void DownloadFinished(){
        File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                .getAbsolutePath() + "/tjcx/tianjintrek.apk");
        if (mFile.getName().endsWith(".apk")) {
            Intent install = new Intent();
            install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            install.setAction(Intent.ACTION_VIEW);
            if (24 > Build.VERSION.SDK_INT) {
                install.setDataAndType(Uri.fromFile(mFile), "application/vnd.android.package-archive");
            } else {
                Uri apkUri =
                        FileProvider.getUriForFile(MainActivity.this, "com.tt.travel_and.fileprovider", mFile);
                install.setDataAndType(apkUri, "application/vnd.android.package-archive");
                install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
            startActivity(install);
            LocationApplication.setUserInfo(null, null, null);
        }
    }

    /**
     * 自定义的更新提示dialog
     */
    private void showUpdateAppAlertDialogs() {
        LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
        AutoLinearLayout layout = (AutoLinearLayout) inflater.inflate(R.layout.updateapp_dialogs, null);
        txt_title = (TextView) layout.findViewById(R.id.txt_title);
        txt_title.setText(getResources().getString(R.string.discovernewversion) + version_name);
        txt_msg = (TextView) layout.findViewById(R.id.txt_msg);
        txt_msg.setText(getResources().getString(R.string.app_name_cn)+version_name+getResources().getString(R.string.update_appcontent));
        //   txt_msg.setText(version_name);
        rl_update_unmust = (AutoLinearLayout) layout.findViewById(R.id.rl_update_unmust);
        rl_updates = (AutoRelativeLayout) layout.findViewById(R.id.rl_update);
        upload_progress = (ProgressBar) layout.findViewById(R.id.upload_progress);//更新进度条
        tv_currentprogress = (TextView) layout.findViewById(R.id.tv_currentprogress); //显示实时更新进度
        Button btn_updateapp = (Button) layout.findViewById(R.id.btn_updateapp);
        Button btn_updateappcancel = (Button) layout.findViewById(R.id.btn_updateappcancel);
        btn_updateappcancel.setOnClickListener(new OnClickListener() {
         @Override
            public void onClick(View v) {
                dialog_update.dismiss();
            }
        });
//点击更新按钮 btn_updateapp.setOnClickListener(new OnClickListener() {
        @Override
          public void onClick(View v) {
        // appupdate();
      rl_update_unmust.setVisibility(View.GONE);
      rl_updates.setVisibility(View.VISIBLE);
      download();//apk下载方法
      }
     });
   dialog_update = new AlertDialog.Builder(MainActivity.this,R.style.NoExitAnimDialog).create(); 
 dialog_update.setCanceledOnTouchOutside(false);//dialog弹出后会点击屏幕,dialog不消失;点击物理返回键dialog消失  
 dialog_update.show();
 dialog_update.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 
 dialog_update.getWindow().setContentView(layout); 
 dialog_update.getWindow().setBackgroundDrawableResource(R.color.transparent);
 }

    private void download() {
 //android6.0以后要动态申请权限
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
            PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(MainActivity.this,
                    "android.permission.WRITE_EXTERNAL_STORAGE")) {//判断当前系统的SDK版本是否大于23
        verifyStoragePermissions(MainActivity.this);//先去动态申请权限 通过用户同意权限 则开启下载线程
        return;
    }
    /**
     * 更新的时候先检查安装包在sd卡中是否存在,如果存在则直接安装,不存在开启下载线程进行下载
     */
    File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
            .getAbsolutePath() + "/tjcx/tianjintrek.apk");
    if (mFile.exists()) {//安装包存在,直接安装
        dialog_update.dismiss();
        DownloadFinished();//下载完成安装apk
    }else{//下载apk
        //最好是用单线程池,或者intentService取代
        new Thread(new DownLoadRunnable(this, url, handler)).start();//url换成你apk的下载地址就可以了
    }

}

/**
  * 运行时权限处理
  * 用户选择允许或拒绝后,会回调onRequestPermissionsResult方法, 该方法类似于onActivityResult  
  * @param requestCode  
  * @param permissions
  * @param grantResults  */

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
               if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//          Log.i("CMCC", "权限被允许");
            /**
             * 更新的时候先检查安装包在sd卡中是否存在,如果存在则直接安装,不存在开启下载线程进行下载
             */
            File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                    .getAbsolutePath() + "/tjcx/tianjintrek.apk");
            if (mFile.exists()) {//安装包存在,直接安装
                dialog_update.dismiss();
                DownloadFinished();
            }else{//下载apk
                //最好是用单线程池,或者intentService取代
                new Thread(new DownLoadRunnable(this, url, handler)).start();
            }
        } else {
            finish();
            Toast.makeText(this, "同意权限后才能进行更新,请手动进入设置打开手机读取sd卡权限", Toast.LENGTH_LONG).show();
        }
    }


3.4 在res节点下创建xml文件夹,在xml文件夹下新建update_path.xml文件(文件内容如下)

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="localApk"
        path="Download/tjcx/" />
    <external-path path="" name="image" />
</paths>

3.5 在Androidmanifest配置清单中声明provider,引用上面创建的update_path.xml文件

<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.tt.travel_and.fileprovider" android:exported="false" android:grantUriPermissions="true"> <!--元数据--> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/update_path" /> </provider>

通过以上代买就可以实现app的版本更新功能了

 

4 apk安装成功后删除安装包

 

  4.1Android 应用程序的安装和卸载事件,是由系统进行监听并全局广播的,支持1.5(android 3)以上因此,如果想要监听获取应用的安装和卸载事件,只需要自定义一个BroadcastReceiver,来对系统广播进行监听和处理

 

  4.2自定义广播InitApkBroadCastReceiver

         

package com.tt.travel_and.service;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;

/**
 * Created by Summer on 2017/10/10.
 */

public class InitApkBroadCastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("-------------",intent.getData().getSchemeSpecificPart());
        if (context.getPackageName().equals(intent.getData().getSchemeSpecificPart())) {
            File mFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
                    .getAbsolutePath() + "/tjcx/tianjintrek.apk");
            if (mFile.exists()) {
                mFile.delete();//app替换成功以后直接删除安装包
                Toast.makeText(context, "已删除安装包", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

 4.3在AndroidManifest中注册广播

<receiver android:name=".service.InitApkBroadCastReceiver">
    <intent-filter>
   <!-- 一个新版本的应用安装到设备,替换之前已经存在的版本  替换时会先收到卸载的再收到替换的, 替换自身也能收到--> 
  <action android:name="android.intent.action.PACKAGE_REPLACED" /> 
<data android:scheme="package" />
 </intent-filter>
 </receiver>

 好了到这通过DownloadManager实现app的版本更新和apk安装完成后删除安装包都已经说完了,希望对大家的学习和工作有所帮助。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值