本章我们将来学习图像采集和存储的基本知识。我们首先探索android提供的内置功能,然后在后续章节中学习如何定制化软件。内置的图像采集和存储功能是学习Android多媒体功能非常好的入门指南,同时也为后续音频和视频的学习铺路。
我们从如何使用内置相机应用(Camera)开始,之后转到MediaStore使用技巧。 MediaStore是Android内置的多媒体和元数据(metadata)存储机制。在此过程中,我们将研究如何减少内存使用和利用EXIF -- 消费电子和图像处理软件界共享元数据的标准。
使用内置相机应用采集图像
随着移动电话快速地向移动电脑转化,它们在许多方面替代了各种消费电子产品。摄像头是最早加入的与通话无关硬件之一。目前,不带相机功能的手机很难为人们所接受。当然,安卓手机也会不例外;从一开始,Android SDK就支持访问手机中内置的摄像头来采集图像。
在Android中,完成很多事情最简单,最直接的方法就是利用设备的已有软件模块,通过Intent来激活。Intent是Android核心组件,在官方文档中是这样描述它的:一个执行动作的描述。在实践中,Intent用于触发其它应用程序做某件事,或者在一个单一的应用程序的各个Activity之间进行切换。
所有出货的带有相应硬件(摄像头)的Android设备都附带了相机应用。相机应用包含了一个Intent过滤器(Intent filter),它允许开发者提供此应用的图像采集功能给用户,而不需开发自己的定制采集例程。
应用开发者利用Intent过滤器来列举其应用提供的特定功能。通过在应用程序的AndroidManifest.xml中写入一个Intent过滤器,Android系统就知道该应用程序,具体来说,包含该Intent过滤器的Activity,能够应要求完成其指定任务。
相机应用在清单文件(manifest file)包含了下列Intent过滤器。显示在这里的Intent过滤器包含在名为Camera的Activity的标签之中。
<intent-filter> <action android:name="android.media.action.IMAGE_CAPTURE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
为了使用相机应用,我们只需建立一个能被上述Intent过滤器捕捉到的Intent。
Intent i = new Intent("android.media.action.IMAGE_CAPTURE");
在实践中,我们可能不希望直接通过动作字符串(action string)来创建Intent。在本例中,MediaStore类指定了一个常量,ACTION_IMAGE_CAPTURE.我们应该使用常量,而不是字符串本身的原因是:如果字符串改变了,定义的常量很可能也随之变化,从而以前的调用仍然能够正常工作。
Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); startActivity(i);
在一个简单的安卓activity中使用这个Intent,该Intent将导致相机应用以静态图片模式启动,如图1-1.
图 1-1. 内置相机应用,通过Intent调用运行在模拟器
从相机应用返回数据
当然,单单使用内置相机应用采集一幅图像,而不返回所得数据给调用Activity,并没有多大实际用处。这个可以通过在我们的Activity中,使startActivityForResult方法替换startActivity方法来实现。使用这个方法我们可以获得相机应用返回的数据,这个数据以位图(Bitmap)的形式存在,内容为用户所捕获的图像。
这里是一个简单的例子:
package com.apress.proandroidmedia.ch1.cameraintent; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.widget.ImageView; public class CameraIntent extends Activity { final static int CAMERA_RESULT = 0; ImageView imv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(i, CAMERA_RESULT); } protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (resultCode == RESULT_OK) { Bundle extras = intent.getExtras(); Bitmap bmp = (Bitmap) extras.get("data"); imv = (ImageView) findViewById(R.id.ReturnedImageView); imv.setImageBitmap(bmp); } } }项目的 layout/mail.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> </LinearLayout>为了完成上述例子,这里是AndroidManifest.xml的内容:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.apress.proandroidmedia.ch1.cameraintent"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".CameraIntent" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>在这个例子中,返回的图像包含在相机应用发给调用Activity的Intent的一个extra中。在调用应用的OnActivityResult中可以取得该Intent。该extra的名字为:data,它包含了一个位图对象,使用的时候,需要从通用对象转换到位图对象。
// Get Extras from the intent Bundle extras = intent.getExtras(); // Get the returned image from that extra Bitmap bmp = (Bitmap) extras.get("data");在我们的布局XML(layout/main.xml)文件中,包含有一个ImageView视图。ImageView为通用视图的扩展,支持图像显示。现在我们有一个id为ReturnImageView的ImageView视图,在Activity中,我们需要取得该视图的一个引用,然后通过setImageBitmap方法,将它显示的位图设置为相机返回的图像,从而用户能够查看捕获到的图像。
为取得ImageView对象的引用,我们使用Activity类的标准方法findViewById。此方法允许我们传入元素的Id,以编程方式引用布局XML文件中指定的元素。布局文件通过调用setContentView来设定。在上述例子中,ImageView对象在XML文件的定义如下:
<ImageView android:id="@+id/ReturnedImageView" android:layout_width="wrap_content" android:layout_height="wrap_content"> </ImageView>为引用ImageView,并告知其显示来自相机的位图,我们使用下面的代码:
imv = (ImageView) findViewById(R.id.ReturnedImageView); imv.setImageBitmap(bmp);当你运行这个例子,你可能发现,得到的图像很小。(在我的手机上,它宽121像素,高162像素。其他设备有其他的缺省尺寸。)这并不是一个错误,相反,它是特意设计成这样的。相机应用,当由Intent激活时,并不返回全尺寸图像给调用Activity。在一般情况下,如此做需要相当多的内存,而移动设备在这方面通常有限制。因而相机应用代之以一个很小的缩略图,放在返回的Intent中,如图1-2所示。
图1-2 取得的121x162像素图像,显示在我们的ImageView