Skip to content

Commit

Permalink
修复 Cursor#getString 方法返回值可能为 null 的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
leavesCZY committed Jun 1, 2023
1 parent b244bc2 commit b31719b
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 84 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
接入指南:[Wiki](https://github.com/leavesCZY/Matisse/wiki/%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97)

关联文章:

- [Android 13 媒体权限适配指南](https://juejin.cn/post/7159999910748618766)
- [Jetpack Compose 实现一个图片选择框架](https://juejin.cn/post/7108420791502372895)
5 changes: 2 additions & 3 deletions matisse/src/main/java/github/leavesczy/matisse/Matisse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package github.leavesczy.matisse

import android.net.Uri
import android.os.Parcelable
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize

/**
Expand Down Expand Up @@ -61,8 +60,8 @@ data class MediaResource(
val bucketDisplayName: String
) : Parcelable {

@IgnoredOnParcel
internal val key = id
internal val key: Long
get() = id

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import github.leavesczy.matisse.MediaResource
import github.leavesczy.matisse.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
* @Author: leavesCZY
Expand Down Expand Up @@ -102,21 +103,22 @@ internal class MatisseViewModel(application: Application, private val matisse: M
matisseViewState = imageLoadingViewState
val allResources = MediaProvider.loadResources(
context = context,
filterMimeTypes = matisse.supportedMimeTypes
supportedMimeTypes = matisse.supportedMimeTypes
)
if (allResources.isEmpty()) {
matisseViewState = imageEmptyViewState
} else {
val allBucket =
MediaProvider.groupByBucket(resources = allResources).toMutableList()
val defaultBucket = MediaBucket(
id = DEFAULT_BUCKET_ID,
displayName = getString(R.string.matisse_default_bucket_name),
displayIcon = allResources[0].uri,
resources = allResources,
supportCapture = matisse.captureStrategy.isEnabled()
)
allBucket.add(0, defaultBucket)
val allBucket = groupByBucket(
defaultBucket = defaultBucket,
resources = allResources
)
matisseViewState = matisseViewState.copy(
state = MatisseState.ImagesLoaded,
allBucket = allBucket,
Expand All @@ -126,6 +128,45 @@ internal class MatisseViewModel(application: Application, private val matisse: M
}
}

private suspend fun groupByBucket(
defaultBucket: MediaBucket,
resources: List<MediaResource>
): List<MediaBucket> {
return withContext(context = Dispatchers.IO) {
val resourcesMap = linkedMapOf<String, MutableList<MediaResource>>()
resources.forEach { res ->
val bucketDisplayName = res.bucketDisplayName
if (bucketDisplayName.isNotBlank()) {
val bucketId = res.bucketId
val list = resourcesMap[bucketId]
if (list == null) {
resourcesMap[bucketId] = mutableListOf(res)
} else {
list.add(element = res)
}
}
}
val allBucketList = buildList {
add(element = defaultBucket)
resourcesMap.forEach {
val bucketId = it.key
val resourcesList = it.value
val bucketDisplayName = resourcesList[0].bucketDisplayName
add(
element = MediaBucket(
id = bucketId,
displayName = bucketDisplayName,
displayIcon = resourcesList[0].uri,
resources = resourcesList,
supportCapture = false
)
)
}
}
return@withContext allBucketList
}
}

fun onSelectBucket(bucket: MediaBucket) {
matisseViewState = matisseViewState.copy(selectedBucket = bucket)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,62 +55,69 @@ internal object MediaProvider {
selectionArgs: Array<String>?
): List<MediaResource>? {
return withContext(context = Dispatchers.IO) {
val idColumn = MediaStore.Images.Media._ID
val dataColumn = MediaStore.Images.Media.DATA
val displayNameColumn = MediaStore.Images.Media.DISPLAY_NAME
val mineTypeColumn = MediaStore.Images.Media.MIME_TYPE
val widthColumn = MediaStore.Images.Media.WIDTH
val heightColumn = MediaStore.Images.Media.HEIGHT
val orientationColumn = MediaStore.Images.Media.ORIENTATION
val sizeColumn = MediaStore.Images.Media.SIZE
val bucketIdColumn = MediaStore.Images.Media.BUCKET_ID
val bucketDisplayNameColumn = MediaStore.Images.Media.BUCKET_DISPLAY_NAME
val externalContentUriColumn = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
val projection = arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.MIME_TYPE,
MediaStore.Images.Media.WIDTH,
MediaStore.Images.Media.HEIGHT,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.ORIENTATION,
MediaStore.Images.Media.DATA,
MediaStore.Images.Media.BUCKET_ID,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
idColumn,
dataColumn,
displayNameColumn,
mineTypeColumn,
widthColumn,
heightColumn,
orientationColumn,
sizeColumn,
bucketIdColumn,
bucketDisplayNameColumn
)
val sortOrder = "${MediaStore.Images.Media.DATE_MODIFIED} DESC"
val mediaResourceList = mutableListOf<MediaResource>()
try {
val mediaCursor = context.contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
externalContentUriColumn,
projection,
selection,
selectionArgs,
sortOrder,
) ?: return@withContext null
mediaCursor.use { cursor ->
while (cursor.moveToNext()) {
val data = cursor.getString(MediaStore.Images.Media.DATA)
if (data.isBlank() || !File(data).exists()) {
val id = cursor.getLong(idColumn, Long.MAX_VALUE)
val data = cursor.getString(dataColumn, "")
if (id == Long.MAX_VALUE || data.isBlank() || !File(data).exists()) {
continue
}
val id = cursor.getLong(MediaStore.Images.Media._ID)
val displayName = cursor.getString(MediaStore.Images.Media.DISPLAY_NAME)
val mimeType = cursor.getString(MediaStore.Images.Media.MIME_TYPE)
val width = cursor.getInt(MediaStore.Images.Media.WIDTH)
val height = cursor.getInt(MediaStore.Images.Media.HEIGHT)
val size = cursor.getLong(MediaStore.Images.Media.SIZE)
val orientation = cursor.getInt(MediaStore.Images.Media.ORIENTATION)
val bucketId = cursor.getString(MediaStore.Images.Media.BUCKET_ID)
val bucketDisplayName =
cursor.getString(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
val displayName = cursor.getString(displayNameColumn, "")
val mimeType = cursor.getString(mineTypeColumn, "")
val width = cursor.getInt(widthColumn, 0)
val height = cursor.getInt(heightColumn, 0)
val orientation = cursor.getInt(orientationColumn, 0)
val size = cursor.getLong(sizeColumn, 0L)
val bucketId = cursor.getString(bucketIdColumn, "")
val bucketDisplayName = cursor.getString(bucketDisplayNameColumn, "")
val imageUri = ContentUris.withAppendedId(externalContentUriColumn, id)
val mediaResource = MediaResource(
id = id,
uri = contentUri,
path = data,
uri = imageUri,
displayName = displayName,
mimeType = mimeType,
width = width,
height = height,
orientation = orientation,
path = data,
size = size,
bucketId = bucketId,
bucketDisplayName = bucketDisplayName,
bucketDisplayName = bucketDisplayName
)
mediaResourceList.add(mediaResource)
mediaResourceList.add(element = mediaResource)
}
}
} catch (e: Throwable) {
Expand All @@ -122,16 +129,16 @@ internal object MediaProvider {

suspend fun loadResources(
context: Context,
filterMimeTypes: List<MimeType>
supportedMimeTypes: List<MimeType>
): List<MediaResource> {
return withContext(context = Dispatchers.IO) {
val selection = if (filterMimeTypes.isEmpty()) {
val selection = if (supportedMimeTypes.isEmpty()) {
null
} else {
val sb = StringBuilder()
sb.append(MediaStore.Images.Media.MIME_TYPE)
sb.append(" IN (")
filterMimeTypes.forEachIndexed { index, mimeType ->
supportedMimeTypes.forEachIndexed { index, mimeType ->
if (index != 0) {
sb.append(",")
}
Expand Down Expand Up @@ -166,53 +173,34 @@ internal object MediaProvider {
}
}

suspend fun groupByBucket(resources: List<MediaResource>): List<MediaBucket> {
return withContext(context = Dispatchers.IO) {
val resourcesMap = linkedMapOf<String, MutableList<MediaResource>>()
resources.forEach { res ->
val bucketId = res.bucketId
val list = resourcesMap[bucketId]
if (list == null) {
resourcesMap[bucketId] = mutableListOf(res)
} else {
list.add(res)
}
}
val allMediaBucketResource = mutableListOf<MediaBucket>()
resourcesMap.forEach {
val resourcesList = it.value
if (resourcesList.isNotEmpty()) {
val bucketId = it.key
val bucketDisplayName = resourcesList[0].bucketDisplayName
allMediaBucketResource.add(
MediaBucket(
id = bucketId,
displayName = bucketDisplayName,
displayIcon = resourcesList[0].uri,
resources = resourcesList,
supportCapture = false
)
)
}
}
return@withContext allMediaBucketResource
}
}

}


private fun Cursor.getInt(columnName: String): Int {
val columnIndex = getColumnIndexOrThrow(columnName)
return getInt(columnIndex)
private fun Cursor.getInt(columnName: String, default: Int): Int {
return try {
val columnIndex = getColumnIndexOrThrow(columnName)
getInt(columnIndex)
} catch (e: Throwable) {
e.printStackTrace()
default
}
}

private fun Cursor.getLong(columnName: String): Long {
val columnIndex = getColumnIndexOrThrow(columnName)
return getLong(columnIndex)
private fun Cursor.getLong(columnName: String, default: Long): Long {
return try {
val columnIndex = getColumnIndexOrThrow(columnName)
getLong(columnIndex)
} catch (e: Throwable) {
e.printStackTrace()
default
}
}

private fun Cursor.getString(columnName: String): String {
val columnIndex = getColumnIndexOrThrow(columnName)
return getString(columnIndex)
private fun Cursor.getString(columnName: String, default: String): String {
return try {
val columnIndex = getColumnIndexOrThrow(columnName)
getString(columnIndex) ?: default
} catch (e: Throwable) {
e.printStackTrace()
default
}
}

0 comments on commit b31719b

Please sign in to comment.