安卓 - 选择并查看任意类型的文件

4

我希望用户能够在他们的手机上选择任何文件类型,然后使用意图再次查看它。

为了选择文件项,我编写了以下代码(我正在使用Kotlin,在Java中也是相同的问题):

    fun goToDocumentPicker() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.addCategory(Intent.CATEGORY_OPENABLE)
        intent.type = "*/*"
        if (intent.resolveActivity(mContext.packageManager) != null)
            mContext.startActivityForResult(intent, REQUEST_DOCUMENT)
    }

展示所选项目的方法:

 fun showDocumentPreviewer(uri: Uri) {
        val i = Intent(Intent.ACTION_VIEW)
        i.data = uri
        mContext.startActivity(i)
    }

文档选择器可以正常工作,在 onActivityResult 中我可以接收所选文档的 Uri 对象,但是文档预览程序无法打开该对象。已尝试将 mime 类型设置为预览器的意图,但没有成功。我打开 Android 中文件的方式有误吗?是否有一种通用的方法在 Android 上显示任何文件类型?(因为我想支持多种文件类型)
选择的 Uri: content://com.android.providers.media.documents/document/image:80 更新: 根据 @CommonsWare 的评论,我编辑了下面的预览函数:
fun showAttachmentPreviewer(uri: Uri, mimeType: String?) {
    Log.d("TEST", "Preview " + uri.toString())
    val intent = Intent(Intent.ACTION_VIEW, uri)
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

    val chooser = Intent.createChooser(intent, "Open with")
    if (intent.resolveActivity(mContext.packageManager) != null)
        mContext.startActivity(chooser)
    else
        mContext.showSnackBar("No suitable application to open file")
}

现在应用程序总是崩溃,以下是崩溃日志:

java.lang.SecurityException: Uid 10202没有访问uri的权限 0 @ content://com.android.providers.media.documents/document/audio:17915

更新2: 我的应用程序在mContext.startActivity(chooser)行崩溃。以下是完整的崩溃日志:

FATAL EXCEPTION: main Process: com.makeit.lite, PID: 12851 java.lang.SecurityException: Uid 10477没有访问uri的权限 0 @ content://com.android.providers.media.documents/document/image:24776 at android.os.Parcel.readException(Parcel.java:1540) at android.os.Parcel.readException(Parcel.java:1493) at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:2514) at android.app.Instrumentation.execStartActivity(Instrumentation.java:1494) at android.app.Activity.startActivityForResult(Activity.java:3913) at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50) at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79) at android.app.Activity.startActivityForResult(Activity.java:3860) at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:859) at android.app.Activity.startActivity(Activity.java:4184) at android.app.Activity.startActivity(Activity.java:4152) at com.makeit.lite.attachment.AttachmentNavigator.showAttachmentPreviewer(AttachmentNavigator.kt:92) at com.makeit.lite.attachment.list.AttachmentListPresenter.onAttachmentClicked(AttachmentListPresenter.kt:37) at com.makeit.lite.attachment.list.AttachmentListFragment$onViewCreated$1.onItemClick(AttachmentListFragment.kt:39) at eu.davidea.viewholders.FlexibleViewHolder.onClick(FlexibleViewHolder.java:121) at android.view.View.performClick(View.java:5156) at android.view.View$PerformClick.run(View.java:20755) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:5835) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

更新3: 如果我将 mimeType 设置为 intent:intent.type = mimeType,则应用程序就不会崩溃了。 mimeType 是我从选择器意图中获取的字符串(除了 content: uri 之外)。 mimeType 值可以是 image/jpeg 或基于所选文件类型的任何内容。虽然它不会崩溃,但给定 uri 上的文件也不会显示。如果我从 Intent-Chooser 中选择 Gallery,则 Gallery 将打开并显示所有图像。我猜第三方应用程序不知道如何确定给定 uri 上的文件。

这是我的函数最新源代码:

fun showAttachmentPreviewer(uri: Uri, mimeType: String?) {
        Log.d("TEST", "Preview " + uri.toString() + " For type" + mimeType)
        val intent = Intent(Intent.ACTION_VIEW, uri)
        intent.type = mimeType //Can be "image/jpeg" or sth corresponding to the filetype.
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        val chooser = Intent.createChooser(intent, "Open with")
        if (intent.resolveActivity(mContext.packageManager) != null)
            mContext.startActivity(chooser)
        else
            mContext.showSnackBar("No suitable application to open file")
    }

你的应用程序崩溃了吗?还是选择显示内容方案的应用程序?这是哪种语言? - greenapps
我的应用程序崩溃了,上面有崩溃日志。编程语言是 Kotlin。 - Nguyen Minh Binh
它在哪个语句崩溃了?为什么不添加try-catch块,这样你的应用程序就不会崩溃呢?相反,你可以向用户显示一个漂亮的toast。显示e.getMessage()。你只有一个android标签。再加一个kotlin的标签。 - greenapps
@ greenapps 看起来您不理解我的情况。我想做的是:让用户选择任何文件(图像、PDF 等)并将其保留在列表中。然后,每当他们单击列表项时,调用意图选择第三方应用程序以打开所选文件。问题在于我可以选择任何文件,并正确获取“content:”URI。但无法使用“Intent.ACTION_VIEW”打开这些文件。代码行的包装方式无法帮助打开文件。 - Nguyen Minh Binh
请考虑加入此答案的聊天室:https://dev59.com/dqDia4cB1Zd3GeqPILdI#43279439 - Nguyen Minh Binh
显示剩余7条评论
4个回答

