演示
简介
从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) //查询一个下载,返回一个Cursor int 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 default Google还提供了一个简单的方法来实例化本类,这个构造方法为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_NOTIFICATION DownloadManager.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() { @Override public 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); } @Override protected 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 { @Override public 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);//设置当前线程的线程局部变量的值 } }
时间: 2024-10-12 17:40:40