首页 > 代码库 > 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日志