在onActivityResult中接收到"content://" Uri后从MediaStore获取PDF?

4
我开始一个 ACTION_GET_CONTENT 意图,以选择一个 PDF 文件:
override fun routeToFilePicker() {
    val intent = Intent()
    intent.type = MediaType.PDF.toString()
    intent.action = Intent.ACTION_GET_CONTENT
    activity.startActivityForResult(
        Intent.createChooser(intent, "Select PDF"),
        REQUEST_CODE_PDF_PICKER
    )
}

然后在 onActivityResult 中,我尝试从 Uri (content//:path) 创建一份 PDF:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_PDF_PICKER ) {
        data?.data?.let { pdfUri: Uri ->
            val pdfFile: File = pdfUri.toFile() <-- chrash
            ...
        }
    }
}

pdfUri.toFile()会导致致命异常:

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1003, result=-1, data=Intent { dat=content://com.android.providers.downloads.documents/document/3569 flg=0x1 }} to activity {my.package.name.activity}: java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://com.android.providers.downloads.documents/document/3569

Caused by: java.lang.IllegalArgumentException: Uri lacks 'file' scheme: content://com.android.providers.downloads.documents/document/3569

为了将页面转换成图像,我需要一个文件。
我如何从MediaStore返回的Uri中获取PDF文件?


1
我看到很少有使用情况需要实际使用文件或文件路径。恐怕我找不到解决方案,非常奇怪。 - M. Usman Khan
1
@M.UsmanKhan “我看到了一些使用案例,实际上你并不需要一个文件或文件路径”-- ACTION_GET_CONTENT 没有要求返回代表文件系统上的文件的 Uri。请参见 https://dev59.com/0VIH5IYBdhLWcg3wkvtX#59123287 和 https://stackoverflow.com/a/59911702/115145 和 https://dev59.com/C7Tma4cB1Zd3GeqP1Bts#56308643 以获取更多信息。 - CommonsWare
@CommonsWare应用程序已经从用户那里获得了文件访问权限。另外,我们如何让用户选择一个视频文件(大文件)并获取其路径而不复制它?我们只能获取内容URI,对吧?我找不到这个简单问题的解决方案。 - M. Usman Khan
@CommonsWare 谢谢。但是如果我的用户愿意给我任何权限怎么办?为什么这个简单的目标如此难以实现。我想我将被迫使用第三方库。 - M. Usman Khan
3
“但是如果我的用户愿意授予我任何权限怎么办?” - 这并不重要。“我想我将被迫使用第三方库” - 那也没有用。“为什么这个简单的目标这么难实现?” - 因为文件系统上的文件只是众多数据源之一,互联网上的内容是另一个主要的选择。因此,Google决定使用抽象化的方法,以便数据可以来自任何来源,本地或远程。 - CommonsWare
显示剩余3条评论
3个回答

5
这是我获取pdf文件的方式:
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
                            type = "application/pdf"
                            addCategory(Intent.CATEGORY_OPENABLE)
                            flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION
                        }
                        startActivityForResult(intent, 111)

在您的OnActivityResult(requestCode:Int,resultCode:Int,data:Intent?)中。
if (resultCode == Activity.RESULT_OK) {
            when (requestCode) {
//                101 -> {
//                    data?.data?.also { uri ->
//                        Log.i(TAG, "Uri: $uri")
//                        baseAdapter?.add(ImageArray(null, null, uri.toString()))
//                    }
//                }
                111 -> {
                    data?.data?.also { documentUri ->
                        baseActivity.contentResolver?.takePersistableUriPermission(
                                documentUri,
                                Intent.FLAG_GRANT_READ_URI_PERMISSION
                        )
                        val file = DocumentUtils.getFile(baseActivity,documentUri)//use pdf as file 
                    }
                }
            }

        }

将Uri转换为文件的单例类:

