使用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文件中。

<?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"  >          <ImageButton android:layout_width="wrap_content"         android:layout_height="wrap_content"        android:id="@+id/ImageButton">    </ImageButton>       <TextView  android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:id="@+id/TitleTextView"        android:text="Image Title"/>  </LinearLayout> 

内部元数据

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月发布。这里提供在线下载:http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf.

总结

在本章中,我们研究了Android的图像采集和储存基础知识。我们看到了如何在Android中使用强大的内置相机应用程序以及如何通过一个Intent,有效地发挥其功能。我们看到了相机应用提供的精巧统一的接口,如何帮助其他Android应用增加图像采集功能。

我们还看到了处理大图像时,需要注意内存的使用。我们学到BitmapFactory类可以帮助我们加载图像的缩小版本以节约内存。对内存的关注提醒我们,手机不是桌面电脑,有看似无限的内存。

我们练习了Android内置图像内容提供者,MediaStore的使用。我们学会如何用它来将图像保存到设备上的标准位置,以及如何快速建立一个应用程序,使用它来查询已拍摄的图像。

最后,我们看了一下如何用EXIF标准来关联图像的特定元数据。EXIF具有便携性且用于各类设备我软件应用程序。

这给了我们一个非常不错的起点,去探索更多我们能在Android上做的媒体有关的东西。

我对此充满期待期待!

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. 箭头函数的基础使用
  3. python起点网月票榜字体反爬案例
  4. NPM 和webpack 的基础使用
  5. Python list sort方法的具体使用
  6. 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
  7. Android常用控件之ListView(一)
  8. Android中资源文件的使用
  9. android 连接远程数据库(转)

随机推荐

  1. 反射类查看类下面的所有方法及变量
  2. [置顶] Android之Fragment的前世今生(二)
  3. Android获取视频文件某一帧并设置图片
  4. android中usb设备驱动不能自动创建设备节
  5. android_8.1 hdmi设备热插拔事件
  6. Android培训班(72)Dex文件里类定义dvmDef
  7. 【Android】通过Java代码替换TabHost中的
  8. sdk platform tools is missing please u
  9. [置顶] android中屏幕触摸事件
  10. Android(安卓)各国语言缩写-各国语言简称