天天看點

Android Camera開發經驗總結以及踩過的那些坑

寫在開頭

需求方:上傳試卷的時候,使用者自己拍的照片有很多問題。如:不清晰、圖檔歪了、錯誤圖檔等。我們要是能夠對拍攝照片進行識别處理就好了,能夠裁切矯正就更好了,最好可以像二維碼掃描一樣,直接識别處理~

開發:滿足你!

一、整體架構邏輯

試卷掃描子產品,最核心的邏輯就是資料采集、解碼識别、圖檔裁切,再加上對識别結果和裁切結果的處理,就構成了整個子產品的主邏輯(感謝多媒體同僚對圖檔識别與處理提供庫的支援)。整個邏輯的實作如下圖所示:

Android Camera開發經驗總結以及踩過的那些坑

在子產品中,除了UI線程,還開啟了一個Deocde線程,用來處理圖檔的解碼識别和裁切。這麼做的原因是因為對于圖檔資料的處理,是比較耗時的,如果在UI線程處理,會有ANR的風險。同時采用這種處理方式,整個子產品的流暢性也更加好,且子產品的結構更加清晰。

那麼線程之間是如何互動的呢?這裡子產品中是采用了最常用的Handler消息傳遞機制。因為通過Handler的Message可以線上程間傳遞較大的圖檔資料(注意如果在Intent的Bundle中傳遞較大的資料,會崩潰報錯)。請看下面這段代碼:

@Override

public void run() {

Looper.prepare();

handler = new DecodeHandler(activity);

handlerInitLatch.countDown();

Looper.loop();

}

上面這個方法是DecodeThread的run方法,在方法中,我們初始化了目前線程對應的Handler對象DecodeHandler。而DecodeHandler初始化是需要傳入目前主線程的上下文activity,通過activity我們可以拿到主線程的Handler對象。這樣的話主線程和解碼線程就建立了聯系,它們之間就可以友善得進行消息傳遞了。最終實作的子產品采集界面如下所示:

Android Camera開發經驗總結以及踩過的那些坑

二、子產品開發相關實作

整個掃碼拍照子產品的邏輯比較瑣碎,就不一一說明了。以下是整理的幾個開發中比較關鍵的點和Camera硬體開發一些經驗,在這裡做記錄,避免以後重複造輪子。

閃光燈設定

  • 開啟閃光燈

public void turnOnFlash(){

if(camera != null){

try {

Camera.Parameters parameters = camera.getParameters();

parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);

camera.setParameters(parameters);

} catch (Exception e) {

e.printStackTrace();

}

}

}

  • 關閉閃光燈

public void turnOffFlash(){

parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);

預覽圖檔分辨率選擇

預覽圖檔的分辨率選擇邏輯是:有1920*1080則選之,否則選硬體支援的最大的分辨率,且滿足圖檔比例為16:9

