首页 > 代码库 > android 利用ZXING扫描二维码代码分析
android 利用ZXING扫描二维码代码分析
之前给公司做了一个摄影相关的应用,现在要添加二维码扫描的功能,网上找资料后,虽然已经成功集成到app里面,但是总感觉心里没底儿。所以趁这段时间不是很忙,总结一下。
首先是启动扫描的UI类:
1,Activity启动,当然是onCreate方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 | private CaptureActivityHandler handler; private ViewfinderView viewfinderView; private boolean hasSurface; private Vector<BarcodeFormat> decodeFormats; private String characterSet; private InactivityTimer inactivityTimer; private MediaPlayer mediaPlayer; private boolean playBeep; private static final float BEEP_VOLUME = 0 .10f; private boolean vibrate; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_capture); // ViewUtil.addTopView(getApplicationContext(), this, // R.string.scan_card); CameraManager.init(getApplication()); viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view); Button mButtonBack = (Button) findViewById(R.id.button_back); mButtonBack.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Scaner. this .finish(); } }); hasSurface = false ; inactivityTimer = new InactivityTimer( this ); } @Override protected void onResume() { super .onResume(); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view); SurfaceHolder surfaceHolder = surfaceView.getHolder(); if (hasSurface) { initCamera(surfaceHolder); } else { surfaceHolder.addCallback( this ); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } decodeFormats = null ; characterSet = null ; playBeep = true ; AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE); if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { playBeep = false ; } initBeepSound(); vibrate = true ; } @Override protected void onPause() { super .onPause(); if (handler != null ) { handler.quitSynchronously(); handler = null ; } CameraManager. get ().closeDriver(); } @Override protected void onDestroy() { inactivityTimer.shutdown(); super .onDestroy(); } /** * * * * @param @param result * @param @param barcode * @author Administrator * @return void */ public void handleDecode(Result result, Bitmap barcode) { inactivityTimer.onActivity(); playBeepSoundAndVibrate(); String resultString = result.getText(); if (resultString.equals( "" )) { Toast.makeText(Scaner. this , "Scan failed!" , 3000 ).show(); } else { //查询keycode 本地数据库 1,优先查询本地库,2,没有本地库,直接跳到知道链接 //分析出keyCode Log.i( "testMain" , "scan_result=====>" +resultString); String keyCode= "" ; String [] split1; if (resultString.lastIndexOf( "?" )< 0 ){ Intent intent = new Intent( this , InnerBrowser. class ); Bundle bundle = new Bundle(); bundle.putString( "result" , resultString); //bundle.putParcelable("bitmap", barcode); intent.putExtras(bundle); startActivity(intent);Scaner. this .finish(); return ; } String [] attr = resultString.substring(resultString.lastIndexOf( "?" )- 1 , resultString.length()).split( "&" ); for ( String string : attr) { split1 = string.split( "=" ); if (split1[ 0 ].equalsIgnoreCase( "keycode" )){ //找到 if (split1.length== 2 ){ keyCode=split1[ 1 ]; } } } Log.i( "testMain" , "keyCode=====>" +keyCode); if (!StringUtils.isBlank(keyCode)){ AttractionDAO dao= new AttractionDAO(Scaner. this ); Attraction a=dao.findAttrByKeyCode(keyCode); Log.i( "testMain" , "a=====>" +a); if (a!= null ){ Intent it= new Intent(); it.setClass(Scaner. this , UIAttractionDetail. class ); it.putExtra( "a" , a); startActivity(it); } else { Intent intent = new Intent( this , InnerBrowser. class ); Bundle bundle = new Bundle(); bundle.putString( "result" , resultString); //bundle.putParcelable("bitmap", barcode); intent.putExtras(bundle); startActivity(intent); //this.setResult(RESULT_OK, resultIntent); //使用内置浏览器打开网站内容 } } else { Intent intent = new Intent( this , InnerBrowser. class ); Bundle bundle = new Bundle(); bundle.putString( "result" , resultString); //bundle.putParcelable("bitmap", barcode); intent.putExtras(bundle); startActivity(intent); //this.setResult(RESULT_OK, resultIntent); //使用内置浏览器打开网站内容 } } Scaner. this .finish(); } private void initCamera(SurfaceHolder surfaceHolder) { try { CameraManager. get ().openDriver(surfaceHolder); } catch (IOException ioe) { return ; } catch (RuntimeException e) { return ; } if (handler == null ) { handler = new CaptureActivityHandler( this , decodeFormats, characterSet); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { if (!hasSurface) { hasSurface = true ; initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false ; } public ViewfinderView getViewfinderView() { return viewfinderView; } public Handler getHandler() { return handler; } public void drawViewfinder() { viewfinderView.drawViewfinder(); } private void initBeepSound() { if (playBeep && mediaPlayer == null ) { // The volume on STREAM_SYSTEM is not adjustable, and users found it // too loud, // so we now play on the music stream. setVolumeControlStream(AudioManager.STREAM_MUSIC); mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setOnCompletionListener(beepListener); AssetFileDescriptor file = getResources().openRawResourceFd( R.raw.beep); try { mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); file.close(); mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); mediaPlayer.prepare(); } catch (IOException e) { mediaPlayer = null ; } } } private static final long VIBRATE_DURATION = 200L; private void playBeepSoundAndVibrate() { if (playBeep && mediaPlayer != null ) { mediaPlayer.start(); } if (vibrate) { Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); vibrator.vibrate(VIBRATE_DURATION); } } /** * When the beep has finished playing, rewind to queue up another one. */ private final OnCompletionListener beepListener = new OnCompletionListener() { public void onCompletion(MediaPlayer mediaPlayer) { mediaPlayer.seekTo( 0 ); } }; |
从上面代码可以看出,做了三件事儿:加载布局文件;初始化了一个相机管理器;设置按钮监听,初始化了一个InactivityTimer实例;
然后,最重要的是他实现了一个CallBack函数:具体参见:
SurfaceHolder.Callback 译文
此时,
1 | surfaceCreated |
这个方法会调用然后就初始化相机的一些参数:
前两个我们好理解,第三个是干嘛的?
我们先看布局文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <?xml version= "1.0" encoding= "utf-8" ?> <FrameLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "fill_parent" android:layout_height= "fill_parent" > <RelativeLayout android:layout_width= "fill_parent" android:layout_height= "fill_parent" > <SurfaceView android:id= "@+id/preview_view" android:layout_width= "fill_parent" android:layout_height= "fill_parent" android:layout_gravity= "center" /> <com.euc.app.scan.view.ViewfinderView android:id= "@+id/viewfinder_view" android:layout_width= "wrap_content" android:layout_height= "wrap_content" /> < include android:id= "@+id/include1" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:layout_alignParentTop= "true" layout= "@layout/activity_title" /> </RelativeLayout> </FrameLayout> |
可以看到里面有一个自定义的View及surfaceView,
对于我这样的初学者来说,surfaceView 是什么东西?
csdn上看到这个文章
Android中SurfaceView的使用详解
虽然不是很明白,但是大致明白这是个什么东西了。
了解了生命周期之后,我们来看他执行的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | private void initCamera(SurfaceHolder surfaceHolder) { try { CameraManager. get ().openDriver(surfaceHolder); //配置摄像头 } catch (IOException ioe) { return ; } catch (RuntimeException e) { return ; } if (handler == null ) { handler = new CaptureActivityHandler( this , decodeFormats, characterSet); //初始化方法里面开启摄像头预览界面。 } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { if (!hasSurface) { hasSurface = true ; initCamera(holder); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { hasSurface = false ; } |
这个surfaceView 创建出来之后,其实也把摄像头的配置信息以及硬件信息初始化好了。
OK,经过上面一个oncreate以及布局文件的加载,我们已经知道,摄像头预览成功,
这个自定义的View又是干嘛的?我们继续看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | private final int maskColor; private final int resultColor; private final int resultPointColor; private Collection<ResultPoint> possibleResultPoints; private Collection<ResultPoint> lastPossibleResultPoints; boolean isFirst; public ViewfinderView(Context context, AttributeSet attrs) { super (context, attrs); density = context.getResources().getDisplayMetrics().density; //将像素转换成dp ScreenRate = ( int )( 20 * density); paint = new Paint(); Resources resources = getResources(); maskColor = resources.getColor(R.color.viewfinder_mask); resultColor = resources.getColor(R.color.result_view); resultPointColor = resources.getColor(R.color.possible_result_points); possibleResultPoints = new HashSet<ResultPoint>( 5 ); } @Override public void onDraw(Canvas canvas) { //中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改 Rect frame = CameraManager. get ().getFramingRect(); if (frame == null ) { return ; } //初始化中间线滑动的最上边和最下边 if (!isFirst){ isFirst = true ; slideTop = frame.top; slideBottom = frame.bottom; } //获取屏幕的宽和高 int width = canvas.getWidth(); int height = canvas.getHeight(); paint.setColor(resultBitmap != null ? resultColor : maskColor); //画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面 //扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边 canvas.drawRect( 0 , 0 , width, frame.top, paint); canvas.drawRect( 0 , frame.top, frame.left, frame.bottom + 1 , paint); canvas.drawRect(frame.right + 1 , frame.top, width, frame.bottom + 1 , paint); canvas.drawRect( 0 , frame.bottom + 1 , width, height, paint); if (resultBitmap != null ) { // Draw the opaque result bitmap over the scanning rectangle paint.setAlpha(OPAQUE); canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint); } else { //画扫描框边上的角,总共8个部分 paint.setColor(Color.GREEN); canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate, frame.top + CORNER_WIDTH, paint); canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top + ScreenRate, paint); canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right, frame.top + CORNER_WIDTH, paint); canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top + ScreenRate, paint); canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left + ScreenRate, frame.bottom, paint); canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left + CORNER_WIDTH, frame.bottom, paint); canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH, frame.right, frame.bottom, paint); canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate, frame.right, frame.bottom, paint); //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE slideTop += SPEEN_DISTANCE; if (slideTop >= frame.bottom){ slideTop = frame.top; } canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/ 2 , frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/ 2 , paint); //画扫描框下面的字 paint.setColor(Color.WHITE); paint.setTextSize(TEXT_SIZE * density); paint.setAlpha( 0x40 ); paint.setTypeface(Typeface.create( "System" , Typeface.BOLD)); canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint); Collection<ResultPoint> currentPossible = possibleResultPoints; Collection<ResultPoint> currentLast = lastPossibleResultPoints; if (currentPossible.isEmpty()) { lastPossibleResultPoints = null ; } else { possibleResultPoints = new HashSet<ResultPoint>( 5 ); lastPossibleResultPoints = currentPossible; paint.setAlpha(OPAQUE); paint.setColor(resultPointColor); for (ResultPoint point : currentPossible) { canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6 .0f, paint); } } if (currentLast != null ) { paint.setAlpha(OPAQUE / 2 ); paint.setColor(resultPointColor); for (ResultPoint point : currentLast) { canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3 .0f, paint); } } //只刷新扫描框的内容,其他地方不刷新 postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom); } } public void drawViewfinder() { resultBitmap = null ; invalidate(); } /** * Draw a bitmap with the result points highlighted instead of the live * scanning display. * * @param barcode * An image of the decoded barcode. */ public void drawResultBitmap(Bitmap barcode) { resultBitmap = barcode; invalidate(); } public void addPossibleResultPoint(ResultPoint point) { possibleResultPoints.add(point); } |
哦,这个就是定义了一个有动态效果的扫描界面
上面的虽然代码不多,当时我们现在回忆一下步骤:
1,启动activity,加载布局文件,初始化surfaceView,初始化自定义的View(动态界面),
2,在初始化surfaceView的时候,同时初始化了摄像头的参数,初始化的handler处理器,启动了摄像头预览。
问题:那什么时候开始监听扫描二维码的呢?
初始化handler 的时候就开始监听了,看一下其构造函数:
1 2 3 4 5 6 7 8 9 10 11 | public CaptureActivityHandler(Scaner activity, Vector<BarcodeFormat> decodeFormats, String characterSet) { this .activity = activity; decodeThread = new DecodeThread(activity, decodeFormats, characterSet, new ViewfinderResultPointCallback(activity.getViewfinderView())); decodeThread.start(); state = State.SUCCESS; // Start ourselves capturing previews and decoding. CameraManager. get ().startPreview(); restartPreviewAndDecode(); } |
再来一个:上面构造函数new了一个对象,这个对象就是用来监听获取扫描的图像的。
直到获取了二维码图像,调用回调函数就结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | final class DecodeThread extends Thread { public static final String BARCODE_BITMAP = "barcode_bitmap" ; private final Scaner activity; private final Hashtable<DecodeHintType, Object > hints; private Handler handler; private final CountDownLatch handlerInitLatch; DecodeThread(Scaner activity, Vector<BarcodeFormat> decodeFormats, String characterSet, ResultPointCallback resultPointCallback) { this .activity = activity; handlerInitLatch = new CountDownLatch( 1 ); hints = new Hashtable<DecodeHintType, Object >( 3 ); if (decodeFormats == null || decodeFormats.isEmpty()) { decodeFormats = new Vector<BarcodeFormat>(); decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); } hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); if (characterSet != null ) { hints.put(DecodeHintType.CHARACTER_SET, characterSet); } hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback); } Handler getHandler() { try { handlerInitLatch.await(); } catch (InterruptedException ie) { // continue? } return handler; } @Override public void run() { Looper.prepare(); handler = new DecodeHandler(activity, hints); handlerInitLatch.countDown(); Looper.loop(); } } |
回调函数:
1 2 3 4 5 6 7 8 9 10 11 | public void handleDecode(Result result, Bitmap barcode) { inactivityTimer.onActivity(); playBeepSoundAndVibrate(); String resultString = result.getText(); if (resultString.equals( "" )) { Toast.makeText(Scaner. this , "Scan failed!" , 3000 ).show(); } else { //扫描结果的处理。 } Scaner. this .finish(); } |
life日志