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()函数正在做的事情:
从中获取所有缩略图的光标 MediaStore.Media.Thumbnails.EXTERNAL_CONTENT_URI
从中获取所有图像的光标 MediaStore.Media.Images.EXTERNAL_CONTENT_URI
加入这些,MediaStore.Media.Thumbnails.IMAGE_ID = MediaStore.Media.Images._ID使用aCursorJoiner
返回结果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,我了解到在任何给定时间都不能指望缩略图表(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)
这是我的测试用例,它演示了 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)
| 归档时间: |
|
| 查看次数: |
2550 次 |
| 最近记录: |