Android多媒體開發 Pro Android Media 第一章 Android圖像編程入門 4

使用MediaStore檢索圖像

Android的共享內容提供者功能很強大,利用他們我們可以非常容易的創建類型畫廊(gallery)的應用。由於內容提供者,本例中是MediaStore,可以在應用間共享,當我們創建使自己的應用顯示圖像時,不必真去創建一個相機應用並保存圖像。既然大多數應用使用缺省的MediaStore,我們可以利用這個來創建我們自己的畫廊應用。

從MediaStore選擇是非常簡單的。我們使用與創建新記錄相同的URI,來選擇它裡面的記錄。

Media.EXTERNAL_CONTENT_URI

MediaStore,實際上,所有的內容提供者,都以一種類似數據庫的方式在運作。我們從中查詢記錄,並獲得Cursor對象,我們可以用它來迭代結果。

為瞭查詢,我們首先需要創建我們想要返回的列的字符串數組。MediaStore中圖像的標準列定義在MediaStore.Images.Media類裡。

String[] columns = { Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME };

為執行實際查詢,我們可以用Activity的managedQuery方法。第一個參數是URI,其次是列名數組,再次是限制WHERE子句,WHERE子句的參數,最後,ORDER BY子句。

下面將選擇最後一個小時內創建的記錄,並按最早到最新進行排序。

首先,我們創建一個名為oneHourAgo的變量,其值為從1970年1月1日到一小時之前所經過的秒數。System.currentTimeMillis()返回從那一天到現在的毫秒數,除以1000我們得到秒數,減去60分*60秒,我們就得到一個小時之前的值。

long oneHourAgo = System.currentTimeMillis()/1000 – (60 * 60);

然後,我們把它的值放入一個字符串數組, 該數組將作為WHERE子句的參數。

String[] whereValues = {“”+oneHourAgo};

接下來我們選擇我們想要返回的列。

String[] columns = { Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME, Media.DATE_ADDED };

最後我們執行查詢。WHERE子句包含瞭一個?號, 執行時它將由下一個參數的值來代替。如果有多個?號,則傳入的數組也必須包含多個值。這裡使用的ORDER BY子句,指定返回的數據以創建時間的升序進行排序。

cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, Media.DATE_ADDED + ” > ?”, whereValues, Media.DATE_ADDED + ” ASC”);

當然,如果你想返回所有記錄,你可以傳遞null給最後三個參數。

Cursor cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, null, null, null);

返回的遊標可以告訴我們所選記錄的每個列的索引。

displayColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

我們需要索引來從遊標中檢索字段的值。首先我們調用moveToFirst方法,確保遊標有效而且包含瞭一些查詢結果。如果遊標沒有包含任何結果,這個方法會返回false。我們使用Cursor類的幾個方法中的某個來取得實際數據。方法的選擇取決於數據的類型,字符串使用getString,整數使用getInt,等等。

if (cursor.moveToFirst()) {
String displayName = cursor.getString(displayColumnIndex);
}

創建圖像瀏覽應用

接下來是一個完整的示例,查詢MediaStore取得圖像,然後以幻燈片的形式一張接著一張顯示給用戶。

package com.apress.proandroidmedia.ch1.mediastoregallery;  
import android.app.Activity;  
import android.database.Cursor;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.os.Bundle;  
import android.provider.MediaStore;  
import android.provider.MediaStore.Images.Media;  
import android.util.Log;  import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.ImageButton;  
import android.widget.TextView;  

public class MediaStoreGallery extends Activity {  
    public final static int DISPLAYWIDTH = 200;  
    public final static int DISPLAYHEIGHT = 200; 

不再按照屏幕的大小來加載和顯示圖像,我們將使用上述的常數來作為圖像顯示大小。

    Cursor cursor;  
    Bitmap bmp;  
    String imageFilePath;  
    int fileColumn;  
    int titleColumn;  
    int displayColumn;  

    @Override  
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        titleTextView = (TextView) this.findViewById(R.id.TitleTextView);  
        imageButton = (ImageButton) this.findViewById(R.id.ImageButton); 

這裡我們指定我們想要返回哪些列。參數必須是以字符串數組的方式。在下一行,我們將數組傳遞給managedQuery方法。

        String[] columns = { Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME };  
        cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, null, null, null); 

對於我們想從Cursor對象中獲取數據的列,我們必須知道他們每個的索引。在本例中,我們從Media.DATA切換到MediaStore.Images.Media.DATA.這僅僅是為瞭說明他們是相同的。使用Media.DATA僅僅是一個簡捷方式,因為我們有一條包含它的import語句:android.provider.MediaStore.Images.Media。

        fileColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);  
        titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE);  
        displayColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);

當我們運行查詢並取得返回的Cursor對象之後,我們調用Cursor對象的moveToFirst檢測它是否包含有查詢結果。

        if (cursor.moveToFirst()) {  
            //titleTextView.setText(cursor.getString(titleColumn));  
            titleTextView.setText(cursor.getString(displayColumn));  
            imageFilePath = cursor.getString(fileColumn);  
            bmp = getBitmap(imageFilePath); 

           //顯示圖像 
           imageButton.setImageBitmap(bmp);  
        }

