内容简介:前言:上篇文章已经让源码运行起来了,但是还存在很多与扫描二维码无关的代码,本篇将删除无用的代码只保留与扫码有关的代码,同时分析解码的步骤。本篇文章的目标是分析出解码的步骤,为了不被无关的代码干扰,将会对源码进行精简,只保留与解码有关的代码。主要删减的代码就是识别出二维码的内容后,一些其他的操作,如分享,记录扫描的历史,搜索解析结果等。删除之后的
前言:上篇文章已经让源码运行起来了,但是还存在很多与扫描二维码无关的代码,本篇将删除无用的代码只保留与扫码有关的代码,同时分析解码的步骤。
精简代码
本篇文章的目标是分析出解码的步骤,为了不被无关的代码干扰,将会对源码进行精简,只保留与解码有关的代码。
主要删减的代码就是识别出二维码的内容后,一些其他的操作,如分享,记录扫描的历史,搜索解析结果等。删除之后的 android
模块的结构如下
当然,这不是最终删减的版本,可能在分析源码的时候,发现无用的代码,会继续删除,最终的代码,我会在文末给出Github链接。
源码分析
为了方便理解及记忆 ZXing
解码的步骤,我会边分析边画UML的序列图,最后,分析完解码的步骤,会有一个完整的序列图。现在,从主程序的入口开始分析,就是 CaptureActivity
的 onCreate
方法。
onCreate源码分析
代码如下
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); } 复制代码
这个方法,主要是用来实例化一些对象和获取配置信息,上面的代码中已经有注释,就不再细说。
onResume源码分析
下面继续看 Activity
生命周期的第二个方法,代码如下
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
构造方法中做了什么,代码如下
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”首发
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- WeIdentity 源码分析 | 狗哥解码(一)
- WeIdentity 源码分析 | 狗哥解码(二)
- 开源编解码器 SOLO 源码解读(一):带宽扩展
- netty源码解解析(4.0)-19 ChannelHandler: codec--常用编解码实现
- Netty-解码器架构与常用解码器
- Glide 缓存与解码复用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Twitter Power
Joel Comm / Wiley / 2009-02-17 / USD 24.95
"Arguably, one of the best tomes...Twitter Power is jam-packed with clever ways to start and dominate a marketplace." (Brandopia.typepad.com, March 23rd 2009) “For months I......一起来看看 《Twitter Power》 这本书的介绍吧!