目錄
- 一、問題背景
- 二、定位原因
- 三、解決辦法
- 1. 通過媒體庫回傳 Uri
- 2. 通過照片墻讀取 Uri
- 3. 將圖片快取后生成 Uri
- 附 Github 原始碼
一、問題背景
- 在選擇 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
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/400602.html
標籤:其他
