Android11正式版已经推出,我们将targetSdkVersion和compileSdkVersion都升级到30,并升级pixel4到Android11,发现分区存储读取图片失败。经过分析,发现问题如下:
context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI
, getPhotoProjectionSQL(bucketId)
, getMediaSelectionSQL(true, bucketId)
, null
, getMediaOrderBySQL(start, count + 20))
private fun getMediaOrderBySQL(start: Int = 0, count: Int = Int.MAX_VALUE) = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC limit $count offset $start"
getMediaOrderBySQL()方法抛出异常:
从log上看,是SQL语句出了问题,指向limit。经过各种尝试,发现sortOrder这个参数在Android11中只支持排序方式,不再支持limit等查询限制。但是通常我们加载相册图片/视频等是需要分页的,从官方文档中没有找到相应的例子说明。在阅读query()方法的源码,我们发现query()的实现如下:
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
@Nullable CancellationSignal cancellationSignal) {
Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
return query(uri, projection, queryArgs, cancellationSignal);
}
将selection, selectionArgs, sortOrder封装成一个Bundle对象传给query()方法:
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
* return all columns, which is inefficient.
* @param queryArgs A Bundle containing additional information necessary for
* the operation. Arguments may include SQL style arguments, such
* as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
* the documentation for each individual provider will indicate
* which arguments they support.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
* @return A Cursor object, which is positioned before the first entry. May return
* null
if the underlying content provider returns null
,
* or if it crashes.
* @see Cursor
*/
@Override
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable CancellationSignal cancellationSignal)
不看这段代码具体的实现,主要是看注释对于queryArgs这个参数的说明:
* @param queryArgs A Bundle containing additional information necessary for
* the operation. Arguments may include SQL style arguments, such
* as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
* the documentation for each individual provider will indicate
* which arguments they support.
such as这句:such as {@link ContentResolver#QUERY_ARG_SQL_LIMIT},猜测QUERY_ARG_SQL_LIMIT可能跟我们需要的limit有关。往回看Bundle的创建过程:
Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
public static @Nullable Bundle createSqlQueryBundle(
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
if (selection == null && selectionArgs == null && sortOrder == null) {
return null;
}
Bundle queryArgs = new Bundle();
if (selection != null) {
queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
}
if (selectionArgs != null) {
queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
}
if (sortOrder != null) {
queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder);
}
return queryArgs;
}
QUERY_ARG_SQL_SELECTION,QUERY_ARG_SQL_SELECTION_ARGS,QUERY_ARG_SQL_SORT_ORDER都是我们的传参。猜测是否可以为queryArgs增加QUERY_ARG_SQL_LIMIT来实现limit呢?于是把createSqlQueryBundle()方法copy到我们的代码中,并增加QUERY_ARG_SQL_LIMIT,如下:
val bundle = createSqlQueryBundle(getMediaSelectionSQL(true, bucketId)
, null
, getMediaOrderBySQLNoLimit(), count + 20, start)
context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI
, getPhotoProjectionSQL(bucketId)
, bundle
, null)
private fun createSqlQueryBundle(
selection: String?,
selectionArgs: Array?,
sortOrder: String?, limitCount: Int = 0, offset: Int = 0): Bundle? {
if (selection == null && selectionArgs == null && sortOrder == null) {
return null
}
val queryArgs = Bundle()
if (selection != null) {
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection)
}
if (selectionArgs != null) {
queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs)
}
if (sortOrder != null) {
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, sortOrder)
}
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_LIMIT, "$limitCount offset $offset")
return queryArgs
}
调用如下:
val bundle = createSqlQueryBundle(getMediaSelectionSQL(false, bucketId)
, null
, getMediaOrderBySQLNoLimit(), count + 20, start)
context.contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI
, getVideoProjectionSQL(bucketId)
, bundle
, null)
经测试分页加载成功。