图像存储和元数据

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被触发.新创建的图像被解码成位图并显示出来。在这个版本中,对相关的用户界面元素也进行了管理。

更多相关文章

  1. Android中Cursor关闭的问题
  2. (Android实战系统二)Android网络互动传输方案选择和实现
  3. Android系统触摸屏的校正——http://carvencao.blog.sohu.com/15
  4. 腾讯T3大牛带你了解 2019 Android开发趋势及必备技术点!
  5. 基于Android的校园跳蚤市场(二手)的设计与实现
  6. 我也分享一下我Android的收入数据
  7. 曝Android机冷冻后变"傻" 加密数据随意访问
  8. Android使用JNI实现Java与C之间传递数据
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. Android(3) 注册界面点击返回登录界面并
  2. Android之反射机制与JSON解析
  3. Android(安卓)ListView根据项数的大小自
  4. Android(安卓)installed app, never used
  5. android 耳机左右声道接反,软件如何修正
  6. android调用平台功能
  7. android如何限制只能输入指定的字符
  8. android framework service开发原理,以震
  9. Android:intent用法实例
  10. Android(安卓)Edittext 显示光标 获取焦