android - 访问 ActivityResult 上的公共(public)下载文件 Android 28 Samsung Galaxy S9+ (Verizon)

标签 android kotlin android-permissions android-storage


UPDATE

我有一台运行 8.0.0 T-Mobile 的三星 Galaxy S8+,它运行良好 8.0.0

我的三星 Galaxy S9+ 运行 8.0.0 Verizon,每次都因非法参数而失败。

我的三星 Galaxy S9+ 运行 8.0.0 T-Mobile 没有任何问题并且工作正常

所以这可能是 OEM 特定型号问题,但不是 确定如何修复它。我也试过重启手机,不行 结果的变化。

另外,我从 Evernote 中打开了公共(public)下载并保存了 文件作为笔记的附件,告诉我 Evernote 可以 访问公共(public)目录就好了并附加文件,所以它是 可以在设备上进行。让我相信这是代码 相关。


所以我最近升级了一个运行良好的项目,现在它有一个错误,因为它正在使用构建工具 28 进行编译,适用于最新版本的 Android。

所以我一直使用这个 PathUtil 从隐式 Intent 中获取我需要的文件路径,以从用户那里获取文件选择。我将在下面分享一个我使用了很长时间的代码的链接。

PathUtil

它只是一个实用程序类,用于检查提供者权限并获取您尝试读取的文件的绝对路径。

当用户从公共(public)下载目录中选择一个文件时,它会返回到 onActivityResult 并显示:

content://com.android.providers.downloads.documents/document/2025

现在 nice 实用程序解析出来并告诉我这是一个下载目录文件,并且是一个 ID 为 2025 的文档。感谢实用程序,这是一个很好的开始。

Next up is to use the content resolver to find the file absolute path. This is what used to work, but no longer does :(.

现在,路径实用程序仅使用他们最有可能从核心库自己获得的合约数据。我尝试导入提供程序类以避免静态字符串,但它似乎不可用,所以我想简单地使用匹配字符串是目前最好的方法。

这里是核心 DownloadProvider 供引用,它为内容解析器提供所有访问权限。 DownloadProvider

NOTE* This DownloadProvider is Androids, not mine

这是为 contentProvider 构建 Uri 的代码

 val id = DocumentsContract.getDocumentId(uri)
 val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOAD_PATH), id.toLong())
 return getDataColumn(context, contentUri, null, null)

调用引用:

    private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
        var cursor: Cursor? = null
        val column = "_data"
        val projection = arrayOf(column)
        try {
            cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
            if (cursor != null && cursor.moveToFirst()) {
                val column_index = cursor.getColumnIndexOrThrow(column)
                return cursor.getString(column_index)
            }
        }catch (ex: Exception){
            A35Log.e("PathUtils", "Error getting uri for cursor to read file: ${ex.message}")
        } finally {
            if (cursor != null)
                cursor.close()
        }
        return null
    }

基本上要解析的 contentUri 最终是

content://downloads/public_downloads/2025

然后当你调用它抛出的查询方法时:

java.lang.IllegalArgumentException: Unknown URI: content://downloads/public_downloads/2025

我已经确认或尝试过的事情

  1. 读取外部权限(附带写入,但还是这样做了)
  2. 写入外部权限
  3. 权限在 list 中并在运行时检索
  4. 我选择了多个不同的文件,看看其中一个是否奇怪
  5. 我已确认已在应用设置中授予权限
  6. 我已将 Uri 硬编码为/1 甚至/#2052,以尝试各种结尾类型
  7. 我研究了核心库上的 uriMatching,以了解它期望它的格式并确保它匹配
  8. 我玩过 uri 中的 all_downloads 目录并解决了!!,但存在安全异常,因此解析器必须存在。

我不知道还有什么可以尝试的,任何帮助将不胜感激。

最佳答案

所以我仍然需要做一些向后兼容的测试,但经过数小时的反复试验,我已经成功解决了我自己的问题。

我的解决方法是修改getPath的isDownloadDirectory路径流程。我还不知道所有的链式 react ,因为质量保证明天就要开始了,如果我从中学到任何新东西,我会更新。

使用直接 URI 获取文件名的 contentResolver(注意*这不是获取文件名的好方法,除非您确定它是根据 Google 确定的本地文件,但对我来说,我确定它已下载.)

然后接下来使用环境外部公共(public)下载常量结合返回的内容解析器名称来获取您的绝对路径。新代码如下所示。

