Android DownloadManager、备份文件和取消的下载
Android DownloadManager, backup files and canceled downloads
在我的应用程序中,我实现了一种通过 DownloadManager 下载数据文件的机制。当用户开始下载时,旁边会出现一个取消按钮,允许用户取消正在进行的下载。
这些数据文件会在服务器上定期更新,因此用户会不时地再次下载相同的文件。文件名在更新过程中保持稳定。
由于用户可能在下载过程中随时点击取消,我想保留旧版本直到下载成功完成。为此,我重命名了现有文件,然后才开始下载。如果用户取消下载(或者由于某种原因下载失败),我想将备份文件恢复到原来的位置。
对于取消的情况,我最初在点击取消按钮时将以下代码添加到运行:
if (downloadManager.remove(reference) > 0) {
if (destFile.exists())
destFile.delete();
backupFile.renameTo(destFile);
}
当我刷新文件时,旧文件在下载开始前被重命名。但是,我取消下载后,部分文件和备份都没有了。
由于我已经使用 FileObserver
来监控下载进度,我将其扩展为还可以监控文件删除并生成日志消息。在 logcat 中,我看到同一文件有两个删除事件,这表明部分下载的文件被删除,备份被重命名,然后重命名的备份也被删除。
很公平,我想,显然 DownloadManager 会在后台负责删除,所以我需要注意它的发生。所以我修改了上面的事件处理程序,只将文件路径存储在一个列表中,而不做任何文件操作。然后我修改了 FileObserver
以将所有已删除的文件与列表进行比较:如果匹配,则重命名备份文件。此外,我为每个操作添加了日志输出。
但是,事件的顺序实际上仍然是相同的:现在部分下载的文件被下载管理器删除,触发我的 FileObserver
,这将反过来重命名备份文件。之后,备份文件被删除。
在我看来,下载管理器似乎过于热心:当下载被取消时,它会删除下载的文件,然后检查它是否真的消失了,如果它仍然在该路径中找到文件,则重试删除。
我怎样才能解决这个问题并防止下载管理器删除它没有下载的文件?
我最终解决了多重删除问题,方法是利用 Android 下载管理器永远不会覆盖现有文件这一事实,自行将下载目标重命名为仍然可用的名称。
再次下载文件时,我不会费心将旧文件移开。下载管理器将检测到文件已经存在,并为下载选择一个不同的名称。下载成功完成后,我删除旧文件并重命名新文件。
唯一的挑战是确定下载管理器选择的文件名,因为 Android 似乎对此没有任何明确的通知。下载开始时没有意图被触发,因此我不得不再次求助于 FileObserver
。
观看 FileObserver.CREATE
似乎是最直接的方式。但是,当我此时查询下载列表时,查询将 return 本地路径的空值。
因此我求助于 FileObserver.MODIFY
,它会在每次修改文件时触发。我已经用它来显示下载进度了,此时必须有一个本地文件。第一次为下载管理器重命名的文件触发此事件时,我将获得一个尚未在我的列表中的文件名。然后我 运行 下面的代码:
// File file: the file being downloaded
// DownloadInfo info: information about a download in progress
/* First progress report for a renamed file */
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterByStatus(~(DownloadManager.STATUS_FAILED | DownloadManager.STATUS_SUCCESSFUL));
Cursor cursor = downloadManager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
do {
Long reference = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
if (file.equals(new File(path))) {
info = downloadsByReference.get(reference);
if (info != null) {
info.downloadFile = file;
downloadsByFile.put(info.downloadFile, info);
}
}
} while (cursor.moveToNext());
cursor.close();
每个正在进行的下载都由一个 DownloadInfo
实例描述,其中包括两个文件名的引用。我将它们保存在三个 Map
中:
downloadsByReference
使用下载管理器的 ID 作为密钥
downloadsByFile
使用本地文件作为key
downloadsByUri
使用 URI 作为键
下载完成后,我在 downloadsByReference
中查找其 ID 以获取两个文件名。如果它们不同,我删除旧文件,然后重命名新文件。
在我的应用程序中,我实现了一种通过 DownloadManager 下载数据文件的机制。当用户开始下载时,旁边会出现一个取消按钮,允许用户取消正在进行的下载。
这些数据文件会在服务器上定期更新,因此用户会不时地再次下载相同的文件。文件名在更新过程中保持稳定。
由于用户可能在下载过程中随时点击取消,我想保留旧版本直到下载成功完成。为此,我重命名了现有文件,然后才开始下载。如果用户取消下载(或者由于某种原因下载失败),我想将备份文件恢复到原来的位置。
对于取消的情况,我最初在点击取消按钮时将以下代码添加到运行:
if (downloadManager.remove(reference) > 0) {
if (destFile.exists())
destFile.delete();
backupFile.renameTo(destFile);
}
当我刷新文件时,旧文件在下载开始前被重命名。但是,我取消下载后,部分文件和备份都没有了。
由于我已经使用 FileObserver
来监控下载进度,我将其扩展为还可以监控文件删除并生成日志消息。在 logcat 中,我看到同一文件有两个删除事件,这表明部分下载的文件被删除,备份被重命名,然后重命名的备份也被删除。
很公平,我想,显然 DownloadManager 会在后台负责删除,所以我需要注意它的发生。所以我修改了上面的事件处理程序,只将文件路径存储在一个列表中,而不做任何文件操作。然后我修改了 FileObserver
以将所有已删除的文件与列表进行比较:如果匹配,则重命名备份文件。此外,我为每个操作添加了日志输出。
但是,事件的顺序实际上仍然是相同的:现在部分下载的文件被下载管理器删除,触发我的 FileObserver
,这将反过来重命名备份文件。之后,备份文件被删除。
在我看来,下载管理器似乎过于热心:当下载被取消时,它会删除下载的文件,然后检查它是否真的消失了,如果它仍然在该路径中找到文件,则重试删除。
我怎样才能解决这个问题并防止下载管理器删除它没有下载的文件?
我最终解决了多重删除问题,方法是利用 Android 下载管理器永远不会覆盖现有文件这一事实,自行将下载目标重命名为仍然可用的名称。
再次下载文件时,我不会费心将旧文件移开。下载管理器将检测到文件已经存在,并为下载选择一个不同的名称。下载成功完成后,我删除旧文件并重命名新文件。
唯一的挑战是确定下载管理器选择的文件名,因为 Android 似乎对此没有任何明确的通知。下载开始时没有意图被触发,因此我不得不再次求助于 FileObserver
。
观看 FileObserver.CREATE
似乎是最直接的方式。但是,当我此时查询下载列表时,查询将 return 本地路径的空值。
因此我求助于 FileObserver.MODIFY
,它会在每次修改文件时触发。我已经用它来显示下载进度了,此时必须有一个本地文件。第一次为下载管理器重命名的文件触发此事件时,我将获得一个尚未在我的列表中的文件名。然后我 运行 下面的代码:
// File file: the file being downloaded
// DownloadInfo info: information about a download in progress
/* First progress report for a renamed file */
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterByStatus(~(DownloadManager.STATUS_FAILED | DownloadManager.STATUS_SUCCESSFUL));
Cursor cursor = downloadManager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
do {
Long reference = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
if (file.equals(new File(path))) {
info = downloadsByReference.get(reference);
if (info != null) {
info.downloadFile = file;
downloadsByFile.put(info.downloadFile, info);
}
}
} while (cursor.moveToNext());
cursor.close();
每个正在进行的下载都由一个 DownloadInfo
实例描述,其中包括两个文件名的引用。我将它们保存在三个 Map
中:
downloadsByReference
使用下载管理器的 ID 作为密钥downloadsByFile
使用本地文件作为keydownloadsByUri
使用 URI 作为键
下载完成后,我在 downloadsByReference
中查找其 ID 以获取两个文件名。如果它们不同,我删除旧文件,然后重命名新文件。