Android DownloadManager API - 下载完成后如何打开文件?

41

我遇到了一些问题,即在通过DownloadManager API成功下载文件后打开已下载的文件。 在我的代码中:

Uri uri=Uri.parse("http://www.nasa.gov/images/content/206402main_jsc2007e113280_hires.jpg");

Environment
    .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    .mkdirs();

lastDownload = mgr.enqueue(new DownloadManager.Request(uri)
    .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |
                            DownloadManager.Request.NETWORK_MOBILE)
    .setAllowedOverRoaming(false)
    .setTitle("app update")
    .setDescription("New version 1.1")
    .setShowRunningNotification(true)
    .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "a.apk"));

Cursor c=mgr.query(new DownloadManager.Query().setFilterById(lastDownload));

if(c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)) == 8) {
    try {
        mgr.openDownloadedFile(c.getLong(c.getColumnIndex(DownloadManager.COLUMN_ID)));
    } catch (NumberFormatException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        Log.d("MGR", "Error");
    } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        Log.d("MGR", "Error");
    }
}
问题出在触发if(c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS))==8)时,我得到了状态-1和一个异常。有没有更好的方法来使用DownloadManager API打开下载的文件?在我的示例中,我正在下载一张大图像,在实际情况下,我将下载一个APK文件,并且需要在更新后立即显示安装对话框。 编辑:我发现status=8表示下载成功。您可能有不同的“检查下载成功”的方法。
谢谢
5个回答

84

问题

Android DownloadManager API - 下载后如何打开文件?

解决方案

/**
 * Used to download the file from url.
 * <p/>
 * 1. Download the file using Download Manager.
 *
 * @param url      Url.
 * @param fileName File Name.
 */
public void downloadFile(final Activity activity, final String url, final String fileName) {
    try {
        if (url != null && !url.isEmpty()) {
            Uri uri = Uri.parse(url);
            activity.registerReceiver(attachmentDownloadCompleteReceive, new IntentFilter(
                    DownloadManager.ACTION_DOWNLOAD_COMPLETE));

            DownloadManager.Request request = new DownloadManager.Request(uri);
            request.setMimeType(getMimeType(uri.toString()));
            request.setTitle(fileName);
            request.setDescription("Downloading attachment..");
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
            DownloadManager dm = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
            dm.enqueue(request);
        }
    } catch (IllegalStateException e) {
        Toast.makeText(activity, "Please insert an SD card to download file", Toast.LENGTH_SHORT).show();
    }
}

/**
 * Used to get MimeType from url.
 *
 * @param url Url.
 * @return Mime Type for the given url.
 */
private String getMimeType(String url) {
    String type = null;
    String extension = MimeTypeMap.getFileExtensionFromUrl(url);
    if (extension != null) {
        MimeTypeMap mime = MimeTypeMap.getSingleton();
        type = mime.getMimeTypeFromExtension(extension);
    }
    return type;
}

/**
 * Attachment download complete receiver.
 * <p/>
 * 1. Receiver gets called once attachment download completed.
 * 2. Open the downloaded file.
 */
BroadcastReceiver attachmentDownloadCompleteReceive = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
            long downloadId = intent.getLongExtra(
                    DownloadManager.EXTRA_DOWNLOAD_ID, 0);
            openDownloadedAttachment(context, downloadId);
        }
    }
};

/**
 * Used to open the downloaded attachment.
 *
 * @param context    Content.
 * @param downloadId Id of the downloaded file to open.
 */
private void openDownloadedAttachment(final Context context, final long downloadId) {
    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    DownloadManager.Query query = new DownloadManager.Query();
    query.setFilterById(downloadId);
    Cursor cursor = downloadManager.query(query);
    if (cursor.moveToFirst()) {
        int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
        String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
        String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
        if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) {
            openDownloadedAttachment(context, Uri.parse(downloadLocalUri), downloadMimeType);
        }
    }
    cursor.close();
}

/**
 * Used to open the downloaded attachment.
 * <p/>
 * 1. Fire intent to open download file using external application.
 *
 * 2. Note:
 * 2.a. We can't share fileUri directly to other application (because we will get FileUriExposedException from Android7.0).
 * 2.b. Hence we can only share content uri with other application.
 * 2.c. We must have declared FileProvider in manifest.
 * 2.c. Refer - https://developer.android.com/reference/android/support/v4/content/FileProvider.html
 *
 * @param context            Context.
 * @param attachmentUri      Uri of the downloaded attachment to be opened.
 * @param attachmentMimeType MimeType of the downloaded attachment.
 */
private void openDownloadedAttachment(final Context context, Uri attachmentUri, final String attachmentMimeType) {
    if(attachmentUri!=null) {
        // Get Content Uri.
        if (ContentResolver.SCHEME_FILE.equals(attachmentUri.getScheme())) {
            // FileUri - Convert it to contentUri.
            File file = new File(attachmentUri.getPath());
            attachmentUri = FileProvider.getUriForFile(activity, "com.freshdesk.helpdesk.provider", file);;
        }

        Intent openAttachmentIntent = new Intent(Intent.ACTION_VIEW);
        openAttachmentIntent.setDataAndType(attachmentUri, attachmentMimeType);
        openAttachmentIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            context.startActivity(openAttachmentIntent);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(context, context.getString(R.string.unable_to_open_file), Toast.LENGTH_LONG).show();
        }
    }
}

