202 lines
6.7 KiB
Kotlin
202 lines
6.7 KiB
Kotlin
package com.sw.inbound.utils
|
|
|
|
import android.content.ContentUris
|
|
import android.content.Context
|
|
import android.graphics.Bitmap
|
|
import android.graphics.BitmapFactory
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.provider.MediaStore
|
|
import androidx.annotation.RequiresApi
|
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
import okhttp3.MultipartBody
|
|
import okhttp3.RequestBody.Companion.asRequestBody
|
|
import timber.log.Timber
|
|
import java.io.File
|
|
|
|
object FileUtils {
|
|
/**
|
|
* 从Uri获取File
|
|
* example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg
|
|
*/
|
|
private fun getFileFromUri(context: Context, uri: Uri): File? {
|
|
Timber.d("getFileFromUri uri = ${uri.scheme}")
|
|
return when (uri.scheme) {
|
|
"file" -> File(uri.path ?: return null)
|
|
"content" -> {
|
|
try {
|
|
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
|
|
val cacheDir = context.cacheDir
|
|
val file = File.createTempFile(
|
|
"upload_${System.currentTimeMillis()}",
|
|
".jpg",
|
|
cacheDir
|
|
)
|
|
file.outputStream().use { output ->
|
|
inputStream.copyTo(output)
|
|
}
|
|
file
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
null
|
|
}
|
|
}
|
|
|
|
else -> null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 通过uri生成http请求体
|
|
*/
|
|
fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? {
|
|
Timber.d("genRequestPart imageUri = $imageUri")
|
|
// 1. 从Uri获取文件
|
|
val file = getFileFromUri(context, imageUri)
|
|
return genRequestPart(file)
|
|
}
|
|
fun genRequestPart(file: File?): MultipartBody.Part? {
|
|
if (file == null) {
|
|
Timber.e("getFileFromUri file is null")
|
|
return null
|
|
}
|
|
// 2. 创建请求体
|
|
val requestFile = file
|
|
.asRequestBody("application/octet-stream".toMediaTypeOrNull())
|
|
val imagePart = MultipartBody.Part.createFormData(
|
|
"file",
|
|
file.name,
|
|
requestFile
|
|
)
|
|
return imagePart
|
|
}
|
|
|
|
/**
|
|
* 通过Uri删除文件
|
|
* @param context 上下文
|
|
* @param uri 文件Uri
|
|
* @return Boolean 是否删除成功
|
|
*/
|
|
fun deleteFileWithUri(context: Context, uri: Uri): Boolean {
|
|
Timber.d("deleteFileWithUri uri = ${uri.scheme}")
|
|
return when {
|
|
// 1. 处理 content:// 类型的Uri (MediaStore)
|
|
uri.scheme.equals("content", ignoreCase = true) -> {
|
|
deleteContentUriFile(context, uri)
|
|
}
|
|
// 2. 处理 file:// 类型的Uri
|
|
uri.scheme.equals("file", ignoreCase = true) -> {
|
|
deleteFileUriFile(uri)
|
|
}
|
|
// 3. 其他情况尝试直接解析路径
|
|
else -> {
|
|
deleteFileFromPath(uri.path ?: return false)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 删除Content Uri文件
|
|
private fun deleteContentUriFile(context: Context, uri: Uri): Boolean {
|
|
Timber.d("deleteContentUriFile uri = ${uri.scheme}")
|
|
return try {
|
|
context.contentResolver.delete(uri, null, null) > 0
|
|
} catch (e: SecurityException) {
|
|
// Android 10+需要特殊处理
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
deleteMediaStoreFile(context, uri)
|
|
} else {
|
|
false
|
|
}
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
// Android 10+删除MediaStore文件
|
|
@RequiresApi(Build.VERSION_CODES.Q)
|
|
private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean {
|
|
Timber.d("deleteMediaStoreFile uri = ${uri.scheme}")
|
|
val contentResolver = context.contentResolver
|
|
val projection = arrayOf(MediaStore.MediaColumns._ID)
|
|
|
|
return try {
|
|
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
|
|
if (cursor.moveToFirst()) {
|
|
val id =
|
|
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
|
val contentUri = ContentUris.withAppendedId(uri, id)
|
|
contentResolver.delete(contentUri, null, null) > 0
|
|
} else {
|
|
false
|
|
}
|
|
} ?: false
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
// 删除File Uri文件
|
|
private fun deleteFileUriFile(uri: Uri): Boolean {
|
|
Timber.d("deleteFileUriFile uri = $uri")
|
|
return try {
|
|
File(uri.path ?: return false).delete()
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
// 直接通过路径删除文件
|
|
private fun deleteFileFromPath(path: String): Boolean {
|
|
Timber.d("deleteFileFromPath path = $path")
|
|
return try {
|
|
File(path).delete()
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将 File 转换为 Bitmap
|
|
* @param file 图片文件
|
|
* @param reqWidth 目标宽度(用于采样压缩,0 表示不压缩)
|
|
* @param reqHeight 目标高度(用于采样压缩,0 表示不压缩)
|
|
* @return 解码后的 Bitmap,失败返回 null
|
|
*/
|
|
fun fileToBitmap(file: File, reqWidth: Int = 0, reqHeight: Int = 0): Bitmap? {
|
|
if (!file.exists() || !file.isFile) return null
|
|
|
|
return if (reqWidth > 0 && reqHeight > 0) {
|
|
// 带采样压缩,避免 OOM
|
|
val options = BitmapFactory.Options().apply {
|
|
inJustDecodeBounds = true
|
|
BitmapFactory.decodeFile(file.absolutePath, this)
|
|
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)
|
|
inJustDecodeBounds = false
|
|
}
|
|
BitmapFactory.decodeFile(file.absolutePath, options)
|
|
} else {
|
|
BitmapFactory.decodeFile(file.absolutePath)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 计算采样率
|
|
*/
|
|
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
|
val (height, width) = options.outHeight to options.outWidth
|
|
var inSampleSize = 1
|
|
if (height > reqHeight || width > reqWidth) {
|
|
val halfHeight = height / 2
|
|
val halfWidth = width / 2
|
|
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
inSampleSize *= 2
|
|
}
|
|
}
|
|
return inSampleSize
|
|
}
|
|
|
|
} |