在Android,多媒体文件(视频和图片)都是有缩略图的,在很多应用中,我们需要获取这些缩略图。比如最近在做一个类似相册的应用,需要扫描相册里面的图片,然后获取其缩略图,使用GridView去展示缩略图,当点击之后,我们需要获取其原始图,所以相关的需求如下:

1)获取缩略图(一个问题是:是否所有的图片以及视频都有缩略图?);

2)将缩略图和原始图关联起来;

 

关于1):

现在采用的方式是:

1 Options options=new Options();
2 options.inSampleSize=32;
3 Bitmap bitmap=BitmapFactory.decodeFile(url, options);
4 Drawable drawable=new BitmapDrawable(bitmap);

但是这种方法有问题:很难把握inSampleSize的大小(这里的32已经显得非常夸张了,但是从相册角度来说,图片数量是以百或者千为单位的,并且我遇到的问题是我的图片并不一样大小,我从网上下载了一些小图片到手机里面,原先的大图设置32没有问题,但是小图却明显太小了)。我很想知道Android系统是否能够聪明的帮我做到缩略到合适的尺寸(另一个原因是:跳过缩略图我就必须自己写一个函数遍历所有的文件夹寻找图片格式的文件,这大大降低了程序的性能)。下面研究直接提取缩略图。

提取图片和视频的缩略图可以直接访问: 

1 android.provider.MediaStore.Images.Thumbnails
2 android.provider.MediaStore.Video.Thumbnails


之前我一直对Android在这方面的数据存储非常感兴趣,很想知道它是如何存储这些数据以及我可以从数据里面获得什么,今天将数据库导了出来,查看了一下存储,下面做出解释:

大家可以先查看一下/data/data/com.android.provider.media目录下面的databases:external-f042911.db 和 internal.db,如图:

android listview 缩略图 安卓缩略图_Android

选中后,点击右上角有个箭头向左的图案的图标即可将数据库导出到电脑文件夹中,然后下载一个SQLite数据库查看软件,我是在Mac下,下载的软件名叫MesaSQLite ,之后打开数据库,我们来查看一下:

首先是external-f042911.db数据库,这个数据库里面有很多表,如下:

android listview 缩略图 安卓缩略图_Android_02

这里很明显有两个thumbnails表,我查看的就是thumbnails表格,下面是表格内容:

android listview 缩略图 安卓缩略图_数据库_03

总共11张缩略图,我们可以看到每张缩略图除了有一个_id之外,还有一个image_id(这个是关键?)。

然后要看的表格当然就是images,这个表格存储的是什么呢?截图如下,这个表表头很长,我截取两端:

android listview 缩略图 安卓缩略图_ide_04

这是第一张,可以看到什么??首先当然是同样的_id字段,这是和上面对应好的么??其次,我们可以找到图片的大小,类型,名称等属性,最重要的是其绝对物理路径!

android listview 缩略图 安卓缩略图_数据库_05

这是第二张,是表格后面一段的属性,这张图上面最最重要的一个列就是"bucket_display_name",稍微对应一下我们就能发现这里面记录的是图片所在的文件夹(做相册的时候是很需要这个的!)

然后我再查看两个表:

第一个是albums表,这个表里面是什么呢?

android listview 缩略图 安卓缩略图_ide_06

这对我是个意外之喜,QIDUO是我创建的一个文件夹,我在里面存储了一些文件和一个amr录音文件,所以我就很好奇了,Android如何判定一个文件夹为album呢?

再看一个表:audio_meta,更是一个惊喜:

android listview 缩略图 安卓缩略图_ide_07

这个神奇的表格里面居然记录了我录制的amr音频,大家看到木有啊?有绝对路径,有尺寸,有时长!让人非常惊讶!

所以,到现在我有如下几个疑问:

1)是否所有的多媒体文件(我指的是视频和图片)都有缩略图?

2)缩略图和原始图是如何对应的?

3)album是如何定义的?

首先回答问题2):其实问题2)写一个程序即可验证,事实不是我们猜想的那样,两个_id字段是对应的,而是:

表thumbnails和images通过thumbnails.image_id与images._id关联的,通过images的_id,就可以找出来thumbnails表中的图片和images表中图片的映射关系了。原始图片的位置就是images表中的_data字段的值。

关于第1)个问题,我们貌似要了解一下,缩略图到底是如何实现的?这里有一个类:MediaScanner(详细要研究可见: )这个类是负责扫描所有的图片并将图片存储进入MediaStore(MediaScannerReceiver用来接收任务的,它收到广播后,会启动MediaService进行扫描工作。好复杂的样子。。。)我插一张图:

android listview 缩略图 安卓缩略图_Android_08

MediaScanner可以通过手动控制,在ANDROID系统中,已经定制了三种事件会触发MediaScanner去扫描磁盘文件:ACTION_BOOT_COMPLETED、ACTION_MEDIA_MOUNTED、 ACTION_MEDIA_SCANNER_SCAN_FILE。其中ACTION_BOOT_COMPLETED是系统启动完后发出这个消息,ACTION_MEDIA_MOUNTED是插卡事件触发的消息,ACTION_MEDIA_SCANNER_SCAN_FILE消息一般是在一些文件操作后,开发人员手动发出的一个重新扫描多媒体文件的消息。发送消息通过sendBroadcast函数完成,比如广播一个ACTION_MEDIA_MOUNTED消息:

1 sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));

