5.1 问题
应用程序需要为设备下载一个大的资源,如电影文件,并且不要求用户让应用程序一直处于激活状态。
5.2解决方案
(API LEVEL 9)
使用DownloadManager API。DownloadManager是API Level 9中加入SDK的一个服务,它让系统完全处理和管理需要多长时间运行的下载操作。使用这个服务最大的有点就是即使在下载失败、连接改变甚至设备重启时,DownloadManager依然会继续尝试下载资源。
5.3 实现机制
以下代码清单是一个示例Activity,它使用DownloadManager来处理一个大图片文件的下载。完成下载后,这个图片会显示在一个ImageView上。在使用DownloadManager访问Web上的内容时,确保在应用程序的清单文件中声明了android.permission.INTERNET权限。
DownloadManager示例Activity
public class DownloadActivity extends Activity {
private static final String DL_ID = "downloadId";
private SharedPreferences prefs;
private DownloadManager dm;
private ImageView imageView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
imageView = new ImageView(this);
setContentView(imageView);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
dm = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
}
@Override
public void onResume() {
super.onResume();
if(!prefs.contains(DL_ID)) {
//开始下载
Uri resource = Uri.parse("http://www.bigfoto.com/dog-animal.jpg");
DownloadManager.Request request = new DownloadManager.Request(resource);
//设置允许的连接来处理下载 request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
request.setAllowedOverRoaming(false);
//在通知栏上显示
request.setTitle("Download Sample");
long id = dm.enqueue(request);
//保存唯一的 id
prefs.edit().putLong(DL_ID, id).commit();
} else {
//下载已经开始,检查下载状态
queryDownloadStatus();
}
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
queryDownloadStatus();
}
};
private void queryDownloadStatus() {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(prefs.getLong(DL_ID, 0));
Cursor c = dm.query(query);
if(c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
Log.d("DM Sample","Status Check: "+status);
switch(status) {
case DownloadManager.STATUS_PAUSED:
case DownloadManager.STATUS_PENDING:
case DownloadManager.STATUS_RUNNING:
//什么也不做,下载依然进行
break;
case DownloadManager.STATUS_SUCCESSFUL:
//下载完成,显示图片
try {
ParcelFileDescriptor file = dm.openDownloadedFile(prefs.getLong(DL_ID, 0));
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(file);
imageView.setImageBitmap(BitmapFactory.decodeStream(fis));
} catch (Exception e) {
e.printStackTrace();
}
break;
case DownloadManager.STATUS_FAILED:
//清除下载并稍后重试
dm.remove(prefs.getLong(DL_ID, 0));
prefs.edit().clear().commit();
break;
}
}
}
}
要点:
本书出版时,SDK中存在一个bug,就是在使用DownloadManager时需要抛出android.permission.ACCESS_ALL_DOWNLOADS异常。实际上是在清单中没有声明android.permission.INTERNET时才会抛出这个异常。
示例中所有有用的工作都是在Activity.onResume()中完成的,这样每次用户返回到Activity时,应用程序都可以确定下载的状态。每次下载都会调用DownloadManager.enqueue()并返回一个long型的ID值,该值可以用来引用管理器中的本次下载。在本例中,为了随时监控和获取下载的内容。这个请求至少要指定远程资源的Uri。另外,还可以为这个请求设置很多有用的属性来控制它的行为。这些有用的属性包括:
- Request.setAllowedNetworkTyped() :指定获取下载所使用的网络类型。
- Request.setAllowedOverRoaming() :设定当设备处于漫游模式时是否要下载。
- Request.setDescription() :设置下载在系统通知栏中显示的描述。
- Request.setTitle :设置下载在系统通知栏中显示的标题。
获取ID后,应用程序会使用那个值来检查下载的状态。通过注册BroadcastReceiver来监听ACTION_DOWNLOAD_COMPLETE广播,在下载完成后,会将图片文件设置到Activity的ImageView以进行响应。如果在下载完成时Activity处于暂停状态,那么就会在下次恢复Activity时检测下载状态,并设置ImageView的内容。
要注意ACTION_DOWNLOAD_COMPLETE是DownloadManager对其管理的所有下载任务发出的广播。正因为如此,我们仍然必须检查下载ID才能判断我们的下载是否完成。
目标
在以下代码清单中,我们并没有告诉DownloadManager文件的下载位置。相反,当我们想要访问文件时,我们会使用DownloadManager.openDownloadedFile()方法和首选项中存储的ID值来获得一个ParcelFileDescriptor,它可转换为应用程序可以读取的数据流。这是访问下载内容简单而直接的方式,但还是有一些事项需要注意。
如果没有指定目标位置,文件会下载到共享下载缓存中,系统随时有权删除这个缓存中的文件来回收空间。正因为如此,这种下载方式可以很方便地快速获取数据,但如果您的下载需求是长期的,应该使用DownloadManager.Request的一个方法在外部存储器上指定固定的目标位置:
- Request.setDestinationInExternalFilesDir() :设置目标位置为外部存储器中的一个隐藏目录。
- Request.setDestinationInExternalPublicDir() :设置目标位置为外部存储器中的一个公共目录。
- Request.setDestinationUri() :设置目标位置为外部存储器中的一个文件Uri。
注意:
所有在外部存储器中设置目标位置的方法都需要应用程序在清单中声明android.permission.WRITE_EXTERNAL_STORAGE权限。
在调用DownloadManager.remove()来清单管理器列表中的条目或者用户清理下载列表时,没有明确指定目标位置的文件通常会被移除;而外部存储器中下载的文件在这些文件下则不会被系统移除。