查询MediaStore:加入缩略图和图像(在ID上)

时间:2015-09-25 12:20:05

标签: android mediastore

我正在开发适用于Android的“照片库”型应用。它最初是作为Udacity开发Android应用程序的最终项目,所以它的整体结构(活动,内容提供商等)应该非常合理,并且它被Udacity / Google接受认证。

然而,它仍然没有100%完成,我仍然在努力改进它。

我想要做的事情应该非常简单;将设备上的所有图像(缩略图)加载到MainActivity中的GridView中,使用DetailActivity显示完整大小的图像+一些元数据(标题,大小,日期等)。

本课程要求我们编写一个ContentProvider,所以我有一个query()函数,它基本上从MediaStore中获取数据,并将游标返回给MainActivity的GridView。在我的设备上,至少,(索尼Xperia Z1,Android 5.1.1)这几乎几乎。有一些错误和怪癖,但总的来说,我可以在我的应用程序中不断找到手机上的所有图像,然后点击它们查看详细信息。

然而,当我尝试在朋友的Sony Xperia Z3上安装应用程序时,一切都失败了。没有图像显示,虽然我明显检查他的手机上实际上有~100张照片。在另一位朋友的手机上(全新的三星S6): - (

这是主要问题。在我的手机上,当东西工作时,“次要”错误涉及当相机拍摄新照片时,它不会自动加载到我的应用程序中(作为缩略图)。我似乎需要弄清楚如何触发扫描,或者加载/生成新拇指所需的任何内容。我的愿望清单上也是如此。

正如我所说,我相信所有这一切都应该非常简单,所以也许我所有的困难都表明我正以完全错误的方式接近问题? 以下是我的query()函数正在执行的操作:

  1. MediaStore.Media.Thumbnails.EXTERNAL_CONTENT_URI

  2. 获取所有缩略图的光标
  3. MediaStore.Media.Images.EXTERNAL_CONTENT_URI

  4. 获取所有图片的光标
  5. 使用MediaStore.Media.Thumbnails.IMAGE_ID = MediaStore.Media.Images._ID

  6. CursorJoiner上加入这些内容
  7. 返回结果retCursor(在联接中生成)

  8. - 请查找完整代码in this previous post.

    虽然这个看起来正确(对我来说),也许真的不是这样的方法吗?顺便说一句,我正在加入大拇指和图像,这样我就可以在GridView中显示一些元数据(例如拍摄日期)和缩略图。我已经确定了加入的问题,特别是因为如果我将其简化为只将大拇指加载到GridView中,那么一切正常 - 也在我朋友的手机上。 (除了加载新照片。)

    不知何故,我假设IMAGE_ID_ID始终一致是不正确的?我见过一个post on AirPair,描述了一个类似的图库应用程序,那里的教程实际上略有不同。他没有尝试加入游标,而是获取缩略图光标并对其进行迭代,adding data from Images using individual queries to the MediaStore ......但这是最有效的方法吗? - 尽管如此,他的解决方案 将缩略图加入ID上的相应图像:

    Cursor imagesCursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                filePathColumn, 
                MediaStore.Images.Media._ID + "=?", new String[]{imageId},  // NB!
                null);
    

    总之,我需要以下方面的帮助:

    • 我正确查询MediaStore吗?
    • 在ID上加入大拇指和图像是否安全 - 它会一直稳定/同步吗?
    • 我的应用程序如何自动生成/获取新图像的缩略图?

3 个答案:

答案 0 :(得分:7)

好的,好像我终于弄明白这一切了。我想在此处与其他可能感兴趣的人分享这个。

我想要实现的目标是什么?

  • 在设备上查询缩略图和图像(通过MediaStore)
  • 将这些加入一个游标,订购降序(最新图片在上面)
  • 处理丢失缩略图的情况

经过大量试验和错误,以及使用MediaStore,我已经了解到缩略图表(MediaStore.Images.Thumbnails)在任何给定时间都不可能是最新的。 成为缺少缩略图的图像,反之亦然(孤立的缩略图)。特别是当相机应用拍摄新照片时,显然它不会立即创建缩略图。直到Gallery应用程序(或同等版本)打开后,缩略图表才会更新。

关于如何解决这个问题,我得到了各种有用的建议,主要集中在查询图像表(MediaStore.Images.Media),然后,不知何故,一次一行地用缩略图扩展光标。虽然这确实有效,但它导致应用程序非常慢,并且在我的设备上消耗了大量内存〜2000个图像。

否则,我们将缩略图DATA列留给null,并自己生成那些特定的丢失缩略图。真正酷的是将这些缩略图插入到MediaStore中,但我尚未调查过。

