前言
在前段时间,google正式发布了android10, Scoped storage(分区存储) 这个功能在android9上就跃跃欲试, 在android10上呼之欲出,某些app甚至在上架之后还会出现targetAPI降低的神奇操作,本文不针对于分区存储介绍etc ,不提及其它解决方案, 仅仅说明笔者在开发过程中遇到的关于图片处理问题以及解决方法, 先说下当前如何解决问题.
兼容模式
如果targetAPI == 29
常规的访问存储目录,或者做相关操作都会出现权限拒绝的异常
android:requestLegacyExternalStorage ="true"
在manifest文件application标签下加入这句话则会回到传统存储模式
如果targetAPI == 28
android:requestLegacyExternalStorage ="false"
在manifest文件application标签下加入此项配置为false , 则会强制开启分区存储, android10上需要适配的工作在android9上也需要
如果targetAPI > 29
配置会失效,google在说明此项配置只针对于临时问题
选取照片适配
先上代码 (笔者一直用kt , java同学应该也不难看懂,有疑问可以留言),这是常规的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为相机输出路径,后面会提及, ?.let句式为内联函数非空判断
REQUEST_CODE_OPEN_PHOTO_ALBUM -> if (resultCode == RESULT_OK && data != null) {
val resolver = applicationContext.contentResolver //在一般情况此处都用contentResolver来直接query文件的absPath然后进行上传操作
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来获取文件流 -> 转化字节码上传 ,retrofit2也正好支持, 挺整好,旧版本也行
将resolver.openInputStream(it).use{}替换成如下这一段代码会更加简洁
val resolver = applicationContext.contentResolver
resolver.openFileDescriptor(uri, "r")?.let { pfd ->//获取ParcelFileDescriptor一样可以获取字节码
mPresenter.uploadFile(FileUtil.getScaledBitmapBytes(pfd))
}
附上FileUtil片段,缩放图片&转换为字节码
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) }
}
拍照处理
先上代码,看调起相机片段
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的方式 ,各位自行选取