一、在实现摄像头拍照功能应用之前,考虑两个风险
【1】 你的应用不是每一款android 机器都可以使用
主要原因:
摄像头拍照功能执行的过程为 应用调用android系统API-->系统API,调用底层驱动-->底层驱动驱动硬件
一般的android手机有厂商自定写驱动,实现操作系统,一些定制小厂商的android系统API实现,不是那怎完整,或者就是不支持,导致的结果是,你的应用装上去就弹出应用程序异常。
【2】 你的应用在不同的型号,品牌机器上执行过程中,反应时间也不一样
主要原因:当前android手机的性能还是有限的,在不同配置先下,手机硬件执行的反应时间也不一样。举例:调用镜头,镜头初始化的时间,镜头响应拍照的指令的时间等
1 拍照第一步 初始化Camera
初始化过程
获取Camera实例
camera =Camera.open();
设置镜头的参数 (无闪光灯,且镜头角度转90度,注:默认摄像头是横拍得)
Camera.Parameters parameters=camera.getParameters(); parameters.setFlashMode("off"); parameters.set("rotation",90); camera.setParameters(parameters);
开启预览(在执行拍照指令前,必须调用)
camera.startPreview();
2 设置拍照指令,调用 反馈事件,实现获取照片,并存储
设置反馈事件:
PictureCallback pictureCallback=new PictureCallback(){ public void onPictureTaken(byte[] data,Camera camera) { FileOutputStream outSteam=null; try{ SimpleDateFormat format=new SimpleDateFormat("yyyyMMddHHmmss"); String times=format.format((new Date())); outSteam=new FileOutputStream("/sdcard/MyImages/"+times+".jpg"); outSteam.write(data); outSteam.close(); } catch(FileNotFoundException e) { Log.d("Camera", "row"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }; };
执行拍照指令
3 拍照完成后,释放镜头
camera.takePicture(null, null, pictureCallback);
4 配置权限:
在 AndroidManifest.xml 文件中设置摄像头权限
<uses-permission android:name="android.permission.CAMERA"/>
二、拍照详细过程
在安卓中使用拍照功能有两种方式,一是调用已有的拍照应用;二是使用android的Camera对象直接操作相机,自己写代码来实现拍照功能。
如果是采用Camera的方式,相当于自己写了个拍照程序。直接使用Camera的好处是拍照界面可以完全自定义,UI风格可以和自己应用保持一致,但也要麻烦一些。
下面来介绍开发一个拍照程序的步骤,实现方法参考了谷歌android开发的官方文档。
整个步骤大概分为三步:
1.启动相机,其实就是打开摄像头。
2.生成摄像的预览图像。
3.拍照
为了完成上述的三个步骤,我们至少需要建立以下三个类,并新建这三个类的对象,他们之间通过一定的关联,就能完成最基本的拍照功能。
Camera对象:管理硬件camera的打开和关闭,触发拍照命令。
一个继承自的SurfaceView相机图像预览类:因为官方文档把这个自定义的SurfaceView类命名为CameraPreview,在此我也这样命名。该类负责在打开相机的时候在activity中显示采集到的图像预览效果。该过程和UI主线程是异步处理的,因此使用SurfaceView。
拍照数据捕获类:当用户点击某个按钮开始拍照的时候,需要有相应的方法能在拍照完成之后将图像数据捕获并存储,该类通过实现Camera.PictureCallback接口来完成捕获过程。
先展示一下拍照的界面:
界面很简单,只有两个部分,上面是图像预览区,下面是拍照按钮,xml布局代码如下:
<?xmlversion="1.0"encoding="utf-8"?>
<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;
publicclass CameraActivity extends Activity {
publicstaticfinalintMEDIA_TYPE_IMAGE = 1;
publicstaticfinalintMEDIA_TYPE_VIDEO = 2;
protectedstaticfinal String TAG = null;
private Camera mCamera;
private CameraPreview mPreview;
@Override
publicvoid 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
publicvoid onClick(View v) {
// get an imagefrom the camera
mCamera.takePicture(null, null, mPicture);
}
});
}
/** A safe way to get an instance of theCamera object. */
publicstatic 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 */
publicclass 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);
}
publicvoid 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());
}
}
publicvoid surfaceDestroyed(SurfaceHolder holder) {
// empty. Takecare of releasing the Camera preview in your
// activity.
}
publicvoid 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
publicvoid 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());
}
}
};
privatestatic 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");
returnnull;
}
}
// 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");
} elseif (type == MEDIA_TYPE_VIDEO) {
mediaFile = new File(mediaStorageDir.getPath() + File.separator
+ "VID_" + timeStamp + ".mp4");
} else {
returnnull;
}
return mediaFile;
}
@Override
protectedvoid onStop() {
Log.i("cameratest", "onStop");
super.onStop(); // Always callthe superclass method first
}
@Override
publicvoid 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. */
publicstatic 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的构造函数,建立了相机图像预览类CameraPreview对象mPreview,将mPreview添加进FrameLayout容器中,如下:
mPreview = new CameraPreview(this, mCamera);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
CameraPreview的实现在上面给出得的activity代码中可以找到,其实他就是一个实现了SurfaceHolder.Callback接口的SurfaceView。关于如何使用SurfaceView这里不做讲解,这里只说明为什么我们仅仅是新建了个继承自SurfaceView的CameraPreview对象,然后将该对象放入布局文件中,就能实现预览效果。其中关键的代码是:
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
publicvoid 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;
}
}