前言:上篇文章已經讓源碼運行起來了,可是還存在不少與掃描二維碼無關的代碼,本篇將刪除無用的代碼只保留與掃碼有關的代碼,同時分析解碼的步驟。java
本篇文章的目標是分析出解碼的步驟,爲了避免被無關的代碼干擾,將會對源碼進行精簡,只保留與解碼有關的代碼。android
主要刪減的代碼就是識別出二維碼的內容後,一些其餘的操做,如分享,記錄掃描的歷史,搜索解析結果等。刪除以後的android
模塊的結構以下git
爲了方便理解及記憶ZXing
解碼的步驟,我會邊分析邊畫UML的序列圖,最後,分析完解碼的步驟,會有一個完整的序列圖。如今,從主程序的入口開始分析,就是CaptureActivity
的onCreate
方法。github
代碼以下數組
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;
//控制activity在一段時間無操做自動finish
inactivityTimer = new InactivityTimer(this);
//管理掃碼後是否有聲音和震動
beepManager = new BeepManager(this);
//用來根據環境的明暗,自動開啓關閉閃光燈
ambientLightManager = new AmbientLightManager(this);
//加載一些默認的配置
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
}
複製代碼
這個方法,主要是用來實例化一些對象和獲取配置信息,上面的代碼中已經有註釋,就再也不細說。ide
下面繼續看Activity
生命週期的第二個方法,代碼以下oop
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());//1
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
viewfinderView.setCameraManager(cameraManager);
resultView = findViewById(R.id.result_view);
statusView = (TextView) findViewById(R.id.status_view);
handler = null;
//省略不重要代碼
//.....
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();//2
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);//3
}
}
複製代碼
上面代碼中的一些語句標記了序號,如今來看序號「1」處的代碼都作了什麼,進入CameraManager
類的構造方法中,代碼以下源碼分析
public CameraManager(Context context) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);//1.1
previewCallback = new PreviewCallback(configManager);//1.2
}
複製代碼
繼續跟進代碼,看下「1.1」處的代碼,CameraConfigurationManager
構造方法中作了什麼,代碼以下post
CameraConfigurationManager(Context context) {
this.context = context;
}
複製代碼
上面的代碼就是注入了context
。如今看「1.2」處的代碼,PreviewCallback
構造方法中作了什麼,代碼以下優化
PreviewCallback(CameraConfigurationManager configManager) {
this.configManager = configManager;
}
複製代碼
上面的這段代碼能夠看出,在PreviewCallback
構造方法中,將CameraConfigurationManager
類的實例,注入到了PreviewCallback
類中。跟完了「1」處的代碼,繼續往下看onResume
方法中的代碼,這裏介紹一下「2」處的代碼,SurfaceHolder
的做用,介紹以下
SurfaceHolder是一個接口,其做用就像一個Surface的監聽器。提供訪問和控制SurfaceView背後的Surface 相關的方法 (providingaccess and control over this SurfaceView's underlying surface),它經過三個回調方法,讓咱們能夠感知到Surface的建立、銷燬或者改變。
繼續往下看代碼,由於在onCreate
方法中,hasSurface
值爲false
,因此,會進入else
語句,也就是「3」處的代碼,這句代碼的做用就是綁定Surface
的監聽器,就是在當前的Activity
中綁定Surface
生命週期的回調方法。 SurfaceHolder.Callback
中定義了三個接口方法:
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height);
當surface發生任何結構性的變化時(格式或者大小),該方法就會被當即調用。
public void surfaceCreated(SurfaceHolder holder);
當surface對象建立後,該方法就會被當即調用。
public void surfaceDestroyed(SurfaceHolder holder);
當surface對象在將要銷燬前,該方法會被當即調用。
知道了這三個方法在何時會調用,因此,這裏綁定回調以後,會首先調用surfaceCreated
這個回調方法,看下這個方法中的代碼,以下
public void surfaceCreated(SurfaceHolder holder) {
if (holder == null) {
Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!");
}
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}
}
複製代碼
繼續跟進代碼,看下initCamera(holder);
方法都作了什麼,代碼以下
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
//相機已經打開
if (cameraManager.isOpen()) {
Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
return;
}
try {
//打開相機並初始化硬件參數
cameraManager.openDriver(surfaceHolder);
// 實例化一個handler並開始預覽.
if (handler == null) {
//3.1
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
decodeOrStoreSavedBitmap(null, null);
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
// Barcode Scanner has seen crashes in the wild of this variety:
// java.?lang.?RuntimeException: Fail to connect to camera service
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
複製代碼
「3.1」處的代碼實例化了一個CaptureActivityHandler
,看下CaptureActivityHandler
的構造方法,代碼以下
CaptureActivityHandler(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
CameraManager cameraManager) {
this.activity = activity;//注入activity
//新建一個線程並啓動
//3.1.1
decodeThread = new DecodeThread(activity, decodeFormats, baseHints, characterSet,
new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// 注入cameraManager
this.cameraManager = cameraManager;
//要求相機硬件開始將預覽幀繪製到屏幕上
cameraManager.startPreview();
//開始預覽,而且解碼
//3.1.2
restartPreviewAndDecode();
}
複製代碼
如今來看「3.1.1」處新建線程都作了什麼,DecodeThread
構造方法的代碼以下
DecodeThread(CaptureActivity activity,
Collection<BarcodeFormat> decodeFormats,
Map<DecodeHintType,?> baseHints,
String characterSet,
ResultPointCallback resultPointCallback) {
this.activity = activity;
handlerInitLatch = new CountDownLatch(1);
hints = new EnumMap<>(DecodeHintType.class);
if (baseHints != null) {
hints.putAll(baseHints);
}
// The prefs can't change while the thread is running, so pick them up once here.
if (decodeFormats == null || decodeFormats.isEmpty()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_PRODUCT, true)) {
decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D_INDUSTRIAL, true)) {
decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) {
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) {
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_AZTEC, false)) {
decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
}
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_PDF417, false)) {
decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
}
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
if (characterSet != null) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
}
複製代碼
上面的代碼能夠發現,在線程的構造方法中主要是設置解碼的格式。
若是想提高掃碼速度,這裏是一個能夠優化的點,能夠不用設置這麼多格式,只設置與本身業務有關的解碼格式。
都知道線程運行,會調用run
方法,看下run
方法中的代碼,以下
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}
複製代碼
這段代碼的做用是在子線程中實例化了一個Handler
與當前線程綁定。繼續跟進代碼,看下DecodeHandler
的構造方法都作了什麼,代碼以下
DecodeHandler(CaptureActivity activity, Map<DecodeHintType,Object> hints) {
multiFormatReader = new MultiFormatReader();
multiFormatReader.setHints(hints);
this.activity = activity;
}
複製代碼
這段代碼的做用就是將線程構造方法中設置的hints
設置給實例化的MultiFormatReader
,同時注入CaptureActivity
的實例。
MultiFormatReader
類的做用是一個便利類,是大多數用途的庫的主要入口點。
分析到這裏,能夠畫出以下的序列圖
接着來分析「3.1.2」處的代碼,調用的方法代碼以下
private void restartPreviewAndDecode() {
if (state == State.SUCCESS) {
state = State.PREVIEW;
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
activity.drawViewfinder();
}
}
複製代碼
重點看下cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
這句代碼,看下CameraManager
中的requestPreviewFrame
方法作了什麼,代碼以下
/** * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, * respectively. * * @param handler The handler to send the message to. * @param message The what field of the message to be sent. */
public synchronized void requestPreviewFrame(Handler handler, int message) {
OpenCamera theCamera = camera;
if (theCamera != null && previewing) {
previewCallback.setHandler(handler, message);
theCamera.getCamera().setOneShotPreviewCallback(previewCallback);
}
}
複製代碼
看下這個方法的介紹,意思是解析一個預覽幀,解析的數據是一個字節數組,放進了message.obj
中,寬和高放到了message.arg1
和message.arg2
中,而後將message
返回給傳進來的handler
,由前文可只,這個handler
是DecodeHandler
的實例。 好了,跟到這個方法,就不繼續網下跟了,這裏能夠猜想一下,一幀圖像解析後會回調PreviewCallback
類中的onPreviewFrame
方法,這個方法的代碼以下
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");
}
}
複製代碼
不難看出,這裏是把解析後的數據發送給了DecodeHandler
,最終會調用DecodeHandler
類中的handleMessage
方法,代碼以下
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;
}
}
複製代碼
而上面代碼中的message.what
的值恰好是R.id.decode
,天然就進入了decode
方法。 分析到這裏,在來看下如今的時序圖,以下
CameraManager
和PreviewCallback
類。surface
回調方法中,初始化相機設置相機的配置參數。DecodeThread
線程並啓動。爲此線程綁定一個DecodeHandler
。byte
數組傳回DecodeHandler
進行解碼。 上文已經完成相機獲取圖像到進行解碼的源碼分析,從前面的分析能夠知道,解碼的方法是在子線程中執行的,那麼子線程解碼成功,怎麼通知主線程能,其實很是簡單,能夠從DecodeHandler
中的decode
方法中知道答案,decode
方法的代碼以下
private void decode(byte[] data, int width, int height) {
long start = System.nanoTime();
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();
}
}
//獲取了CaptureActivity中的handler
Handler handler = activity.getHandler();
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.nanoTime();
Log.d(TAG, "Found barcode in " + TimeUnit.NANOSECONDS.toMillis(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發送給CaptureActivity中的handler
message.sendToTarget();
}
} else {
if (handler != null) {
Message message = Message.obtain(handler, R.id.decode_failed);
message.sendToTarget();
}
}
}
複製代碼
從上面的代碼中能夠發現將解碼的結果發送給主線程是利用Android的Handler機制。
由於本文的目標是掌握解碼的步驟,因此一些細節性的代碼並無進行分析,如配置相機的參數,掃碼後的圖像是豎屏仍是橫屏,怎樣獲取最佳的圖像數據進行解析等。細節性的東西將會放到後面的文章進行講解,後面的文章還會分析具體是怎麼獲取圖像上的二維碼並進行解碼的。
本文已由公衆號「AndroidShared」首發