Fix Bug针对 Google Photos 返回的图片Uri,裁剪照片失败

Posted Lucky_William

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fix Bug针对 Google Photos 返回的图片Uri,裁剪照片失败相关的知识,希望对你有一定的参考价值。

一、问题背景

  • 在选择 Google Photos 的照片后,会返回 uri,然后再去调用照片裁剪功能会失败。系统提示 “Error, could not load media” 或 “发生错误,无法加载媒体”。

二、定位原因

  • 在选择 Google Photos 的照片后,返回的 uri 为:
content://com.google.android.apps.photos.contentprovider/-1/1/content://media/external/images/media/80/ORIGINAL/NONE/image/jpeg/122783088
  • 常规相册返回的照片 uri 为:
content://media/external/images/media/80

因为 Google Photos 返回的照片 Uri 不能被解析,所以导致无法加载图片进行裁剪。

三、解决办法

1. 通过媒体库返回 Uri

有其他小伙伴提出的办法比较简单,首先获取 Google Photos 照片 Uri 的输入流,然后将 输入流 转为 bitmap 插入到媒体库,插入完成后会返回一个 媒体库 新的 uri ,此时这个 uri 就是我们想要的能被正确解析的格式。

核心代码如下:

// java

// 1. 获取 Google Photos 照片 Uri 的输入流,并转为 Bitmap
InputStream is = context.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(is);

// 2. 将 bitmap 保存到手机本地相册中获取返回的 uri
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), bitmap, "temp", null);
// 能被正确解析的 uri
Uri result = Uri.parse(path);


2. 通过照片墙读取 Uri

在选择照片并裁剪的流程中,增加一个照片墙的页面。即先将手机相册中的图片 uri 读取出来,然后展示在照片墙页面,用户在照片墙页面选择想要的照片。照片的 uri 此时是我们从媒体库中读取的,是可以被正确解析的格式。

3. 将图片缓存后生成 Uri

  • 第一个方案的弊端是,需要向手机的相册中插入一张和所选照片完全一样的图片,这会让用户感到疑惑。当选择次数较多时,会产生多个重复的图片。
  • 可以将第一种方案 改为 将图片文件存入缓存,然后生成对应的 Uri,再给到系统去裁剪。这样就避免了生成另外一张完全一样的图片。
  • 具体做法是:先判断是谷歌相册返回的 uri,通过图片的输入流将图片文件存入到文件缓存目录,再根据系统版本生成对应的Uri。

核心代码如下:

// kotlin 

// 判断是谷歌相册返回的 uri。如果后续谷歌的规则发生并更,这里也需要更改
val googlePrefix = "content://com.google.android.apps.photos.contentprovider"
val newUri = if (uri.toString().startsWith(googlePrefix, true)) 
    // 处理谷歌相册返回的图片
    saveImageToCache(context, uri)


/**
 * 将谷歌相册图片保存到外置存储目录,然后返回 uri
 */
private suspend fun saveImageToCache(context: Context, uri: Uri): Uri 
    val imageName = "$System.currentTimeMillis().jpg"
    val parent = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) 
        context.externalCacheDir?.absolutePath
     else 
        context.cacheDir?.absolutePath
    
    val path = parent + File.separator + imageName

    withContext(Dispatchers.IO) 
        copyInputStream(context, uri, path)
    

    val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 
        FileProvider.getUriForFile(
            context, "$context.packageName.fileprovider",
            File(parent, imageName)
        )
     else 
        Uri.fromFile(File(path))
    
    "uri: $result".logV()
    return result


/**
 * 字节流读写复制文件
 * @param context 上下文
 * @param uri 图片uri
 * @param outputPath 输出地址
 */
private fun copyInputStream(context: Context, uri: Uri, outputPath: String) 
    "copy file begin...".logV()
    var inputStream: InputStream? = null
    var outputStream: FileOutputStream? = null
    try 
        inputStream = context.contentResolver.openInputStream(uri)
        outputStream = FileOutputStream(outputPath)
        val bytes = ByteArray(1024)
        var num: Int
        while (inputStream?.read(bytes).also  num = it ?: -1  != -1) 
            outputStream.write(bytes, 0, num)
            outputStream.flush()
        
     catch (e: Exception) 
        "exception: $e".logE()
     finally 
        try 
            outputStream?.close()
            inputStream?.close()
            "copy file end...".logV()
         catch (e: IOException) 
            "exception: $e".logE()
        
    

                        

附 Github 源码

RegisterForResultActivity

以上是关于Fix Bug针对 Google Photos 返回的图片Uri,裁剪照片失败的主要内容,如果未能解决你的问题,请参考以下文章

text bug-fix重复资源

读取数据库,重大BUG fix

text bug-fix:悬空元字符

列出某个patch所fix的bug ID?

fix bug:page-helper分页不正确

Bug Fix 之后的随笔