由上可知是通过发送了一个广播(传递对应的扫描要求)来触发重新扫描磁盘事件,那么可以猜测系统肯定存在一个广播接收器(何时何地注册?),在收到这个广播消息后,通过对应参数启动MediaScannerService。MediaScannerService调用一个公用类MediaScanner去处理真正的工作。MediaScannerReceiver维持两种扫描目录:一种是内部卷(internal volume)指向$(ANDROID_ROOT)/media. 另一种是外部卷(external volume)指向$(EXTERNAL_STORAGE),扫描的位置可以修改(一般外部不用修改,默认为SDCARD,内部根据驱动命名的INAND路经名做对应的修改)。

所以对于问题1),如果你存储了图片但是没有启动磁盘扫描,就会造成缩略图不全。

关于第三点待续~

代码:

1)获取缩略图:

1 cr = getContentResolver();
 2 String[] projection = { Thumbnails._ID, Thumbnails.IMAGE_ID, Thumbnails.DATA };
 3 Cursor cursor = cr.query(Thumbnails.EXTERNAL_CONTENT_URI, projection, null, null, null);
 4 getColumnData(cursor);
 5 
 6 private void getColumnData(Cursor cur) {
 7     if (cur.moveToFirst()) {
 8         int _id;
 9         int image_id;
10         String image_path;
11         int _idColumn = cur.getColumnIndex(Thumbnails._ID);
12         int image_idColumn = cur.getColumnIndex(Thumbnails.IMAGE_ID);
13         int dataColumn = cur.getColumnIndex(Thumbnails.DATA);
14 
15         do {
16             // Get the field values
17             _id = cur.getInt(_idColumn);
18             image_id = cur.getInt(image_idColumn);
19             image_path = cur.getString(dataColumn);
20 
21             // Do something with the values.
22             Log.i(TAG, _id + " image_id:" + image_id + " path:"
23                         + image_path + "---");
24             HashMap<String, String> hash = new HashMap<String, String>();
25             hash.put("image_id", image_id + "");
26             hash.put("path", image_path);
27             list.add(hash);
28 
29         } while (cur.moveToNext());
30 
31      }
32 }

2)获取实际图片

1 String columns[] = new String[] { Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME, Media.SIZE };  
 2 // 得到一个游标  
 3 cursor = this.getContentResolver().query(Media.EXTERNAL_CONTENT_URI, columns, null, null, null);  
 4 // 获取指定列的索引  
 5 photoIndex = cursor.getColumnIndexOrThrow(Media.DATA);  
 6 photoNameIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME);  
 7 photoIDIndex = cursor.getColumnIndexOrThrow(Media._ID);  
 8 photoTitleIndex = cursor.getColumnIndexOrThrow(Media.TITLE);  
 9 photoSizeIndex = cursor.getColumnIndexOrThrow(Media.SIZE);  
10 // 获取图片总数  
11 totalNum = cursor.getCount();

3)缩略图和原始图的对应

1 OnItemClickListener listener = new OnItemClickListener() {   
 2     @Override  
 3     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {  
 4         // TODO Auto-generated method stub  
 5         String image_id = list.get(position).get("image_id");  
 6         Log.i(TAG, "---(^o^)----" + image_id);  
 7        String[] projection = { Media._ID, Media.DATA };  
 8        Cursor cursor = cr.query(Media.EXTERNAL_CONTENT_URI, projection,  
 9                              Media._ID + "=" + image_id, null, null);  
10        if (cursor != null) {  
11            cursor.moveToFirst();  
12            String path = cursor.getString(cursor.getColumnIndex(Media.DATA));  
13            Intent intent = new Intent(ThumbnailActivity.this, ImageViewer.class);  
14            intent.putExtra("path", path);  
15            startActivity(intent);  
16        } else {  
17            Toast.makeText(ThumbnailActivity.this, "Image doesn't exist!",  
18                                  Toast.LENGTH_SHORT).show();  
19           }
20       }  
21 };

有关具体的缩略图可以通过getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options) 或getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options) 方法获取,这两种方法返回Bitmap类型,而缩略图的分辨率可以从HEIGHT和WIDTH两个字段提取,在Android上缩略图分为两种,通过读取 KIND字段来获得,分别为MICRO_KIND和MINI_KIND 分别为微型和迷你两种缩略模式,前者的分辨率更低。这样我们平时获取文件系统的某个图片预览时,可以直接调用系统缩略图,而不用自己重新计算。

缩略图保存在SD卡的DCIM目录,里面的.thumbnails是图片的,而.video_thumbnails是视频的,这两个文件夹为隐藏属性。

从Android2.2开始系统新增了一个缩略图ThumbnailUtils类,位于framework的 android.media.ThumbnailUtils位置,可以帮助我们从mediaprovider中获取系统中的视频或图片文件的缩略图,该类提供了三种静态方法可以直接调用获取。

1. static Bitmap createVideoThumbnail(String filePath, int kind)
//获取视频文件的缩略图,第一个参数为视频文件的位置,比如/sdcard/android123.3gp,而第二个参数可以为MINI_KIND或 MICRO_KIND最终和分辨率有关

2. static Bitmap extractThumbnail(Bitmap source, int width, int height, int options)
//直接对Bitmap进行缩略操作,最后一个参数定义为OPTIONS_RECYCLE_INPUT,来回收资源

3. static Bitmap extractThumbnail(Bitmap source, int width, int height)
// 这个和上面的方法一样,无options选项