java - 在 Android Q 上使用 DownloadManager 和 FileProvider 打开下载后的 PDF (10)

标签 java android mobile native

目标SdkVersion:30

在我们的应用程序中,我们有一个功能,我们可以将文件(主要是 pdf)下载到公共(public)下载文件夹,然后启动 Intent 将其打开。我们的代码适用于 api <= 28 和 >= 30 的 Android 应用程序。只有我们在 Android 10 (sdkVersion 29) 上的应用程序会尝试打开文档并立即关闭尝试显示 pdf 的 Activity 。 logcat 显示以下错误:

22-03-17 14:23:42.486 12161-15168/? E/DisplayData: openFd: java.io.FileNotFoundException: open failed: EACCES (Permission denied)
22-03-17 14:23:42.486 12161-15168/? E/PdfLoader: Can't load file (doesn't open)  Display Data [PDF : download.pdf] +ContentOpenable, uri: content://com.example.fileprovider/Download/download.pdf

如果我在应用程序设置中授予文件权限,它将完美地工作,但如果我正确理解 Android 文档,那么自 Android 10 起就不需要这样做了。特别是因为 Android 11 和 Android 12 设备上没有问题没有这个权限。在所有 Android 版本上,文件都会正确下载,并且用户可以从设备的下载部分手动打开它。

这是文件提供程序的 Android list 部分

<provider
    android:name="androidx.core.content.FileProvider"        
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
       android:name="android.support.FILE_PROVIDER_PATHS"
       android:resource="@xml/filepaths" />
</provider>

文件路径 XML 文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="external_files"
        path="." />
    <external-path
        name="Download"
        path="Download/"/>
    <files-path
        name="files"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
</paths>

这是使用 DownloadManager 下载文件的代码

public static long downloadFile(Context context, String url, String fileName) {
    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);

    try {
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setTitle(fileName)
                .setDescription(Localization.getStringClient("file_download_progress"))
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

        return downloadManager.enqueue(request);
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
    }
    return -1;
}

监听下载进度的广播接收器

public class DownloadBroadcastReceiver extends BroadcastReceiver {

    private long downloadId = -2;

    public void setDownloadId(long downloadId) {
        this.downloadId = downloadId;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (id == downloadId) {
            DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(id);
            Cursor c = downloadManager.query(query);
            if (c != null) {
                c.moveToFirst();
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
                    String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                    String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
                    Uri fileUri = Uri.parse(uriString);

                    DownloadUtils.openFile(context, fileUri, mediaType);
                } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
                    Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
                }
                c.close();
            }
        }
    }
}

打开文件的代码。仅在 Android 10 上,应用会再次立即关闭 Activity。

public static void openFile(Context context, Uri fileUri, String mediaType) {
    File file = new File(fileUri.getPath());
    Uri uri = FileProvider.getUriForFile(
            context,
            context.getApplicationContext().getPackageName() + ".fileprovider",
            file
    );

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(uri, mediaType);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    try {
        context.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        e.printStackTrace();
        Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
    }
}

下载调用如下所示

@Override
public void startPDFDownload(String pdfDownloadUrl, String fileName) {
    long downloadId = DownloadUtils.downloadFile(requireContext(), pdfDownloadUrl, fileName);

    if (downloadId > -1) {
        DownloadBroadcastReceiver receiver = new DownloadBroadcastReceiver();
        receiver.setDownloadId(downloadId);
        requireContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }
}

我认为我对 Android 10 中文件处理的工作原理有错误的理解,但我不知道我必须在哪里调整代码或配置。非常感谢帮助。目前,作为一种解决方法,我们请求 WRITE_EXTERNAL_STORAGE 的权限,以便在 Android 10 上打开下载的文件。但我更愿意以正确的方式进行操作。

解决方案:

我将BroadcastReceiver调整为以下代码。我从光标中删除了 LOCAL_URI,并使用了 DownloadManager 方法中的 URI。

public class DownloadBroadcastReceiver extends BroadcastReceiver {

    private long downloadId = -2;

    public void setDownloadId(long downloadId) {
        this.downloadId = downloadId;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (id == downloadId) {
            DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(id);
            Cursor c = downloadManager.query(query);
            if (c != null) {
                c.moveToFirst();
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
                    String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));

                    DownloadUtils.openFile(context, downloadManager.getUriForDownloadedFile(id), mediaType);
                } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
                    Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
                }
                c.close();
            }
        }
    }
}

我将使用 uri 打开文件的方法调整为以下代码。我删除了 FileProvider 代码并使用了 DownloadManager 中的 uri。

public static void openFile(Context context, Uri fileUri, String mediaType) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(fileUri, mediaType);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    try {
        context.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        e.printStackTrace();
        Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
    }
}

其他方法的代码保持不变。

最佳答案

您不应使用 FileProvider 来获取文件的 uri。

您可以从 DownloadManager 获取 uri 并使用它来提供您的文件。

所有 Android 版本的代码都相同。

不需要任何权限。

关于java - 在 Android Q 上使用 DownloadManager 和 FileProvider 打开下载后的 PDF (10),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71513303/

相关文章:

java - 使用 jsoup 获取 java.net.SocketTimeoutException : Read timed out exception

android - 使用 java.lang.ClassNotFoundException 扩展布局时出错

Java Thread.sleep() 无法正常工作

java - 只使用一个 key 来加密整个(小)文件系统是个好主意吗?

flutter - 为什么我的步进器不断产生Null输出? flutter

java - Activity 之间的值(value)转移

java - 图中的 "CPU"是否代表 "core"?

android - 我应该把与 ImageButton 相关的 PNG 文件放在哪里?

mobile - 用于 Android 开发的 Monotouch/Mono 的跨平台性如何?

java - 如何使用 Jsoup 登录 ASPX 网站