private val PUBLIC_DOWNLOAD_PATH = "content://downloads/public_downloads"
private val EXTERNAL_STORAGE_DOCUMENTS_PATH = "com.android.externalstorage.documents"
private val DOWNLOAD_DOCUMENTS_PATH = "com.android.providers.downloads.documents"
private val MEDIA_DOCUMENTS_PATH = "com.android.providers.media.documents"
private val PHOTO_CONTENTS_PATH = "com.google.android.apps.photos.content"

//HELPER METHODS
    private fun isExternalStorageDocument(uri: Uri): Boolean {
        return EXTERNAL_STORAGE_DOCUMENTS_PATH == uri.authority
    }
    private fun isDownloadsDocument(uri: Uri): Boolean {
        return DOWNLOAD_DOCUMENTS_PATH == uri.authority
    }
    private fun isMediaDocument(uri: Uri): Boolean {
        return MEDIA_DOCUMENTS_PATH == uri.authority
    }
    private fun isGooglePhotosUri(uri: Uri): Boolean {
        return PHOTO_CONTENTS_PATH == uri.authority
    }

 fun getPath(context: Context, uri: Uri): String? {
    if (DocumentsContract.isDocumentUri(context, uri)) {
        if (isExternalStorageDocument(uri)) {
            val docId = DocumentsContract.getDocumentId(uri)
            val split = docId.split(COLON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val type = split[0]
            val storageDefinition: String
            if (PRIMARY_LABEL.equals(type, ignoreCase = true)) {
                return Environment.getExternalStorageDirectory().toString() + FORWARD_SLASH + split[1]
            } else {
                if (Environment.isExternalStorageRemovable()) {
                    storageDefinition = EXTERNAL_STORAGE
                } else {
                    storageDefinition = SECONDARY_STORAGE
                }
                return System.getenv(storageDefinition) + FORWARD_SLASH + split[1]
            }
        } else if (isDownloadsDocument(uri)) {
            //val id = DocumentsContract.getDocumentId(uri) //MAY HAVE TO USE FOR OLDER PHONES, HAVE TO TEST WITH REGRESSION MODELS
            //val contentUri = ContentUris.withAppendedId(Uri.parse(PUBLIC_DOWNLOAD_PATH), id.toLong()) //SAME NOTE AS ABOVE
            val fileName = getDataColumn(context, uri, null, null)
            var uriToReturn: String? = null
            if(fileName != null){
                uriToReturn = Uri.withAppendedPath(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath), fileName).toString()
            }
            return uriToReturn
        } else if (isMediaDocument(uri)) {
            val docId = DocumentsContract.getDocumentId(uri)
            val split = docId.split(COLON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val type = split[0]
            var contentUri: Uri? = null
            if (IMAGE_PATH == type) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
            } else if (VIDEO_PATH == type) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
            } else if (AUDIO_PATH == type) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
            }
            val selection = "_id=?"
            val selectionArgs = arrayOf(split[1])
            return getDataColumn(context, contentUri!!, selection, selectionArgs)
        }
    } else if (CONTENT.equals(uri.scheme, ignoreCase = true)) {
        return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
    } else if (FILE.equals(uri.scheme, ignoreCase = true)) {
        return uri.path
    }
    return null
}




    private fun getDataColumn(context: Context, uri: Uri, selection: String?, selectionArgs: Array<String>?): String? {
        var cursor: Cursor? = null
        //val column = "_data" REMOVED IN FAVOR OF NULL FOR ALL   
        //val projection = arrayOf(column) REMOVED IN FAVOR OF PROJECTION FOR ALL 
        try {
            cursor = context.contentResolver.query(uri, null, selection, selectionArgs, null)
            if (cursor != null && cursor.moveToFirst()) {
                val columnIndex = cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME) //_display_name
                return cursor.getString(columnIndex) //returns file name
            }
        }catch (ex: Exception){
            A35Log.e(SSGlobals.SEARCH_STRING + "PathUtils", "Error getting uri for cursor to read file: ${ex.message}")
        } finally {
            if (cursor != null)
                cursor.close()
        }
        return null
    }

关于android - 访问 ActivityResult 上的公共(public)下载文件 Android 28 Samsung Galaxy S9+ (Verizon),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52750508/

相关文章:

java.net.协议(protocol)异常 : method does not support a request body: GET

c# - TCP 流中丢失数据

android - 如果用户拒绝打开蓝牙,则退出 Android 应用程序

javascript - 如何从电池优化权限弹出窗口获取授予状态

android - 在不同的 Activity 中添加新内容后刷新 SQLite 列表

安卓:NumberPicker 不工作

generics - 如何链接两个 Kotlin 函数参数的泛型类型?

kotlin - 如何指定默认子命令?

android - 无法解析库模块中的 Kotlin Android 扩展布局

android - 为什么Android 12不会记录旅行?