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

joa*_*imk 7 android mediastore

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

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

我想要做的应该是非常直截了当的; 将设备上的所有图像(作为缩略图)加载到MainActivity中的GridView中,使用DetailActivity显示完整大小的图像+一些元数据(标题,大小,日期等).

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

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

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

正如我所说,我相信所有这一切都应该非常简单,所以也许我所有的困难都表明我正以完全错误的方式解决问题?这是我的query()函数正在做的事情:

  1. 从中获取所有缩略图的光标 MediaStore.Media.Thumbnails.EXTERNAL_CONTENT_URI

  2. 从中获取所有图像的光标 MediaStore.Media.Images.EXTERNAL_CONTENT_URI

  3. 加入这些,MediaStore.Media.Thumbnails.IMAGE_ID = MediaStore.Media.Images._ID使用aCursorJoiner

  4. 返回结果retCursor(在连接中生成)

- 请在上一篇文章中找到完整的代码.

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

不知怎的,我的假设IMAGE_ID_ID始终一致是不正确的?我在AirPair上看过一篇文章,描述了一个类似的画廊应用程序,那里的教程实际上与此略有不同.他没有尝试加入游标,而是获取缩略图光标并对其进行迭代,使用单个查询将图像中的数据添加到MediaStore ......但这是最有效的方法吗?- 尽管如此,他的解决方案确实将缩略图连接到ID上的相应图像:

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

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

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

joa*_*imk 7

好吧,所以看来我终于想到了这一切.以为我会在这里分享这个,对于其他可能感兴趣的人.

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

  • 查询设备上的缩略图和图像(通过MediaStore)
  • 将这些加入一个光标,顺序降序(顶部最新图像)
  • 处理丢失缩略图的情况

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

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

真的应该可以简单地使用图像表JOIN(左外连接)缩略图表,这样我们就可以得到所有图像和缩略图.否则,我们将缩略图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))");
Run Code Online (Sandbox Code Playgroud)

但是,只要行匹配,就可以正常工作(BOTH案例).但是当你遇到任何一个游标都是唯一的行(LEFT或者RIGHT情况)时,反向排序会混淆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;
Run Code Online (Sandbox Code Playgroud)

然后,在"PhotoAdapter"类中,为我创建元素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;
    }
}
Run Code Online (Sandbox Code Playgroud)


joa*_*imk 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!");
Run Code Online (Sandbox Code Playgroud)

和输出:

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!
Run Code Online (Sandbox Code Playgroud)