Matisse: Android Q 设备上query查询图片资源crash

Created on 15 Aug 2019  ·  4Comments  ·  Source: zhihu/Matisse

Android Q 设备上query查询图片资源crash
设备:Android Q模拟器
compileSdkVersion: 29

2019-08-15 17:10:37.338 W/System.err: java.lang.RuntimeException: An error occurred while executing doInBackground() 2019-08-15 17:10:37.338 W/System.err: at androidx.loader.content.ModernAsyncTask$3.done(ModernAsyncTask.java:164) 2019-08-15 17:10:37.338 W/System.err: at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383) 2019-08-15 17:10:37.338 W/System.err: at java.util.concurrent.FutureTask.setException(FutureTask.java:252) 2019-08-15 17:10:37.338 W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:271) 2019-08-15 17:10:37.338 W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 2019-08-15 17:10:37.338 W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 2019-08-15 17:10:37.338 W/System.err: at java.lang.Thread.run(Thread.java:919) 2019-08-15 17:10:37.339 W/System.err: Caused by: java.lang.IllegalArgumentException: Invalid column COUNT(*) AS count 2019-08-15 17:10:37.339 W/System.err: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:170) 2019-08-15 17:10:37.347 W/System.err: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140) 2019-08-15 17:10:37.347 W/System.err: at android.content.ContentProviderProxy.query(ContentProviderNative.java:423) 2019-08-15 17:10:37.352 W/System.err: at android.content.ContentResolver.query(ContentResolver.java:944) 2019-08-15 17:10:37.355 W/System.err: at android.content.ContentResolver.query(ContentResolver.java:880) 2019-08-15 17:10:37.355 W/System.err: at androidx.core.content.ContentResolverCompat.query(ContentResolverCompat.java:81) 2019-08-15 17:10:37.355 W/System.err: at androidx.loader.content.CursorLoader.loadInBackground(CursorLoader.java:63) 2019-08-15 17:10:37.355 W/System.err: at com.zhihu.matisse.internal.loader.AlbumLoader.loadInBackground(AlbumLoader.java:98) 2019-08-15 17:10:37.356 W/System.err: at com.zhihu.matisse.internal.loader.AlbumLoader.loadInBackground(AlbumLoader.java:34) 2019-08-15 17:10:37.383 W/System.err: at androidx.loader.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:307) 2019-08-15 17:10:37.384 W/System.err: at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:60) 2019-08-15 17:10:37.384 W/System.err: at androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:48) 2019-08-15 17:10:37.384 W/System.err: at androidx.loader.content.ModernAsyncTask$2.call(ModernAsyncTask.java:141) 2019-08-15 17:10:37.384 W/System.err: at java.util.concurrent.FutureTask.run(FutureTask.java:266)

Most helpful comment

encounter the same situation. looking for solution

All 4 comments

AlbumLoader类做了Android Q的处理,代码如下:

package com.zhihu.matisse.internal.loader;

import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.SparseArray;

import androidx.loader.content.CursorLoader;

import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;