所有这一切的主要问题是使用CursorJoiner。出于某种原因,它需要以升序命令两个游标,让我们说出ID。然而,这意味着首先是最古老的图像,这真的是一个糟糕的画廊应用程序。我发现CursorJoiner可以被愚弄"但是,通过简单地按ID*(-1)排序允许降序:

Cursor c_thumbs = getContext().getContentResolver().query(
                    MediaStore.Images.Thumnails.EXTERNAL_CONTENT_URI,
                    null, null, null, 
                    "(" + MediaStore.Images.Thumnails.IMAGE_ID + "*(-1))");

Cursor c_images= getContext().getContentResolver().query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    null, null, null, 
                    "(" + MediaStore.Images.Media._ID + "*(-1))");

只要行匹配,但这样可以正常工作(BOTH情况)。但是当你遇到任何游标都是唯一的行(LEFTRIGHT情况)时,反向排序会混淆CursorJoiner类的内部工作。但是,左右光标的简单补偿就足以重新对齐"加入,让它回到正轨。请注意moveToNext()moveToPrevious()来电。

// join these and return
// the join is on images._ID = thumbnails.IMAGE_ID
CursorJoiner joiner = new CursorJoiner(
        c_thumbs, new String[] { MediaStore.Images.Thumnails.IMAGE_ID },  // left = thumbnails
        c_images, new String[] { MediaStore.Images.Media._ID }   // right = images
);

String[] projection = new String{"thumb_path", "ID", "title", "desc", "datetaken", "filename", "image_path"};

MatrixCursor retCursor = new MatrixCursor(projection);

try {
    for (CursorJoiner.Result joinerResult : joiner) {

        switch (joinerResult) {
            case LEFT:
                // handle case where a row in cursorA is unique
                // images is unique (missing thumbnail)

                // we want to show ALL images, even (new) ones without thumbnail!
                // data = null will cause a temporary thumbnail to be generated in PhotoAdapter.bindView()

                retCursor.addRow(new Object[]{
                        null, // data
                        c_images.getLong(1), // image id
                        c_images.getString(2), // title
                        c_images.getString(3),  // desc
                        c_images.getLong(4),  // date
                        c_images.getString(5),  // filename
                        c_images.getString(6)
                });

                // compensate for CursorJoiner expecting cursors ordered ascending...
                c_images.moveToNext();
                c_thumbs.moveToPrevious();
                break;

            case RIGHT:
                // handle case where a row in cursorB is unique
                // thumbs is unique (missing image)

                // compensate for CursorJoiner expecting cursors ordered ascending...
                c_thumbs.moveToNext();
                c_images.moveToPrevious();
                break;

            case BOTH:

                // handle case where a row with the same key is in both cursors
                retCursor.addRow(new Object[]{
                        c_thumbs.getString(1), // data
                        c_images.getLong(1), // image id
                        c_images.getString(2), // title
                        c_images.getString(3),  // desc
                        c_images.getLong(4),  // date
                        c_images.getString(5),  // filename
                        c_images.getString(6)
                });

                break;
        }
    }
} catch (Exception e) {
    Log.e("myapp", "JOIN FAILED: " + e);
}

c_thumbs.close();
c_images.close();

return retCursor;

然后,在" PhotoAdapter" class,它为我的GridView创建元素,并将数据从ContentProvider返回的光标(上面的retCursor)绑定到这些元素中,我以下列方式创建缩略图(当thumb_path字段时是null):

String thumbData = cursor.getString(0);  // thumb_path
if (thumbData != null) {
    Bitmap thumbBitmap;
    try {
        thumbBitmap = BitmapFactory.decodeFile(thumbData);
        viewHolder.iconView.setImageBitmap(thumbBitmap);
    } catch (Exception e) {
        Log.e("myapp", "PhotoAdapter.bindView() can't find thumbnail (file) on disk (thumbdata = " + thumbData + ")");
        return;
    }

} else {

    String imgPath = cursor.getString(6);   // image_path
    String imgId = cursor.getString(1);  // ID 
    Log.v("myapp", "PhotoAdapter.bindView() thumb path for image ID " + imgId + " is null. Trying to generate, with path = " + imgPath);

    try {
        Bitmap thumbBitmap = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(imgPath), 512, 384);
        viewHolder.iconView.setImageBitmap(thumbBitmap);
    }  catch (Exception e) {
        Log.e("myapp", "PhotoAdapter.bindView() can't generate thumbnail for image path: " + imgPath);
        return;
    }
}

答案 1 :(得分:0)

接受的答案让我开始讨论这个问题,但它包含了一些小错误。

case LEFT:
            // handle case where a row in cursorA is unique
            // images is unique (missing thumbnail)
