android中利用Camera编写拍照应用

在安卓中使用拍照功能有两种方式,一是调用已有的拍照应用;二是使用android的Camera对象直接操作相机,自己写代码来实现拍照功能。

如果是采用Camera的方式,相当于自己写了个拍照程序。直接使用Camera的好处是拍照界面可以完全自定义,UI风格可以和自己应用保持一致,但也要麻烦一些。

下面来介绍开发一个拍照程序的步骤,实现方法参考了谷歌android开发的官方文档。

整个步骤大概分为三步:

1.启动相机,其实就是打开摄像头。

2.生成摄像的预览图像。

3.拍照

为了完成上述的三个步骤,我们至少需要建立以下三个类,并新建这三个类的对象,他们之间通过一定的关联,就能完成最基本的拍照功能。

Camera****对象:管理硬件camera的打开和关闭,触发拍照命令。

一个继承自的SurfaceView****相机图像预览类:因为官方文档把这个自定义的SurfaceView类命名为CameraPreview,在此我也这样命名。该类负责在打开相机的时候在activity中显示采集到的图像预览效果。该过程和UI主线程是异步处理的,因此使用SurfaceView。

拍照数据捕获类:当用户点击某个按钮开始拍照的时候,需要有相应的方法能在拍照完成之后将图像数据捕获并存储,该类通过实现Camera.PictureCallback接口来完成捕获过程。

先展示一下拍照的界面:

界面很简单,只有两个部分,上面是图像预览区,下面是拍照按钮,xml布局代码如下:

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<FrameLayout

android:id="@+id/camera_preview"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:layout_weight="1"

/>

<Button

android:id="@+id/button_capture"

android:text="Capture"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

/>

</LinearLayout>

预览区域我们采用的是FrameLayout,但是预览效果其实和FrameLayout没什么关系,能在FrameLayout中产生预览效果是因为我们往FrameLayout中添加了上面提到的一个继承自的SurfaceView相机图像预览类。添加该类到FrameLayout的代码在activity的onCreate方法中,下面贴出整个activity的代码:

package com.example.cameratest;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.text.SimpleDateFormat;

import java.util.Date;

importandroid.net.Uri;

import android.os.Bundle;

import android.os.Environment;

importandroid.provider.MediaStore;

import android.app.Activity;

import android.content.Context;

importandroid.content.Intent;

import android.hardware.Camera;

import android.hardware.Camera.PictureCallback;

import android.util.Log;

importandroid.view.Menu;

import android.view.Surface;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

import android.view.View;

import android.widget.Button;

import android.widget.FrameLayout;

importandroid.widget.Toast;

public****class CameraActivity extends Activity {

publicstaticfinal****intMEDIA_TYPE_IMAGE = 1;

publicstaticfinal****intMEDIA_TYPE_VIDEO = 2;

protectedstaticfinal String TAG = null;

private Camera mCamera;

private CameraPreview mPreview;

@Override

public****void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.camera_activity);

// Create an instance ofCamera

mCamera = getCameraInstance();

// Create our Preview viewand set it as the content of our activity.

mPreview = new CameraPreview(this, mCamera);

FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);

preview.addView(mPreview);

Button captureButton = (Button) findViewById(R.id.button_capture);

captureButton.setOnClickListener(new View.OnClickListener() {

@Override

public****void onClick(View v) {

// get an imagefrom the camera

mCamera.takePicture(null, null, mPicture);

}

});

}

/** A safe way to get an instance of theCamera object. */

public****static Camera getCameraInstance() {

Camera c = null;

try {

c = Camera.open(); // attempt to get a Camera instance

} catch (Exception e) {

// Camera is notavailable (in use or does not exist)

}

return c; // returns nullif camera is unavailable

}

/** A basic Camera preview class */

public****class CameraPreview extends SurfaceView implements

SurfaceHolder.Callback {

private SurfaceHolder mHolder;

private Camera mCamera;

public CameraPreview(Context context, Camera camera) {

super(context);

mCamera = camera;

// Install aSurfaceHolder.Callback so we get notified when the

// underlyingsurface is created and destroyed.

mHolder = getHolder();

mHolder.addCallback(this);

// deprecatedsetting, but required on Android versions prior to 3.0

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

}

public****void surfaceCreated(SurfaceHolder holder) {

// The Surfacehas been created, now tell the camera where to draw

// the preview.

try {

mCamera.setPreviewDisplay(holder);

mCamera.setDisplayOrientation(90);

mCamera.startPreview();

} catch (IOException e) {

Log.d(TAG, "Errorsetting camera preview: " + e.getMessage());

}

}

public****void surfaceDestroyed(SurfaceHolder holder) {

// empty. Takecare of releasing the Camera preview in your

// activity.

}

public****void surfaceChanged(SurfaceHolder holder, int format, int w,

int h) {

// If yourpreview can change or rotate, take care of those events

// here.

// Make sure tostop the preview before resizing or reformatting it.

if (mHolder.getSurface() == null) {

// previewsurface does not exist

return;

}

// stop previewbefore making changes

try {

mCamera.stopPreview();

} catch (Exception e) {

// ignore: triedto stop a non-existent preview

}

// set previewsize and make any resize, rotate or

// reformattingchanges here

// start previewwith new settings

try {

mCamera.setPreviewDisplay(mHolder);

mCamera.setDisplayOrientation(90);

mCamera.startPreview();

} catch (Exception e) {

Log.d(TAG, "Errorstarting camera preview: " + e.getMessage());

}

}

}

