簡體   English   中英

如何通過 Android 中的 MediaStore API 檢索和打開保存到下載的 PDF 文件?

[英]How to retrieve and open PDF file saved to downloads through MediaStore API in Android?

我正在從服務器下載 PDF 文件並將響應正文字節流傳遞到下面的 function 中,該文件將 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)
    }
}

現在一旦這個 function 返回,我想打開保存的 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)

(還嘗試了 /test.pdf 在此 URI 的末尾,並用我的權限名稱替換媒體)

我還在應用程序標簽中將此添加到我的清單文件中:

<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,並進入文件資源管理器並從那里打開它工作正常。 嘗試共享而不是打開共享時也會失敗。

按照此步驟和代碼,它將管理從下載 pdf 到打開它的所有內容。

創建一個 class 名稱為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。

根據Android Developer的說法, MediaStore不用於訪問非媒體文件,例如 pdf 文件:

如果您的應用程序使用不專門包含媒體內容的文檔和文件,例如使用 EPUB 或 PDF 文件擴展名的文件,請使用 ACTION_OPEN_DOCUMENT 意圖操作,如有關如何存儲和訪問文檔和其他文件的指南中所述.

此外,沒有任何官方解決方案可以通過使用CursorContent 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中可以找到的官方解決方案。

這是我用來用 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))
    }

這里 pdf 的類型"application/pdf" 您正在itemUri變量中創建 pdf uri,將其傳遞給此 function 的第一個參數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM