Android多媒体开发 Pro Android Media 第一章 Android图像编程入门 3

图像存储和元数据

Android提供了一个标准的方式在应用程序之间分享数据。负责此功能的那些类被称为内容提供者(content provider。内容提供者提供了一个存储和检索各类数据的标准接口。

图像的标准内容提供者(同时也是音频和视频的)是MediaStoreMediaStore允许将文件配置到设备上的标准位置,并提供了便利的存储和检索文件元数据的方法。元数据是关于数据的数据。它可以包含它所在文件本身的信息,比如文件大小和名称。但MediaStore还可以设置各种各样的附加信息,例如标题,描述,纬度和经度。

我们来改变我们的 SizedCameraIntent activity,使用MediaStore来存储图像和相关元数据,代替之前将图像随意存储到SD卡上。

为图像获取URI

要获得存储图像的标准位置,我们首先需要得到一个MediaStore的引用。为此,我们使用内容解析器(content resolver)。内容解析器是获取,诸如MediaStore这样内容提供者的途径。

通过传入一个指定的URI,内容解析器知道提供一个接口给MediaStore,作为内容提供者。

因为我们要插入一个新图像,我们要用的方法是insertURI是定义在android.provider.MediaStore.Images.Media类的常量,名为EXTERNAL_CONTENT_URI.这表示我们想将图像保存到设备的主外部存储空间上,一般而言是SD卡。如果我们想保存到设备的内部存储空间,我们可以用INTERNAL_CONTENT_URI.不过, 一般来说,媒体内容,如图像,音频,视频都相当大,你更可能用EXTERNAL_CONTENT_URI.

之前所示的insert调用返回一个URI,我们可以用来写入图像文件的二进制数据。在我们的例子中,如同我们在CameraActivity所做的那样,我们只是想要把它作为激活相机应用的Intent的一个extra。

Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, 
    new ContentValues());  
Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);  
i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri); 
startActivityForResult(i, CAMERA_RESULT); 

你会发现,我们还传递了一个新建ContentValues对象。ContentValues对象,是记录创建时我们想要与之关联的元数据。在前述的例子中,我们传递了一个空的ContentValues对象。

预先填入关联的元数据

如果我们想预先填写元数据,我们可使用put方法来添加一些数据到其中。ContentValues以名称-值对接收数据。名称是标准的,以常量定义在android.provider.MediaStore.Images.Media类中。(某些常量实际上是存在于android.provider.MediaStore.MediaColumns接口,Media类所实现的。)

//保存图像的名称和描述到ContentValues的映射表。 
ContentValues contentValues = new ContentValues(3);  
contentValues.put(Media.DISPLAY_NAME, "This is a test title");  
contentValues.put(Media.DESCRIPTION, "This is a test description");  
contentValues.put(Media.MIME_TYPE, "image/jpeg");
 
//添加一个新纪录,不带位图,但是设置了一些值。
//insert()返回新纪录的URI。 
Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, 
    contentValues); 

同样,这个调用返回的是一个URI,通过Intent传递给相机应用,指定了图像存储位置。

如果你通过Log来输出这个URI,它可能看起来是这样:

content://media/external/images/media/16

你可能首先发现它看起来像一个普通的URL,如同你在web浏览器所用的;只不过开头用Content替换了httphttp是网页传输协议。在Android中,如果一个URIcontent开头,那么它必是用于content provider(如MediaStore)。

检索已存图像

之前取得的用于存储图像的URI,也能作为访问图像的路径。但我们不再传递文件的全路径给BitmapFactory,而是通过内容解析器(content provider)为该图像打开一个InputStream,传递给BitmapFactory。

Bitmap bmp = BitmapFactory.decodeStream(  
    getContentResolver().openInputStream(imageFileUri), null, bmpFactoryOptions);

创建过后添加元数据

如果我们想在图像采集到MediaStore之后,给它关联更多的元数据,可以使用内容解析器(content provider)的update方法。这跟我们之前使用的insert非常类似,除了我们是通过图像文件的URI来访问它。

//更新记录的标题和描述 
ContentValues contentValues = new ContentValues(3);  
contentValues.put(Media.DISPLAY_NAME, "This is a test title");  
contentValues.put(Media.DESCRIPTION, "This is a test description");  
getContentResolver().update(imageFileUri,contentValues,null,null); 

更新CameraActivity,使用MediaStore来存储图像和关联元数据

下面是对之前的例子的更新,新版本将图像存储到MediaStore,并给我们展示了如何添加标题和描述。另外,这个版本还有几个用户界面元素(UI element),它们的显示和隐藏基于用户对程序的操作。 

package com.apress.proandroidmedia.ch1.mediastorecameraintent;
  
import java.io.FileNotFoundException;  
import android.app.Activity;  
import android.content.Intent;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.net.Uri; 
import android.os.Bundle;  
import android.util.Log;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.EditText;  
import android.widget.ImageView;  
import android.widget.TextView;  
import android.widget.Toast;  
import android.provider.MediaStore.Images.Media;  
import android.content.ContentValues;  

public class MediaStoreCameraIntent extends Activity {
  
    final static int CAMERA_RESULT = 0;  

    Uri imageFileUri; 

    // 用户界面元素,定义在/res/layout/main.xml
    ImageView returnedImageView;  
    Button takePictureButton;  
    Button saveDataButton;  
    TextView titleTextView;  
    TextView descriptionTextView;  
    EditText titleEditText;  
    EditText descriptionEditText; 

我们包含了一些用户界面元素它们已经定义在layout/main.xml中,其对象在上述的代码中做了声明。

    @Override  
    public void onCreate(Bundle savedInstanceState)  {  
        super.onCreate(savedInstanceState); 

         //将Content View设置为定义在res/layout/main.xml内的视图
         setContentView(R.layout.main); 

         //取得用户界面元素的引用
         returnedImageView = (ImageView) findViewById(R.id.ReturnedImageView);  
         takePictureButton = (Button) findViewById(R.id.TakePictureButton);  
         saveDataButton = (Button) findViewById(R.id.SaveDataButton);  
         titleTextView = (TextView) findViewById(R.id.TitleTextView);  
         descriptionTextView = (TextView) findViewById(R.id.DescriptionTextView);  
         titleEditText = (EditText) findViewById(R.id.TitleEditText);  
         descriptionEditText = (EditText) findViewById(R.id.DescriptionEditText); 



在Activity的标准方法OnCreate里,调用setContentView之后,我们实例化需要代码控制的用户界面元素。 通过findViewById取得用户界面的引用,然后转义为对应的类型。

        // 除了takePictureButton之外,所有界面元素初始化为不可见
        //View.GONE 表示不可见,而且不占空间。 
        returnedImageView.setVisibility(View.GONE);  
        saveDataButton.setVisibility(View.GONE);  
        titleTextView.setVisibility(View.GONE);  
        descriptionTextView.setVisibility(View.GONE);  
        titleEditText.setVisibility(View.GONE);  
        descriptionEditText.setVisibility(View.GONE); 

接着,我们将设置所有的用户界面元素不可见,同时不在布局中占用空间。为此我们传递常量View.GONE给setVisibility方法。另一个常量View.INVISIBLE,可以隐藏上述元素,但是仍要在布局中占用空间。

        //当点击拍照按钮(Take Picture Button)时
        takePictureButton.setOnClickListener(new OnClickListener() {  
            public void onClick(View v)  {
                //添加一项不带位图的新记录
                //返回新记录的URI
                imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI,
                    new ContentValues()); 

                // 启动相机应用                
                Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
                i.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageFileUri);
                startActivityForResult(i, CAMERA_RESULT);
            }  
        }); 

在takePictureButton的点击监听类OnClickListener里,我们为内置相机创建了一个标准的Intent,然后调用startAcitivityForResult。放在这里比直接放在onCreate,用户体验要稍微好一些。

        saveDataButton.setOnClickListener(new OnClickListener() {  
            public void onClick(View v)  { 
                //更新MediaStore记录的标题和描述
                ContentValues contentValues = new ContentValues(3);  
                contentValues.put(Media.DISPLAY_NAME,titleEditText.getText().toString());  
                contentValues.put(Media.DESCRIPTION, 
                    descriptionEditText.getText().toString());  
                getContentResolver().update(imageFileUri,contentValues,null,null); 

                // 通知用户
                Toast bread = Toast.makeText(MediaStoreCameraIntent.this, 
                   "Record Updated", Toast.LENGTH_SHORT);                  
                bread.show(); 

                //回到初始状态,设置拍照按钮可见
                //隐藏其他的用户界面元素
                takePictureButton.setVisibility(View.VISIBLE);  
                returnedImageView.setVisibility(View.GONE);  
                saveDataButton.setVisibility(View.GONE);  
                titleTextView.setVisibility(View.GONE);  
                descriptionTextView.setVisibility(View.GONE);                  
                titleEditText.setVisibility(View.GONE);  
                descriptionEditText.setVisibility(View.GONE);  
           }
        });  
    }

