做爲移動客戶端開發者來講,對二維碼識別或二維碼生成相關的開發需求確定並不陌生,Android開發二維碼相關的功能一般都會使用或參考大名鼎鼎的zxing庫。而本文則主要是經過源碼分析一下該開源庫掃描二維碼的工做流程,對這塊能有個更深的瞭解。java
首先使用git將項目代碼clone到本地,新建項目,將zxing文件夾中的android
以及core
文件夾代碼覆蓋到對應的目錄下,稍做一些修改便可運行一個簡單的二維碼掃描的示例應用。android
Demo代碼運行起來後,會進入一個掃描的主功能界面,將掃描框對準一個二維碼便可彈出解析結果信息的浮框。經過AndroidManifest.xml
文件中能夠得知這個頁面對應的類爲CaptureActivity.java
,咱們便從這個類開始,分析整個二維碼掃描的流程。git
要分析一個Activity,固然要從它的生命週期所對應的各個方法提及。首先咱們來看它的onCreate()
方法:github
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//保持屏幕常亮
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.capture);
hasSurface = false;
inactivityTimer = new InactivityTimer(this);
beepManager = new BeepManager(this);
ambientLightManager = new AmbientLightManager(this);
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
}
複製代碼
這個方法代碼很少,也很容易看懂,主要就是作一些初始化的工做。InactivityTimer
主要是用來監聽當手機是使用電池而不是充電狀態時,若是5分鐘內沒有作任何操做,則主動finish掉activity。BeepManager
負責掃描到結果後震動或鈴聲相關,AmbientLightManager
則是負責控制閃光燈。數組
繼續往下走看onResume()
方法:ide
@Override
protected void onResume() {
super.onResume();
...
// CameraManager must be initialized here, not in onCreate(). This is necessary because we don't
// want to open the camera driver and measure the screen size if we're going to show the help on
// first launch. That led to bugs where the scanning rectangle was the wrong size and partially
// off screen.
cameraManager = new CameraManager(getApplication());
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
viewfinderView.setCameraManager(cameraManager);
...
SurfaceView 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.
initCamera(surfaceHolder);
} else {
// Install the callback and wait for surfaceCreated() to init the camera.
surfaceHolder.addCallback(this);
}
}
複製代碼
這個方法很重要,初始化了CameraManager
,掃描二維碼毋庸置疑是須要用到相機,經過相機預覽的一幀一幀的圖片,去解析上面可能存在的二維碼信息。而在最後面還初始化了SurfaceView
,經過hasSurface
來決定是走initCamera(surfaceHolder)
仍是surfaceHolder.addCallback(this)
。在上面的onCreate()
中咱們能夠看到hasSurface
被初始化成false
,因此這裏走的應該是else
的代碼塊。CaptureActivity
實現了SurfaceHolder.Callback
接口,所以該方法綁定了surfaceHolder
的回調。當SurfaceView
添加到 activity 中時,會調用surfaceCreated()
:oop
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
}
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}
}
複製代碼
這裏咱們看到會改變hasSurface
的狀態,而後走initCamera(holder)
,和onResume()
中 hasSurface
爲true
時作的操做是同樣的:源碼分析
cameraManager.openDriver(surfaceHolder);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
複製代碼
cameraManager
打開了驅動,而且把本身傳入一個CaptureActivityHandler
對象中去,那這個CaptureActivityHandler
看起來像是一個進行消息通知的 Handler,它的具體做用又是什麼呢?咱們來看看它的構造方法:ui
CaptureActivityHandler(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
CameraManager cameraManager) {
this.activity = activity;
decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}
複製代碼
經過進入CaptureActivityHandler.java
能夠看到該類確實繼承了Handler
,而且在它的構造方法中開啓了一個DecodeThread
的線程,而且調用了cameraManager
的startPreview()
方法:this
Asks the camera hardware to begin drawing preview frames to the screen.
開啓相機預覽後,再看下面的restartPreviewAndDecode()
:
private void restartPreviewAndDecode() {
if (state == State.SUCCESS) {
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
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);
}
}
複製代碼
能夠看到這個 handler 會一直傳遞到一個previewCallback
對象中去,而PreviewCallback
是setOneShotPreviewCallback()
方法的一個回調,setOneShotPreviewCallback
方法上的註釋說明:
Installs a callback to be invoked for the next preview frame in addition to displaying it on the screen. After one invocation, the callback is cleared. This method can be called any time, even when preview is live. Any other preview callbacks are overridden.
使用此方法註冊預覽回調接口時,會將下一幀數據回調給onPreviewFrame()
方法,調用完成後這個回調接口將被銷燬,也就是隻會回調一次預覽幀數據。繼續順着這個方法走下去,看回調方法onPreviewFrame()
:
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
Handler thePreviewHandler = previewHandler;
if (cameraResolution != null && thePreviewHandler != null) {
Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,cameraResolution.y, data);
message.sendToTarget();
previewHandler = null;
} else {
Log.d(TAG, "Got preview callback, but no handler or resolution available");
}
}
複製代碼
這裏將返回的 byte 數組數據和預覽幀的寬高信息經過 handler 進行通知,這個 handler 就是上文中傳過來的decodeThread.getHandler()
,previewMessage
爲R.id.decode
,目的就是把圖片數據拿到該線程中進行解析。咱們跟進到DecodeHandler.java
中查看handleMessage()
方法:
@Override
public void handleMessage(Message message) {
if (message == null || !running) {
return;
}
switch (message.what) {
case R.id.decode:
decode((byte[]) message.obj, message.arg1, message.arg2);
break;
case R.id.quit:
running = false;
Looper.myLooper().quit();
break;
}
}
private void decode(byte[] data, int width, int height) {
long start = System.currentTimeMillis();
//省略具體解析代碼
...
Handler handler = activity.getHandler();//CaptureActivityHandler
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);
Bundle bundle = new Bundle();
bundleThumbnail(source, bundle);
message.setData(bundle);
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}
複製代碼
上面代碼很清晰,DecodeHandler
接收到R.id.decode
的消息後,會調用decode()
方法去解析傳過來的圖片數據。通過一系列解析操做,獲得結果。若是結果爲不爲空,則經過CaptureActivityHandler
將解析成功的消息傳到CaptureActivity
中進行後續解析結果展現。而若是解析結果爲空呢,說明二維碼信息解析失敗了,傳了一個R.id.decode_failed
到CaptureActivityHandler
中:
@Override
public void handleMessage(Message message) {
switch (message.what) {
...
case R.id.decode_failed:
// We're decoding as fast as possible, so when one decode fails, start another.
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
break;
...
}
}
複製代碼
能夠看到,解析失敗時,從新調用requestPreviewFrame
獲取下一幀預覽照片,再拿去解析,知道返回正確結果或者手動退出。
整個過程的時序圖以下: