使用CursorLoader查询图片和相册

Categories: android

原来项目中使用到需要选择Android本地文件,就写了这个文件选择器FancyFilePicker ,其中还有一个仿微信的图片选择器。原来这个图片选择器查询图片使用的是ContentResolver, 然后自己根据图片类型什么的条件组合查询。最近看到一个官方提供的异步查询的工具CursorLoader,于是本着学习的心态,重写了这个图片选择器,就使用的这个异步工具CursorLoader

首先,Activity提供了一个LoaderManager的管理工具来管理这个CursorLoader,如果用的是support-v4包,里面也同样有这个getSupportLoaderManager

supportLoaderManager.initLoader(PHOTO_LOADER_ID, args, callback)

通过这个initLoader初始化这个CursorLoader并在后台查询数据返回给前台,具体是通过这个callback进行操作的。这个callback是这个接口的实现LoaderManager.LoaderCallbacks<Cursor>

这个接口有下面3个函数:

override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor>? {
   when (id) {
            PHOTO_LOADER_ID -> {
                var albumId = args?.getString(ALBUM_ID_KEY) ?: ""
                return createCursorLoader(albumId)
            }
            else -> {
                Log.e(TAG, "do not recognize onCreateLoader id , id:$id")
                return null
            }
        }
}
override fun onLoaderReset(loader: Loader<Cursor>?) {
  mAdapter.changeCursor(null)
}
override fun onLoadFinished(loader: Loader<Cursor>?, cursor: Cursor?) {
  mAdapter.changeCursor(cursor)
}

onCreateLoader就是提供一个查询的CursorLoader给后台查询使用,onLoadFinished返回查询结果的CursoronLoaderReset如果数据或者内容有变化就会执行。

比如查询全部图片,我们在onCreateLoader返回一个如下的Loader

 val PROJECTION = arrayOf(MediaStore.Images.Media._ID,
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.DATA,
            MediaStore.Images.Media.DATE_TAKEN)
 val ORDER_BY = " ${MediaStore.Images.Media.DATE_TAKEN } DESC"
 val SELECTION_SIZE =  "${MediaStore.Images.Media.SIZE} > ? or ${MediaStore.Images.Media.SIZE} is null"

CursorLoader(context,
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    PROJECTION,
                    SELECTION_SIZE,
                    arrayOf("0"),
                    ORDER_BY)

然后CursorLoader后台查询数据完成后调用这个onLoadFinished返回查询结果,这个结果要提供给前台展现,因为前台使用的是RecyclerView,返回的cursor提供给RecyclerViewAdapter,根据官方的android.support.v4.widget.CursorAdapter ,写了这个PictureCursorRecyclerViewAdapter ,主要是给Adapter提供数据以及数据绑定进行一些封装。changeCursor每次数据查询后将Adapter中的cursor替换并刷新RecyclerView

fun changeCursor(cursor: Cursor?) {
        var old:Cursor? = swapCursor(cursor)
        if (old!=null) {
            old.close()
        }
    }
    
    private fun swapCursor(newCursor: Cursor?): Cursor? {
        if (newCursor == mCursor) {
            return null
        }
        var old = mCursor
        if (old!=null) {
            old.unregisterContentObserver(mChangeObserver)
            old.unregisterDataSetObserver(mDataSetObserver)
        }
        mCursor = newCursor
        if (newCursor!=null) {
            newCursor.registerContentObserver(mChangeObserver)
            newCursor.registerDataSetObserver(mDataSetObserver)
            mRowIDColumn = newCursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
            mDataValid = true
            notifyDataSetChanged()
        }else {
            mRowIDColumn = -1
            mDataValid = false
            notifyDataSetChanged()
        }
        return old
    }

还有个相册的查询,方式是一样的,就是查询条件不一样,然后因为要有一个全部图片的功能,所以会多一个叫“最近”的相册,这个我就继承了CursorLoader,定义了一个叫AlbumCursorLoader的Loader。

/**
     * 重写CursorLoader 里面的loadInBackground函数
     * 主要是添加一个“最近”的相册出来,按照使用时间排序
     */
    open class AlbumCursorLoader : CursorLoader {

        constructor(context: Context?): super(context,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                arrayOf(MediaStore.Images.Media.BUCKET_ID,
                        MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                        MediaStore.Images.Media.DATA,
                        "count(bucket_id) as cou",
                        MediaStore.Images.Media._ID),
                " _size > ? or _size is null ) GROUP BY  1,(2",
                arrayOf("0"),
                "MAX(datetaken) DESC")

        override fun loadInBackground(): Cursor {
            val albums =  super.loadInBackground()
            val recentAlbum = MatrixCursor(arrayOf(MediaStore.Images.Media.BUCKET_ID,
                    MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                    MediaStore.Images.Media.DATA,
                    "count(bucket_id) as cou",
                    MediaStore.Images.Media._ID))
            var count = 0L
            if (albums.count>0) {
                while (albums.moveToNext()) {
                    count += albums.getLong(3)
                }
            }

            recentAlbum.addRow(arrayOf(Utils.RECENT_ALBUM_ID,
                    context.getString(R.string.recent_photo),
                    "",//TODO 没有图片
                    "$count",
                    Utils.EMPTY_MEDIA_ID))
            return MergeCursor(arrayOf(recentAlbum, albums))
        }
    }

就是重写了这个loadInBackground, 把查询的结果,就是所有的相册,查询到的照片数量加起来,作为这个“最近”相册的照片数量。

PS:supportLoaderManager.initLoader(PHOTO_LOADER_ID, args, callback)是第一次查询使用,如果是多次查询,那查询使用的是同一个Loader,所以后面使用的是supportLoaderManager.restartLoader(PHOTO_LOADER_ID, args, callback)

上面已经把使用CursorLoader的主要代码片段都提了一遍,具体可以参考Github上的全部代码。

https://github.com/fancylou/FancyFilePicker/blob/master/fancyfilepickerlibrary/src/main/java/net/muliba/fancyfilepickerlibrary/ui/PictureLoaderActivity.kt