3

我在Android中打开文件的方式不正确吗?

首先,可能没有适合查看该文件类型的应用程序。

其次,您尚未授予应用程序查看内容的权限。在 ACTION_VIEWIntent 上使用 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)


1
@ greenapps:“我认为是这样的,因为Intent.CATEGORY_OPENABLE被使用”-- 好吧, startActivity() 可能会抛出 ActivityNotFoundException。安卓设备可能没有应用程序来查看AutoCAD文件,或者Photoshop PSD文件,或许多其他文件类型。 - CommonsWare
@CommonsWare 您指导我设置标志 Intent.FLAG_GRANT_READ_URI_PERMISSION 后,应用程序总是崩溃。它抛出了 java.lang.SecurityException: Uid 10202 does not have permission to uri 0 @ content://com.android.providers.media.documents/document/image:24776。您能否给我一些更多的提示,让它正常工作?谢谢。 - Nguyen Minh Binh
@NguyenMinhBinh 这个问题在这里被commonsware本人很好地解释了。如上面的评论所述,您需要注意两个不同的问题。一个是在Android 7.0 Nougat及以上版本中的文件访问权限,另一个是检查设备中是否存在可以处理给定文件类型的ACTION_VIEW的任何活动。对于第一个问题,请参阅此处。此外,继续... - Ankit
@GreenApps 不好意思,据我所知无法转发权限。这里需要授权的权限是让其他应用程序访问属于您的应用程序的文件。请查看此链接:https://inthecheesefactory.com/blog/how-to-share-access-to-file-with-fileprovider-on-android-nougat/en?fb_comment_id=927823067327697_928849473891723#f29bdd99f5f2078 - Ankit
@NguyenMinhBinh,你看了我的第一条评论吗?你提到的错误与Android 7.0及以上版本有关,原因类似于NetworkOnMainThreadException。解决方案是使用FileProvider。 - Ankit
显示剩余10条评论

3
  val intent = Intent(Intent.ACTION_VIEW, uri)
  intent.type = mimeType 

改为

  val intent = Intent(Intent.ACTION_VIEW);
  intent.setDataAndType (uri, mimeType ); 

您应该使用一个语句来设置uri和mime类型。否则,接收应用程序将会收到空的uri。大多数应用程序不会告诉您它们没有收到uri。例如,相册应用程序就不会。

我不知道Kotlin的确切函数名称。


不,这并没有帮助。如果我单独设置mimeType,应用程序就不会崩溃。如果同时更改urimimeType,它会以相同的崩溃日志崩溃,就像我在问题中描述的那样。 - Nguyen Minh Binh
也许我会将源代码提取到Github上的简单项目中,这样每个人都可以拉下来尝试。 - Nguyen Minh Binh
是的。我确认setDataAndType在Kotlin中存在,并且我已经尝试过了。而且我确信Java中的大多数函数在Kotlin中都可用(只是语法略有不同)。 - Nguyen Minh Binh
继续使用setDataAndType。其他任何操作都没有意义。放弃ListView,并直接从第一个Intent的onActivityResult中调用第二个Intent。展示那段代码。 - greenapps
我终于找到了我的愚蠢问题,请帮忙检查一下我的描述,链接在这里https://dev59.com/dqDia4cB1Zd3GeqPILdI#43336067。 - Nguyen Minh Binh
显示剩余2条评论

1
首先,我想对@GreenApps、@Ankit和@CommonsWare说声感谢。他们花时间调查了我的问题。
当我将源代码提取到一个简单的项目中时,我终于找到了根本原因。这是因为我以这种方式解析uri字符串成为uri实例(根本原因的代码未包含在我的问题中。我很抱歉):
val uri = Uri.parse(URLDecoder.decode(uriString, "UTF-8")) as Uri

当我改用val uri = Uri.parse(uriString)时,问题得到了解决。
顺便说一下,我想在我的Github上分享选择器和预览器的示例代码,以供需要查看的人参考。Java源代码在“master”分支上,Kotlin源代码在“kotlin-version”分支上。使用这个简单的代码,现在我可以选择任何文件类型(图片、音频、视频、PDF、xlsx等),然后通过Intent.ACTION_VIEW稍后打开它。
代码如下:

https://github.com/IHNEL/UriPickerPreviewer


当我改为使用val uri = Uri.parse(item.uri)时,问题得到了解决。为什么不直接使用val uri = item.uri呢? - greenapps
好问题。我从我的大项目中提取源代码。在实体对象中保存uri字符串时,当呈现到UI时,我会准备另一个视图模型对象,然后将字符串转换为uri实例。算了,我会编辑代码,以便其他查看者更容易理解。 - Nguyen Minh Binh

0

我对文档提供程序可以帮助选择和返回URI感到满意。我的问题是如何启动一个意图来查看选择的URI。 - Nguyen Minh Binh

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