使用Camera進行拍照 & 后台靜默拍照的思路


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM