代码之家  ›  专栏  ›  技术社区  ›  Sam

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

  •  6
  • Sam  · 技术社区  · 6 年前

    更新

    8.0.0

    我的三星Galaxy S9+运行的是8.0.0 Verizon,每次都有非法参数导致失败。

    我的三星Galaxy S9+运行8.0.0T-Mobile没有问题,运行正常

    确定怎么修了吗。我也试过重启手机,不是吗

    另外,我打开了Evernote的公共下载并保存了 文件作为便笺的附件,它告诉我Evernote 可以访问公共目录并附加文件,所以 可以在设备上执行。让我相信这是密码 相关的。


    因此,我最近升级了一个工作正常的项目,现在它有一个bug,因为它是用最新版本的Android的buildtools 28编译的。

    PathUtil

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

    当用户从公共下载目录中选择一个文件时,它将返回到 活动结果 使用:

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

    现在nice实用程序解析了这个,并告诉我这是一个下载目录文件,是一个id为2025的文档。

    这是过去的工作,但现在不再是:(。

    现在,path实用程序只使用他们最有可能从核心库本身获得的契约数据。我试图导入provider类以避免静态字符串,但它似乎不可用,因此我想现在最好的方法是使用匹配字符串。

    下面是核心下载提供程序(core DownloadProvider)以供参考,它为内容解析器提供了所有访问权限。 DownloadProvider

    注意*这个下载提供者是Androids,不是我的

    下面是为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

    然后调用查询方法时,它会抛出:

    我确认或尝试过的事情

    1. 读取外部权限(与写入一起提供,但无论如何都要这样做)
    2. 权限在清单中,并在运行时检索
    3. 我选择了多个不同的文件,看看是否有一个是奇怪的
    4. 我已确认已在应用程序设置中授予权限
    5. 我已经将Uri硬编码为/1,甚至在结尾处为/#2052,以尝试各种结尾类型
    6. 我研究了核心库上的uriMatching,寻找它希望如何格式化并确保它匹配

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

    2 回复  |  直到 6 年前
        1
  •  6
  •   Sam    6 年前

    我是如何决定修改getPath的isDownloadDirectory路径流的。我还不知道所有的连锁反应,虽然QA将在明天开始,我会更新,如果我从这学到什么新的东西。

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

    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
        }
    
        2
  •  1
  •   Vasily Tserej    6 年前

    我的解决方案不同 我正在尝试获取路径,以便可以将文件复制到我的应用程序文件夹。 在没有找到答案之后,我尝试了以下方法。

               // DownloadsProvider
                else if (IsDownloadsDocument(uri))
                {
                    if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
                    {
                        //Hot fix for android oreo
                        bool res = MediaService.MoveAssetFromURI(uri, ctx, ref error);
                        return res ? "copied" : null;
                    }
                    else
                    {
                        string id = DocumentsContract.GetDocumentId(uri);
    
                        Android.Net.Uri contentUri = ContentUris.WithAppendedId(
                                        Android.Net.Uri.Parse("content://downloads/public_downloads"), long.Parse(id));
    
                        //System.Diagnostics.Debug.WriteLine(contentUri.ToString());
    
                        return GetDataColumn(ctx, contentUri, null, null);
                    }
                }
    
     public static bool MoveAssetFromURI(Android.Net.Uri uri, Context ctx, ref string error)
        {           
            string directory = PhotoApp.App.LastPictureFolder;
    
            var type = ctx.ContentResolver.GetType(uri);
    
            string assetName = FileName(ctx, uri);
    
            string extension = System.IO.Path.GetExtension(assetName);
    
            var filename = System.IO.Path.GetFileNameWithoutExtension(assetName);
    
            var finalPath = $"{directory}/{filename}{extension}";
    
            if (File.Exists(finalPath))
            {
                error = "File already exists at the destination";
                return false;
            }
    
            if (extension != ".pdf" && extension == ".jpg" && extension == ".png")
            {
                error = "File extension not suported";
                return false;
            }
    
            using (var input = ctx.ContentResolver.OpenInputStream(uri))
            {
                using (var fileStream = File.Create(finalPath))
                {
                    //input.Seek(0, SeekOrigin.Begin);
                    input.CopyTo(fileStream);
                }
            }
    
            if (extension == ".pdf")
            {
                var imagePDFIcon = BitmapFactory.DecodeResource(ctx.Resources, Resource.Drawable.icon_pdf);
    
                var imagePDFPortrait = BitmapFactory.DecodeResource(ctx.Resources, Resource.Drawable.pdf_image);
    
                using (var stream = new FileStream($"{directory}/{filename}", FileMode.Create))
                {
                    imagePDFIcon.Compress(Bitmap.CompressFormat.Jpeg, 90, stream);
                }
    
                using (var stream = new FileStream($"{directory}/{filename}.jpg", FileMode.Create))
                {
                    imagePDFPortrait.Compress(Bitmap.CompressFormat.Jpeg, 90, stream);
                }
    
                return true;
            }
            else
            {
                if (extension == ".jpg" || extension == ".png")
                {
                    MoveImageFromGallery(finalPath);
    
                    File.Delete(finalPath);
    
                    return true;
                }
            }
    
            return false;
    

    因此,我没有尝试获取路径,而是创建了一个输入流,并将该流复制到我想要的位置。