case RIGHT:
            // handle case where a row in cursorB is unique
            // thumbs is unique (missing image)

这些是向后的。文档与自身相矛盾,很可能是错误发生的地方。来自source code of CursorJoiner

case LEFT:
        // handle case where a row in cursorA is unique

然后在源代码的结果枚举中显示:

public enum Result {
    /** The row currently pointed to by the left cursor is unique */
    RIGHT,
    /** The row currently pointed to by the right cursor is unique */
    LEFT,
    /** The rows pointed to by both cursors are the same */
    BOTH
}

所以我猜这就是你强行增加游标的原因。

 //compensate for CursorJoiner expecting cursors ordered ascending...
                c_images.moveToNext();
                c_thumbs.moveToPrevious();

CursorJoiner中的迭代器会自动为您增加游标。

这应该是工作代码(此代码还将内部存储和外部存储合并为一个游标):

        Cursor[] thumbs = new Cursor[2];
        thumbs[0] = mActivity.getContentResolver().query(
                MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Images.Thumbnails._ID ,
                        MediaStore.Images.Thumbnails.IMAGE_ID,
                        MediaStore.Images.Thumbnails.DATA
                },
                null,
                null,
                MediaStore.Images.Thumbnails.IMAGE_ID + "*(-1)"
        );
        thumbs[1] = mActivity.getContentResolver().query(
                MediaStore.Images.Thumbnails.INTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Images.Thumbnails._ID ,
                        MediaStore.Images.Thumbnails.IMAGE_ID,
                        MediaStore.Images.Thumbnails.DATA
                },
                null,
                null,
                MediaStore.Images.Thumbnails.IMAGE_ID + "*(-1)"
        );
        Cursor thumbCursor = new MergeCursor(thumbs);
        Cursor[] cursors = new Cursor[2];
        cursors[0] = mActivity.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Images.Media._ID,
                        MediaStore.Images.Media.DATA,
                        MediaStore.Images.Media.ORIENTATION,
                        MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                        MediaStore.Images.Media.BUCKET_ID,
                        MediaStore.Images.Media.MIME_TYPE
                },
                null,
                null,
                MediaStore.Images.Media._ID + "*(-1)"
        );
        cursors[1] = mActivity.getContentResolver().query(
                MediaStore.Images.Media.INTERNAL_CONTENT_URI,
                new String[]{
                        MediaStore.Images.Media._ID,
                        MediaStore.Images.Media.DATA,
                        MediaStore.Images.Media.ORIENTATION,
                        MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                        MediaStore.Images.Media.BUCKET_ID,
                        MediaStore.Images.Media.MIME_TYPE
                },
                null,
                null,
                MediaStore.Images.Media._ID + "*(-1)"
        );
        Cursor photoCursor = new MergeCursor(cursors);
        CursorJoiner cursorJoiner = new CursorJoiner(
                thumbCursor,
                new String[]{
                        MediaStore.Images.Thumbnails.IMAGE_ID
                },
                photoCursor,
                new String[]{
                        MediaStore.Images.Media._ID,
                }
        );
        Cursor finalCursor= new MatrixCursor(new String[]{
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.ORIENTATION,
                MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
                MediaStore.Images.Media.BUCKET_ID,
                MediaStore.Images.Media.MIME_TYPE,
                "thumb_data"
        });
        for (CursorJoiner.Result joinerResult : cursorJoiner) {
            switch (joinerResult) {
                case RIGHT:
                    finalCursor.addRow(new Object[]{
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media._ID)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.DATA)),
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)),
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)),
                            null
                    });
                    break;
                case BOTH:
                    finalCursor.addRow(new Object[]{
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media._ID)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.DATA)),
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME)),
                            photoCursor.getLong(photoCursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID)),
                            photoCursor.getString(photoCursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE)),
                            thumbCursor.getString(thumbCursor.getColumnIndex(MediaStore.Images.Thumbnails.DATA)),
                    });
                    break;
            }

        }
        photoCursor.close();
        thumbCursor.close();

答案 2 :(得分:0)

这是我的测试用例,它表明CursorJoiner缺少对降序有序游标的支持。但是,这在CursorJoiner源代码中有详细记载,所以我不是试图批评,而只是说明如何规避(或黑客攻击)。

测试用例显示了升序排序的假设如何需要“翻转”或反转CursorJoiner所做的所有选择(比较结果,游标递增等)。我接下来要尝试的是直接修改CursorJoiner类,尝试添加对DESC排序的支持。

请注意,似乎关于ID *( - 1)订购的部分可能并非严格必要。在下面的例子中,我没有否定ID列(简单的DESC排序,而不是带有负序列的“伪ASC”),它仍然有效。

