源碼地址
谷歌官方有一個關(guān)于二維碼的zxing項目,地址為zxing,但是這個庫對于安卓應(yīng)用來說太大了。有一個開發(fā)者將這個庫進(jìn)行了簡化,地址為SimpleZxing,使得可以非常方便地使用在安卓工程中。
源碼解析
我們可以通過以下代碼來跳轉(zhuǎn)到掃描二維碼界面:
private void startCaptureActivityForResult() {
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
Bundle bundle = new Bundle();
bundle.putBoolean(CaptureActivity.KEY_NEED_BEEP, CaptureActivity.VALUE_BEEP);
bundle.putBoolean(CaptureActivity.KEY_NEED_VIBRATION, CaptureActivity.VALUE_VIBRATION);
bundle.putBoolean(CaptureActivity.KEY_NEED_EXPOSURE, CaptureActivity.VALUE_NO_EXPOSURE);
bundle.putByte(CaptureActivity.KEY_FLASHLIGHT_MODE, CaptureActivity.VALUE_FLASHLIGHT_OFF);
bundle.putByte(CaptureActivity.KEY_ORIENTATION_MODE, CaptureActivity.VALUE_ORIENTATION_AUTO);
bundle.putBoolean(CaptureActivity.KEY_SCAN_AREA_FULL_SCREEN, CaptureActivity.VALUE_SCAN_AREA_FULL_SCREEN);
bundle.putBoolean(CaptureActivity.KEY_NEED_SCAN_HINT_TEXT, CaptureActivity.VALUE_SCAN_HINT_TEXT);
intent.putExtra(CaptureActivity.EXTRA_SETTING_BUNDLE, bundle);
startActivityForResult(intent, CaptureActivity.REQ_CODE);
}
很容易就能看出,這里僅僅是傳遞了一些參數(shù),跳轉(zhuǎn)到了一個新的activity,那么我們進(jìn)入到這個新的activity中去查看。由于onCreate中沒有重要的代碼,所以我們查看onResume方法。
@Override
protected void onResume() {
super.onResume();
if (orientationMode == VALUE_ORIENTATION_AUTO) {
myOrientationDetector.enable();
}
cameraManager = new CameraManager(getApplication(), needExposure, needFullScreen);
//viewfinderView實(shí)際上就是我們需要繪出的掃描二維碼的框,以及正在掃描的線
viewfinderView = findViewById(R.id.viewfinder_view);
viewfinderView.setCameraManager(cameraManager);
viewfinderView.setNeedDrawText(needScanHintText);
viewfinderView.setScanAreaFullScreen(needFullScreen);
handler = null;
beepManager.updatePrefs();
if (ambientLightManager != null) {
ambientLightManager.start(cameraManager);
}
//用來實(shí)時顯示相機(jī)的圖像
SurfaceView surfaceView = findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (hasSurface) {
// The activity was paused but not stopped, so the surface still exists. Therefore
// surfaceCreated() won't be called, so init the camera here.
//初始化并且打開相機(jī)
initCamera(surfaceHolder);
} else {
// Install the callback and wait for surfaceCreated() to init the camera.
//
surfaceHolder.addCallback(this);
}
}
與之對應(yīng)的xml界面為:
<SurfaceView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.acker.simplezxing.view.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
看到這里,我想大家應(yīng)該明白了。用SurfaceView來顯示相機(jī)的圖像,然后用viewfinderView來畫出一個藍(lán)色的掃描矩形、掃描射線等。這樣就能夠?qū)ο鄼C(jī)進(jìn)行定制,接下來,我們來看一下其中的細(xì)節(jié)吧~
首先來看前文中提到的initCamera方法。
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) { //如果用來顯示相機(jī)圖像的容器為null的話,就跑出異常
throw new IllegalStateException("No SurfaceHolder provided");
}
if (cameraManager.isOpen()) { //如果已經(jīng)開啟了相機(jī),那么就返回
//Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
return;
}
try {
//重要方法!
cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
//重要方法!
handler = new CaptureActivityHandler(this, cameraManager);
}
} catch (Exception e) {
//Log.w(TAG, e);
returnResult(RESULT_CANCELED, getString(R.string.msg_camera_framework_bug));
}
}
public synchronized void openDriver(SurfaceHolder holder) throws IOException {
OpenCamera theCamera = camera;
if (theCamera == null) {
//使用系統(tǒng)api,打開相機(jī)
theCamera = OpenCameraInterface.open(OpenCameraInterface.NO_REQUESTED_CAMERA);
if (theCamera == null) {
throw new IOException("Camera.open() failed to return object from driver");
}
camera = theCamera;
}
if (!initialized) {
//設(shè)置掃描二維碼的區(qū)域位置和大小
initialized = true;
configManager.initFromCameraParameters(theCamera);
if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) {
setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight);
requestedFramingRectWidth = 0;
requestedFramingRectHeight = 0;
}
}
Camera cameraObject = theCamera.getCamera();
Camera.Parameters parameters = cameraObject.getParameters();
String parametersFlattened = parameters == null ? null : parameters.flatten(); // Save these, temporarily
try {
configManager.setDesiredCameraParameters(theCamera, false);
} catch (RuntimeException re) {
// Driver failed
//Log.w(TAG, "Camera rejected parameters. Setting only minimal safe-mode parameters");
//Log.i(TAG, "Resetting to saved camera params: " + parametersFlattened);
// Reset:
if (parametersFlattened != null) {
parameters = cameraObject.getParameters();
parameters.unflatten(parametersFlattened);
try {
cameraObject.setParameters(parameters);
configManager.setDesiredCameraParameters(theCamera, true);
} catch (RuntimeException re2) {
// Well, darn. Give up
//Log.w(TAG, "Camera rejected even safe-mode parameters! No configuration");
}
}
}
//將相機(jī)獲取的畫面展示在容器中
cameraObject.setPreviewDisplay(holder);
}
這里我們就知道了如何將相機(jī)獲取的畫面展示在容器中,那么現(xiàn)在最重要的問題也就是:怎么樣才能夠?qū)崟r地獲取照片,并且檢測照片中是否存在二維碼呢?讓我們回到initCamera方法中找答案,可以看到另一個重要的代碼:handler = new CaptureActivityHandler(this, cameraManager); 跟進(jìn)去構(gòu)造函數(shù):
CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager) {
this.activity = activity;
//創(chuàng)建并開啟一個專門用來檢測二維碼的線程,防止主線程卡頓
decodeThread = new DecodeThread(activity, new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
//在startPreview方法執(zhí)行之后,SurfaceView才真的開始顯示照相機(jī)內(nèi)容
cameraManager.startPreview();
//重要方法
restartPreviewAndDecode();
}
private void restartPreviewAndDecode() {
if (state == State.SUCCESS) {
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
//重新繪制藍(lán)色邊緣矩形、掃描線等
activity.drawViewfinder();
}
}
public synchronized void requestPreviewFrame(Handler handler, int message) {
OpenCamera theCamera = camera;
if (theCamera != null && previewing) {
previewCallback.setHandler(handler, message);
theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
}
}
可以看出來,setOneShotPreviewCallback方法將照相機(jī)的某一時刻的截屏發(fā)送給DecodeHandler,所以接下來的邏輯應(yīng)該在它的handleMessage方法中:
@Override
public void handleMessage(Message message) {
if (!running) {
return;
}
if (message.what == R.id.decode) {
decode((byte[]) message.obj, message.arg1, message.arg2);
} else if (message.what == R.id.quit) {
running = false;
Looper.myLooper().quit();
}
}
正常情況下,邏輯應(yīng)該跳轉(zhuǎn)到decode方法中:
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
if (width < height) {
// portrait
byte[] rotatedData = new byte[data.length];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++)
rotatedData[y * width + width - x - 1] = data[y + x * height];
}
data = rotatedData;
}
Result rawResult = null;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't Log the barcode contents for security.
long end = System.currentTimeMillis();
//Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}
無論這個圖片中是否被掃描到了二維碼,都會轉(zhuǎn)移到activity.getHandler()方法所獲取的CaptureActivityHandler類中,所以看看它的handleMessage方法。
@Override
public void handleMessage(Message message) {
if (message.what == R.id.decode_succeeded) {
state = State.SUCCESS;
//檢索二維碼成功時
activity.handleDecode((Result) message.obj);
} else if (message.what == R.id.decode_failed) {// We're decoding as fast as possible, so when one decode fails, start another.
state = State.PREVIEW;
//當(dāng)沒有檢索到二維碼時,重新進(jìn)行剛才我們所分析的流程
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
}
}
public void handleDecode(Result rawResult) {
//播放掃描成功的聲音
beepManager.playBeepSoundAndVibrate();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//將掃描結(jié)果返回到上一個頁面
returnResult(RESULT_OK, rawResult.getText());
}
哈,這樣就把兩個最重要的部分分析完了!另一個我想提的是,這個代碼中有自動對焦的功能,它的實(shí)現(xiàn)方法為:
public synchronized void setTorch(boolean newSetting) {
OpenCamera theCamera = camera;
if (theCamera != null) {
if (newSetting != configManager.getTorchState(theCamera.getCamera())) {
boolean wasAutoFocusManager = autoFocusManager != null;
if (wasAutoFocusManager) {
autoFocusManager.stop();
autoFocusManager = null;
}
configManager.setTorch(theCamera.getCamera(), newSetting);
if (wasAutoFocusManager) {
//實(shí)現(xiàn)自動對焦
autoFocusManager = new AutoFocusManager(theCamera.getCamera());
autoFocusManager.start();
}
}
}
}
希望大家有所收獲~