标签:
演示
简介
从Android 2.3开始新增了一个下载管理类,在SDK的文档中我们查找android.app.DownloadManager可以看到。下载管理类可以长期处理多个HTTP下载任务,客户端只需要给出请求的Uri和存放目标文件的位置即可,下载管理使用了一个AIDL服务器,所以可以放心的在后台执行,同时实例化的方法需要使用getSystemService(Context.DOWNLOAD_SERVICE) ,我们可以轻松的通过新增的这个API实现Android平台上的文件下载操作。DownloadManager类提供了以下几种方法来处理,long enqueue(DownloadManager.Request request) //存入队列一个新的下载项ParcelFileDescriptor openDownloadedFile(long id) //打开一个下载后的文件用于读取,参数中的long型id是一个provider中的一条记录Cursor query(DownloadManager.Query query) //查询一个下载,返回一个Cursorint remove(long... ids) //取消下载,同时从下载管理中移除这些条我们可以看到提供的方法都比较简单,给我们操作的最终封装成为一个provider数据库的方式进行添加、查询和移除,但是对于查询和添加任务的细节,我们要看看DownloadManager.Request类和DownloadManager.Query 类了。DownloadManager.Request类addRequestHeader(String header, String value) // 添加一个Http请求报头,比如说User-Agent值可以为Android123或Windows XP等等了,主要是给服务器提供标识setAllowedNetworkTypes(int flags) //设置允许使用的网络类型,目前仅有两种定义,分别为NETWORK_MOBILE(移动网络)和NETWORK_WIFI,可以使用"|"运算setAllowedOverMetered(boolean allow) //是否允许“计量式的网络连接”执行下载操作,默认是true允许的setAllowedOverRoaming(boolean allowed) //是否允许使用漫游,默认是true允许的setDescription(CharSequence description) //设置Notification的描述信息setDestinationInExternalFilesDir(Context context, String dirType, String subPath) //设置将文件存储在【data/data/包名/files】目录;第2个参数是files目录下新建目录的目录名,不存在会自己创建;第3个参数是文件名,如果第3个参数带路径,要确保路径存在;不设置会存在【data/data/com.Android.provider.downloads/cache/】下面setDestinationInExternalPublicDir(String dirType, String subPath) //设置将文件存储在sd卡目录setDestinationUri(Uri uri) //设置需要下载目标的Uri,可以是http、ftp等等了setMimeType(String mimeType) //设置mime类型,这里看服务器配置,一般国家化的都为utf-8编码setShowRunningNotification(boolean show) //是否显示下载进度的提示,设为false需要权限【DOWNLOAD_WITHOUT_NOTIFICATION】,已经被废弃了setNotificationVisibility(int visibility) //设置Notification的显示和隐藏。取值:DownloadManager.Request.VISIBILITY_VISIBLE(默认值,在下载任务执行的过程中显示,下载完成后自动消失),_VISIBLE_NOTIFY_COMPLETED(下载进行时和完成之后都会显示),_HIDDEN(权限),_VISIBLE_NOTIFY_ONLY_COMPLETION(只有当任务完成时才会显示)setTitle(CharSequence title) //设置Notification的标题setVisibleInDownloadsUi(boolean isVisible) //设置下载管理类在处理过程中的界面是否显示。Set whether this download should be displayed in the system‘s Downloads UI. True by defaultGoogle还提供了一个简单的方法来实例化本类,这个构造方法为DownloadManager.Request(Uri uri) ,我们直接填写一个Uri即可,上面的设置使用默认情况。DownloadManager.Query类对于当前下载内容的状态,我们可以使用DownloadManager.Query类来获取,本类比较简单,仅仅提供了两个方法。setFilterById(long... ids) //根据id来过滤查找setFilterByStatus(int flags) //根据任务的状态来查找
定义的常量
下载的状态完成均是以广播的形式通知大家,目前定义了下面三种Action:ACTION_DOWNLOAD_COMPLETE//下载完成的动作ACTION_NOTIFICATION_CLICKED //当用户单击notification中下载管理的某项时触发ACTION_VIEW_DOWNLOADS //查看下载项设置Notification的显示和隐藏:DownloadManager.Request.VISIBILITY_VISIBLE//默认值,在下载任务执行的过程中显示,下载完成后自动消失DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED//下载进行时和完成之后都会显示DownloadManager.Request.VISIBILITY_HIDDEN//将不会显示,如果设置该属性的话,必须添加权限android.permission.DOWNLOAD_WITHOUT_NOTIFICATIONDownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION//只有当任务完成时才会显示DownloadManager类提供的query方法返回一个Cursor对象,其中定义的属性有以下几种:任务目前的状态保存在这个游标的COLUMN_STATUS 字段中,任务的状态有:STATUS_FAILED //失败STATUS_PAUSED //暂停STATUS_PENDING //等待将开始STATUS_RUNNING //正在处理中STATUS_SUCCESSFUL //已经下载成功对于一个尚未完成的项,在Cursor中我们查找COLUMN_REASON字段,可能是以下定义:ERROR_CANNOT_RESUME //不能够继续,由于一些其他原因ERROR_DEVICE_NOT_FOUND //外部存储设备没有找到,比如SD卡没有插入ERROR_FILE_ALREADY_EXISTS //要下载的文件已经存在了,下载管理类是不会覆盖已经存在的文件,所以如果需要重新下载,请先删除以前的文件ERROR_FILE_ERROR //可能由于SD卡原因导致了文件错误ERROR_HTTP_DATA_ERROR //在Http传输过程中出现了问题ERROR_INSUFFICIENT_SPACE //由于SD卡空间不足造成的ERROR_TOO_MANY_REDIRECTS //这个Http有太多的重定向,导致无法正常下载ERROR_UNHANDLED_HTTP_CODE //无法获取http出错的原因,比如说远程服务器没有响应ERROR_UNKNOWN //未知的错误类型有关暂停的一些状态,同样COLUMN_REASON字段的值,可能是以下定义:PAUSED_QUEUED_FOR_WIFI //由于移动网络数据问题,等待WiFi连接能用后再重新进入下载队列。PAUSED_UNKNOWN //未知原因导致了任务下载的暂停.PAUSED_WAITING_FOR_NETWORK //可能由于没有网络连接而无法下载,等待有可用的网络连接恢复。.PAUSED_WAITING_TO_RETRY //由于重重原因导致下载暂停,等待重试。
关于下载目录
指定下载位置,及文件名称1、request.setDestinationInExternalFilesDir(context,"TestDownload","Test.apk")官方说明:Set the local destination for the downloaded file to a path within the application‘s external files directory (as returned by getExternalFilesDir(String).翻译:给下载文件 “制定” 一个路径,文件路径的“特性”跟 getExternalFilesDir(String)类似。关于getExternalFilesDir(String):这个方法的返回值是一个文件夹,这个文件夹是被创建在【/data/data/包名/files/】目录下的,他一般是用来存储你的app运行所需的文件的(如图片的缓存)。默认情况下这个文件夹只有当前app有访问权限,当你的应用程序被卸载之后这个文件夹中的数据也会被清除。2、request.setDestinationInExternalPublicDir("TestDownload","Test.apk");官方说明:Set the local destination for the downloaded file to a path within the public external storage directory (as returned by getExternalStoragePublicDirectory(String)).翻译:这个方法也是用来“制定”一个路径的,这个路径的特性类似于getExternalStoragePublicDirectory(String))关于getExternalStoragePublicDirectory(String):这个方法的返回值是一个文件夹,这个文件夹是被创建在你的SD卡根目录的【mnt/sdcard/】这个文件夹中的内容其他程序都是可以访问的,当你的应用程序被卸载的时候,这个文件夹中的内容不会丢失。通过Context.getExternalFilesDir()方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据通过Context.getExternalCacheDir()方法可以获取到 SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/你的应用的包名/ 这个目录下的所有文件都会被删除,不会留下垃圾信息。而且上面二个目录分别对应 设置->应用->应用详情里面的”清除数据“与”清除缓存“选项如果要保存下载的内容,就不要放在以上目录下
Activity
public class DownloadManagerActivity extends ListActivity {public static final String URL_SMALL_FILE = "http://f2.market.xiaomi.com/download/AppStore/0b6c25446ea80095219649f646b8d67361b431127/com.wqk.wqk.apk";public static final String URL_BIG_FILE = "http://f3.market.xiaomi.com/download/AppChannel/099d2b4f6006a4c883059f459e0025a3e1f25454e/com.pokercity.bydrqp.mi.apk";private DownloadCompleteReceiver receiver;private TextView tv_info;private int status = 1;public static final int MSG_WHAT_DOWNLOAD_ID = 1;private boolean isQueryDownTaskById = false;@SuppressLint("HandlerLeak")private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_WHAT_DOWNLOAD_ID:if (isQueryDownTaskById) {long id = (long) msg.obj;tv_info.append("查询结果\n");Map<String, String> map = queryDownTaskById(DownloadManagerActivity.this, id);for (Map.Entry<String, String> k_v : map.entrySet()) {//增强for遍历键值对tv_info.append(k_v + "\n");}}break;}};};protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);String[] array = { "注册接收DownloadManager三个广播", "取消注册广播接收者", //"下载文件到SD卡下的Download目录", "下载文件到SD卡指定目录", "下载文件到/data/data/包名/files/目录", //"通过状态查询下载任务", "通过id查询下载任务", };for (int i = 0; i < array.length; i++) {array[i] = i + "、" + array[i];}tv_info = new TextView(this);// 将内容显示在TextView中tv_info.setTextColor(Color.BLUE);tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);tv_info.setPadding(20, 10, 20, 10);getListView().addFooterView(tv_info);setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));//加入到ThreadLocal中HandlerManager.setHandler(mHandler);}@Overrideprotected void onListItemClick(ListView l, View v, int position, long id) {switch (position) {case 0:if (receiver == null) receiver = new DownloadCompleteReceiver();IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);//下载完成的动作intentFilter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED);//当用户单击notification中下载管理的某项时触发intentFilter.addAction(DownloadManager.ACTION_VIEW_DOWNLOADS);//查看下载项registerReceiver(receiver, intentFilter);break;case 1:if (receiver != null) {unregisterReceiver(receiver);receiver = null;}break;case 2:simpleDownLoadFileToSdNoUI(this, URL_SMALL_FILE);break;case 3:downLoadFile(this, URL_BIG_FILE, "包青天", new Random().nextInt(1000) + ".apk", DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED, //DownloadManager.Request.NETWORK_WIFI | DownloadManager.Request.NETWORK_MOBILE, true);break;case 4:simpleDownLoadFileToDataNoUI(this, URL_SMALL_FILE);break;case 5:tv_info.setText("\n" + getStatusString(status) + " 的任务有:");List<Map<String, String>> runningList = queryDownTaskByStatus(this, status);//共有五种状态// 遍历for (Map<String, String> hm : runningList) {tv_info.append("\n**********************************");Set<String> set = hm.keySet();for (String key : set) {String value = hm.get(key);tv_info.append("\n" + key + "-" + value);}}//下一个状态status = status << 1;if (status > (1 << 4)) status = 1;break;case 6:isQueryDownTaskById = true;break;}}public static String getStatusString(int status) {switch (status) {case DownloadManager.STATUS_FAILED:return "失败";case DownloadManager.STATUS_PAUSED:return "暂停";case DownloadManager.STATUS_PENDING:return "等待";case DownloadManager.STATUS_RUNNING:return "正在下载";case DownloadManager.STATUS_SUCCESSFUL:return "下载成功";default:return "没有这种状态";}}public static void simpleDownLoadFileToSdNoUI(Context mContext, String url) {downLoadFile(mContext, url, null, null, DownloadManager.Request.VISIBILITY_HIDDEN, DownloadManager.Request.NETWORK_WIFI, true);}public static void simpleDownLoadFileToDataNoUI(Context mContext, String url) {downLoadFile(mContext, url, null, null, DownloadManager.Request.VISIBILITY_HIDDEN, DownloadManager.Request.NETWORK_WIFI, false);}/*** 使用DownloadManager下载文件* @param mContext 上下文* @param url 文件路径,请自行确保路径正确* @param filePath 子目录名,设为null则不创建子目录* @param fileName 文件名,设为null则使用服务器路径中的文件名* @param visibility 通知显示的类型,请使用DownloadManager.Request中定义的常量,设置为不显示需要权限DOWNLOAD_WITHOUT_NOTIFICATION* @param networkType 只允许在指定的网络类型下下周,请使用DownloadManager.Request中定义的常量,可以使用"|"运算* @param isToSdCard 是否保存到SD卡上,设为false则保存到【/data/data/包名/files/】目录下*/public static void downLoadFile(Context mContext, String url, String filePath, String fileName, int visibility, int networkType, boolean isToSdCard) {if (url == null || url == "") url = "http://www.sinaimg.cn/dy/slidenews/3_img/2016_22/77542_379697_224394.jpg";if (filePath == null || filePath == "") filePath = Environment.DIRECTORY_DOWNLOADS;if (fileName == null || fileName == "") fileName = url.substring(url.lastIndexOf("/") + 1);//截取文件名及后缀名if (fileName == null || fileName == "") fileName = new SimpleDateFormat("yyyy.MM.dd HH-mm-ss", Locale.CHINA).format(new Date());//截取失败时自动命名DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)).setNotificationVisibility(visibility).setAllowedNetworkTypes(networkType);if (isToSdCard) request.setDestinationInExternalPublicDir(filePath, fileName);//以SD卡路径为根路径else request.setDestinationInExternalFilesDir(mContext, filePath, fileName);//以【/data/data/包名/files/】为根路径((DownloadManager) mContext.getSystemService(DOWNLOAD_SERVICE)).enqueue(request);//将下载请求放入队列}public static List<Map<String, String>> queryDownTaskByStatus(Context mContext, int status) {DownloadManager.Query query = new DownloadManager.Query();query.setFilterByStatus(status);Cursor cursor = ((DownloadManager) mContext.getSystemService(DOWNLOAD_SERVICE)).query(query);if (cursor != null) {List<Map<String, String>> data = new ArrayList<Map<String, String>>();while (cursor.moveToNext()) {String id = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));String title = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE));String uri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));String name = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));//String mStatu = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));String sizeNow = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//已下载String sizeTotal = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));Map<String, String> map = new HashMap<String, String>();map.put("id", id);map.put("title", title);map.put("name", name);map.put("uri", uri);map.put("status", sizeTotal + ":" + sizeNow);data.add(map);}cursor.close();return data;}return null;}public static Map<String, String> queryDownTaskById(Context mContext, long id) {DownloadManager.Query query = new DownloadManager.Query();query.setFilterById(id);Cursor cursor = ((DownloadManager) mContext.getSystemService(DOWNLOAD_SERVICE)).query(query);if (cursor != null) {Map<String, String> data = new HashMap<String, String>();if (cursor.moveToNext()) {String sizeNow = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));String sizeTotal = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));data.put("sizeNow", sizeNow);data.put("sizeTotal", sizeTotal);}cursor.close();return data;}return null;}}
广播
/** 注册一个广播接收器,当下载完毕后会收到一个android.intent.action.DOWNLOAD_COMPLETE的广播,在这里取出队列里下载任务,进行安装*/public class DownloadCompleteReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);Handler mHandler = HandlerManager.getHandler();mHandler.sendMessage(Message.obtain(mHandler, DownloadManagerActivity.MSG_WHAT_DOWNLOAD_ID, id));if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {Toast.makeText(context, "编号 " + id + " 的下载任务已经完成!", Toast.LENGTH_SHORT).show();} else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {Toast.makeText(context, "别瞎点-" + id, Toast.LENGTH_SHORT).show();} else if (intent.getAction().equals(DownloadManager.ACTION_VIEW_DOWNLOADS)) {Toast.makeText(context, "查看下载项-" + id, Toast.LENGTH_SHORT).show();}//如果下载的是APK文件,则自动安装DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);Query query = new DownloadManager.Query();query.setFilterById(id);Cursor cursor = downloadManager.query(query);if (cursor != null) {if (cursor.moveToFirst()) {int status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));String name = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));String uri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));String end = name.substring(name.lastIndexOf("."));if (end != null && end.equals(".apk") && status == DownloadManager.STATUS_SUCCESSFUL) {Intent mIntent = new Intent(Intent.ACTION_VIEW);mIntent.setDataAndType(Uri.parse(uri), "application/vnd.android.package-archive");mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(mIntent);}}cursor.close();}}}
UI线程共享的Handler
/**在UI线程中均可以使用此Handler,如Activity、Service、BroadcastReceiver,他们用的都是同一个对象,可以方便在相互通讯* ThreadLocal会为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/public class HandlerManager {private static ThreadLocal<Handler> threadLocal = new ThreadLocal<Handler>();public static Handler getHandler() {return threadLocal.get();//返回当前线程所对应的线程局部变量}public static void setHandler(Handler mHandler) {threadLocal.set(mHandler);//设置当前线程的线程局部变量的值}}
标签:
原文地址:http://www.cnblogs.com/baiqiantao/p/5562008.html