测试用例

String[] colA = new String[] { "_id", "data", "B_id" };
String[] colB = new String[] { "_id", "data" };

MatrixCursor cursorA = new MatrixCursor(colA);
MatrixCursor cursorB = new MatrixCursor(colB);

// add 4 items to cursor A, linked to cursor B
// the data is ordered DESCENDING
// all cases, LEFT/RIGHT/BOTH, are included
cursorA.addRow(new Object[] { 5, "Item A", 1004 });  // BOTH
cursorA.addRow(new Object[] { 4, "Item B", 1003 });  // LEFT
cursorA.addRow(new Object[] { 3, "Item C", 1002 });  // BOTH
cursorA.addRow(new Object[] { 2, "Item D", 1001 });  // LEFT
cursorA.addRow(new Object[] { 1, "Item E", 1000 });  // BOTH
cursorA.addRow(new Object[] { 0, "Item F", 500 });  // LEFT

// similarily for cursorB (DESC)
cursorB.addRow(new Object[] { 1004, "X" });   // BOTH
cursorB.addRow(new Object[] { 1002, "Y" });   // BOTH
cursorB.addRow(new Object[] { 999,  "Z" });    // RIGHT
cursorB.addRow(new Object[] { 998,  "S" });    // RIGHT
cursorB.addRow(new Object[] { 900,  "A" });    // RIGHT
cursorB.addRow(new Object[] { 1000, "G" });   // BOTH

// join these on ID
CursorJoiner cjoiner = new CursorJoiner(
        cursorA, new String[] { "B_id" },   // left = A
        cursorB, new String[] { "_id" }     // right = B
);

// enable workaround
boolean desc = true;

int count = 0;
for (CursorJoiner.Result joinerResult : cjoiner) {
    Log.v("TEST", "Processing (left)=" + (cursorA.isAfterLast() ? "<empty>" : cursorA.getLong(2))
                + " / (right)=" + (cursorB.isAfterLast() ? "<empty>" : cursorB.getLong(0)));

     // flip the CursorJoiner.Result (unless Result.BOTH, or either cursor is exhausted)
    if (desc && joinerResult != CursorJoiner.Result.BOTH
             && !cursorB.isAfterLast() && !cursorA.isAfterLast())
        joinerResult = (joinerResult == CursorJoiner.Result.LEFT ? CursorJoiner.Result.RIGHT : CursorJoiner.Result.LEFT);

    switch (joinerResult) {
        case LEFT:
            // handle case where a row in cursorA is unique
            Log.v("TEST", count + ") join LEFT. cursorA is unique");

            if (desc) {
                // compensate cursor increments
                if (!cursorB.isAfterLast()) cursorB.moveToPrevious();
                if (!cursorA.isLast()) cursorA.moveToNext();
            }
            break;

        case RIGHT:
            Log.v("TEST", count + ") join RIGHT. cursorB is unique");
            // handle case where a row in cursorB is unique

            if (desc) {
                if (!cursorB.isLast()) cursorB.moveToNext();
                if (!cursorA.isAfterLast()) cursorA.moveToPrevious();
            }
            break;

        case BOTH:
            Log.v("TEST", count + ") join BOTH: " + cursorA.getInt(0) + "," + cursorA.getString(1) + "," + cursorA.getInt(2) + "/" + cursorB.getInt(0) + "," + cursorB.getString(1));
            // handle case where a row with the same key is in both cursors
            break;

    }

    count++;
}
Log.v("TEST", "Join done!");

和输出:

V/TEST: Processing (left)=5 / (right)=1004
V/TEST: 0) join BOTH: 4,Item A,1004/1004,X
V/TEST: Processing (left)=4 / (right)=1002
V/TEST: 1) join LEFT. cursorA is unique
V/TEST: Processing (left)=3 / (right)=1002
V/TEST: 2) join BOTH: 2,Item C,1002/1002,Y
V/TEST: Processing (left)=2 / (right)=999
V/TEST: 3) join RIGHT. cursorB is unique
V/TEST: Processing (left)=2 / (right)=998
V/TEST: 4) join RIGHT. cursorB is unique
V/TEST: Processing (left)=2 / (right)=900
V/TEST: 5) join RIGHT. cursorB is unique
V/TEST: Processing (left)=2 / (right)=1000
V/TEST: 6) join LEFT. cursorA is unique
V/TEST: Processing (left)=1 / (right)=1000
V/TEST: 7) join BOTH: 0,Item D,1000/1000,F
V/TEST: Processing (left)=0 / (right)=---
V/TEST: 8) join LEFT. cursorA is unique
V/TEST: Join done!