在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
這兩個數據庫,即可查詢出來縮略圖(這是contentprovider,具體的解釋可見:【Android】ContentProvider)
之前我一直對Android在這方面的數據存儲非常感興趣,很想知道它是如何存儲這些數據以及我可以從數據里面獲得什么,今天將數據庫導了出來,查看了一下存儲,下面做出解釋:
大家可以先查看一下/data/data/com.android.provider.media目錄下面的databases:external-f042911.db 和 internal.db,如圖:
選中后,點擊右上角有個箭頭向左的圖案的圖標即可將數據庫導出到電腦文件夾中,然后下載一個SQLite數據庫查看軟件,我是在Mac下,下載的軟件名叫MesaSQLite ,之后打開數據庫,我們來查看一下:
首先是external-f042911.db數據庫,這個數據庫里面有很多表,如下:
這里很明顯有兩個thumbnails表,我查看的就是thumbnails表格,下面是表格內容:
總共11張縮略圖,我們可以看到每張縮略圖除了有一個_id之外,還有一個image_id(這個是關鍵?)。
然后要看的表格當然就是images,這個表格存儲的是什么呢?截圖如下,這個表表頭很長,我截取兩端:
這是第一張,可以看到什么??首先當然是同樣的_id字段,這是和上面對應好的么??其次,我們可以找到圖片的大小,類型,名稱等屬性,最重要的是其絕對物理路徑!
這是第二張,是表格后面一段的屬性,這張圖上面最最重要的一個列就是"bucket_display_name",稍微對應一下我們就能發現這里面記錄的是圖片所在的文件夾(做相冊的時候是很需要這個的!)
然后我再查看兩個表:
第一個是albums表,這個表里面是什么呢?
這對我是個意外之喜,QIDUO是我創建的一個文件夾,我在里面存儲了一些文件和一個amr錄音文件,所以我就很好奇了,Android如何判定一個文件夾為album呢?
再看一個表:audio_meta,更是一個驚喜:
這個神奇的表格里面居然記錄了我錄制的amr音頻,大家看到木有啊?有絕對路徑,有尺寸,有時長!讓人非常驚訝!
所以,到現在我有如下幾個疑問:
1)是否所有的多媒體文件(我指的是視頻和圖片)都有縮略圖?
2)縮略圖和原始圖是如何對應的?
3)album是如何定義的?
首先回答問題2):其實問題2)寫一個程序即可驗證,事實不是我們猜想的那樣,兩個_id字段是對應的,而是:
表thumbnails和images通過thumbnails.image_id與images._id關聯的,通過images的_id,就可以找出來thumbnails表中的圖片和images表中圖片的映射關系了。原始圖片的位置就是images表中的_data字段的值。
關於第1)個問題,我們貌似要了解一下,縮略圖到底是如何實現的?這里有一個類:MediaScanner(詳細要研究可見:http://blog.csdn.net/zqiang_55/article/details/7060171 )這個類是負責掃描所有的圖片並將圖片存儲進入MediaStore(MediaScannerReceiver用來接收任務的,它收到廣播后,會啟動MediaService進行掃描工作。好復雜的樣子。。。)我插一張圖:
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選項