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
