近期使用Android拍照功能时发现,一些Android11版本及以上的手机拍照时无法将图片保存到应用程序指定目录中。只会创建文件但是文件的大小为0KB。这个问题会导致应用中一些更换图片的逻辑失效比如用户头像替换、背景图片的替换等。如何解决这个问题给大家提供一个兼容Android 24版本以上的拍照、视频及打开相册的兼容工具类。工具类如下:
class PhotoUtils private constructor() {
private val mTag: String by lazy {
TTLog.makeLogTag(PhotoUtils::class.java)
}
companion object {
private const val GALLERY_REQUEST_CODE = 105 // 相册选图标记
private const val CAMERA_REQUEST_CODE = 106 // 相机拍照标记
private const val VIDEO_REQUEST_CODE = 107 //相册选择视频
//拍照图片地址
private var mTempPhotoPath: String = ""
//工具列对象
private var mInstance: PhotoUtils? = null
//上下文
private lateinit var mContext: Context
/**
* 获取工具列对象方法
*/
fun getInstance() = mInstance ?: PhotoUtils().also {
mInstance = it
}
}
/**
* 打开相册
* @param activity 所在Activity对象
*/
fun openPhotoPick(activity: Activity) {
//设置对象
mContext = activity
val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
// 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型"
pickIntent.type = "image/*"
activity.startActivityForResult(pickIntent, GALLERY_REQUEST_CODE)
}
/**
* 打开视频
* @param activity 所在Activity对象
*/
fun openVideoPick(activity: Activity) {
//设置对象
mContext = activity
val pickIntent = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
pickIntent.type = CONTENT_TYPE
activity.startActivityForResult(pickIntent, VIDEO_REQUEST_CODE)
}
/**
* 开启照相机
* @param activity 所在Activity对象
*/
fun openPhotograph(activity: Activity) {
val takeIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
//设置对象
mContext = activity
//生成图片路径
mTempPhotoPath = activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath + File.separator + "${System.currentTimeMillis()}.jpeg"
//解决Android11无法拍照存储问题
val imageUri = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
FileProvider.getUriForFile(activity, "${activity.packageName}.fileprovider", File(mTempPhotoPath))
} else {
Uri.fromFile(File(mTempPhotoPath))
}
//下面这句指定调用相机拍照后的照片存储的路径
takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
//
activity.startActivityForResult(takeIntent, CAMERA_REQUEST_CODE)
}
/**
* 该方法必须放置在对应Activity的onActivityResult方法中
* @param requestCode 请求码
* @param resultCode 响应码
* @param data 数据传输对象
* @param method 图片或视频地址回调方法
*/
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?, method: ((type: MediaTypeEnum, uri: Uri, path: String) -> Unit)? = null) {
if (resultCode == BaseOtherActivity.RESULT_OK) {
when (requestCode) {
CAMERA_REQUEST_CODE -> {// 调用相机拍照
val temp = File(mTempPhotoPath)
TTLog.e(mTag, "image_temp:${temp.absoluteFile}")
method?.let {
it(MediaTypeEnum.IMAGE_TYPE, Uri.fromFile(temp), temp.absolutePath)
}
}
GALLERY_REQUEST_CODE -> {// 直接从相册获取图片地址
data?.let { dataIntent ->
TTLog.e(mTag, "image_temp:${dataIntent.data}")
getFilePath(MediaTypeEnum.IMAGE_TYPE, dataIntent.data
?: Uri.parse(""), method)
}
}
VIDEO_REQUEST_CODE -> {// 直接从相册获取视频地址
data?.let { dataIntent ->
TTLog.e(mTag, "video_temp:${dataIntent.data}")
getFilePath(MediaTypeEnum.VIDEO_TYPE, dataIntent.data
?: Uri.parse(""), method)
}
}
else -> {
//todo:未知类型
}
}
}
}
/**
* 获得文件sd卡路径
* @param type 多媒体类型[MediaTypeEnum]
* @param uri 获取ContentProvider共享数据地址
* @param method 回到方法
*/
@SuppressLint("CheckResult")
private fun getFilePath(type: MediaTypeEnum, uri: Uri, method: ((type: MediaTypeEnum, uri: Uri, path: String) -> Unit)? = null) {
Single.create<String> {
val cursor = mContext.contentResolver.query(uri, null, null, null, null)
if (cursor != null && cursor.count > 0) {
if (cursor.moveToFirst()) {
val data = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA))
it.onSuccess(data)
}
cursor.close()
} else {
it.onError(Throwable("query uri is null"))
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ path ->
method?.let {
it(type, uri, path ?: "")
}
}, {
method?.let {
it(type, uri, "")
}
})
}
/**
* 多媒体类型枚举
*/
enum class MediaTypeEnum {
IMAGE_TYPE,
VIDEO_TYPE,
DEFAULT_TYPE
}
}
该工具类中定义了三个常量分别是GALLERY_REQUEST_CODED、CAMERA_REQUEST_CODE、VIDEO_REQUEST_CODE依次代表相册选图标记、相册拍照标记及相册选择视频标记。也提供了三个open方法对应打开相应的功能。在onActivityResult方法中获取到的相册中的图片或者视频信息往往都是uri地址(content开头),而我们需要的并不是uri而是具体的图片地址。这样的话就需要通过ContentProvider进行数据库查询,所以在工具类中提供了一个getFilePath()方法对其进行数据库查询获取对应图片或者视频的绝对地址数据。当获取到这个地址就可以进行下一步操作,比如上传、发送文件等。