/**

  • Load all albums (grouped by bucket_id) into a single cursor.
    /
    public class AlbumLoader extends CursorLoader {
    public static final String COLUMN_COUNT = "count";
    private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
    private static final String COLUMN_BUCKET_ID = "bucket_id";
    private static final String COLUMN_BUCKET_NAME = "bucket_display_name";
    private static final String[] COLUMNS = {
    MediaStore.Files.FileColumns._ID,
    COLUMN_BUCKET_ID,
    COLUMN_BUCKET_NAME,
    MediaStore.MediaColumns.DATA,
    COLUMN_COUNT};
    private static final String[] PROJECTION = {
    MediaStore.Files.FileColumns._ID,
    COLUMN_BUCKET_ID,
    COLUMN_BUCKET_NAME,
    MediaStore.MediaColumns.DATA,
    "COUNT(
    ) AS " + COLUMN_COUNT};
    private static final String[] PROJECTION_29 = {
    MediaStore.Files.FileColumns._ID,
    COLUMN_BUCKET_ID,
    COLUMN_BUCKET_NAME,
    MediaStore.MediaColumns.DATA};
private static final String SELECTION =
        "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                + " OR "
                + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                + ") GROUP BY (bucket_id";
private static final String SELECTION_29 =
        "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                + " OR "
                + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
                + " AND " + MediaStore.MediaColumns.SIZE + ">0";
private static final String[] SELECTION_ARGS = {
        String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
        String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
};

private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE =
        MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                + " AND " + MediaStore.MediaColumns.SIZE + ">0"
                + ") GROUP BY (bucket_id";
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 =
        MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
                + " AND " + MediaStore.MediaColumns.SIZE + ">0";

private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
    return new String[]{String.valueOf(mediaType)};
}

private static final String BUCKET_ORDER_BY = "datetaken DESC";

private AlbumLoader(Context context, String[] projection, String selection, String[] selectionArgs) {
    super(context, QUERY_URI, projection, selection, selectionArgs, BUCKET_ORDER_BY);
}

public static CursorLoader newInstance(Context context) {
    String selection;
    String[] selectionArgs;
    String[] projection;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        projection = PROJECTION_29;
        if (SelectionSpec.getInstance().onlyShowImages()) {
            selection = SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
            selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
        } else if (SelectionSpec.getInstance().onlyShowVideos()) {
            selection = SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
            selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
        } else {
            selection = SELECTION_29;
            selectionArgs = SELECTION_ARGS;
        }
    } else {
        projection = PROJECTION;
        if (SelectionSpec.getInstance().onlyShowImages()) {
            selection = SELECTION_FOR_SINGLE_MEDIA_TYPE;
            selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
        } else if (SelectionSpec.getInstance().onlyShowVideos()) {
            selection = SELECTION_FOR_SINGLE_MEDIA_TYPE;
            selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
        } else {
            selection = SELECTION;
            selectionArgs = SELECTION_ARGS;
        }
    }

    return new AlbumLoader(context, projection, selection, selectionArgs);
}

@Override
public Cursor loadInBackground() {
    Cursor albums = super.loadInBackground();
    MatrixCursor allAlbum = new MatrixCursor(COLUMNS);
    int totalCount = 0;
    String allAlbumCoverPath = "";
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        if (albums == null) {
            return allAlbum;
        }
        SparseArray<Album> albumList = new SparseArray<>();
        while (albums.moveToNext()) {
            int bucketId = albums.getInt(albums.getColumnIndex(COLUMN_BUCKET_ID));
            String bucketName = albums.getString(albums.getColumnIndex(COLUMN_BUCKET_NAME));
            Album album = albumList.get(bucketId);
            if (album == null) {
                album = new Album(String.valueOf(bucketId), allAlbumCoverPath, bucketName, 0);
                albumList.append(bucketId, album);
            }
            album.addCaptureCount();
        }
        for (int i = 0; i < albumList.size(); i++) {
            totalCount += albumList.valueAt(i).getCount();
        }
        if (albums.moveToFirst()) {
            allAlbumCoverPath = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.DATA));
            allAlbum.addRow(new String[]{Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, allAlbumCoverPath,
                    String.valueOf(totalCount)});
        }
        for (int i = 0; i < albumList.size(); i++) {
            Album album = albumList.valueAt(i);
            totalCount += album.getCount();
            allAlbum.addRow(new String[]{album.getId(), album.getId(), album.getDisplayName(getContext()),
                    album.getCoverPath(), String.valueOf(album.getCount())});
        }
        return allAlbum;
    } else {
        if (albums != null) {
            while (albums.moveToNext()) {
                totalCount += albums.getInt(albums.getColumnIndex(COLUMN_COUNT));
            }
            if (albums.moveToFirst()) {
                allAlbumCoverPath = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.DATA));
            }
        }
        allAlbum.addRow(new String[]{Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, allAlbumCoverPath,
                String.valueOf(totalCount)});

        return new MergeCursor(new Cursor[]{allAlbum, albums});
    }
}

@Override
public void onContentChanged() {
    // FIXME a dirty way to fix loading multiple times
}

}

encounter the same situation. looking for solution

当使用兼容的AlbumLoader 之后,如果视频库没有文件,会出现以下错误:
Process: com.zhihu.matisse.sample, PID: 10492
android.database.CursorIndexOutOfBoundsException: After last row.
at android.database.MatrixCursor.get(MatrixCursor.java:79)
at android.database.MatrixCursor.getString(MatrixCursor.java:266)
at com.zhihu.matisse.internal.entity.Album.valueOf(Album.java:70)
at com.zhihu.matisse.internal.ui.widget.AlbumsSpinner.onItemSelected(AlbumsSpinner.java:75)
at com.zhihu.matisse.internal.ui.widget.AlbumsSpinner.setSelection(AlbumsSpinner.java:68)
at com.zhihu.matisse.ui.MatisseActivity$1.run(MatisseActivity.java:364)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

On Android Q,the projection parameter of ContentResolver.query() no longer support custom column like xxx AS xx,instead, it throws IllegalArgumentException because of the setting in MediaProvider.java : qb.setStrict(true).

    SQLiteBuilder#computeProjection():
    if (!mStrict &&
            ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
        /* A column alias already exist */
        projection[i] = maybeWithOperator(operator, userColumn);
        continue;
    }

    // If greylist is configured, we might be willing to let
    // this custom column bypass our strict checks.
    if (mProjectionGreylist != null) {
        ...
    }

    throw new IllegalArgumentException("Invalid column "
            + projectionIn[i]);

In MediaProvider.java:

    MediaProvider#getQueryBuilderInternal():
    final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    if (parseBoolean(uri.getQueryParameter("distinct"))) {
        qb.setDistinct(true);
    }
    qb.setProjectionAggregationAllowed(true);
    qb.setStrict(true);//Only on Android Q
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Mahan3340 picture Mahan3340  ·  4Comments

lucazin picture lucazin  ·  7Comments

meiniepan picture meiniepan  ·  7Comments

HuRuWo picture HuRuWo  ·  4Comments

xiaotianchang picture xiaotianchang  ·  4Comments