object DocumentUtils {
    fun getFile(mContext: BaseActivity?, documentUri: Uri): File {
        val inputStream = mContext?.contentResolver?.openInputStream(documentUri)
        var file =  File("")
        inputStream.use { input ->
            file =
                File(mContext?.cacheDir, System.currentTimeMillis().toString()+".pdf")
            FileOutputStream(file).use { output ->
                val buffer =
                    ByteArray(4 * 1024) // or other buffer size
                var read: Int = -1
                while (input?.read(buffer).also {
                        if (it != null) {
                            read = it
                        }
                    } != -1) {
                    output.write(buffer, 0, read)
                }
                output.flush()
            }
        }
        return file
    }
} 

PS:不要忘记在运行时请求权限。

Manifest.permission.WRITE_EXTERNAL_STORAGE
Manifest.permission.READ_EXTERNAL_STORAGE

有人能确认这是否有效吗? 我现在很忙,但我不想让赏金浪费。 @M.UsmanKhan 可以吗? - Joel Broström
这是我获取 PDF 文件并发送到服务器的方式。 - chand mohd

2

随着最新的API更新,Android已经推动使用Content resolver进行文件相关处理。

使用收到的路径,需要使用Content resolver进行解析。

示例可在Google Github中找到。

简要概述,选择文件后接收到的URI需要像以下这样在Fragment中传递。

创建一个用于显示PDF的Fragment,并将与之关联的ViewModel。

val parcelFileDescriptor = activity?.contentResolver?.openFileDescriptor(fileURI, "r")

在ViewModel中,借助协程的帮助,我们将使用以下过程:
val scope = CoroutineScope(executor.asCoroutineDispatcher() + job)

scope.launch {
            openPdfRenderer()
            showPage(0)
        }

fun openPdfRenderer() {
        
        pdfRenderer = PdfRenderer(fileURI!!) //android.graphics.pdf.PdfRenderer
    }

fun showPage(index: Int) {

        currentPage?.let { page ->
            currentPage = null
            page.close()
        }
        pdfRenderer?.let { renderer ->

            val page = renderer.openPage(index).also {
                currentPage = it
            }
            val bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
            page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT)
            _pageBitmap.postValue(bitmap)
            val count = renderer.pageCount
            _pageInfo.postValue(index to count)
            _previousEnabled.postValue(index > 0)
            _nextEnabled.postValue(index + 1 < count)
        }
    }

完整的示例源代码可以在Google Github示例链接中找到。

我同意,这可能看起来是一种开销!但由于多种原因,Android地理位置也发生了变化。

编码愉快!干杯!


这仍然不能给我一个文件,而只是用于呈现PDF的URI?我们想要的是一种获取实际文件或创建副本以便进行操作的方法。 - Joel Broström
对于这个问题,“我需要一个文件才能将页面转换为图像。”在提供的示例中已经完成了此操作。并且在 PDF 查看器片段中显示为图像本身。但是它不会公开创建或复制文件,以便您进行操作。 - Sreehari
如果你只想在一个Fragment中显示PDF内容的预览,那么这就足够了。 - Sreehari

-1
    public static File getFileFromUri(Uri uri, Context context) {
        if (uri == null) {
            return null;
        }
        switch (uri.getScheme()) {
            case "content":
                return getFileFromContentUri(uri, context);
            case "file":
                return new File(uri.getPath());
            default:
                return null;
        }
    }
    
    private static File getFileFromContentUri(Uri contentUri, Context context) {
        if (contentUri == null) {
            return null;
        }
        File file = null;
        String filePath;
        String fileName;
        String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(contentUri, filePathColumn, null,
                null, null);
        if (cursor != null) {
            cursor.moveToFirst();
            filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
            fileName = cursor.getString(cursor.getColumnIndex(filePathColumn[1]));
            cursor.close();
            if (!TextUtils.isEmpty(filePath)) {
                file = new File(filePath);
            }
            if (!file.exists() || file.length() <= 0 || TextUtils.isEmpty(filePath)) {
                filePath = getPathFromInputStreamUri(context, contentUri, fileName);
            }
            if (!TextUtils.isEmpty(filePath)) {
                file = new File(filePath);
            }
        }
        return file;
    }


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