private static Point findBestPreviewSizeValue(List<Camera.Size> sizeList, Point screenResolution) {

int bestX = 0;

int bestY = 0;

int size = 0;

for(int i = 0; i < sizeList.size(); i ++){

// 如果有符合的分辨率,則直接傳回

if(sizeList.get(i).width == DEFAULT_WIDTH && sizeList.get(i).height == DEFAULT_HEIGHT){

Log.d(TAG, "get default preview size!!!");

return new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT);

}

int newX = sizeList.get(i).width;

int newY = sizeList.get(i).height;

int newSize = Math.abs(newX * newX) + Math.abs(newY * newY);

float ratio = (float)newY / (float)newX;

Log.d(TAG, newX + ":" + newY + ":" + ratio);

if (newSize >= size && ratio != 0.75) { // 確定圖檔是16:9的

bestX = newX;

bestY = newY;

size = newSize;

} else if (newSize < size) {

continue;

}

if (bestX > 0 && bestY > 0) {

return new Point(bestX, bestY);

return null;

拍照圖檔分辨率選擇

在硬體支援的拍照圖檔分辨率清單中,拍照圖檔分辨率選擇邏輯:

  1. 有1920*1080則選之
  2. 選擇大于螢幕分辨率且圖檔比例為16:9的
  3. 選擇圖檔分辨率盡可能大且圖檔比例為16:9的

private static Point findBestPictureSizeValue(List<Camera.Size> sizeList, Point screenResolution){

List<Camera.Size> tempList = new ArrayList<>();

Log.d(TAG, "get default picture size!!!");

if(sizeList.get(i).width >= screenResolution.x && sizeList.get(i).height >= screenResolution.y){

tempList.add(sizeList.get(i));

int diff = Integer.MAX_VALUE;

if(tempList != null && tempList.size() > 0){

for(int i = 0; i < tempList.size(); i ++){

int newDiff = Math.abs(tempList.get(i).width - screenResolution.x) + Math.abs(tempList.get(i).height - screenResolution.y);

float ratio = (float)tempList.get(i).height / tempList.get(i).width;

Log.d(TAG, "ratio = " + ratio);

if(newDiff < diff && ratio != 0.75){ // 確定圖檔是16:9的

bestX = tempList.get(i).width;

bestY = tempList.get(i).height;

diff = newDiff;

}

}else {

return findMaxPictureSizeValue(sizeList);

預覽模式循環自動對焦

預覽模式時,支援自動對焦。目前處理邏輯是在AutoFocusCallback的回調方法onAutoFocus中,延遲發送Message資訊。這樣在上一次聚焦完成後,固定時間的延遲後會發送下一次的自動聚焦消息,如此達到循環聚焦的目的。

public void onAutoFocus(boolean success, Camera camera) {

Log.d(TAG, "onAutoFocus");

PaperScanConstant.isAutoFocusSuccess = true;

if (autoFocusHandler != null) {

Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);

autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);

autoFocusHandler = null;

} else {

Log.d(TAG, "Got auto-focus callback, but no handler for it");

}

預覽畫面不失真展示

如果預覽圖檔的分辨率比例和手機畫面上展示拍攝畫面的區域比例不一緻的話,就會出現畫面拉伸或者壓縮的現象。為了解決這個問題,取得更好的使用者體驗。子產品在布局的時候,對螢幕展示區域是動态計算的,以保證預覽區域比例與圖檔的分辨率比例是一緻的。

三、子產品開發中的那些坑

掃碼子產品開發,因為是跟手機硬體Camera打交道,基于目前市場中Android手機衆多的型号和搭載的五花八門的ROM,沒坑那是不可能的!!!下面是本子產品開發過程中的相關坑。

部分機子拍攝照片分辨率不高

開發過程中碰到過這麼一種情況,在部分機子上,明明已經聚焦,手機的分辨率也很高,但是拍出的照片分辨率卻很小。究其原因,就是不同的手機ROM,擷取的預設的照片分辨率是不同的。有的手機預設照片分辨率高,則照片就清晰;有的預設分辨率是最低的一檔,則無論你手機分辨率多高,拍出來的照片還是很模糊的。解決方案就是需要顯示設定拍照的圖檔分辨率:

parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);

parameters.setPictureSize(pictureResolution.x, pictureResolution.y);

部分機子拍攝照片發生了旋轉

還是由于Android手機碎片化的問題,每個手機預設拍照的旋轉角度是不一樣的。剛開始子產品中是按照預設旋轉90度處理,在大多數機子上是沒有問題的。但是在碰到Nexus 5X的時候就出問題了,圖檔上下導緻了。查閱了相關資料,Google官方提供了下面的方法,解決了這個問題。

public void setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) {

android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();

android.hardware.Camera.getCameraInfo(cameraId, info);

int rotation = BaseApplication.getInstance().getCurrentActivity().getWindowManager().getDefaultDisplay()

.getRotation();

int degrees = 0;

switch (rotation) {

case Surface.ROTATION_0: degrees = 0; break;

case Surface.ROTATION_90: degrees = 90; break;

case Surface.ROTATION_180: degrees = 180; break;

case Surface.ROTATION_270: degrees = 270; break;

int result;

if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {

result = (info.orientation + degrees) % 360;

result = (360 - result) % 360; // compensate the mirror

} else { // back-facing

result = (info.orientation - degrees + 360) % 360;

// 記錄本機子相機的旋轉角度

PaperScanConstant.cameraRotation = result;

camera.setDisplayOrientation(result);

private int findFrontFacingCameraID() {

int cameraId = -1;

// Search for the back facing camera

int numberOfCameras = Camera.getNumberOfCameras();

for (int i = 0; i < numberOfCameras; i++) {

Camera.CameraInfo info = new Camera.CameraInfo();

Camera.getCameraInfo(i, info);

if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {

Log.d(TAG, "Camera found");

cameraId = i;

break;

return cameraId;

頻繁點選螢幕應用崩潰

因為應用支援點選螢幕自動聚焦功能,但在某些機子上,使用者頻繁點選螢幕進行自動聚焦,應用發生了崩潰。究其原因是因為在某些ROM上,當上一次聚焦沒有完成時,就進行下一次聚焦,就會發生崩潰。解決方案是通過設定标志位,隻有在上一次聚焦完成後,才能進行下一次聚焦。

第三方ROM禁止了應用的攝像頭權限

有些第三方ROM會有自己的權限管理機制,當應用的攝像頭權限被禁止了,進入掃碼頁,會發生崩潰。這樣的互動體驗肯定不是很好,互動要求這邊權限被禁止以後,還是需要有一個溫和的提示,提醒使用者去設定頁面重新賦予應用攝像頭權限。但是系統也沒有提供接口說目前應用這個權限被禁止了。是以子產品中采用了一個折中的方案,監獄應用沒有攝像頭權限時候,開啟攝像頭會崩潰。是以我們捕獲開啟Camera的異常,在捕獲異常時候彈框提醒使用者去開啟權限。

try {

CameraManager.get().openDriver(surfaceHolder);

} catch (Throwable tr){

showOpenCameraErrorDialog();

return;

Pad進入掃碼頁應用崩潰

實際上線時候,發現使用者使用pad的話,一進入掃碼頁面就崩潰。因為我們應用首次進入掃碼頁面預設是開啟裝置閃光燈的。但是pad沒有閃光燈,是以就崩潰了。剛開始用如下方式檢測裝置是否支援閃光燈:

getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)

但是失敗了。原因是好多pad的ROM是從手機ROM改過去的,有可能改得不是那麼徹底。是以在Pad上調用如上代碼進行判斷時,還是會傳回true。這是隻能求助于try catch了。就是在開關閃光燈的時候進行異常捕獲,這樣在Pad上開關閃光燈崩潰問題就解決了。

部分機子拍照後閃光燈自動關閉

部分機子,在閃光燈開啟的狀态下,點選拍照按鈕,閃光燈關閉了。目前沒有找到原因,隻能在子產品中加了特殊處理。針對目前有此問題的手機,拍照完後主動再去開關一次閃光燈,這樣拍照完成後,閃光燈還是可以亮着。隻是在拍照的過程中,會出現閃光燈閃爍的情況。

部分機子拍照完後預覽畫面卡住了

部分機子,當點選拍照完成一張照片的拍攝後,後面就停止不動了。出現這種現象是因為在拍照的時候,Camera會停止Preview,拍照完成後,有的機子可以恢複回來重新Preview,有的則不會。是以隻需在拍照完成後,手動調用一次Camera的startPreview()方法即可。

本文來自網易雲社群,經作者鄭睿授權釋出。

原文位址:Android Camera開發經驗總結以及踩過的那些坑

更多網易研發、産品、營運經驗分享請通路網易雲社群。