private PictureCallback mPicture = new PictureCallback() {

@Override

public****void onPictureTaken(byte[] data, Camera camera) {

File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);

if (pictureFile == null) {

return;

}

try {

FileOutputStream fos = new FileOutputStream(pictureFile);

fos.write(data);

fos.close();

Log.i("cameratest", "pictureFiledata=" + data.length);

} catch (FileNotFoundException e) {

Log.i("cameratest", "File notfound: " + e.getMessage());

} catch (IOException e) {

Log.i("cameratest", "Erroraccessing file: " + e.getMessage());

}

}

};

private****static File getOutputMediaFile(int type) {

// To be safe, you shouldcheck that the SDCard is mounted

// usingEnvironment.getExternalStorageState() before doing this.

File mediaStorageDir = new File(

Environment

.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),

"MyCameraApp");

// This location works bestif you want the created images to be shared

// between applications andpersist after your app has been uninstalled.

// Create the storagedirectory if it does not exist

if (!mediaStorageDir.exists()) {

if (!mediaStorageDir.mkdirs()) {

Log.d("MyCameraApp", "failed tocreate directory");

return****null;

}

}

// Create a media file name

String timeStamp = newSimpleDateFormat("yyyyMMdd_HHmmss")

.format(new Date());

File mediaFile;

if (type == MEDIA_TYPE_IMAGE) {

mediaFile = new File(mediaStorageDir.getPath() + File.separator

+ "IMG_" + timeStamp + ".jpg");

} else****if (type == MEDIA_TYPE_VIDEO) {

mediaFile = new File(mediaStorageDir.getPath() + File.separator

+ "VID_" + timeStamp + ".mp4");

} else {

return****null;

}

return mediaFile;

}

@Override

protected****void onStop() {

Log.i("cameratest", "onStop");

super.onStop(); // Always callthe superclass method first

}

@Override

public****void onPause() {

super.onPause(); // Alwayscall the superclass method first

Log.i("cameratest", "onPause");

// Release the Camera becausewe don't need it when paused

// and other activities mightneed to use it.

if (mCamera != null) {

mCamera.release();

mCamera = null;

}

}

}

代码解读:

获得一个Camera对象不是通过new的方式,而是通过Camera.open()返回一个Camera对象,这一过程必须要有异常处理,为此我们将整个过程封装在getCameraInstance函数中,getCameraInstance函数的代码如下:

/** A safeway to get an instance of the Camera object. */

public****static Camera getCameraInstance(){

Camera c = null;

try {

c = Camera.open(); // attempt to get a Camera instance

}

catch (Exception e){

// Camera is not available (in use or does not exist)

}

return c; // returns null if camera is unavailable

}

mCamera = getCameraInstance();

然后在activity的onCreate方法中我们将mCamera作为参数传递给CameraPreview的构造函数,建立了相机图像预览类Ca****meraPreview对象mPreview,将mPreview添加进FrameLayout容器中,如下:

mPreview = new CameraPreview(this, mCamera);

FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);

preview.addView(mPreview);

Ca****meraPreview的实现在上面给出得的activity代码中可以找到,其实他就是一个实现了SurfaceHolder.Callback接口的SurfaceView。关于如何使用SurfaceView这里不做讲解,这里只说明为什么我们仅仅是新建了个继承自SurfaceView的Ca****meraPreview对象,然后将该对象放入布局文件中,就能实现预览效果。其中关键的代码是:

mHolder = getHolder();

mHolder.addCallback(this);

……

……

mCamera.setPreviewDisplay(mHolder);

将获得的mHolder和mCamera关联起来。非常简单吧,其实android系统已经为我们做了绝大多数的工作。

通过这几步,我们已经能够得到一个相机拍照的界面,并且带有预览功能,但是好像还缺少点什么,对,还去少触发拍照的动作。

点击如上图所示的captrue按钮即开始触发拍照行为,在这里其实是调用mCamera 的takePicture方法:

ButtoncaptureButton = (Button) findViewById(R.id.button_capture);

captureButton.setOnClickListener(newView.OnClickListener() {

@Override

publicvoid onClick(View v) {

//get an image from the camera

mCamera.takePicture(null,null, mPicture);

}

});

我们注意到mCamera.takePicture(null,null, mPicture);中mPicture这个变量还一直没有提到,他就是我们的拍照数据捕获类PictureCallback,它的任务很简单,就是在拍照完成后,处理图片,这里我们是将图片保存下来。onPictureTaken方法中可以通过data参数获得拍照的照片数据。我们将这些数据存储在公共目录的picture目录里面,就是存放相册图片的地方,获得这个存放地址的方法为getOutputMediaFile,具体的代码参见上面的activity的代码。

上面基本描述了拍照程序的实现流程,下面介绍一些细节。

1.默认情况下,拍照预览窗口的角度是横屏显示的。如果你的activity是竖屏状态,那么需要设置预览窗口的角度。本例子中我们将角度设置为90度,这样刚好能正确显示:

mCamera.setDisplayOrientation(90);

2.Camera不能同时被两个activity使用,否则会引起程序崩溃,所以需要在不需要camera的时候释放资源,一般我们是在onPause中释放。比如这个例子中:

@Override

public****void onPause() {

super.onPause(); // Alwayscall the superclass method first

// Release the Camera becausewe don't need it when paused

// and other activities mightneed to use it.

if (mCamera != null) {

mCamera.release();

mCamera = null;

}

}