Android提供Content Provider來實現應用程式之間的資料共用,provider提供了標準的介面用於儲存和檢索多種類型的資料。映像 、音頻和視頻的標準content provider就是MediaStore。
1)擷取映像的URI
要獲得標準的映像儲存路徑,我們需要獲得MediaStore的引用,而這是通過content resolver來實現的(因為使用Content resolver可以擷取content provider,而MediaStore就是一個content provider)。
傳遞指定的URI給content resolver,可以得到對應的content provider,由於是新增一張映像,所以使用insert方法,相應的URI是android.provider.MediaStore.Images.Media類定義的常量EXTERNAL_CONTENT_URI。這個常量說明我們要將映像儲存到主外部儲存空間中,通常就是SD卡;如果要將映像儲存到裝置記憶體中,則使用INTERNAL_CONTENT_URI。當然對於媒體檔案的儲存而言,由於尺寸一般都比較大,因此會優先考慮使用EXTERNAL_CONTENT_URI。
Content resolver類的insert函數傳回值是URI類型:
Uri imageFileUri = getContentResolver().insert(<br />Media.EXTERNAL_CONTENT_URI, new ContentValues());<br />// Start the Camera App<br />Intent it = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);<br />it.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri);<br />startActivityForResult(it, CAMERA_RESULT);
上面代碼中的ContentValues對象是捕獲的映像在建立時要關聯的中繼資料,當然,上面的中繼資料是空的。我們可以使用put函數將中繼資料資訊寫入ContentValues中,ContentValues是以索引值對的形式儲存資料的,鍵名是定義在android.provider.MediaStore.Images.Media類中的常量:
// Save the name and description of an image in a ContentValues map<br />ContentValues contentValues = new ContentValues(3);<br />contentValues.put(Media.DISPLAY_NAME, "ASCE1885_TITLE");<br />contentValues.put(Media.DESCRIPTION, "ASCE1885_DESCRIPTION");<br />contentValues.put(Media.MIME_TYPE, "image/jpeg");</p><p>// Add a new recode without the bitmap, but with some values set.<br />// insert() returns the URI of the new record<br />Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, contentValues);
上面擷取的Uri可能類似於:
content://media/external/images/media/16
這裡說明一點,以content開頭的Uri一般都是被content provider使用的,例如上面的Uri是被MediaStore使用的一樣。
反過來根據Uri,我們可以用來檢索這個Uri對應路徑中的映像資料,代碼如下:
Bitmap bmp = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageFileUri),null,bmpFactory);</p><p>
在我們捕獲映像並存放在MediaStore中後,如果還想再增加中繼資料資訊,那麼可以使用ContentResolver的update函數來實現:
// Update the MediaStore record with Title and Description<br />ContentValues contentValues = new ContentValues(3);<br />contentValues.put(Media.DISPLAY_NAME, "WEN1885_TITLE");<br />contentValues.put(Media.DESCRIPTION, "WEN1885_DESCRIPTION");<br />getContentResolver().update(imageFileUri, contentValues, null, null);
完整的代碼例子如下,先看layout/main.xml檔案:
<?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> ><br /><ImageView<br />android:id="@+id/ReturnedImageView"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"/><br /><TextView<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="Title:"<br />android:id="@+id/TitleTextView" /><br /><EditText<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:id="@+id/TitleEditText"/><br /><TextView<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="Description"<br />android:id="@+id/DescriptionTextView"/><br /><EditText<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:id="@+id/DescriptionEditText"/><br /><Button<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:id="@+id/TakePictureButton"<br />android:text="Take Picture"/><br /><Button<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:id="@+id/SaveDataButton"<br />android:text="Save Data"/><br /></LinearLayout>
完整的Java代碼如下:
package hust.iprai.asce1885.promedia;</p><p>import java.io.FileNotFoundException;</p><p>import android.app.Activity;<br />import android.content.ContentValues;<br />import android.content.Intent;<br />import android.graphics.Bitmap;<br />import android.graphics.BitmapFactory;<br />import android.net.Uri;<br />import android.os.Bundle;<br />import android.provider.MediaStore.Images.Media;<br />import android.util.Log;<br />import android.view.View;<br />import android.view.View.OnClickListener;<br />import android.widget.Button;<br />import android.widget.EditText;<br />import android.widget.ImageView;<br />import android.widget.TextView;<br />import android.widget.Toast;</p><p>public class MediaStoreCameraActivity extends Activity {<br />final static int CAMERA_RESULT = 0;</p><p>Uri imageFileUri = null;</p><p>// User interface elements, specified in res/layout/main.xml<br />ImageView returnedImageView;<br />Button takePictureButton;<br />Button saveDataButton;<br />TextView titleTextView;<br />TextView descriptionTextView;<br />EditText titleEditText;<br />EditText descriptionEditText;</p><p>@Override<br />protected void onCreate(Bundle savedInstanceState) {<br />super.onCreate(savedInstanceState);</p><p>// Set the content view to be what is defined in the res/layout/main.xml file<br />setContentView(R.layout.main);</p><p>// Get references to UI elements<br />returnedImageView = (ImageView) findViewById(R.id.ReturnedImageView);<br />takePictureButton = (Button) findViewById(R.id.TakePictureButton);<br />saveDataButton = (Button) findViewById(R.id.SaveDataButton);<br />titleTextView = (TextView) findViewById(R.id.TitleTextView);<br />descriptionTextView = (TextView) findViewById(R.id.DescriptionTextView);<br />titleEditText = (EditText) findViewById(R.id.TitleEditText);<br />descriptionEditText = (EditText) findViewById(R.id.DescriptionEditText);</p><p>// Set all except takePictureButton to not be visible initially<br />// View.GONE is invisible and doesn't take up space in the layout<br />returnedImageView.setVisibility(View.GONE);<br />saveDataButton.setVisibility(View.GONE);<br />titleTextView.setVisibility(View.GONE);<br />descriptionTextView.setVisibility(View.GONE);<br />titleEditText.setVisibility(View.GONE);<br />descriptionEditText.setVisibility(View.GONE);</p><p>// When the Take Picture Button is clicked<br />takePictureButton.setOnClickListener(new OnClickListener() {</p><p>public void onClick(View v) {<br />// Add a new record without the bitmap<br />// return the URI of the new record<br />imageFileUri = getContentResolver().insert(<br />Media.EXTERNAL_CONTENT_URI, new ContentValues());<br />// Start the Camera App<br />Intent it = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);<br />it.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri);<br />startActivityForResult(it, CAMERA_RESULT);<br />}</p><p>});</p><p>saveDataButton.setOnClickListener(new OnClickListener() {</p><p>public void onClick(View v) {<br />// Update the MediaStore record with Title and Description<br />ContentValues contentValues = new ContentValues(3);<br />contentValues.put(Media.DISPLAY_NAME, titleEditText.getText().toString());<br />contentValues.put(Media.DESCRIPTION, descriptionEditText.getText().toString());<br />getContentResolver().update(imageFileUri, contentValues, null, null);</p><p>// Tell the user<br />Toast bread = Toast.makeText(MediaStoreCameraActivity.this, "Record Updated", Toast.LENGTH_LONG);<br />bread.show();</p><p>// Go back to the initial state, set Take Picture Button Visible<br />// hide other UI elements<br />takePictureButton.setVisibility(View.VISIBLE);<br />returnedImageView.setVisibility(View.GONE);<br />titleTextView.setVisibility(View.GONE);<br />descriptionTextView.setVisibility(View.GONE);<br />titleEditText.setVisibility(View.GONE);<br />descriptionEditText.setVisibility(View.GONE);<br />}</p><p>});<br />}</p><p>@Override<br />protected void onActivityResult(int requestCode, int resultCode, Intent data) {<br />super.onActivityResult(requestCode, resultCode, data);</p><p>if (RESULT_OK == resultCode) {<br />// The Camera App has returned<br />// Hide the Take Picture Button<br />takePictureButton.setVisibility(View.GONE);</p><p>// Show the other UI elements<br />saveDataButton.setVisibility(View.VISIBLE);<br />returnedImageView.setVisibility(View.VISIBLE);<br />titleTextView.setVisibility(View.VISIBLE);<br />descriptionTextView.setVisibility(View.VISIBLE);<br />titleEditText.setVisibility(View.VISIBLE);<br />descriptionEditText.setVisibility(View.VISIBLE);</p><p>// Scale the image<br />int dw = 200; // Make it at most 200 pixels wide<br />int dh = 200; // Make it at most 200 pixels tall</p><p>BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();<br />bmpFactoryOptions.inJustDecodeBounds = true;<br />Bitmap bmp = null;<br />try {<br />bmp = BitmapFactory.decodeStream(<br />getContentResolver().openInputStream(imageFileUri), null, bmpFactoryOptions);<br />} catch (FileNotFoundException e) {<br />e.printStackTrace();<br />}<br />int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight/(float)dh);<br />int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth/(float)dw);</p><p>Log.v("HEIGHTRATIO", "" + heightRatio);<br />Log.v("WIDTHRATIO", "" + widthRatio);</p><p>// If both of the ratios are greater than 1<br />// one of the sides of the image is greater than the screen<br />if ((heightRatio > 1) && (widthRatio > 1)) {<br />if (heightRatio > widthRatio) {<br />// Height ratio is larger, scale according to it<br />bmpFactoryOptions.inSampleSize = heightRatio;<br />} else {<br />// Width ratio is larger, scale according to it<br />bmpFactoryOptions.inSampleSize = widthRatio;<br />}<br />}</p><p>// Decode it for real<br />bmpFactoryOptions.inJustDecodeBounds = false;<br />try {<br />bmp = BitmapFactory.decodeStream(<br />getContentResolver().openInputStream(imageFileUri), null, bmpFactoryOptions);<br />} catch (FileNotFoundException e) {<br />e.printStackTrace();<br />Log.v("ERROR", e.toString());<br />}</p><p>// Display it<br />returnedImageView.setImageBitmap(bmp);<br />}<br />}<br />}
2)使用MediaStore來檢索映像資料
MediaStore,跟所有的content provider一樣使用類似於資料庫操作的方式來檢索資料。從指定的Uri中選擇資料記錄,之後通過Cursor對象來對結果進行迭代處理。
首先需要建立一個字串數組來表示希望返回的列類型,MediaStore中映像資料的標準列類型在MediaStore.Images.Media類中:
String[] columns =<br /> {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME};
執行實際的查詢操作使用Activity的managedQuery函數,第一個參數是URI,第二個參數是列名組成的字串數組,第三個參數是WHERE語句,後面跟的參數是WHERE包含的參數,最後一個參數是ORDER BY語句:
long oneHourAgo = System.currentTimeMillis()/1000 - (60*60);<br />String[] whereValues = {"" + oneHourAgo};<br />// 指定返回結果的列<br />String[] columns = {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME, Media.DATE_ADDED};<br />// 獲得遊標<br />Cursor cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, Media.DATE_ADDED + " > ?",whereValues, Media.DATE_ADDED + " ASC");<br />// 返回指定列的索引<br />int displayColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);<br />// 移到遊標的開始處<br />if (cursor.moveToFirst()) {<br />String displayName = cursor.getString(displayColumnIndex);<br />}
完整的例子如下所示,先是layout/main.xml檔案:
<?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> ><br /><ImageButton<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:id="@+id/ImageButton"/><br /><TextView<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:id="@+id/TitleTextView"<br />android:text="Image Title"/><br /></LinearLayout>
Java代碼如下:
package hust.iprai.asce1885.promedia;</p><p>import android.app.Activity;<br />import android.database.Cursor;<br />import android.graphics.Bitmap;<br />import android.graphics.BitmapFactory;<br />import android.os.Bundle;<br />import android.provider.MediaStore;<br />import android.provider.MediaStore.Images.Media;<br />import android.util.Log;<br />import android.view.View;<br />import android.view.View.OnClickListener;<br />import android.widget.ImageButton;<br />import android.widget.TextView;</p><p>public class MediaStoreGallery extends Activity {</p><p>public final static int DISPLAYWIDTH = 200;<br />public final static int DISPLAYHEIGHT = 200;</p><p>TextView titleTextView;<br />ImageButton imageButton;</p><p>Cursor cursor;<br />Bitmap bmp;<br />String imageFilePath;<br />int fileColumn;<br />int titleColumn;<br />int displayColumn;</p><p>@Override<br />protected void onCreate(Bundle savedInstanceState) {<br />super.onCreate(savedInstanceState);<br />setContentView(R.layout.main);</p><p>titleTextView = (TextView) findViewById(R.id.TitleTextView);<br />imageButton = (ImageButton) findViewById(R.id.ImageButton);</p><p>String[] columns = {Media.DATA, Media._ID, Media.TITLE, Media.DISPLAY_NAME};<br />cursor = managedQuery(Media.EXTERNAL_CONTENT_URI, columns, null, null, null);</p><p>// 注意:Media.DATA是MediaStore.Images.Media.DATA的縮寫<br />fileColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);<br />titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE);<br />displayColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME);</p><p>if (cursor.moveToFirst()) {<br />titleTextView.setText(cursor.getString(titleColumn));</p><p>imageFilePath = cursor.getString(fileColumn);<br />bmp = getBitmap(imageFilePath);</p><p>// Display it<br />imageButton.setImageBitmap(bmp);<br />}</p><p>imageButton.setOnClickListener(new OnClickListener() {</p><p>public void onClick(View v) {<br />if (cursor.moveToNext()) {<br />titleTextView.setText(cursor.getString(displayColumn));</p><p>imageFilePath = cursor.getString(fileColumn);<br />bmp = getBitmap(imageFilePath);<br />imageButton.setImageBitmap(bmp);<br />}<br />}</p><p>});<br />}</p><p>private Bitmap getBitmap(String imageFilePath) {<br />// Load up the image's dimensions not the image itself<br />BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();<br />bmpFactoryOptions.inJustDecodeBounds = true;<br />Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);</p><p>int heightRatio = (int) Math.ceil(bmpFactoryOptions.outHeight/(float)DISPLAYHEIGHT);<br />int widthRatio = (int) Math.ceil(bmpFactoryOptions.outWidth/(float)DISPLAYWIDTH);</p><p>Log.v("HEIGHTRATIO", "" + heightRatio);<br />Log.v("WIDTHRATIO", "" + widthRatio);</p><p>// If both of the ratios are greater than 1, one of the sides of<br />// the image is greater than the screen<br />if ((heightRatio > 1) && (widthRatio > 1)) {<br />if (heightRatio > widthRatio) {<br />bmpFactoryOptions.inSampleSize = heightRatio;<br />} else {<br />bmpFactoryOptions.inSampleSize = widthRatio;<br />}<br />}</p><p>// Decode it for real<br />bmpFactoryOptions.inJustDecodeBounds = false;<br />bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);</p><p>return bmp;<br />}<br />}
2)內部中繼資料
EXIF,可交換影像檔格式(Exchangeable Image File Format),是將中繼資料儲存到影像檔裡的標準格式。它的資料存放區與JPEG格式是完全相同的,它就是在JPEG格式頭部插入了數位照片的拍攝資訊。
EXIF資料中包含很多與映像拍攝緊密相關的技術參數,例如曝光時間ExposureTime和快門速度ShutterSpeedValue等。還有一些參數是我們可以在後續進行填充或修改的,例如:
UserComment: 使用者評論
ImageDescription:映像的描述
Artist:映像的建立者或者拍攝者
Copyright:著作權
Software:建立映像使用的軟體
Android提供了方便的介面ExifInterface來讀寫EXIF資料:
ExifInterface ei = new ExifInterface(imageFilePath);<br />String imageDescription = ei.getAttribute("ImageDescription");<br />if (null != imageDescription) {<br />Log.v("EXIF", imageDescription);<br />}
儲存EXIF資料到影像檔中的程式碼片段如下:
ExifInterface ei = new ExifInterface(imageFilePath);<br />ei.setAttribute("ImageDescription", "ASCE1885");