Android中的Camera可以用来进行自定义相机、取景框实时预览、拍照等操作。在5.0中,这个类不推荐使用了,新出了一个Camera2,那个东西没怎么研究过,反正一时半会用不到。本篇讲解的是如果用这个对象进行拍照,最后在提及下如何进行后台的静默拍照。
API翻译:http://bbs.51cto.com/thread-1063856-1.html(挺简单易懂的)
一、CameraManager
这个类是我自己封装的,不是API提供的。我在这里封装了打开相机,获取相机ID,保存拍照图片的操作。
1.1 初始化
初始化时需要传入一个camera对象,还有一个SurfaceHolder对象。传入Camera对象的目的是用于之后的拍照等操作,surfaceHloder是用于操作取景框预览图片的。
private Camera mCamera; private SurfaceHolder mHolder; public CameraManager(Camera camera, SurfaceHolder holder) { mCamera = camera; mHolder = holder; } public Camera getCamera() { return mCamera; }
1.2 打开相机
这部分的步骤是,先找到Camera的id,然后通过Camera.open(id)打开这个id的摄像头(获取id的方法下面会说到),并且返回一个Camera对象,用于以后的操作。
Camera android.hardware.Camera.open(int cameraId)
如果开启失败,那么camera对象就是null,如果开启成功,那么就需要提供实时预览,让用户能看到摄像头中的东西。
重要代码:
mCamera.setPreviewDisplay(mHolder); // 如果成功开始实时预览 mCamera.startPreview();
代码片段:
// 将摄像头中的图像展示到holder中 try { // 这里的myCamera为已经初始化的Camera对象 mCamera.setPreviewDisplay(mHolder); } catch (IOException e) { e.printStackTrace(); // 如果出错立刻进行处理,停止预览照片 mCamera.stopPreview(); mCamera.release(); mCamera = null; } // 如果成功开始实时预览 mCamera.startPreview();
全部代码:
/** * 打开相机 * * @param camera * 照相机对象 * @param holder * 用于实时展示取景框内容的控件 * @param tagInfo * 摄像头信息,分为前置/后置摄像头 Camera.CameraInfo.CAMERA_FACING_FRONT:前置 * Camera.CameraInfo.CAMERA_FACING_BACK:后置 * @return 是否成功打开某个摄像头 */ public boolean openCamera(int tagInfo) { // 尝试开启摄像头 try { mCamera = Camera.open(getCameraId(tagInfo)); } catch (RuntimeException e) { e.printStackTrace(); return false; } // 开启前置失败 if (mCamera == null) { return false; } // 将摄像头中的图像展示到holder中 try { // 这里的myCamera为已经初始化的Camera对象 mCamera.setPreviewDisplay(mHolder); } catch (IOException e) { e.printStackTrace(); // 如果出错立刻进行处理,停止预览照片 mCamera.stopPreview(); mCamera.release(); mCamera = null; } // 如果成功开始实时预览 mCamera.startPreview(); return true; }
1.3 获取摄像头的id
获取id的步骤是:先得到本机摄像头的数目,开始一个个判断前置/后置摄像头的id
/** * @param tagInfo * @return 得到特定camera info的id */ private int getCameraId(int tagInfo) { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); // 开始遍历摄像头,得到camera info int cameraId, cameraCount; for (cameraId = 0, cameraCount = Camera.getNumberOfCameras(); cameraId < cameraCount; cameraId++) { Camera.getCameraInfo(cameraId, cameraInfo); if (cameraInfo.facing == tagInfo) { break; } } return cameraId; }
获取前置/后置摄像头的id
通过传入不同的参数,就可以得到前置/后置的id了
/** * @return 前置摄像头的ID */ public int getFrontCameraId() { return getCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT); } /** * @return 后置摄像头的ID */ public int getBackCameraId() { return getCameraId(Camera.CameraInfo.CAMERA_FACING_BACK); }
1.4 设定拍照成功后照片保存的路径和照片名称
首先要保证拍摄时存放照片的根目录存在,因为有时候用户会自己闲的蛋疼的删除掉你建立好的目录,拍照时你的程序找不到目录就会报错,一报错用户就觉得你的程序差劲,不好用。为了避免这个问题,我们每次保存前都需要判断下图片保存的目录是否存在。
// 创建并保存图片文件 File mFile = new File(PHOTO_PATH); if (!mFile.exists()) { mFile.mkdirs(); }
图片的名字一般是根据当前时间来设定的,如果有其他需要请自行修改。
/** * 定义图片保存的路径和图片的名字 */ public final static String PHOTO_PATH = "mnt/sdcard/CAMERA_DEMO/Camera/"; public static String getPhotoFileName() { Date date = new Date(System.currentTimeMillis()); SimpleDateFormat dateFormat = new SimpleDateFormat("'LOCK'_yyyyMMdd_HHmmss"); return dateFormat.format(date) + ".jpg"; }
1.5 处理拍照的结果
当拍照成功后会回调一个方法,在这个方法中我们就可以进行相片的处理了。由于android拍照后的照片和真实照片是垂直的,所以需要旋转处理。最后去保存到SD卡。在保存完毕后,请务必对camera进行收尾处理,释放资源。
/** * 拍照成功回调 */ public class PicCallback implements PictureCallback { private String TAG = getClass().getSimpleName(); private Camera mCamera; public PicCallback(Camera camera) { // TODO 自动生成的构造函数存根 mCamera = camera; } /* * 将拍照得到的字节转为bitmap,然后旋转,接着写入SD卡 * @param data * @param camera */ @Override public void onPictureTaken(byte[] data, Camera camera) { // 将得到的照片进行270°旋转,使其竖直 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Matrix matrix = new Matrix(); matrix.preRotate(270); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); // 创建并保存图片文件 File mFile = new File(PHOTO_PATH); if (!mFile.exists()) { mFile.mkdirs(); } File pictureFile = new File(PHOTO_PATH, getPhotoFileName()); try { FileOutputStream fos = new FileOutputStream(pictureFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); bitmap.recycle(); fos.close(); Log.i(TAG, "拍摄成功!"); } catch (Exception error) { Log.e(TAG, "拍摄失败"); error.printStackTrace(); } finally { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } }
1.6 本类的全部代码
package com.kale.camerademo; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.hardware.Camera; import android.hardware.Camera.PictureCallback; import android.util.Log; import android.view.SurfaceHolder; public class CameraManager { private Camera mCamera; private SurfaceHolder mHolder; public CameraManager(Camera camera, SurfaceHolder holder) { mCamera = camera; mHolder = holder; } public Camera getCamera() { return mCamera; } /** * 打开相机 * * @param camera * 照相机对象 * @param holder * 用于实时展示取景框内容的控件 * @param tagInfo * 摄像头信息,分为前置/后置摄像头 Camera.CameraInfo.CAMERA_FACING_FRONT:前置 * Camera.CameraInfo.CAMERA_FACING_BACK:后置 * @return 是否成功打开某个摄像头 */ public boolean openCamera(int tagInfo) { // 尝试开启摄像头 try { mCamera = Camera.open(getCameraId(tagInfo)); } catch (RuntimeException e) { e.printStackTrace(); return false; } // 开启前置失败 if (mCamera == null) { return false; } // 将摄像头中的图像展示到holder中 try { // 这里的myCamera为已经初始化的Camera对象 mCamera.setPreviewDisplay(mHolder); } catch (IOException e) { e.printStackTrace(); // 如果出错立刻进行处理,停止预览照片 mCamera.stopPreview(); mCamera.release(); mCamera = null; } // 如果成功开始实时预览 mCamera.startPreview(); return true; } /** * @return 前置摄像头的ID */ public int getFrontCameraId() { return getCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT); } /** * @return 后置摄像头的ID */ public int getBackCameraId() { return getCameraId(Camera.CameraInfo.CAMERA_FACING_BACK); } /** * @param tagInfo * @return 得到特定camera info的id */ private int getCameraId(int tagInfo) { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); // 开始遍历摄像头,得到camera info int cameraId, cameraCount; for (cameraId = 0, cameraCount = Camera.getNumberOfCameras(); cameraId < cameraCount; cameraId++) { Camera.getCameraInfo(cameraId, cameraInfo); if (cameraInfo.facing == tagInfo) { break; } } return cameraId; } /** * 定义图片保存的路径和图片的名字 */ public final static String PHOTO_PATH = "mnt/sdcard/CAMERA_DEMO/Camera/"; public static String getPhotoFileName() { Date date = new Date(System.currentTimeMillis()); SimpleDateFormat dateFormat = new SimpleDateFormat("'LOCK'_yyyyMMdd_HHmmss"); return dateFormat.format(date) + ".jpg"; } /** * 拍照成功回调 */ public class PicCallback implements PictureCallback { private String TAG = getClass().getSimpleName(); private Camera mCamera; public PicCallback(Camera camera) { // TODO 自动生成的构造函数存根 mCamera = camera; } /* * 将拍照得到的字节转为bitmap,然后旋转,接着写入SD卡 * @param data * @param camera */ @Override public void onPictureTaken(byte[] data, Camera camera) { // 将得到的照片进行270°旋转,使其竖直 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Matrix matrix = new Matrix(); matrix.preRotate(270); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); // 创建并保存图片文件 File mFile = new File(PHOTO_PATH); if (!mFile.exists()) { mFile.mkdirs(); } File pictureFile = new File(PHOTO_PATH, getPhotoFileName()); try { FileOutputStream fos = new FileOutputStream(pictureFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); bitmap.recycle(); fos.close(); Log.i(TAG, "拍摄成功!"); } catch (Exception error) { Log.e(TAG, "拍摄失败"); error.printStackTrace(); } finally { mCamera.stopPreview(); mCamera.release(); mCamera = null; } } } }
二、布局文件
在讲解java代码之前,先贴下布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <SurfaceView android:id="@+id/front_surfaceview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <SurfaceView android:id="@+id/back_surfaceview" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" > <Button android:id="@+id/openFront_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:onClick="buttonListener" android:text="open front camera" /> <Button android:id="@+id/openBack_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:onClick="buttonListener" android:text="open back camera" /> </RelativeLayout> </LinearLayout>
这里主要是放了两个预览框,一个是预览前置的一个是预览后置的。最下面是两个按钮,用来照相。这里的行为是打开摄像头后自动照相,所以就用了open xxxx camera作名字。
三、MainActivity
为了便于讲解,这部分我只说下对前置的操作,后置的操作完全一致,就不赘述了。
3.1 对象
private CameraManager frontCameraManager; /** * 定义前置有关的参数 */ private SurfaceView frontSurfaceView; private SurfaceHolder frontHolder; private boolean isFrontOpened = false; private Camera mFrontCamera;
3.2 初始化对象
先找到surfaceView对象,然后产生一个holder对象,这个holder对象实际上可以做的事情有很多
Allows you to control the surface size and format, edit the pixels in the surface, and monitor changes to the surface.
private void initView() { /** * 初始化前置相机参数 */ // 初始化surface view frontSurfaceView = (SurfaceView) findViewById(R.id.front_surfaceview); // 初始化surface holder frontHolder = frontSurfaceView.getHolder(); }
frontCameraManager = new CameraManager(mFrontCamera, frontHolder);
需要注意的是这时的Camera对象还没有初始化,只有当打开相机时才能初始化,这部分在CameraManager中的打开相机部分有解释。
3.3 开启摄像头并照相
先打开摄像头,如果打开成功就试图对焦,并且表示摄像头已经打开,最后进行照相。
/** * @return 开启前置摄像头照相 */ private void takeFrontPhoto() { if (isFrontOpened == false && frontCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_FRONT)) { mFrontCamera = frontCameraManager.getCamera(); //自动对焦 mFrontCamera.autoFocus(mAutoFocus); isFrontOpened = true; // 拍照 mFrontCamera.takePicture(null, null, frontCameraManager.new PicCallback(mFrontCamera)); } }
/** * 自动对焦的回调方法,用来处理对焦成功/不成功后的事件 */ private AutoFocusCallback mAutoFocus = new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { //TODO:空实现 } };
3.4 本类的全部代码
package com.kale.camerademo; import android.app.Activity; import android.hardware.Camera; import android.hardware.Camera.AutoFocusCallback; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; /** * @author:Jack Tony * @description : * 参考自:http://blog.csdn.net/a740169405/article/details/12207229 * * 还可以参考:http://mobile.51cto.com/amedia-376703.htm * @date :2015年1月16日 */ public class MainActivity extends Activity { private CameraManager frontCameraManager; private CameraManager backCameraManager; /** * 定义前置有关的参数 */ private SurfaceView frontSurfaceView; private SurfaceHolder frontHolder; private boolean isFrontOpened = false; private Camera mFrontCamera; /** * 定义后置有关的参数 */ private SurfaceView backSurfaceView; private SurfaceHolder backHolder; private boolean isBackOpened = false; private Camera mBackCamera; /** * 自动对焦的回调方法,用来处理对焦成功/不成功后的事件 */ private AutoFocusCallback mAutoFocus = new AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { //TODO:空实现 } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } public void buttonListener(View view) { switch (view.getId()) { case R.id.openFront_button: takeFrontPhoto(); break; case R.id.openBack_button: takeBackPhoto(); break; default: break; } } private void initView() { /** * 初始化前置相机参数 */ // 初始化surface view frontSurfaceView = (SurfaceView) findViewById(R.id.front_surfaceview); // 初始化surface holder frontHolder = frontSurfaceView.getHolder(); frontCameraManager = new CameraManager(mFrontCamera, frontHolder); /** * 初始化后置相机参数 */ // 初始化surface view backSurfaceView = (SurfaceView) findViewById(R.id.back_surfaceview); // 初始化surface holder backHolder = backSurfaceView.getHolder(); backCameraManager = new CameraManager(mBackCamera, backHolder); } /** * @return 开启前置摄像头照相 */ private void takeFrontPhoto() { if (isFrontOpened == false && frontCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_FRONT)) { mFrontCamera = frontCameraManager.getCamera(); //自动对焦 mFrontCamera.autoFocus(mAutoFocus); isFrontOpened = true; // 拍照 mFrontCamera.takePicture(null, null, frontCameraManager.new PicCallback(mFrontCamera)); } } /** * @return 开启后置摄像头照相 */ private void takeBackPhoto() { if (isBackOpened == false && backCameraManager.openCamera(Camera.CameraInfo.CAMERA_FACING_BACK)) { mBackCamera = backCameraManager.getCamera(); //自动对焦 mBackCamera.autoFocus(mAutoFocus); isBackOpened = true; // 拍照 mBackCamera.takePicture(null, null, backCameraManager.new PicCallback(mBackCamera)); } } }
3.5 权限
<!-- 调用相机权限 --> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <!-- 读写SD卡权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
四、后台静默照相的思路
由于保证安全性,android要求camera对象必须提供一个实时的预览框给用户,然用户知道当前相机已经开启。如果我们想要用户在不知情的情况下被照相,那么将预览框做成1dp的大小就行了。
这里还需要注意,由于打开相机需要时间,照相必须要在打开相机之后进行。如果要进行全自动照相,就必须要等打开相机之后再触发照相的代码。我提供的思路是新开一个线程放开启相机的代码,等待两秒后开始照相。
protected void takePhoto(){ //这里得开线程进行拍照,因为Activity还未完全显示的时候,是无法进行拍照的,SurfaceView必须先显示 new Thread(new Runnable() { @Override public void run() { takeFrontPhoto(); } }).start(); } //开启前置摄像头照相 private boolean takeFrontPhoto() { if(openFacingFrontCamera()) { try { //因为开启摄像头需要时间,这里让线程睡2秒 Thread.sleep(2000); } catch (InterruptedException e) {} //拍照 myCamera.takePicture(null, null, myPicCallback); return true; } else{ return false; } }
如果你想要同时拍摄前后相机的照片,我目前没有找到很好的方法,理论上是完全可以的。想的办法是在前置拍摄成功后,触发拍摄后置的代码。这个思路挺蠢的,不是很推荐。
如果你不想要用户打开activity照相,你完全可以用service来做这个事情。做一个悬浮窗,然后给悬浮窗中放预览框,设置悬浮窗大小为1dp。开启服务后自动执行照相的代码。
源码下载:http://download.csdn.net/detail/shark0017/8370303
参考自:
http://bbs.51cto.com/thread-1063856-1.html