引言
最近公司需要用到二维码扫描识别的功能,回去翻看以前的使用,发现搞了很久都没有弄明白。上网搜索更是一堆杂乱的信息,很难去抽离自己需要的信息。于是,狠下心来跟着调用的思路,一步一步的分析源码。最后有种豁然开朗的感觉,哈哈
用法
1.1 添加core-3.0.0.jar
1.2 配置权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
1.3 通过startActivityForResult 启动CaptureActivity
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN);
1.4 在onActivityResult中接收回调
分析
1 CaptureActiivity分析
1.1 在onCreate中做了一些初始化工作
1.1.1 设置保持屏幕唤醒状态
1.1.2 创建一个Timer:如果设备使用电池供电,一段时间不活动之后结束activity
1.1.3创建一个BeepManager:主要用于扫描成功后提示声的
1.2 在onResume中
1.2.1 初始化camera:使用CameraManager,这个类主要提供关于camera的一些基本操作
1.2.2 初始化ViewfinderView:覆盖在预览图(SurfaceView)上方的一个view,主要作用是增加了部分透明的取景框和扫描动画;我们可以根据需要改变取景框的大小形状,改变扫描动画等,即可以自定义
1.2.3 初始化SurfaceView
2 CaptureActivityHandler分析
2.1 在初始化camera时,创建了一个CaptureActivityHandler:
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provided");
}
if (cameraManager.isOpen()) {
return;
}
try {
// 打开Camera硬件设备
cameraManager.openDriver(surfaceHolder);
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats,
decodeHints, characterSet, cameraManager);
}
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
2.2 初始化过程分析
public 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;
this.cameraManager = cameraManager;
cameraManager.startPreview();
restartPreviewAndDecode();
}
我们来看下这几句代码的执行:
2.2.1 启动一个DecodeThread:用于解析二维码的子线程,下一节再详细分析
decodeThread = new DecodeThread(activity, decodeFormats, baseHints,characterSet, new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
2.2.2 cameraManager.startPreview();:开始拍摄预览,内部主要就是调用了camera的startPreview()方法
2.2.3 restartPreviewAndDecode();:开始解码,下边进入这个方法分析
cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);我们详细看这个方法的实现
1 previewCallback.setHandler(handler, message);
该PreviewCallback主要实现了Camera.PreviewCallback接口,提供了一个setHandler方法,当回调onPreviewFrame这个方法时,通过设置的Handler来派发消息2 theCamera.setOneShotPreviewCallback(previewCallback);:利用Camera对象上的这个方法注册Camera.PreviewCallback,从而当下一幅预览图像可用时调用一次onPreviewFrame
3 这两步主要作用就是获取一帧的数据,将这帧数字放在Message中,然后通过decodeThread.getHandler()获取到的handler发送出去;该封装了帧数据的Message包含了一个值为R.id.decode的what(主要用于消息的区分)
3 DecodeThread分析
3.1 DecodeThread继承Thread,是一个专门用于解码的线程
3.2 既然继承Thread,我们可以集中看它的run()方法做了什么操作
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
Log.i("test", "跑进来啦!!");
handlerInitLatch.countDown();
Looper.loop();
}
创建了一个DecodeHandler,该类才真正实现decode的功能
4 DecodeHandler分析
decodeThread.getHandler()获取到的对象为DecodeHandler,该类继承Handler,主要用于DecodeThread解码线程的消息分发和处理。下边看下消息的处理
@Override
public void handleMessage(Message message) {
if (!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;
}
}
4.1 通过对比message.what,会进入R.id.decode这个分支,这里就是解码真正实现的地方,下边进去decode()这个方法
// 上边是一堆解码的代码,这里不纠结
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);
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();
}
}
4.2 看到解码完成,会通过tivity.getHandler()获取Handler对象,该对象的实例为CaptureActivityHandler,通过它去分发和处理消息,这里会发送两种消息
5重入CaptureActivityHandler
分析消息的处理(handleMessage()方法)
当为R.id.decode_succeeded时,这是解码成功
1 生成一个bitmap
2 通过调用activity.handleDecode将bitmap 和Result对象返回给activity处理
当为R.id. decode_failed时,这是解码失败
继续调用cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);方法获取一帧数字,发送给DecodeThread线程解码,不断重复该步骤
6 CountDownLatch使用
6.1 在DecodeThread创建了CountDownLatch:
handlerInitLatch = new CountDownLatch(1);
6.2 分析作用
public Handler getHandler() {
try {
handlerInitLatch.await();
} catch (InterruptedException ie) {
// continue?
}
return handler;
}
6.2.1 可以看到每次调用getHandler()时,都会调用handlerInitLatch.await();:调用此方法会一直阻塞当前线程,直到计时器的值为0;而创建CountDownLatch时,传入的是1,所以计数器的值只会从1-0
6.2.2 那在哪里调用调用了getHandler()方法呢?全局搜索下,有三个地方
1 CaptureActivity的onPause()方法中:主要为了停止消息的派发
2 识别失败、3restartPreviewAndDecode:重新预览识别:
这两个方法都是调用了cameraManager.requestPreviewFrame(decodeThread.getHandler(),R.id.decode);
这是重新扫描
于是,我猜测这里使用CountDownLatch,主要是为了让获取到一帧数据前,先保证DecodeHandler已经被创建,可用于解码
7 总结
其实,二维码识别的功能已经封装好了。对于一些界面的变化,我们可以修改CaptureActivity和ViewfinderView来实现
了解清楚整个流程,更利于我们以后的应用