在前面,我介绍了使用 Volley 传输网络数据。戳这里Volley是一个很好用的网络框架,但是Volley 不适合用来下载大的数据文件。因为 Volley 会保持在解析的过程中所有的响应。对于下载大量的数据操作,Google建议我们使用 DownloadManager。

DownloadManager类介绍

官方文档上对DownloadManager是这样介绍的:

The download manager is a system service that handles long-running HTTP downloads. Clients may request that a URI be downloaded to a particular destination file. The download manager will conduct the download in the background, taking care of HTTP interactions and retrying downloads after failures or across connectivity changes and system reboots. Instances of this class should be obtained through getSystemService(String) by passing DOWNLOAD_SERVICE. Apps that request downloads through this API should register a broadcast receiver for ACTION_NOTIFICATION_CLICKED to appropriately handle when the user clicks on a running download in a notification or from the downloads UI. Note that the application must have the INTERNET permission to use this class.

简而言之:

  • DownloadManager是一个为了处理长时间下载Http网络任务的系统下载管理器
  • 客户端可以通过URI(下载地址)下载相应的文件。DownloadManager是运行在后台,并负责http交互和重新下载(在失败或者链接改变和系统重启)。
  • DownLoadManager mDownloadManager =(DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
  • 请求下载完成时应当注册一个Broadcast Receiver(action为 ACTION_NOTIFICATION_CLICKED)来适当处理当用户点击运行下载的通知或下载界面;
  • 使用网络权限: <uses-permission Android:name="android.permission.INTERNET" />

相关类

DownLoadManager包含两个内部类:

Android 下载管理DownloadManager输入错误网址还会执行下载任务 安卓downloadmanager_android

  1. DownloadManager.Request(主要用于发起一个下载请求):
  1. addRequestHeader(String header, String value):添加http的header
  2. setAllowedNetworkTypes(int flags):设置允许的网络类型
  3. setDestinationUri(Uri uri):自定义下载目录
  4. setNotificationVisibility(int visibility):设置Notification的显示情况
  5. setTitle(CharSequence title):设置notification中显示的标题
  1. DownloadManager.Query(主要用于查询下载信息):
  1. setFilterById(long… ids):添加下载的id,便于查询对应的下载情况

应用下载更新

实例实现后台更新

Step 1:在Activity中通过startService启动service,并传递参数。

不了解IntentService可以戳这里

MainActivity.java:

Intent serviceIntent = new Intent(MainActivity.this,DownloadService.class);
//将下载地址url放入intent中
 serviceIntent.setData(Uri.parse(url));
 startService(serviceIntent);

Step 2:在IntentService中获得url,通过DownloadManager下载应用

DownloadService.java:

public class DownloadService extends IntentService {
    private String TAG = "DownloadService";
    public static final String BROADCAST_ACTION =
            "com.example.android.threadsample.BROADCAST";
    public static final String EXTENDED_DATA_STATUS =
            "com.example.android.threadsample.STATUS";

    private LocalBroadcastManager mLocalBroadcastManager;

    public DownloadService() {
        super("DownloadService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //获取下载地址
        String url = intent.getDataString();
        Log.i(TAG,url);
        //获取DownloadManager对象
        DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));

    //指定APK缓存路径和应用名称,可在SD卡/Android/data/包名/file/Download文件夹中查看
        request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "mydown.app");
        //设置网络下载环境为wifi
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        //设置显示通知栏,下载完成后通知栏自动消失
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        //设置通知栏标题
        request.setTitle("下载");
        request.setDescription("应用正在下载");
        request.setAllowedOverRoaming(false);
        //获得唯一下载id
        long requestId = downloadManager.enqueue(request);
        //将id放进Intent
        Intent localIntent = new Intent(BROADCAST_ACTION);
        localIntent.putExtra(EXTENDED_DATA_STATUS,requestId);
        //查询下载信息
        DownloadManager.Query query=new DownloadManager.Query();
        query.setFilterById(requestId);
        try{
            boolean isGoging=true;
            while(isGoging){
                Cursor cursor = downloadManager.query(query);
                if (cursor != null && cursor.moveToFirst()) {
                    int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch(status){
                       //如果下载状态为成功 
                        case DownloadManager.STATUS_SUCCESSFUL:
                            isGoging=false;
                            //调用LocalBroadcastManager.sendBroadcast将intent传递回去
                            mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
                            mLocalBroadcastManager.sendBroadcast(localIntent);
                            break;
                    }
                }

                if(cursor!=null){
                    cursor.close();
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }


    }
}

Step 3:Activity中注册BroadcastReceiver,监听广播,更新UI。

MainActivity.java:

注册广播的方法(动态注册):

private void regist() {

        IntentFilter intentFilter = new IntentFilter(DownloadService.BROADCAST_ACTION);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, intentFilter);
    }