我們為ImageButton設定一個新的事件監聽類OnClickListener,它調用Cursor對象的moveToNext方法。這將迭代整個返回記錄,取出並顯示每一張返回的圖像。

        imageButton.setOnClickListener(  new OnClickListener() {  
            public void onClick(View v) {  
                if (cursor.moveToNext())  {  
                   //titleTextView.setText(cursor.getString(titleColumn));
                   titleTextView.setText(cursor.getString(displayColumn));
                   imageFilePath = cursor.getString(fileColumn);
                   bmp = getBitmap(imageFilePath);
                   imageButton.setImageBitmap(bmp);  
                }
           }
        });  
    }

在這有個名為getBitmap的函數,它封裝瞭圖像的縮放和加載。我們之前討論過,對圖像的這些處理是為瞭避免圖像顯示引起內存方面的問題。

    private Bitmap getBitmap(String imageFilePath)  { 
        // 加載圖像尺寸,非圖像自身
        BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
        bmpFactoryOptions.inJustDecodeBounds = true;
        Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);

        int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight / (float)DISPLAYHEIGHT);
        int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth / (float)DISPLAYWIDTH);
        Log.v("HEIGHTRATIO", "" + heightRatio);
        Log.v("WIDTHRATIO", "" + widthRatio);
      
        // 如果兩個比值都大於1,圖像的某一邊大於屏幕
        if (heightRatio > 1 && widthRatio > 1) {  
            if (heightRatio > widthRatio) { 
                // 高度比較大,根據它進行縮放
                bmpFactoryOptions.inSampleSize = heightRatio;  
            }
            else {
                // 寬度比較大,根據它進行縮放
                bmpFactoryOptions.inSampleSize = widthRatio;
            }
        }  

        // 真正解碼
        bmpFactoryOptions.inJustDecodeBounds = false;
        bmp = BitmapFactory.decodeFile(imageFilePath,bmpFactoryOptions);
      
        return bmp;
      }
}

隨同activity的佈局XML如下,它應該放在res/layout/main.xml文件中。

  
  
    
    
    
   
      
 

內部元數據

EXIF(exchangeable image file format), 是在圖像文件中保存元數據的標準方式。很多相機和桌面應用都支持EXIF數據。因為EXIF數據是作為文件的一部分,它不會在文件從一個地方傳遞到其他地方時丟失。比如,從Android設備的SD卡拷貝一個文件到傢中電腦,EXIF數據保持不變。如果你用某個應用程序,比如iPhoto打開這個文件,這些數據就會呈現出來。

一般來說,EXIF數據是非常技術導向的;標準中的大部分標簽都是關於圖像自身采集的,比如曝光時間,快門速度。然而,還是有些標簽對我們而言是有意義的,我們可以填寫或者改動。下面是其中一些:

UserComment: 用戶寫的評論

ImageDescription: 標題

Artist: 圖片的創建者或者拍攝者

Copyright: 圖片的版權所有者

Software: 用於創建圖片的軟件

幸運的是,Android給我們提供瞭一個很好的方法用於讀寫EXIF數據。ExifInterface是其大類。

這是如何用ExifInterface從圖像文件中讀取特定的EXIF數據:

ExifInterface ei = new ExifInterface(imageFilePath);
String imageDescription = ei.getAttribute("ImageDescription");

if (imageDescription != null)  {  

    Log.v("EXIF", imageDescription);  
}  

這是如何用ExifInterface將EXIF數據保持到一個圖像文件中去:

ExifInterface ei = new ExifInterface(imageFilePath);  
ei.setAttribute("ImageDescription","Something New"); 

ExifInterface包含一組常量,定義瞭圖像的典型元數據,這些數據由相機應用在拍攝圖像時加入進來。

EXIF規格的最新版本是2.3, 2010年4月發佈。這裡提供在線下載:https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf.

總結

在本章中,我們研究瞭Android的圖像采集和儲存基礎知識。我們看到瞭如何在Android中使用強大的內置相機應用程序以及如何通過一個Intent,有效地發揮其功能。我們看到瞭相機應用提供的精巧統一的接口,如何幫助其他Android應用增加圖像采集功能。

我們還看到瞭處理大圖像時,需要註意內存的使用。我們學到BitmapFactory類可以幫助我們加載圖像的縮小版本以節約內存。對內存的關註提醒我們,手機不是桌面電腦,有看似無限的內存。

我們練習瞭Android內置圖像內容提供者,MediaStore的使用。我們學會如何用它來將圖像保存到設備上的標準位置,以及如何快速建立一個應用程序,使用它來查詢已拍攝的圖像。

最後,我們看瞭一下如何用EXIF標準來關聯圖像的特定元數據。EXIF具有便攜性且用於各類設備我軟件應用程序。

這給瞭我們一個非常不錯的起點,去探索更多我們能在Android上做的媒體有關的東西。

我對此充滿期待期待!

發佈留言