前言
在前段時(shí)間,google正式發(fā)布了android10, Scoped storage(分區(qū)存儲) 這個(gè)功能在android9上就躍躍欲試, 在android10上呼之欲出,某些app甚至在上架之后還會出現(xiàn)targetAPI降低的神奇操作,本文不針對于分區(qū)存儲介紹etc ,不提及其它解決方案, 僅僅說明筆者在開發(fā)過程中遇到的關(guān)于圖片處理問題以及解決方法, 先說下當(dāng)前如何解決問題.
兼容模式
如果targetAPI == 29
常規(guī)的訪問存儲目錄,或者做相關(guān)操作都會出現(xiàn)權(quán)限拒絕的異常
android:requestLegacyExternalStorage ="true"
在manifest文件application標(biāo)簽下加入這句話則會回到傳統(tǒng)存儲模式
如果targetAPI == 28
android:requestLegacyExternalStorage ="false"
在manifest文件application標(biāo)簽下加入此項(xiàng)配置為false , 則會強(qiáng)制開啟分區(qū)存儲, android10上需要適配的工作在android9上也需要
如果targetAPI > 29
配置會失效,google在說明此項(xiàng)配置只針對于臨時(shí)問題
選取照片適配
先上代碼 (筆者一直用kt , java同學(xué)應(yīng)該也不難看懂,有疑問可以留言),這是常規(guī)的pick操作, 沒什么好說的
private fun photoFromGallery() {
try {
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Intent(Intent.ACTION_OPEN_DOCUMENT)
} else {
Intent(Intent.ACTION_PICK)
}
intent.type = "image/*"
startActivityForResult(intent, REQUEST_CODE_OPEN_PHOTO_ALBUM)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
showToast(R.string.open_photo_album_error)
}
}
然后來看看activityResult怎么處理,慣例先上代碼
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_TAKE_PHOTOS -> if (resultCode == RESULT_OK)
mOutPutUri?.let { mPresenter.uploadFile(it) } //mOutPutUri為相機(jī)輸出路徑,后面會提及, ?.let句式為內(nèi)聯(lián)函數(shù)非空判斷
REQUEST_CODE_OPEN_PHOTO_ALBUM -> if (resultCode == RESULT_OK && data != null) {
val resolver = applicationContext.contentResolver //在一般情況此處都用contentResolver來直接query文件的absPath然后進(jìn)行上傳操作
data.data?.let {
resolver.openInputStream(it).use { stream ->
stream?.readBytes()?.let { bytes -> mPresenter.uploadFile(bytes, System.currentTimeMillis().toString() + ".png") }
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
注意看這一段
resolver.openInputStream(it).use { stream ->//此處的stream為自動回收流,不可引用
stream?.readBytes()?.let { bytes -> mPresenter.uploadFile(bytes, System.currentTimeMillis().toString() + ".png") }
}
resolver.openInputStream(it).use來獲取文件流 -> 轉(zhuǎn)化字節(jié)碼上傳 ,retrofit2也正好支持, 挺整好,舊版本也行
將resolver.openInputStream(it).use{}替換成如下這一段代碼會更加簡潔
val resolver = applicationContext.contentResolver
resolver.openFileDescriptor(uri, "r")?.let { pfd ->//獲取ParcelFileDescriptor一樣可以獲取字節(jié)碼
mPresenter.uploadFile(FileUtil.getScaledBitmapBytes(pfd))
}
附上FileUtil片段,縮放圖片&轉(zhuǎn)換為字節(jié)碼
fun getScaledBitmapBytes(pfd: ParcelFileDescriptor, needRecycle: Boolean = true): ByteArray? {
val opt = BitmapFactory.Options()
opt.inJustDecodeBounds = true
BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor, null, opt)
var inSampleSize = 1
val height = opt.outHeight
val width = opt.outWidth
if (width > 720 || height > 720) {//長或者寬>720則等比縮放
inSampleSize = if (width >= height) {
height / 720
} else {
width / 720
}
}
opt.inJustDecodeBounds = false
opt.inSampleSize = inSampleSize
val bitmap = BitmapFactory.decodeFileDescriptor(pfd.fileDescriptor, null, opt)
if (needRecycle)
pfd.close()
return bitmap?.let { bmpToByteArray(it, needRecycle) }
}
拍照處理
先上代碼,看調(diào)起相機(jī)片段
private fun photoFromCamera() {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)//通過MediaStore訪問
intent.resolveActivity(packageManager)?.let {
mOutPutUri = FileUtil.insertExternalStorageImage(this)
mOutPutUri?.also {
intent.putExtra(MediaStore.EXTRA_OUTPUT, it)
startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTOS)
} ?: showToast("路徑生成失敗")
} ?: showToast("沒有可用的拍照程序")
}
附上FileUtil片段 , MediaStore插入圖片操作
fun insertExternalStorageImage(context: Context): Uri? {
return context.applicationContext.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues().apply {
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA).format(Date())
put(MediaStore.Images.Media.DISPLAY_NAME, "${JPEG_FILE_PREFIX}${timeStamp}${JPEG_FILE_SUFFIX}")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
})
}
通過上述片段可獲取content Uri媒體路徑, 然后可以照葫蘆畫瓢, 一樣可以用流的方式或者ParcelFileDescriptor的方式 ,各位自行選取