前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(六十一)文件下载管理DownloadManager

Android开发笔记(六十一)文件下载管理DownloadManager

作者头像
aqi00
发布2019-01-18 11:08:49
4.7K1
发布2019-01-18 11:08:49
举报

下载管理DownloadManager

文件下载其实是网络数据访问的一种特殊形式,使用普通的http请求也能完成,就是实现起来会繁琐一些。因为下载功能比较常用,而且业务功能相对统一,所以从Android 2.3(API level 9)开始,Android提供了DownloadManager用于统一管理下载功能。

下载请求

要想使用下载功能,首先得构建一个下载请求,说明从哪里下载、下载参数为何、下载的文件保存到哪里等等。这个下载请求便是DownloadManager的子类Request,下面是该类的常用方法 Request构造函数 : 指定从哪个网络地址下载文件。 Request.setAllowedNetworkTypes : 指定允许进行下载的网络类型。Request.NETWORK_WIFI表示wifi环境(推荐),Request.NETWORK_MOBILE表示数据连接环境(不推荐),Request.NETWORK_BLUETOOTH表示蓝牙环境。 Request.setDestinationInExternalFilesDir : 设置下载文件在本地的保存路径。 Request.addRequestHeader : 给HTTP请求添加头部参数。 Request.setMimeType : 设置下载文件的媒体类型。 Request.setVisibleInDownloadsUi : 设置下载页面是否可见。 Request.setNotificationVisibility : 设置通知栏上的下载任务的可见类型。Request.VISIBILITY_HIDDEN表示隐藏,Request.VISIBILITY_VISIBLE表示下载时可见(下载完成后消失),Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED表示下载进行时与完成后都可见,Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION表示只有下载完成后可见。注意可见类型设置为VISIBILITY_HIDDEN时,需要在AndroidManifest.xml中加入对应权限,即android.permission.DOWNLOAD_WITHOUT_NOTIFICATION Request.setTitle : 设置通知栏上的消息标题。不建议自行设置标题,因为默认标题是下载的文件名。 Request.setDescription : 设置通知栏上的消息描述。不建议自行设置描述,因为默认描述是系统估算的下载剩余时间。

下载操作

构建下载请求完毕,然后才能进行下载的相关操作。下面是DownloadManager常用的下载方法: enqueue : 将下载请求加入到任务队列中,即开始下载任务。该方法返回本次下载任务的编号。 remove : 取消指定编号的下载任务。 restartDownload : 重新下载指定编号的任务。 openDownloadedFile : 打开下载完成的文件。 getMimeTypeForDownloadedFile : 获取下载完成的文件的媒体类型。

查询下载进度

虽然下载进度可在通知栏上查看,但是有时APP自身也想了解当前的下载进度,那就要调用DownloadManager的query方法。该方法的输入参数是一个Query对象,返回结果集的Cursor游标,有关Cursor的用法参见《Android开发笔记(三十一)SQLite游标及其数据结构》。下面是Query类的常用方法: query : 查询指定编号任务的当前下载信息。 Query.setFilterById : 根据编号来过滤下载任务。 Query.setFilterByStatus : 根据状态来过滤下载任务。

下载事件

与文件下载有关的事件不是由监听器实现,而是由广播来实现。主要的下载事件有下面三个: 1、下载完成事件:在下载完成时,系统会发出一个action为DownloadManager.ACTION_DOWNLOAD_COMPLETE(android.intent.action.DOWNLOAD_COMPLETE)的广播,因此可注册一个该广播的接收器,用来判断当前下载任务是否已下载完毕,以及后续的处理。 2、下载进行时的通知栏点击事件:在下载过程中,用户点击通知栏上的下载任务,系统便发出action为DownloadManager.ACTION_NOTIFICATION_CLICKED(android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED)的广播,所以可注册该广播的接收器进行相关处理,比如说跳转到该任务的下载进度页面; 3、下载完成后的通知栏点击事件:在不同时刻点击下载任务,会触发不同的事件。下载未完成时点击,触发的是系统广播DownloadManager.ACTION_NOTIFICATION_CLICKED;下载完成后点击,触发的是系统的ACTION_VIEW即浏览页,该动作由系统根据媒体类型去寻找对应的程序来打开,并没有发出广播消息。如果我们要控制此时的点击行为,可以在Request中通过setMimeType方法设置媒体类型,这样Android就会按照这个类型做对应的浏览处理。

断点续传及其他

