原文地址:https://blog.stylingandroid.com/downloadmanager-part-1/
在App中一个相当普遍的操作就是下载内容到我们的设备中,虽然通过网络下载所需要的内容是很简单的,但是还是有另外一种构建方法,如果下载下来的内容可以与其他的应用共享或者存储到设备上一个公共的位置的话将会是非常有用的,在这一系列文章中,将看一下DownloadManager,他是一个易于使用的API,并且能够解决很多常见的问题。
在我们深入了解之前,先提一下,DownloadManager在API 9以来就一直存在,虽然从那之后有一些小的调整,但是大体上已经是相当稳定的了。
现在创建个简单的Activity来包含我们的UI,他包含一个Button,当点击的时候,可以下载一个PDF文件。
重要的一点必须说明的是有一个Downloader
对象包含所有的DownloadManager的逻辑,并且当下载成功的时候里面的fileDownloaded()
方法会被回调。还有一个很重要的事情就是我们需要在onDestroy
中调用Downloader
的unregister()
方法。
public class MainActivity extends AppCompatActivity implements Downloader.Listener {
private static final String URI_STRING = "http://www.cbu.edu.zm/downloads/pdf-sample.pdf";
private Button download;
private Downloader downloader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
download = (Button) findViewById(R.id.download);
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
downloadOrCancel();
}
});
downloader = Downloader.newInstance(this);
}
void downloadOrCancel() {
if (downloader.isDownloading()) {
cancel();
} else {
download();
}
updateUi();
}
private void cancel() {
downloader.cancel();
}
private void download() {
Uri uri = Uri.parse(URI_STRING);
downloader.download(uri);
}
@Override
public void fileDownloaded(Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimeType);
startActivity(intent);
updateUi();
}
@Override
public Context getContext() {
return getApplicationContext();
}
希望上面的代码是简单明了的,接着我们会继续探索DownloadManager的神奇,并更好的了解在 fileDownloaded
中做了什么。
现在让我们深入了解Downloader类,看一下我们该如何使用DownloadManager
class Downloader implements DownloadReceiver.Listener {
private final Listener listener;
private final DownloadManager downloadManager;
private DownloadReceiver receiver = null;
private long downloadId = -1;
static Downloader newInstance(Listener listener) {
Context context = listener.getContext();
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
return new Downloader(downloadManager, listener);
}
Downloader(DownloadManager downloadManager, Listener listener) {
this.downloadManager = downloadManager;
this.listener = listener;
}
void download(Uri uri) {
if (!isDownloading()) {
register();
DownloadManager.Request request = new DownloadManager.Request(uri);
downloadId = downloadManager.enqueue(request);
}
}
boolean isDownloading() {
return downloadId >= 0;
}
void register() {
if (receiver == null && isDownloading()) {
receiver = new DownloadReceiver(this);
receiver.register(listener.getContext());
}
}
@Override
public void downloadComplete(long completedDownloadId) {
if (downloadId == completedDownloadId) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
downloadId = -1;
unregister();
Cursor cursor = downloadManager.query(query);
while (cursor.moveToNext()) {
getFileInfo(cursor);
}
cursor.close();
}
}
void unregister() {
if (receiver != null) {
receiver.unregister(listener.getContext());
}
receiver = null;
}
private void getFileInfo(Cursor cursor) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
Long id = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
Uri uri = downloadManager.getUriForDownloadedFile(id);
String mimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
listener.fileDownloaded(uri, mimeType);
}
}
void cancel() {
if (isDownloading()) {
downloadManager.remove(downloadId);
downloadId = -1;
unregister();
}
}
interface Listener {
void fileDownloaded(Uri uri, String mimeType);
Context getContext();
}
}
可以通过调用getSystemService(Context.DOWNLOAD_SERVICE)
来获取一个DownloadManager的实例。
初始化一个简单的下载并且在download()
方法中执行,我们首先注册了一个BroadcastReceiver
用来处理DownloadManager一旦下载成功的回调,最后我们通过Uri构造一个DownloadManager.Request
,最后,将下载请求加入到DownloadManager中,同时返回一个唯一的ID,用来标识我们此次的下载请求。
我们可以调用cancel()
方法来取消下载请求并且使用ID将此次下载从DownloadManager中移除掉。
我们在Activity中onDestroy()
中调用的unregister()
方法使用来取消注册BroadcastReceiver的。很重要的一点事,我们不能保证在退出应用程序的时候能够下载完成,这也是使用DownloadManager的好处之一,我们不需要创建后台服务来执行下载任务,DownloadManager已经为我们做过了,在Activity中的onDestroy取消下载视为了确保,当App在未激活的状况下下载完成我们不用去尝试做任何事情。
downloadComplete()
将在下载完成时由BroadcastReceiver调用,首先会检查他是否匹配我们的下载Id,如果一致的话,我们会查询DownloadManager然后获取当前的下载信息,如果是多个任务同时下载的话,那么我们需要定义多个下载ID在查询中,然后Cursor会返回多条数据,在上面的例子中,就下载了一个任务,但是那些代码同样适用于多个任务的情况。
首先我们需要提取下载状态的字段,用来指示下载的状态,在我们的情况下,只有在下载完成时会被触发,所以我们只需要检查下载是否成功。然而在一个完整的实现中,恢复之前启动应用的下载状态将会非常有用。
一旦下载成功的时候,我们可以提取内容的本地Uri(我们下载所存储的位置)和MIME 类型,转换之后返回给我们的监听者。
让我们看一下BroadcastReceiver的代码:
class DownloadReceiver extends BroadcastReceiver {
private final Listener listener;
DownloadReceiver(Listener listener) {
this.listener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
listener.downloadComplete(downloadId);
}
public void register(Context context) {
IntentFilter downloadFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
context.registerReceiver(this, downloadFilter);
}
public void unregister(Context context) {
context.unregisterReceiver(this);
}
interface Listener {
void downloadComplete(long downloadId);
}
}
这里值得注意的是我们需要注册的操作是DownloadManager.ACTION_DOWNLOAD_COMPLETE
,然后将被下载完成的事件唤醒,我们也可以注册其他事件,例如用户点击下载通知事件,可以将用户直接带到应用中来控制或者查看下载的状态,但是通常我们只对完成感兴趣。
还有一个值得注意的是,当我们接收到下载完成的Intent时,可以从Intent extras中获取downloadId。
@Override
public void fileDownloaded(Uri uri, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, mimeType);
startActivity(intent);
updateUi();
}
下一篇文章,我们将会深入的剖析DownloadManager更多强大的功能。