初始化FileProvider详情

在AndroidManifest中声明FileProvider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.freshdesk.helpdesk.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_path"/>
</provider>

请添加以下文件 "res -> xml -> file_path.xml"。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="attachment_file" path="."/>
</paths>

注意

为什么要使用FileProvider

  1. 从Android 7.0开始,我们不能与其他应用程序共享FileUri。
  2. 使用“DownloadManager.COLUMN_LOCAL_URI”,我们只会得到FileUri,因此需要将其转换为ContentUri并与其他应用程序共享。

使用“DownloadManager.getUriForDownloadedFile(long id)”存在的问题

  1. 不要使用“DownloadManager.getUriForDownloadedFile(long id)” - 通过downloadId获取Uri以使用外部应用程序打开文件。
  2. 因为从Android 6.0和7.0开始,“getUriForDownloadedFile”方法返回本地uri(只能由我们的应用程序访问),我们无法与其他应用程序共享该uri,因为它们无法访问该uri(但在Android 7.1中已经修复了这个问题,请参见Android Commit Here)。
  3. 请参考Android源代码DownloadManager.javaDownloads.java
  4. 因此,始终使用列“DownloadManager.COLUMN_LOCAL_URI”获取Uri。

参考资料

  1. https://developer.android.com/reference/android/app/DownloadManager.html
  2. https://developer.android.com/reference/android/support/v4/content/FileProvider.html

1
请问您能详细说明如何使用 DownloadManager.COLUMN_LOCAL_URI 来获取本地URI吗? - Gokul NC
com.freshdesk.helpdesk.provider 是从哪里来的?您需要明确知道要使用哪个应用程序打开文件吗?您可以使用默认应用程序打开吗? - Sampo
我们无法与其他应用程序共享该Uri,因为它们无法访问该Uri。我认为FLAG_GRANT_READ_URI_PERMISSION)可以解决这个问题? - charlag
1
这个应该保存 dm.enqueue 的结果以便在 BroadcastReceiver 中进行比较,不然我认为这会尝试打开其他应用程序启动的下载。 - Taylor Buchanan
<external-path name="attachment_file" path="."/> attachment_file 是什么?它只是一个名称还是必须引用某个地方? - Mostafa Imani
显示剩余2条评论

34

您需要注册一个接收器来监听下载完成事件:

registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

以及BroadcastReceiver处理程序

BroadcastReceiver onComplete=new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent intent) {
        // Do Something
    }
};

不要跟我抢,我建议你查看这里的内容。

编辑:

只是建议,我不推荐使用API 9: http://developer.android.com/resources/dashboard/platform-versions.html

有办法规避这个问题,就像我做的一样,创建自己的下载处理程序,因为我们不想将大部分android用户孤立在外。你需要创建一个AsyncTask来处理文件下载。

我建议创建某种下载对话框(如果文件很大,可以将其显示在通知区域)。

然后你需要处理打开文件的操作:

protected void openFile(String fileName) {
    Intent install = new Intent(Intent.ACTION_VIEW);
    install.setDataAndType(Uri.fromFile(new File(fileName)),
            "MIME-TYPE");
    startActivity(install);
}

谢谢,我已经将mgr.openDownloadedFile移动到广播接收器中,但仍然遇到相同的问题 :-( - Waypoint
看一下我的编辑,你需要在BroadcastReciever上有一些打开它的方法 - 就像我在这里发布的openFile()方法。 - Itai Sagi
谢谢,我已经制作了异步任务下载器,但公司希望有一个现成的DownloadManager解决方案,与下载解决方案并排站立。 - Waypoint
我正在使用标准的 MIME 类型 application/vnd.android.package-archive 安装应用程序包,但没有成功 :-( 它打开了一个带有“通过应用程序完成操作”的选项卡,我有一列可能的操作,但安装不在其中... - Waypoint
DDMS 给出了什么样的错误?还是只是未处理的异常? - Itai Sagi
显示剩余3条评论

0
对我来说,帮助添加了Intent.FLAG_GRANT_READ_URI_PERMISSION,这是我忘记的。

-1
对于 Kotlin,你可以轻松使用 URL.openStream() 方法来读取并保存文件到你的目录中。
如果你想做得更高级,比如后台线程的话, 你应该查看 Elye 在 Medium 上的文章。

https://medium.com/mobile-app-development-publication/download-file-in-android-with-kotlin-874d50bccaa2

private fun downloadVcfFile() {
    CoroutineScope(Dispatchers.IO).launch {
        val url = "https://srv-store5.gofile.io/download/JXLVFW/vcard.vcf"
        val path = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/contacts.vcf"

        URL(url).openStream().use { input ->
            FileOutputStream(File(path)).use { output ->
                input.copyTo(output)

                val file = File(path)
                file.createNewFile()
                onMain { saveVcfFile(file) }
            }
        }
    }
}

-3

记得在你的 AndroidMannifest.xml 文件中添加 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />


为什么需要这个? - rmtheis
请查看Android 9.0文档。 - xiaoshitou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接