博主一开始学习DownloadManager时,就觉得好奇怪,该工具竟然没有提供暂停方法和恢复方法,这岂不意味着,文件下载没法断点续传了么?后来在实际开发中测试发现,DownloadManager其实比较智能,当网络一直是允许类型时,任务会一直下载;当网络断开或者不在允许范围内时,任务会自动暂停下载;只要网络连上或者切换到允许范围内,那么任务会自动恢复下载(这里会断点续传)。所以呢,开发者不用关心异常中断,也不用关心网络切换时的额外处理了,原来DownloadManager都已经帮我们实现了。 另外,同一个文件被重复下载时,已经下载完的文件并不会被覆盖,后来下载的文件会自动重命名。所以有时会发现下载下来的文件名与源文件名不一致,这很可能是重复下载造成了文件重命名。

自定义进度条

文件下载和上传都经常用到进度条,可是Android自带的ProgressBar无法显示进度百分比的文本。既然如此,我们还是基于ProgressBar自定义一个附带百分比文本的进度条,顺便复习一下自定义视图的用法。 首先在自定义类CustomProgressBar中声明一个画笔与百分比文本,然后提供百分比文本的设置和获取方法,最后重写onDraw方法,在控件中央使用drawText函数画上百分比文本。百分比文本的颜色可通过画笔的setColor来设置,文本大小可通过画笔的setTextSize来设置。 下面是CustomProgressBar的代码示例:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.ProgressBar;

public class CustomProgressBar extends ProgressBar {
	
	private String mProgressText;
	private Paint mPaint;

	public CustomProgressBar(Context context) {
		super(context);
		initPaint();
	}

	public CustomProgressBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		initPaint();
	}

	public CustomProgressBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initPaint();
	}
	
	private void initPaint() {
		mPaint = new Paint();
		mPaint.setColor(Color.BLACK);
		mPaint.setTextSize(30);
	}
	
	public void setProgressText(String text) {
		mProgressText = text;
	}
	
	public String getProgressText() {
		return mProgressText;
	}

	@Override
	protected synchronized void onDraw(Canvas canvas) {
	    super.onDraw(canvas);
	    Rect rect = new Rect();
	    mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), rect);
	    int x = (getWidth() / 2) - rect.centerX();
	    int y = (getHeight() / 2) - rect.centerY();
	    canvas.drawText(mProgressText, x, y, this.mPaint);
	}
	
}

系统服务清单

到本文为止,Android的系统服务基本都用过了一遍,下面统计一下这些系统服务及其对应的章节说明: CONNECTIVITY_SERVICE : 网络连接服务(android.net.ConnectivityManager),参见《Android开发笔记(五十五)手机设备基本操作》与《Android开发笔记(六十)网络检测与连接》。 TELEPHONY_SERVICE : 电话设备服务(android.telephony.TelephonyManager),参见《Android开发笔记(四十六)手机相关事件》和《Android开发笔记(五十五)手机设备基本操作》。 WIFI_SERVICE : wifi与热点服务(android.net.wifi.WifiManager),参见《Android开发笔记(五十五)手机设备基本操作》与《Android开发笔记(六十)网络检测与连接》。 LAYOUT_INFLATER_SERVICE : 布局填充服务(android.view.LayoutInflater),参见《Android开发笔记(十二)测量尺寸与下拉刷新》。 SEARCH_SERVICE : 搜索管理服务(android.app.SearchManager),参见《Android开发笔记(二十)顶部导航栏ActionBar》。 WINDOW_SERVICE : 视窗管理服务(android.view.WindowManager),参见《Android开发笔记(三)屏幕分辨率》。 LOCATION_SERVICE : 定位服务(android.location.LocationManager),参见《Android开发笔记(四十六)手机相关事件》和《Android开发笔记(五十五)手机设备基本操作》。 ALARM_SERVICE : 闹钟/定时器服务(android.app.AlarmManager),参见《Android开发笔记(五十)定时器AlarmManager》。 NOTIFICATION_SERVICE : 通知推送服务(android.app.NotificationManager),参见《Android开发笔记(五十二)通知推送Notification》。 AUDIO_SERVICE : 铃声服务(android.media.AudioManager),参见《Android开发笔记(五十八)铃声与震动》。 VIBRATOR_SERVICE : 震动服务(android.os.Vibrator),参见《Android开发笔记(五十八)铃声与震动》。 SENSOR_SERVICE : 传感器服务(android.hardware.SensorManager),参见《Android开发笔记(五十九)巧用传感器》。 ACTIVITY_SERVICE : 活动管理服务(android.app.ActivityManager),参见《Android开发笔记(七十九)资源与权限校验》和《Android开发笔记(八十)运行状态检查》。 DOWNLOAD_SERVICE : 下载管理服务(android.app.DownloadManager),参见《Android开发笔记(六十一)文件下载与上传》。 INPUT_METHOD_SERVICE : 输入法服务(android.view.inputmethod.InputMethodManager),参见《Android开发笔记(三十六)展示类控件》。 KEYGUARD_SERVICE : 键盘锁服务(android.app.KeyguardManager) MEDIA_ROUTER_SERVICE : 媒体路由服务(android.media.MediaRouter) POWER_SERVICE : 电源管理服务(android.os.PowerManager),参见《Android开发笔记(一百一十七)app省电方略》 STORAGE_SERVICE : 存储管理服务(android.os.storage.StorageManager),参见《Android开发笔记(七十九)资源与权限校验》。 UI_MODE_SERVICE : 界面模式服务(android.app.UiModeManager) CLIPBOARD_SERVICE : 剪贴板服务(android.content.ClipboardManager),参见《Android开发笔记(一百零五)社会化分享SDK》。