接受到广播,处理传递过来的数据,下载完成,自动安装应用:

private class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String data = intent.getStringExtra(DownloadService.EXTENDED_DATA_STATUS);
            Log.i("test", data);

            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            Toast.makeText(MainActivity.this, "编号:"+id+"的下载任务已经完成!", Toast.LENGTH_SHORT).show();
            intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
           intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/myApp.apk")),
                   "application/vnd.android.package-archive");
            startActivity(intent);

              }
    }

最后取消注册广播:

protected void onDestroy() {
        super.onDestroy();
        cancel();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
    }

最后别忘了加上权限:

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

最终效果图如下:

Android 下载管理DownloadManager输入错误网址还会执行下载任务 安卓downloadmanager_android_02


放上MainActivity.java的完整代码:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    MyReceiver receiver = new MyReceiver();
    private Button myselfButton;
    private String url = "http://172.30.18.222:8080/com.android36kr.app_16101017.apk";
    private DownloadDialog downloadDialog;

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

    }

    private void initView() {

        myselfButton = (Button)this.findViewById(R.id.myself_update);

        myselfButton.setOnClickListener(this);
    }

    private void regist() {

        IntentFilter intentFilter = new IntentFilter(DownloadService.BROADCAST_ACTION);
        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, intentFilter);
    }

    public void download(){
            showDialog();
        Intent myserviceIntent = new Intent(MainActivity.this,MyDownloadService.class);
        myserviceIntent.setData(Uri.parse(url));
        startService(myserviceIntent);
    }  

    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.myself_update:
                Intent serviceIntent = new Intent(MainActivity.this,DownloadService.class);
                serviceIntent.setData(Uri.parse(url));
                startService(serviceIntent);
                break;
        }
    }

    private class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String data = intent.getStringExtra(DownloadService.EXTENDED_DATA_STATUS);
            Log.i("test", data);

            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            Toast.makeText(MainActivity.this, "编号:"+id+"的下载任务已经完成!", Toast.LENGTH_SHORT).show();
            intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
           intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/myApp.apk")),
                   "application/vnd.android.package-archive");
            startActivity(intent);

              }
    }

    protected void onDestroy() {
        super.onDestroy();
        cancel();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
    }
}

实例实现强制更新:

Step 1:创建一个不可取消的dialog(按back键不能取消,按弹窗外不可取消)

首先我们设置Dialog的 布局文件,download_dialog.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold"
        android:textColor="@color/colorPrimary"
        android:text="版本更新"/>

    <ProgressBar
        android:id="@+id/mProgressBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"/>

    <TextView
        android:id="@+id/mTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:textStyle="bold"
        android:textSize="16sp"
        android:textColor="@color/colorPrimary"
        android:text="0%"/>

</LinearLayout>

进度条显示下载进度,TextView显示下载百分比。

然后自定义一个AlertDialog,将布局文件设置进去,DownloadDialog.java :

public class DownloadDialog extends AlertDialog {
    private Context mContext;
    private TextView mTextView;
    private ProgressBar mProgressBar;
    private View view;
    protected DownloadDialog(Context context) {
        super(context);
        this.mContext = context;

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //设置对话框样式
        setStyle();
        //初始化控件
        initView();
    }

    private void initView() {
        view = View.inflate(mContext,R.layout.dwonlaod_dialog,null);
        mTextView = (TextView)view.findViewById(R.id.mTextView);
        mProgressBar = (ProgressBar)view.findViewById(R.id.mProgressBar);
        setContentView(view);
    }

    private void setStyle() {
        //设置对话框不可取消
        this.setCancelable(false);
        //设置触摸对话框外面不可取消
        this.setCanceledOnTouchOutside(false);
        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindow().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        //获得应用窗口大小
        WindowManager.LayoutParams layoutParams = this.getWindow().getAttributes();
        //设置对话框居中显示
        layoutParams.gravity = Gravity.CENTER;
        //设置对话框宽度为屏幕的3/5
        layoutParams.width = (displaymetrics.widthPixels/5)*3;
    }

    //设置进度条
    public void setProgress(int progress){
        mTextView.setText(progress+"%");
        mProgressBar.setProgress(progress);
    }
}

创建显示对话框,取消对话框的方法,MainActivity :

