源码地址
谷歌官方有一个关于二维码的zxing项目,地址为zxing,但是这个库对于安卓应用来说太大了。有一个开发者将这个库进行了简化,地址为SimpleZxing,使得可以非常方便地使用在安卓工程中。
源码解析
我们可以通过以下代码来跳转到扫描二维码界面:
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);
}
很容易就能看出,这里仅仅是传递了一些参数,跳转到了一个新的activity,那么我们进入到这个新的activity中去查看。由于onCreate中没有重要的代码,所以我们查看onResume方法。
@Override
protected void onResume() {
super.onResume();
if (orientationMode == VALUE_ORIENTATION_AUTO) {
myOrientationDetector.enable();
}
cameraManager = new CameraManager(getApplication(), needExposure, needFullScreen);
//viewfinderView实际上就是我们需要绘出的扫描二维码的框,以及正在扫描的线
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);
}
//用来实时显示相机的图像
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);
}
}
与之对应的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" />
看到这里,我想大家应该明白了。用SurfaceView来显示相机的图像,然后用viewfinderView来画出一个蓝色的扫描矩形、扫描射线等。这样就能够对相机进行定制,接下来,我们来看一下其中的细节吧~
首先来看前文中提到的initCamera方法。
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) { //如果用来显示相机图像的容器为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);
// 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) {
//使用系统api,打开相机
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) {
//设置扫描二维码的区域位置和大小
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");
}
}
}
//将相机获取的画面展示在容器中
cameraObject.setPreviewDisplay(holder);
}
这里我们就知道了如何将相机获取的画面展示在容器中,那么现在最重要的问题也就是:怎么样才能够实时地获取照片,并且检测照片中是否存在二维码呢?让我们回到initCamera方法中找答案,可以看到另一个重要的代码:handler = new CaptureActivityHandler(this, cameraManager); 跟进去构造函数:
CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager) {
this.activity = activity;
//创建并开启一个专门用来检测二维码的线程,防止主线程卡顿
decodeThread = new DecodeThread(activity, new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
this.cameraManager = cameraManager;
//在startPreview方法执行之后,SurfaceView才真的开始显示照相机内容
cameraManager.startPreview();
//重要方法
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);
}
}
可以看出来,setOneShotPreviewCallback方法将照相机的某一时刻的截屏发送给DecodeHandler,所以接下来的逻辑应该在它的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();
}
}
正常情况下,逻辑应该跳转到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();
}
}
}
无论这个图片中是否被扫描到了二维码,都会转移到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;
//当没有检索到二维码时,重新进行刚才我们所分析的流程
cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
}
}
public void handleDecode(Result rawResult) {
//播放扫描成功的声音
beepManager.playBeepSoundAndVibrate();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将扫描结果返回到上一个页面
returnResult(RESULT_OK, rawResult.getText());
}
哈,这样就把两个最重要的部分分析完了!另一个我想提的是,这个代码中有自动对焦的功能,它的实现方法为:
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) {
//实现自动对焦
autoFocusManager = new AutoFocusManager(theCamera.getCamera());
autoFocusManager.start();
}
}
}
}
希望大家有所收获~