代码示例

下面是文件下载查询进度的效果截图:

下面是文件下载的代码例子:

import java.util.HashMap;

import com.example.exmload.R;
import com.example.exmload.util.Utils;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

@SuppressLint({ "DefaultLocale", "SimpleDateFormat" })
public class DownloadActivity extends Activity implements OnClickListener {
	
    private static final String TAG = "DownloadActivity";
	private ConnectivityManager mConnectMgr;
    private DownloadManager mDownloadManager;
    private Button btn_download_notify_apk;
    private Button btn_download_notify_jpg;
    private Button btn_download_without_notify;
    private Button btn_cancel;
    private static TextView tv_download;
    private static TextView tv_notify;
    private ProgressBar pb_download;
    
    private static long mDownloadId = 0;
    private Uri mApkUri = Uri.parse("http://www.lenovomm.com/appdown/20091038-2");
    private Uri mJpgUri = Uri.parse("http://c.hiphotos.baidu.com/news/q%3D100/sign=b8e532c9ee50352ab76121086343fb1a/9c16fdfaaf51f3de3699841a93eef01f3a2979a0.jpg");
    private String mApkDir = "Download/alipay.apk";
    private String mJpgDir = "Download/news.jpg";
    private HashMap<Integer,String> mStatusMap = new HashMap<Integer,String>();
    
    @Override  
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        
		mConnectMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        btn_download_notify_apk = (Button) findViewById(R.id.btn_download_notify_apk);
        btn_download_notify_jpg = (Button) findViewById(R.id.btn_download_notify_jpg);
        btn_download_without_notify = (Button) findViewById(R.id.btn_download_without_notify);
        btn_cancel = (Button) findViewById(R.id.btn_cancel);
        tv_download = (TextView) findViewById(R.id.tv_download);
        tv_notify = (TextView) findViewById(R.id.tv_notify);
        pb_download = (ProgressBar) findViewById(R.id.pb_download);
        pb_download = (ProgressBar) findViewById(R.id.pb_download);
        
