如何在 Android 中通过 MediaStore API 检索和打开保存到下载的 PDF 文件?
Posted
技术标签:
【中文标题】如何在 Android 中通过 MediaStore API 检索和打开保存到下载的 PDF 文件?【英文标题】:How to retrieve and open PDF file saved to downloads through MediaStore API in Android? 【发布时间】:2021-04-25 01:15:04 【问题描述】:我正在从服务器下载 PDF 文件并将响应正文字节流传递到下面的函数中,该函数将 PDF 文件成功存储在用户下载文件夹中。
@RequiresApi(Build.VERSION_CODES.Q)
fun saveDownload(pdfInputStream: InputStream)
val values = ContentValues().apply
put(MediaStore.Downloads.DISPLAY_NAME, "test")
put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
put(MediaStore.Downloads.IS_PENDING, 1)
val resolver = context.contentResolver
val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val itemUri = resolver.insert(collection, values)
if (itemUri != null)
resolver.openFileDescriptor(itemUri, "w").use parcelFileDescriptor ->
ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
.write(pdfInputStream.readBytes())
values.clear()
values.put(MediaStore.Downloads.IS_PENDING, 0)
resolver.update(itemUri, values, null, null)
现在,一旦此函数返回,我想打开保存的 PDF 文件。我已经尝试了几种方法来让它工作,但选择器总是说没有什么可以打开文件。我认为仍然存在权限问题(也许我使用 FileProvider 错误?),或者路径错误,或者可能完全是其他问题。
以下是我尝试过的几个示例:
fun uriFromFile(context: Context, file: File): Uri
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
一)
val openIntent = Intent(Intent.ACTION_VIEW)
openIntent.putExtra(Intent.EXTRA_STREAM, uriFromFile(this, File(this.getExternalFilesDir(DIRECTORY_DOWNLOADS)?.absolutePath.toString(), "test")))
openIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
openIntent.type = "application/pdf"
startActivity(Intent.createChooser(openIntent, "share.."))
b)
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.putExtra(Intent.EXTRA_STREAM, uriFromFile(this, File(this.getExternalFilesDir(null)?.absolutePath.toString(), "test.pdf")))
shareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
shareIntent.type = "application/pdf"
startActivity(Intent.createChooser(shareIntent, "share.."))
c)
val file = File(itemUri.toString()) //itemUri from the saveDownload function
val target = Intent(Intent.ACTION_VIEW)
val newFile = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file);
target.setDataAndType(newFile, "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Open File")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
ContextCompat.startActivity(this, intent, null)
d)
val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(Uri.parse("content://media/external_primary/downloads/2802"), "application/pdf"
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Open File")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
ContextCompat.startActivity(this, intent, null)
(也尝试在此 URI 末尾添加 /test.pdf,并将 media 替换为我的权限名称)
我还在应用程序标签内的清单文件中添加了这个:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="$applicationId.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
@xml/provider_paths 如下,尽管我尝试了除此之外的各种组合,包括路径为“。”:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path name="files_root" path="/"/>
<files-path name="files_root" path="/"/>
<external-path name="files_root" path="/"/>
</paths>
附带说明,肯定有可用的选择器能够打开 PDF,然后进入文件资源管理器并从那里打开它可以正常工作。尝试共享而不是打开共享时也会失败。
【问题讨论】:
不用加粗,不用加粗也能正常阅读:) @a_local_nobody 哈哈,对不起,我的错 :) 很遗憾,我无法真正帮助您解决问题,所以我能做的最好的就是编辑,但我相信您会找到答案 你为什么会选择?你已经有一个uri。这个:val itemUri = resolver.insert(collection...... @blackapps 我尝试使用该 Uri,但它不起作用,请参阅 (c) 【参考方案1】:按照此步骤和代码,它将管理从下载 pdf 到打开它的所有内容。
创建一个类名DownloadTask,并把完整的代码放在下面
public class DownloadTask
private static final String TAG = "Download Task";
private Context context;
private String downloadFileUrl = "", downloadFileName = "";
private ProgressDialog progressDialog;
long downloadID;
private BroadcastReceiver onDownloadComplete = new BroadcastReceiver()
@Override
public void onReceive(Context context, Intent intent)
//Fetching the download id received with the broadcast
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
//Checking if the received broadcast is for our enqueued download by matching download id
if (downloadID == id)
downloadCompleted(downloadID);
;
public DownloadTask(Context context, String downloadUrl)
this.context = context;
this.downloadFileUrl = downloadUrl;
downloadFileName = downloadFileUrl.substring(downloadFileUrl.lastIndexOf('/') + 1);//Create file name by picking download file name from URL
Log.e(TAG, downloadFileName);
context.registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
downloadFile(downloadFileUrl);
public void downloadFile(String url)
try
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), downloadFileName);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)// Visibility of the download Notification
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
downloadFileName
)
.setDestinationUri(Uri.fromFile(file))
.setTitle(downloadFileName)// Title of the Download Notification
.setDescription("Downloading")// Description of the Download Notification
.setAllowedOverMetered(true)// Set if download is allowed on Mobile network
.setAllowedOverRoaming(true);// Set if download is allowed on roaming network
request.allowScanningByMediaScanner();
DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
downloadID = downloadManager.enqueue(request);// enqueue puts the download request in the queue.
progressDialog = new ProgressDialog(context);
progressDialog.setMessage("Downloading...");
progressDialog.setCancelable(false);
progressDialog.show();
catch (Exception e)
Log.d("Download", e.toString());
void downloadCompleted(long downloadID)
progressDialog.dismiss();
new AlertDialog.Builder(context)
.setTitle("Document")
.setMessage("Document Downloaded Successfully")
.setPositiveButton("Open", new DialogInterface.OnClickListener()
public void onClick(DialogInterface dialog, int which)
openDownloadedAttachment(downloadID);
)
// A null listener allows the button to dismiss the dialog and take no further action.
.setNegativeButton(android.R.string.no, null)
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
context.unregisterReceiver(onDownloadComplete);
Uri path;
private void openDownloadedAttachment(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)
path = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", new File(Uri.parse(downloadLocalUri).getPath()));
//path = Uri.parse(downloadLocalUri);
Intent pdfIntent = new Intent(Intent.ACTION_VIEW);
pdfIntent.setDataAndType(path, downloadMimeType);
pdfIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_TOP);
try
context.startActivity(pdfIntent);
catch (ActivityNotFoundException e)
Toast.makeText(context, "No Application available to view PDF", Toast.LENGTH_SHORT).show();
cursor.close();
然后像这样从您的活动中下载您的 pdf。
new DownloadTask(this, "PDF_URL");
从你的片段中
new DownloadTask(getContext(), "PDF_URL");
下载完成后会自动打开你的pdf。
【讨论】:
您的课程中有一些已弃用的调用:Environment.getExternalStoragePublicDirectory、ProgressDialog 和 request.allowScanningByMediaScanner(),如果可能,您应该避免这种情况,谢谢。 android deprecation 也是这个工具的另一个令人头疼的问题。 支持这项工作。但如果可能,请避免不推荐使用的调用。【参考方案2】:根据Android Developer,MediaStore
未用于访问非媒体文件,例如 pdf 文件:
如果您的应用可以处理非独占性的文档和文件 包含媒体内容,例如使用 EPUB 或 PDF 文件的文件 扩展,使用 ACTION_OPEN_DOCUMENT 意图操作,如中所述 存储指南和access documents and other files。
此外,通过Cursor
和Content Provider
访问非媒体文件也没有任何官方解决方案。但是,我已经在 Android 11 上对其进行了测试,并按预期工作了一种官方且干净的代码方法。这里是:
public class retrieve_pdf_file
@RequiresApi(Build.VERSION_CODES.Q)
public static void get(Activity activity)
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(intent, main_activity.PICK_PDF_FILE);
public static void get(Activity activity, String filename) // filename is used for lower that API level 29
// older that API level 29 approaches
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
// TODO
此外,要获取所选 pdf 文件的 Uri
,您必须监听活动的结果:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData)
if (requestCode == PICK_PDF_FILE && resultCode == Activity.RESULT_OK)
System.out.println("request code: PICK_PDF_FILE && result code: OK");
// The result data contains a URI for the document or directory that
// the user selected.
Uri uri = null;
if (resultData != null)
uri = resultData.getData();
// Perform operations on the document using its URI.
System.out.println(uri);
else
System.out.println("resultData is null");
else
System.out.println("result code: NOT OK");
这是 API 级别 29 或更高级别的官方解决方案,可在 Android Developer 中找到。
【讨论】:
【参考方案3】:这是我用 Uri 打开 doc 文件的代码。
fun viewPDFIntent(fileUri: Uri?, context: Context, title: String?, type: String)
val viewPDFIntent = Intent(Intent.ACTION_VIEW).apply
setDataAndType(fileUri, type)
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.startActivity(Intent.createChooser(viewPDFIntent, title))
这里 type for pdf 是 "application/pdf"。 您正在 itemUri 变量中创建 pdf uri,将其传递给此函数的第一个参数。
【讨论】:
以上是关于如何在 Android 中通过 MediaStore API 检索和打开保存到下载的 PDF 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Android 中通过 URL 加载 ImageView? [关闭]
如何压缩 JSONObject 在 Android 中通过 Http 发送它?