一旦相机应用返回图像,saveDataButton变得可见。其监听类OnClickListener为图像关联元数据。它接收用户输入到各个EditText元素的值,创建一个ContentValues对象,用来更新MediaStore中该图像的记录。

    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);

        if (resultCode == RESULT_OK)  { 
            // 相机应用返回了
            // 隐藏拍照按钮 
            takePictureButton.setVisibility(View.GONE); 
            
            // 显示其他用户界面元素            
            saveDataButton.setVisibility(View.VISIBLE);  
            returnedImageView.setVisibility(View.VISIBLE);  
            titleTextView.setVisibility(View.VISIBLE);
            descriptionTextView.setVisibility(View.VISIBLE);
            titleEditText.setVisibility(View.VISIBLE); 
            descriptionEditText.setVisibility(View.VISIBLE);

            // 缩放图像 
            int dw = 200; //使其最多200像素宽 
            int dh = 200; //使其最多200像素高 

            try  {
                // 加载图像的尺寸,而非图像本身
                BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
                bmpFactoryOptions.inJustDecodeBounds = true;
                Bitmap bmp = BitmapFactory.decodeStream(
                    getContentResolver().openInputStream(imageFileUri),
                    null, bmpFactoryOptions);
                int heightRatio = (int)Math.ceil(bmpFactoryOptions.outHeight/(float)dh);
                int widthRatio = (int)Math.ceil(bmpFactoryOptions.outWidth/(float)dw);
                Log.v("HEIGHTRATIO",""+heightRatio);
                Log.v("WIDTHRATIO",""+widthRatio);
            
                // 如果两个比值都大于1 那么图像的某一边大于屏幕 
                //(译注:此处注释不恰当,把dh和dw当作了屏幕尺寸)
                if (heightRatio > 1 && widthRatio > 1)  {
                    if (heightRatio > widthRatio)  { 
                        // 高度比较大,根据它进行缩放
                        bmpFactoryOptions.inSampleSize = heightRatio;  
                    }  
                    else  {
                        // 宽度比较大,根据它进行缩放
                        bmpFactoryOptions.inSampleSize = widthRatio; 
                    } 
                } 

                // 真正解码图像
                bmpFactoryOptions.inJustDecodeBounds = false;  
                bmp = BitmapFactory.decodeStream(
                   getContentResolver().openInputStream(imageFileUri),
                   null, bmpFactoryOptions); 

                //显示图像 
                returnedImageView.setImageBitmap(bmp); 
            }  
            catch (FileNotFoundException e)  {
                Log.v("ERROR",e.toString());
            }  
        }  
    }  
} 

这是布局的XML文件:main.xml.用于上面的例子。
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      
    android:orientation="vertical"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  >  

    <ImageView 
        android:id="@+id/ReturnedImageView"         
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content">
    </ImageView>  

    <TextView 
        android:layout_width="wrap_content"         
        android:layout_height="wrap_content"
        android:text="Title:" 
        android:id="@+id/TitleTextView">
    </TextView>  

    <EditText 
        android:layout_height="wrap_content"         
        android:id="@+id/TitleEditText"  
        android:layout_width="fill_parent">
    </EditText>  

    <TextView 
        android:layout_width="wrap_content"         
        android:layout_height="wrap_content"  
        android:text="Description" 
        android:id="@+id/DescriptionTextView">
    </TextView>  

    <EditText         android:layout_height="wrap_content" 
        android:layout_width="fill_parent"  
        android:id="@+id/DescriptionEditText">
    </EditText>  
    
    <Button         android:layout_width="wrap_content" 
        android:layout_height="wrap_content"  
        android:id="@+id/TakePictureButton" 
        android:text="Take Picture">
    </Button>  
    <Button 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:id="@+id/SaveDataButton" 
        android:text="Save Data">
    </Button>  
</LinearLayout> 

在前面的例子里,当相机应用返回时,onActivityResult被触发.新创建的图像被解码成位图并显示出来。在这个版本中,对相关的用户界面元素也进行了管理。

Android多媒体开发 Pro Android Media 第一章 Android图像编程入门 3

上一篇:Android4.2.2 A31 Camera数据、控制流2图


下一篇:android中seekbar的一点用法