        btn_download_notify_apk.setOnClickListener(this);
        btn_download_notify_jpg.setOnClickListener(this);
        btn_download_without_notify.setOnClickListener(this);
        btn_cancel.setOnClickListener(this);
        mStatusMap.put(DownloadManager.STATUS_PENDING, "挂起");
        mStatusMap.put(DownloadManager.STATUS_RUNNING, "运行中");
        mStatusMap.put(DownloadManager.STATUS_PAUSED, "暂停");
        mStatusMap.put(DownloadManager.STATUS_SUCCESSFUL, "成功");
        mStatusMap.put(DownloadManager.STATUS_FAILED, "失败");
    }

    //接收下载完成后的intent
    public static class DownloadCompleteReceiver extends BroadcastReceiver {
        @Override  
        public void onReceive(Context context, Intent intent) {
            if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
                long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                Log.d(TAG," download complete! id : "+downId+", mDownloadId="+mDownloadId);
                tv_download.setVisibility(View.VISIBLE);
                tv_download.setText(Utils.getNowDateTime()+" 编号"+downId+"的下载任务已完成");
            }
        }
    }

    //接收通知栏点击的intent
    public static class NotificationClickReceiver extends BroadcastReceiver {
        @Override  
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG," NotificationClickReceiver onReceive");
            if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
            	long[] downIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
                for (long downId : downIds) {
                    Log.d(TAG," notify click! id : "+downId+", mDownloadId="+mDownloadId);
                    if (downId == mDownloadId) { 
                        tv_notify.setVisibility(View.VISIBLE);
                        tv_notify.setText(Utils.getNowDateTime()+" 编号"+downId+"的下载进度条被点击了一下");
                    }
            	}
            }
        }
    }

	@Override
	public void onClick(View v) {
		pb_download.setVisibility(View.GONE);
		tv_download.setVisibility(View.GONE);
        tv_notify.setVisibility(View.GONE);
		NetworkInfo info = mConnectMgr.getActiveNetworkInfo();
		if (info == null || info.getState() != NetworkInfo.State.CONNECTED) {
			Toast.makeText(this, "当前无可用的上网连接", Toast.LENGTH_LONG).show();
		} else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
			Toast.makeText(this, "当前非wifi环境,请连接wifi后下载", Toast.LENGTH_LONG).show();
		}
		if (v.getId() == R.id.btn_download_notify_apk) {
            Request down = new Request(mApkUri);
            down.setAllowedNetworkTypes(Request.NETWORK_MOBILE|Request.NETWORK_WIFI);
//            down.setTitle("APK下载信息");
//            down.setDescription("这是一个APK下载任务");
            down.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            down.setVisibleInDownloadsUi(true);
            down.setDestinationInExternalFilesDir(this, null, mApkDir);
            mDownloadId = mDownloadManager.enqueue(down);
		} else if (v.getId() == R.id.btn_download_notify_jpg) {
            Request down_request = new Request(mJpgUri);
            down_request.setAllowedNetworkTypes(Request.NETWORK_MOBILE|Request.NETWORK_WIFI);
            down_request.setTitle("JPG下载信息");
            down_request.setDescription("这是一个JPG下载任务");
            down_request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            down_request.setVisibleInDownloadsUi(true);
            down_request.setDestinationInExternalFilesDir(this, null, mJpgDir);
            mDownloadId = mDownloadManager.enqueue(down_request);
		} else if (v.getId() == R.id.btn_download_without_notify) {
            Request down_request = new Request(mApkUri);
            down_request.setAllowedNetworkTypes(Request.NETWORK_MOBILE|Request.NETWORK_WIFI);
            down_request.setNotificationVisibility(Request.VISIBILITY_HIDDEN);
            down_request.setVisibleInDownloadsUi(false);
            down_request.setDestinationInExternalFilesDir(this, null, mApkDir);
            mDownloadId = mDownloadManager.enqueue(down_request);
            
            mHandler.postDelayed(mRefresh, 1000);
		} else if (v.getId() == R.id.btn_cancel) {
			mDownloadManager.remove(mDownloadId);
            tv_download.setVisibility(View.VISIBLE);
            tv_download.setText(Utils.getNowDateTime()+" 编号"+mDownloadId+"的下载任务已取消");
            mHandler.removeCallbacks(mRefresh);
		}
	}
	
	private Handler mHandler = new Handler();
	private Runnable mRefresh = new Runnable() {
		@Override
		public void run() {
			boolean bFinish = false;
            Query down_query = new Query();
            down_query.setFilterById(mDownloadId);
            Cursor cursor = mDownloadManager.query(down_query);
    		if (cursor.moveToFirst()) {
    			for (;; cursor.moveToNext()) {
        			int nameIdx = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
        			int mediaTypeIdx = cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE);
        			int totalSizeIdx = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
        			int nowSizeIdx = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
        			int statusIdx = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
        			int progress = (int)(pb_download.getMax()*
        					cursor.getLong(nowSizeIdx)/cursor.getLong(totalSizeIdx));
        			pb_download.setVisibility(View.VISIBLE);
        			pb_download.setProgress(progress);
        			if (cursor.getString(nameIdx) == null) {
        				break;
        			}
        			String desc = "";
        			desc = String.format("%s文件路径:%s\n", desc, cursor.getString(nameIdx));
        			desc = String.format("%s媒体类型:%s\n", desc, cursor.getString(mediaTypeIdx));
        			desc = String.format("%s文件总大小:%d\n", desc, cursor.getLong(totalSizeIdx));
        			desc = String.format("%s已下载大小:%d\n", desc, cursor.getLong(nowSizeIdx));
        			desc = String.format("%s下载进度:%d%%\n", desc, progress);
        			desc = String.format("%s下载状态:%s\n", desc, mStatusMap.get(cursor.getInt(statusIdx)));
                    tv_notify.setVisibility(View.VISIBLE);
                    tv_notify.setText(desc);
                    if (progress == pb_download.getMax()) {
                    	bFinish = true;
                    }
    				if (cursor.isLast() == true) {
    					break;
    				}
    			}
    		}
    		cursor.close();
    		if (bFinish != true) {
                mHandler.postDelayed(this, 1000);
    		}
		}
	};
	
}

点击下载本文用到的文件下载的工程代码 点此查看Android开发笔记的完整目录

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年02月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 下载管理DownloadManager
    • 下载请求
      • 下载操作
        • 查询下载进度
          • 下载事件
            • 断点续传及其他
            • 自定义进度条
            • 系统服务清单
            • 代码示例
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档