private void showDialog() {
        if(downloadDialog==null){
            downloadDialog = new DownloadDialog(this);
        }

        if(!downloadDialog.isShowing()){
            downloadDialog.show();
        }
    }
private void canceledDialog() {
        if(downloadDialog!=null&&downloadDialog.isShowing()){
            downloadDialog.dismiss();
        }
    }

Step 2:通过DownloadManager开始下载

创建一个download方法用于下载应用:

private void download() {
        showDialog();
        //最好是用单线程池,或者intentService
        new Thread(new DownLoadRunnable(this,url, handler)).start();
    }

接下来看看在DownloadRunnable中的具体处理方法:

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

    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获得下载信息
        long 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;
                            handler.obtainMessage(downloadManager.STATUS_SUCCESSFUL).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) {

        DownloadManager.Request  request=new DownloadManager.Request(Uri.parse(url));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);// 隐藏notification

        request.setAllowedNetworkTypes(request.NETWORK_WIFI);//设置下载网络环境为wifi

        request.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_DOWNLOADS,"MyApp.app");//指定apk缓存路径,默认是在SD卡中的Download文件夹

        return  request;
    }
}

Step 3:在dialog里中更新下载状态(handler回调主线程中,更新UI)

在主线程中通过Handler获取Message,并回调handleMessage方法处理信息,更新UI,MainActivity.java:

Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case DownloadManager.STATUS_SUCCESSFUL:
                    downloadDialog.setProgress(100);
                    canceledDialog();
                    Toast.makeText(MainActivity.this, "下载任务已经完成!", Toast.LENGTH_SHORT).show();
                    break;

                case DownloadManager.STATUS_RUNNING:
                    //int progress = (int) msg.obj;
                    downloadDialog.setProgress((int) msg.obj);
                    //canceledDialog();
                    break;

                case DownloadManager.STATUS_FAILED:
                    canceledDialog();
                    break;

                case DownloadManager.STATUS_PENDING:
                    showDialog();
                    break;
            }
        }
    };

Step 4:下载完成后进行安装(静态注册广播接收器,实现安装功能)

AndroidManifest.xml:

<receiver
            android:name=".InstallApkBroadcast">
            <intent-filter>
                <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
                <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
            </intent-filter>
        </receiver>

接受广播并自行安装应用,InstallApkBroadcast.java:

public class InstallApkBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        install(context);
    }

    private void install(Context context) {
        Intent installintent = new Intent();
        installintent.setAction(Intent.ACTION_VIEW);
        // 在Boradcast中启动活动需要添加Intent.FLAG_ACTIVITY_NEW_TASK
        installintent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        installintent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/myApp.apk")),
                "application/vnd.android.package-archive");//存储位置为Android/data/包名/file/Download文件夹
        context.startActivity(installintent);
    }
}

最后别忘了加上权限:

<uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />

最终效果为:

Android 下载管理DownloadManager输入错误网址还会执行下载任务 安卓downloadmanager_android_03

这里放上MainActivity的完整代码:

MainActivity.java:

public class MainActivity extends AppCompatActivity {
    private Button force_button;
    private DownloadDialog downloadDialog;
    private DownloadManager mDownloadManager;
    private String url = "http://172.30.18.222:8080/lanota.apk";

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case DownloadManager.STATUS_SUCCESSFUL:
                    downloadDialog.setProgress(100);
                    canceledDialog();
                    Toast.makeText(MainActivity.this, "下载任务已经完成!", Toast.LENGTH_SHORT).show();
                    break;

                case DownloadManager.STATUS_RUNNING:
                    //int progress = (int) msg.obj;
                    downloadDialog.setProgress((int) msg.obj);
                    //canceledDialog();
                    break;

                case DownloadManager.STATUS_FAILED:
                    canceledDialog();
                    break;

                case DownloadManager.STATUS_PENDING:
                    showDialog();
                    break;
            }
        }
    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        force_button = (Button)this.findViewById(R.id.force_button);
        force_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                download();
            }
        });
    }

    private void download() {
        showDialog();
        //最好是用单线程池,或者intentService取代
        new Thread(new DownLoadRunnable(this,url, handler)).start();
    }



    private void showDialog() {
        if(downloadDialog==null){
            downloadDialog = new DownloadDialog(this);
        }

        if(!downloadDialog.isShowing()){
            downloadDialog.show();
        }
    }

    private void canceledDialog() {
        if(downloadDialog!=null&&downloadDialog.isShowing()){
            downloadDialog.dismiss();
        }
    }
}