1. 程式人生 > >【Android】縮圖Thumbnails

【Android】縮圖Thumbnails

在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選項

轉:http://www.cnblogs.com/lqminn/archive/2012/10/16/2726583.html