前言
最近在忙著港股券商上線,在做港股的過程中,用戶開戶功能涉及到拍照識(shí)別身份證的功能,如圖:調(diào)用相機(jī)的兩種方法
一.使用相機(jī)應(yīng)用程序進(jìn)行拍照
利用一個(gè)描述了執(zhí)行目的Intent對(duì)象怎茫,Android可以將某些執(zhí)行任務(wù)委托給其他應(yīng)用,比如調(diào)用相機(jī)。整個(gè)過程包含三部分: Intent 本身状原,一個(gè)函數(shù)調(diào)用來啟動(dòng)外部的 Activity嘱吗,當(dāng)焦點(diǎn)返回到我們的Activity時(shí)船惨,處理返回圖像數(shù)據(jù)的代碼债沮。
下面的函數(shù)通過發(fā)送一個(gè)Intent來捕獲照片:
static final int REQUEST_IMAGE_CAPTURE = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
注意在調(diào)用startActivityForResult()方法之前,先調(diào)用resolveActivity()鹿鳖,這個(gè)方法會(huì)返回能處理該Intent的第一個(gè)Activity(即檢查有沒有能處理這個(gè)Intent的Activity)扁眯。執(zhí)行這個(gè)檢查非常重要,因?yàn)槿绻谡{(diào)用startActivityForResult()時(shí)翅帜,沒有應(yīng)用能處理你的Intent姻檀,應(yīng)用將會(huì)崩潰。所以只要返回結(jié)果不為null涝滴,使用該Intent就是安全的绣版。(因?yàn)檫@種方法并不能實(shí)現(xiàn)我們的需求,因此不做過多解釋,需要的童鞋可以去搜索一下具體用法)
二.通過使用Android框架所提供的API來直接控制相機(jī)硬件,實(shí)現(xiàn)自定義相機(jī)模塊狭莱。
1.打開相機(jī)對(duì)象
獲取一個(gè) Camera 實(shí)例是直接控制相機(jī)的第一步僵娃。Camera是最主要的類概作,用于管理和操作camera資源腋妙。它提供了完整的相機(jī)底層接口,支持相機(jī)資源切換讯榕,設(shè)置預(yù)覽/拍攝尺寸骤素,設(shè)定光圈、曝光愚屁、聚焦等相關(guān)參數(shù)济竹,獲取預(yù)覽/拍攝幀數(shù)據(jù)等功能,我會(huì)在下面介紹一下他的主要方法.
編寫一個(gè)打開相機(jī)的方法,在onResume()方法里面去調(diào)用執(zhí)行霎槐,單獨(dú)的方法使得代碼更容易重用送浊,也便于保持控制流程更加簡(jiǎn)單。
private int mCameraId = 0;
/**
* 獲取Camera實(shí)例
*
* @return
*/
private void safeOpenCamera() {
try {
releaseCamera();
mCamera = Camera.open(mCameraId);
} catch (Exception e) {
Log.e(getString(R.string.app_name), "failed to open Camera");
e.printStackTrace();
}
}
/**
* 釋放相機(jī)資源
*/
private void releaseCamera() {
if (mCamera != null) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
需要注意的是:由于Android機(jī)制的緣故丘跌,Android相機(jī)只能夠被一個(gè)APP線程所綁定袭景,也就是說如果你正在使用相機(jī)進(jìn)行拍照攝像的時(shí)候,另一個(gè)程序便不能使用Camera類來啟用相機(jī)闭树,而且會(huì)報(bào)出Can not release的錯(cuò)誤耸棒,因?yàn)槲覀冃枰褂胻ry語(yǔ)句塊進(jìn)行捕獲一下.那么如何判定你是否使用了相機(jī)呢,其實(shí)只要使用了Camera.open()方法獲得了相機(jī)的示例报辱,系統(tǒng)就認(rèn)為你使用了相機(jī)与殃,所以當(dāng)你使用了完了相機(jī)一定記得要釋放相機(jī)的資源,不然別的應(yīng)用程序用不了呀,我們可以使用 Camera.release()來釋放相機(jī)資源幅疼,Google官方的意見是重寫Activity的onPause()方法來Camera.release().
另外從API level 9開始米奸,相機(jī)框架可以支持多個(gè)攝像頭的打開操作。如果使用舊的API衣屏,在調(diào)用open()時(shí)不傳入?yún)?shù)指定打開哪個(gè)攝像頭躏升,默認(rèn)情況下會(huì)使用后置攝像頭。0是后置攝像頭,1是前置攝像頭.
2.創(chuàng)建相機(jī)預(yù)覽界面
拍照通常需要向用戶提供一個(gè)預(yù)覽界面來顯示待拍攝的畫面內(nèi)容狼忱。我們可以使用SurfaceView來呈現(xiàn)相機(jī)采集到的圖像畫面膨疏。SurfaceView這個(gè)類是用于繪制相機(jī)預(yù)覽圖像的,提供給用戶實(shí)時(shí)的預(yù)覽圖像钻弄。普通的view以及派生類都是共享同一個(gè)surface的佃却,所有的繪制都必須在UI線程中進(jìn)行。而surfaceview是一種比較特殊的view窘俺,它并不與其他普通view共享surface饲帅,而是在內(nèi)部持有了一個(gè)獨(dú)立的surface,surfaceview負(fù)責(zé)管理這個(gè)surface的格式、尺寸以及顯示位置瘤泪。由于UI線程還要同時(shí)處理其他交互邏輯灶泵,因此對(duì)view的更新速度和幀率無法保證,而surfaceview由于持有一個(gè)獨(dú)立的surface对途,因而可以在獨(dú)立的線程中進(jìn)行繪制赦邻,因此可以提供更高的幀率。自定義相機(jī)的預(yù)覽圖像由于對(duì)更新速度和幀率要求比較高实檀,所以比較適合用surfaceview來顯示惶洲。
SurfaceHolder :surfaceholder是控制surface的一個(gè)抽象接口,它能夠控制surface的尺寸和格式膳犹,修改surface的像素恬吕,監(jiān)視surface的變化等等,surfaceholder的典型應(yīng)用就是用于surfaceview中须床。surfaceview通過getHolder()方法獲得surfaceholder 實(shí)例铐料,通過后者管理監(jiān)聽surface 的狀態(tài)。
SurfaceHolder.Callback 接口 :負(fù)責(zé)監(jiān)聽surface狀態(tài)變化的接口豺旬,有三個(gè)方法:
surfaceCreated(SurfaceHolder holder):在surface創(chuàng)建后立即被調(diào)用钠惩。在開發(fā)自定義相機(jī)時(shí),可以通過重載這個(gè)函數(shù)調(diào)用camera.open()哈垢、camera.setPreviewDisplay()妻柒,來實(shí)現(xiàn)獲取相機(jī)資源、連接camera和surface等操作耘分。
surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface發(fā)生format或size變化時(shí)調(diào)用举塔。在開發(fā)自定義相機(jī)時(shí)绑警,可以通過重載這個(gè)函數(shù)調(diào)用camera.startPreview來開啟相機(jī)預(yù)覽,使得camera預(yù)覽幀數(shù)據(jù)可以傳遞給surface央渣,從而實(shí)時(shí)顯示相機(jī)預(yù)覽圖像计盒。
surfaceDestroyed(SurfaceHolder holder):在surface銷毀之前被調(diào)用。在開發(fā)自定義相機(jī)時(shí)芽丹,可以通過重載這個(gè)函數(shù)調(diào)用camera.stopPreview()北启,camera.release()來實(shí)現(xiàn)停止相機(jī)預(yù)覽及釋放相機(jī)資源等操作。
在這里你可以直接在activity中直接implements SurfaceHolder.Callback 的接口,也可以自定義一個(gè)surfaceview然后把camera這個(gè)實(shí)例傳給她來使用.
3.設(shè)置和啟動(dòng)Preview
一個(gè)Camera實(shí)例與它相關(guān)的Preview必須按照特定的順序來創(chuàng)建拔第,通常來說Camera對(duì)象優(yōu)先被創(chuàng)建,而Preview對(duì)象必須在surfaceChanged()這一回調(diào)方法里面重新啟用(restart),寫一個(gè)方法用來設(shè)置相機(jī)的參數(shù)和執(zhí)行相機(jī)的預(yù)覽Camera.startPreview()方法.
/**
* 預(yù)覽相機(jī)
*/
private void startPreview(Camera camera, SurfaceHolder holder) {
try {
setupCamera(camera);
camera.setPreviewDisplay(holder);
CameraUtil.getInstance().setCameraDisplayOrientation(this, mCameraId, camera);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
4.修改相機(jī)設(shè)置
相機(jī)參數(shù)的修改可以改變拍照的成像效果咕村,例如縮放大小,曝光補(bǔ)償值等等蚊俺。
/**
* 設(shè)置
*/
private void setupCamera(Camera camera) {
Camera.Parameters parameters = camera.getParameters();
if (parameters.getSupportedFocusModes().contains(
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
//這里第三個(gè)參數(shù)為最小尺寸 getPropPreviewSize方法會(huì)對(duì)從最小尺寸開始升序排列 取出所有支持尺寸的最小尺寸
Camera.Size previewSize = CameraUtil.getInstance().getPropPreviewSize(parameters.getSupportedPreviewSizes(), 800);
parameters.setPreviewSize(previewSize.width, previewSize.height);
Camera.Size pictureSize = CameraUtil.getInstance().getPropPictureSize(parameters.getSupportedPictureSizes(), 800);
parameters.setPictureSize(pictureSize.width, pictureSize.height);
camera.setParameters(parameters);
}
5.設(shè)置預(yù)覽方向
借用官方文檔的說明:
Most camera applications lock the display into landscape mode because that is the natural orientation of the camera sensor. This setting does not prevent you from taking portrait-mode photos, because the orientation of the device is recorded in the EXIF header. The setCameraDisplayOrientation() method lets you change how the preview is displayed without affecting how the image is recorded. However, in Android prior to API level 14, you must stop your preview before changing the orientation and then restart it.
大多數(shù)相機(jī)程序會(huì)鎖定預(yù)覽方向?yàn)闄M屏狀態(tài)懈涛,因?yàn)樵摲较蚴窍鄼C(jī)傳感器的自然放置方向。當(dāng)然這一設(shè)定并不妨礙我們?nèi)ヅ呢Q屏的照片泳猬,這個(gè)時(shí)候設(shè)備的方向角度信息會(huì)被記錄在EXIF文件頭中批钠。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下棕兼,改變預(yù)覽的方向曲掰。然而,對(duì)于Android API level 14及更舊版本的系統(tǒng)柑司,在改變方向之前忙上,我們必須先停止相機(jī)預(yù)覽拷呆,設(shè)置方向之后,然后再重啟預(yù)覽晨横。
6.拍攝照片
一旦預(yù)覽啟動(dòng)成功之后洋腮,可以使用Camera.takePicture()方法拍攝照片箫柳。我們可以創(chuàng)建Camera.PictureCallback與Camera.ShutterCallback對(duì)象并將他們傳遞到Camera.takePicture()中手形。下面的代碼創(chuàng)建了一個(gè)Camera.PictureCallback.
Camera.PictureCallback mRectJpegPictureCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
Bitmap b = null;
if (null != data) {
b = BitmapFactory.decodeByteArray(data, 0, data.length);
mCamera.stopPreview();
isPreviewing = false;
}
if (null != b) {
Bitmap rotaBitmap = ImageUtil.getRotateBitmap(b, 90.0f);
int x = rotaBitmap.getWidth() / 2 - DST_RECT_WIDTH / 2;
int y = rotaBitmap.getHeight() / 2 - DST_RECT_HEIGHT / 2;
Bitmap rectBitmap = Bitmap.createBitmap(rotaBitmap, x, y, DST_RECT_WIDTH, DST_RECT_HEIGHT);
FileUtil.saveBitmap(rectBitmap);
Intent intent = new Intent();
intent.putExtra(AppConstant.KEY.IMG_PATH, FileUtil.getImgPath());
intent.putExtra(AppConstant.KEY.PIC_WIDTH, rectBitmap.getWidth());
intent.putExtra(AppConstant.KEY.PIC_HEIGHT, rectBitmap.getHeight());
setResult(AppConstant.RESULT_CODE.RESULT_OK, intent);
finish();
if (rotaBitmap.isRecycled()) {
rotaBitmap.recycle();
rotaBitmap = null;
}
if (rectBitmap.isRecycled()) {
rectBitmap.recycle();
rectBitmap = null;
}
}
mCamera.startPreview();
isPreviewing = true;
if (!b.isRecycled()) {
b.recycle();
b = null;
}
}
};
8.停止預(yù)覽并釋放相機(jī)
當(dāng)應(yīng)用使用完相機(jī)之后,我們有必要進(jìn)行清理釋放資源的操作悯恍。尤其是库糠,我們必須釋放Camera對(duì)象,不然的話可能會(huì)引起其他應(yīng)用程序使用Camera實(shí)例的時(shí)候發(fā)生崩潰涮毫,包括我們自己應(yīng)用也同樣會(huì)遇到這個(gè)問題瞬欧。
那么何時(shí)應(yīng)該停止預(yù)覽并釋放相機(jī)呢?在預(yù)覽SurfaceView組件被銷毀之后罢防,調(diào)用釋放相機(jī)的方法.
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releaseCamera();
}
9.遇到的問題 SurfaceView預(yù)覽圖像拉伸變形艘虎,拍攝照片尺寸不對(duì)
說明這個(gè)問題之前,同樣先說一下幾個(gè)跟相機(jī)有關(guān)的尺寸咒吐。
SurfaceView尺寸 :即自定義相機(jī)應(yīng)用中用于顯示相機(jī)預(yù)覽圖像的View的尺寸野建,當(dāng)它鋪滿全屏?xí)r就是屏幕的大小属划。這里surfaceview顯示的預(yù)覽圖像暫且稱作手機(jī)預(yù)覽圖像。
Previewsize :相機(jī)硬件提供的預(yù)覽幀數(shù)據(jù)尺寸候生。預(yù)覽幀數(shù)據(jù)傳遞給SurfaceView同眯,實(shí)現(xiàn)預(yù)覽圖像的顯示。這里預(yù)覽幀數(shù)據(jù)對(duì)應(yīng)的預(yù)覽圖像暫且稱作相機(jī)預(yù)覽圖像唯鸭。
Picturesize :相機(jī)硬件提供的拍攝幀數(shù)據(jù)尺寸须蜗。拍攝幀數(shù)據(jù)可以生成位圖文件,最終保存成.jpg或者.png等格式的圖片目溉。這里拍攝幀數(shù)據(jù)對(duì)應(yīng)的圖像稱作相機(jī)拍攝圖像明肮。圖4說明了以上幾種圖像及照片之間的關(guān)系。手機(jī)預(yù)覽圖像是直接提供給用戶看的圖像缭付,它由相機(jī)預(yù)覽圖像生成晤愧,拍攝照片的數(shù)據(jù)則來自于相機(jī)拍攝圖像。
原因是沒有正確設(shè)置比例 parameter.setPictureSize(width,height)蛉腌,這個(gè)比例不是你決定的官份,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機(jī)支持的尺寸。
自定義相機(jī)其實(shí)還是比較容易的,按著步驟來都可以一點(diǎn)一點(diǎn)實(shí)現(xiàn).項(xiàng)目中的自定義相機(jī)部分首先是寫了一個(gè)遮罩view,預(yù)覽的時(shí)候是四周暗中間亮,其次就是對(duì)相機(jī)拍攝出來的照片的處理,在拍攝的時(shí)候會(huì)選取適合屏幕大小,但是我們想拍攝出中間亮的部分區(qū)域的照片,所以我們需要對(duì)拍攝出來的照片做一些處理,實(shí)現(xiàn)和源碼會(huì)在下一篇文章中展現(xiàn)Android 自定義相機(jī) 識(shí)別身份證(下).