# MediaLoader
媒体查询 android 读取本地相册 视频照片 之前读取的方式 是视频照片 分开读取 然后还要 按照时间倒序排序 如果照片视频很多的话就会加载缓慢 2800{视频+照片(照片多数)} 加载大概需要7-8秒的样子 不能忍受后来查资料的时候我发现一个不错的开源库
github.com/sucese/phoe… 知道了LoaderManger 之前加载 都是AsyncTask -> ContentResolver ...
# LoaderManger
- package androidx.loader.app
- getInstance()
- initLoader()
- interface LoaderCallbacks {}
- destroyLoader()
# getInstance()
@NonNull
public static LoaderManager getInstance(
@NonNull T owner) {
return new LoaderManagerImpl(owner, owner.getViewModelStore());
}
复制代码
# LifecycleOwner
# ViewModelStoreOwner
# initLoader()
/**
* @param id {ps:不能重复哦用完需要释放}此加载程序的唯一标识符。可以是任何你想要的。标识符的
* 作用域是特定的LoaderManager实例
* @param callback Interface the LoaderManager will call to report about changes in the state of the loader. * Required.
*/
@MainThread
@NonNull
public abstract Loader initLoader(int id, @Nullable Bundle args,
@NonNull LoaderManager.LoaderCallbacks callback) ;
复制代码
# LoaderCallbacks {}
/**
* Callback interface for a client to interact with the manager.
*/
public interface LoaderCallbacks<D> {
/**
* Instantiate and return a new Loader for the given ID.
*
* This will always be called from the process's main thread.
*
* @param id The ID whose loader is to be created.
* @param args Any arguments supplied by the caller.
* @return Return a new Loader instance that is ready to start loading.
* 创建 loader
*/
@MainThread
@NonNull
Loader onCreateLoader(int id, @Nullable Bundle args) ;
/**
* Called when a previously created loader has finished its load. Note
* that normally an application is not allowed to commit fragment
* transactions while in this call, since it can happen after an
* activity's state is saved. See {@link androidx.fragment.app.FragmentManager#beginTransaction()
* FragmentManager.openTransaction()} for further discussion on this.
*
* This function is guaranteed to be called prior to the release of
* the last data that was supplied for this Loader. At this point
* you should remove all use of the old data (since it will be released
* soon), but should not do your own release of the data since its Loader
* owns it and will take care of that. The Loader will take care of
* management of its data so you don't have to. In particular:
*
*
* -
The Loader will monitor for changes to the data, and report
* them to you through new calls here. You should not monitor the
* data yourself. For example, if the data is a {@link android.database.Cursor}
* and you place it in a {@link android.widget.CursorAdapter}, use
* the {@link android.widget.CursorAdapter#CursorAdapter(android.content.Context,
* android.database.Cursor, int)} constructor without passing
* in either {@link android.widget.CursorAdapter#FLAG_AUTO_REQUERY}
* or {@link android.widget.CursorAdapter#FLAG_REGISTER_CONTENT_OBSERVER}
* (that is, use 0 for the flags argument). This prevents the CursorAdapter
* from doing its own observing of the Cursor, which is not needed since
* when a change happens you will get a new Cursor throw another call
* here.
*
- The Loader will release the data once it knows the application
* is no longer using it. For example, if the data is
* a {@link android.database.Cursor} from a {@link android.content.CursorLoader},
* you should not call close() on it yourself. If the Cursor is being placed in a
* {@link android.widget.CursorAdapter}, you should use the
* {@link android.widget.CursorAdapter#swapCursor(android.database.Cursor)}
* method so that the old Cursor is not closed.
*
*
* This will always be called from the process's main thread.
*
* @param loader The Loader that has finished.
* @param data The data generated by the Loader.
* 返回初始化的loader data: Cursor?
*/
@MainThread
void onLoadFinished(@NonNull Loader loader, D data) ;
/**
* Called when a previously created loader is being reset, and thus
* making its data unavailable. The application should at this point
* remove any references it has to the Loader's data.
*
* This will always be called from the process's main thread.
*
* @param loader The Loader that is being reset.
*/
@MainThread
void onLoaderReset(@NonNull Loader loader) ;
}
复制代码
# MediLoader.kt
companion object {
private const val TYPE_ALL = 0 //全部
private const val TYPE_IMAGE = 1 //照片
private const val TYPE_VIDEO = 2 //视频
private val ALL_QUERY_URI = MediaStore.Files.getContentUri("external") //external-db/files 表
private const val DURATION = "duration"
private const val SIZE = "_size"
private const val LATITUDE = "latitude"
private const val LONGITUDE = "longitude"
/**
* 全部媒体数据 - SELECTION_ARGS
*/
private val ALL_SELECTION_ARGS = arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString())
/**
* 全部媒体数据 - PROJECTION
*/
private val ALL_PROJECTION = arrayOf(MediaStore.Images.Media._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.DATE_ADDED,
MediaStore.Files.FileColumns.MEDIA_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.Video.VideoColumns.DURATION)
/**
* 全部媒体数据 - SELECTION
*/
private const val ALL_SELECTION = (
MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
+ " OR "
+ MediaStore.Files.FileColumns.SIZE + ">0"
+ " AND "
+ DURATION + ">0")
/**
* 图片 - PROJECTION
*/
private val IMAGE_PROJECTION = arrayOf(MediaStore.Images.Media._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.DATE_ADDED,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.WIDTH,
MediaStore.MediaColumns.HEIGHT,
LATITUDE,
LONGITUDE)
/**
* 图片 - SELECTION
*/
private const val IMAGE_SELECTION = (
MediaStore.Images.Media.MIME_TYPE + "=? or " +
MediaStore.Images.Media.MIME_TYPE + "=?"
+ " or "
+ MediaStore.Images.Media.MIME_TYPE + "=?"
+ " AND "
+ MediaStore.MediaColumns.WIDTH +
">0"
)
/**
* 图片 - SELECTION_ARGS
*/
private val IMAGE_SELECTION_ARGS = arrayOf("image/jpeg", "image/png", "image/webp")
/**
* 视频 - PROJECTION
*/
private val VIDEO_PROJECTION = arrayOf(MediaStore.Images.Media._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.DATE_ADDED,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.WIDTH,
MediaStore.MediaColumns.HEIGHT,
LATITUDE,
LONGITUDE,
DURATION)
/**
* 视频 - SELECTION
*/
private const val VIDEO_SELECTION = (
MediaStore.Images.Media.MIME_TYPE + "=?"
+ " AND "
+ MediaStore.MediaColumns.WIDTH + ">0"
+ " AND "
+ DURATION + ">0"
)
/**
* 视频 - SELECTION_ARGS
*/
private val VIDEO_SELECTION_ARGS = arrayOf("video/mp4")
/**
* 音频 - PROJECTION
*/
private val AUDIO_PROJECTION = arrayOf(MediaStore.Images.Media._ID,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.DATE_ADDED,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
DURATION)
/**
* 音频 - SELECTION
*/
private val AUDIO_SELECTION = (
MediaStore.Images.Media.MIME_TYPE + "=?"
+ " AND "
+ DURATION + ">0"
)
/**
* 音频 - SELECTION_ARGS
*/
private val AUDIO_SELECTION_ARGS = arrayOf("audio/wav")
/**
* 获取全部图片和视频,但过滤掉gif图片
*/
private val SELECTION_NOT_GIF = (
MediaStore.Images.Media.MIME_TYPE + "=?"
+ " OR "
+ MediaStore.Images.Media.MIME_TYPE + "=?"
+ " OR "
+ MediaStore.Images.Media.MIME_TYPE + "=?"
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " AND "
+ MediaStore.MediaColumns.SIZE + ">0"
+ " AND "
+ MediaStore.MediaColumns.WIDTH + ">0"
)
private val SELECTION_NOT_GIF_ARGS = arrayOf("image/jpeg", "image/png", "image/webp", MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString())
private val ORDER_BY = MediaStore.Files.FileColumns._ID + " DESC"
//相册列表
private val ALL_ALBUM_PROJECTION = arrayOf(MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.DATA)
}
/**
* 读取本地 MediaFile
* @param activity
* @param videoFilterTime
* @param mediaFilterSize
* @param mediaLoaderListener
*/
fun loadMedia(activity: AppCompatActivity, @IntRange(from = 0, to = 2) type: Int, videoFilterTime: Int = 0, mediaFilterSize: Int = 0,
mediaLoaderListener: LocalMediaLoadListener) {
val instance = getInstance(activity)
instance.initLoader(type, null, object : LoaderManager.LoaderCallbacks {
override fun onCreateLoader(id: Int, args: Bundle?): Loader {
//视频长度
val durationCondition = if (videoFilterTime > 0) " AND $DURATION<$videoFilterTime" else ""
//文件大小
val sizeCondition = if (mediaFilterSize > 0) " AND $SIZE<$mediaFilterSize" else ""
return when (id) {
TYPE_ALL -> CursorLoader(activity, ALL_QUERY_URI, ALL_PROJECTION, ALL_SELECTION
+ durationCondition
+ sizeCondition, null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC")
TYPE_IMAGE -> CursorLoader(activity, ALL_QUERY_URI, ALL_PROJECTION, IMAGE_SELECTION + sizeCondition, IMAGE_SELECTION_ARGS,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC")
TYPE_VIDEO -> CursorLoader(activity, ALL_QUERY_URI, ALL_PROJECTION, VIDEO_SELECTION, VIDEO_SELECTION_ARGS,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC")
else -> CursorLoader(
activity,
ALL_QUERY_URI,
ALL_PROJECTION,
ALL_SELECTION + durationCondition + sizeCondition,
null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC")
}
}
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
if (data != null) {
if (data.count > 0) {
val fileModels = ArrayList()
while (data.moveToNext()) {
val m = FileModel()
val filePath = data.getString(1)
if (!isNotImageFile(filePath)) {
m.fileId = data.getString(0)
m.filePath = "file://" + data.getString(1)
when (type) {
TYPE_ALL -> {
if (data.getInt(4) == 1) {
m.thumbnail = "file://" + data.getString(1)
m.fileType = FileModel.FileType.FILE_TYPE_IMG
} else if (data.getInt(4) == 3) {
initVideoThumbnail(m, activity, data)
m.videoTime = data.getLong(6)
}
}
TYPE_VIDEO -> {
initVideoThumbnail(m, activity, data)
m.videoTime = data.getLong(6)
}
TYPE_IMAGE -> {
m.thumbnail = "file://" + data.getString(1)
m.fileType = FileModel.FileType.FILE_TYPE_IMG
}
}
m.fileName = data.getString(2)
m.shootDate = getDate(data.getLong(3))
m.isLocal = true
m.fileLength = data.getLong(5)
fileModels.add(m)
}
}
data.close()
instance.destroyLoader(type)
if (fileModels.size > 0) {
mediaLoaderListener.loadComplete(fileModels)
}
}
}
}
override fun onLoaderReset(loader: Loader<Cursor>) {}
})
}
复制代码
# CursorLoader
注:
用loaderManger 加 cursorLoader 加载 2800 张 大概 冷家在 3s 热加载 1s 导致加载缓慢的原因 1:Corsor 循环处理判断 复杂 处理太多问题耗时 2.查询太多无用的列 3.分别读取