前言
最近公司的Android項目需要用到攝像頭做條碼或二維碼的掃描,Google一下,發現一個以 Apache License 2.0 ?開源的 ?ZXing 項目。Zxing項目里的Android實現太過復雜多余東西太多,得對其進行簡化。
前提條件
下載源代碼:點擊 這里
編譯核心庫:Zxing的主頁上有介紹具體步驟,大家也可以參照這篇博文: android 條碼識別軟件開發全解析(續2詳解絕殺!)
導入項目
打開Eclipse 導入 源碼中的 Android 項目,然后右擊項目 選擇“Build path”——》"Add External Archives" 把核心庫 core.jar文件加入到項目中。
此時編譯一下項目,會發現報錯,“? Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute? ”之類的。打開raw 下的Values 發現錯誤是在一個<String>上。這里把 “ preferences_custom_product_search_summary ” 里的 ?%s ?%f ?全部都改成 ?%1$s ?%1$f(因為我們用不到多國語言,建議只保留默認的Value ,其他全部刪除)。
原因:由于新的SDK采用了新版本的aapt(Android項目編譯器),這個版本的aapt編譯起來會比老版本更加的嚴格,然后在Android最新的開發文檔的描述String的部分,已經說明如何去設置 %s 等符號
“If you need to format your strings using String.format(String, Object...) , then you can do so by putting your format arguments in the string resource. For example, with the following resource:
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguements from your application...“
經過以上步驟后項目應該就可以運行了。
但是ZXing的android項目東西太多了,有很多是我們不需要的,得新建另一個項目簡化它。
簡化
在開始前大致介紹一下簡化ZXing需要用到各個包 、類的職責。
- CaptureActivity。這個是啟動Activity 也就是掃描器(如果是第一安裝,它還會跳轉到幫助界面)。
- CaptureActivityHandler 解碼處理類,負責調用另外的線程進行解碼。
- DecodeThread 解碼的線程。
- com.google.zxing.client.android.camera 包,攝像頭控制包。
- ViewfinderView 自定義的View,就是我們看見的拍攝時中間的框框了。
新建另一個項目
新建另一個項目將啟動的Activity命名為CaptureActivity,并導入核心庫。項目新建完成后我們打開 CaptureActivity 的布局文件,我這里為main。把里面的XML修改為:
1
<
FrameLayout
xmlns:android
="http://schemas.android.com/apk/res/android"
2
android:layout_width
="fill_parent"
android:layout_height
="fill_parent"
>
3
<
SurfaceView
android:id
="@+id/preview_view"
4
android:layout_width
="fill_parent"
android:layout_height
="fill_parent"
5
android:layout_centerInParent
="true"
/>
6
7
<
com.Zxing.Demo.view.ViewfinderView
8
android:id
="@+id/viewfinder_view"
android:layout_width
="fill_parent"
9
android:layout_height
="fill_parent"
android:background
="@android:color/transparent"
/>
10
<
TextView
android:layout_width
="wrap_content"
11
android:id
="@+id/txtResult"
12
android:layout_height
="wrap_content"
android:text
="@string/hello"
/>
13
14
?
</
FrameLayout
>
可以看到在XML里面用到了 ViewfinderView 自定義view 。所以新建一個View 的包,然后把:ViewfinderView 和?ViewfinderResultPointCallback 靠到里面(記得對應修改XML里面的包)。
打開?CaptureActivity 覆蓋 onCreate 方法:
1
@Override
2
public
void
onCreate(Bundle savedInstanceState) {
3
super
.onCreate(savedInstanceState);
4
setContentView(R.layout.main);
5
//
初始化 CameraManager
6
?
CameraManager.init(getApplication());
7
8
viewfinderView
=
(ViewfinderView) findViewById(R.id.viewfinder_view);
9
txtResult
=
(TextView) findViewById(R.id.txtResult);
10
hasSurface
=
false
;
11
inactivityTimer
=
new
InactivityTimer(
this
);
12
}
??這里調用到的 CameraManager 類是控制攝像頭的包里的類。新建一個camera包把:com.google.zxing.client.android.camera 里面的類全部拷入,另外我把PlanarYUVLuminanceSource也拷入到這個包里面。根據錯誤的提示來修正代碼,主要是修改正包結構。(整個簡化的流程都是如此:“ 根據錯誤提示,修改代碼 ”)。
在修改的過程中,有很多是關于R 資源的問題,在此我們需要將Values ?里面的兩個xml資源文件拷入項目中:colos.xml 和ids.xml 。 ctrl+b 一下看看error 是不是少了很多。在CameraManager中有些地方需要用到項目的配置,這里需要把配置直接寫入代碼中:
//
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
//
是否使用前燈
//
if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {
//
FlashlightManager.enableFlashlight();
//
}
FlashlightManager.enableFlashlight();
? 使用攝像頭需要加入相應的權限:
<
uses
-
permission android:name
=
"
android.permission.CAMERA
"
></
uses
-
permission
>
<
uses
-
permission android:name
=
"
android.permission.WRITE_EXTERNAL_STORAGE
"
></
uses
-
permission
>
<
uses
-
feature android:name
=
"
android.hardware.camera
"
/>
<
uses
-
feature android:name
=
"
android.hardware.camera.autofocus
"
/>
<
uses
-
permission android:name
=
"
android.permission.VIBRATE
"
/>
<
uses
-
permission android:name
=
"
android.permission.FLASHLIGHT
"
/>
當View 和 camera 包里的錯誤修正完成后,我們繼續來看CaptureActivity。
覆蓋onResume方法初始化攝像頭:
@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
; }
1
private
void
initCamera(SurfaceHolder surfaceHolder) {
2
try
{
3
CameraManager.get().openDriver(surfaceHolder);
4
}
catch
(IOException ioe) {
5
return
;
6
}
catch
(RuntimeException e) {
7
return
;
8
}
9
if
(handler
==
null
) {
10
handler
=
new
CaptureActivityHandler(
this
, decodeFormats,
11
characterSet);
12
}
13
}
@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
; }
initCamera () 方法用于初始化攝像頭,如果排除了所有的error ,運行項目時就可以看到大致掃描界面了。? surfaceHolder.addCallback( this );表示讓CaptureActivity實現其callback接口。
handler = new CaptureActivityHandler(this, decodeFormats, characterSet) 用于進行掃描解碼處理。
解碼
上面的步驟主要都是用于對攝像頭的控制,而解碼的真正工作入口是在CaptureActivityHandler 里面的。新建一個Decoding包把以下文件拷入包中:
- CaptureActivityHandler
- DecodeFormatManager
- DecodeHandler
- DecodeThread
- FinishListener
- InactivityTimer
- Intents
由于我們的包結構和Zxing 項目的有所不同所以需要注意一下類的可訪問性
同樣開始ctrl+B 編譯一下,然后開始修正錯誤。
在CaptureActivityHandler 里 把?handleMessage 里的部分方法先注釋掉如:“decode_succeeded ”分支,這是解碼成功時調用 CaptureActivity 展示解碼的結果。
在DecodeThread 類里,修改部分涉及Preference配置的代碼:
DecodeThread(CaptureActivity activity, Vector
<
BarcodeFormat
>
decodeFormats, String characterSet, ResultPointCallback resultPointCallback) {
this
.activity
=
activity; handlerInitLatch
=
new
CountDownLatch(
1
); hints
=
new
Hashtable
<
DecodeHintType, Object
>
(
3
);
//
//
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 = new Vector<BarcodeFormat>();
//
if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D, true)) {
//
decodeFormats.addAll(DecodeFormatManager.ONE_D_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
(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); }
這里是設置 解碼的類型,我們現在默認將所有類型都加入。
錯誤類型基本上都是:包結構、PreferencesActivity 的配置 、類可訪問性的問題。根據錯誤提示耐心把錯誤解決。
返回解碼結果
? 還記得在?CaptureActivityHandler 的 messagehandler 里注銷掉的Case分支嗎?現在CaptureActivity 里實現它。
public
void
handleDecode(Result obj, Bitmap barcode) { inactivityTimer.onActivity(); viewfinderView.drawResultBitmap(barcode); playBeepSoundAndVibrate(); txtResult.setText(obj.getBarcodeFormat().toString()
+
"
:
"
+
obj.getText()); }
最后
ZXing的簡化已基本完成,有幾位是可以運行成功的?呵呵。
下面是CaptureActivity的源碼:
public
class
CaptureActivity
extends
Activity
implements
Callback {
private
CaptureActivityHandler handler;
private
ViewfinderView viewfinderView;
private
boolean
hasSurface;
private
Vector
<
BarcodeFormat
>
decodeFormats;
private
String characterSet;
private
TextView txtResult;
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.main);
//
初始化 CameraManager
CameraManager.init(getApplication()); viewfinderView
=
(ViewfinderView) findViewById(R.id.viewfinder_view); txtResult
=
(TextView) findViewById(R.id.txtResult); 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(); }
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(); }
public
void
handleDecode(Result obj, Bitmap barcode) { inactivityTimer.onActivity(); viewfinderView.drawResultBitmap(barcode); playBeepSoundAndVibrate(); txtResult.setText(obj.getBarcodeFormat().toString()
+
"
:
"
+
obj.getText()); }
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
); } };
簡化過的包結構圖:
簡化后的ZXing 更加方便我們了解ZXing項目 是如何解碼的。只要仔細查看源碼,進行單點跟蹤調試,相信大家很容易能理解。
顧客是上帝
?? 很多人留言要源碼, 其實我這不是什么源碼,我只是把ZXing的東西簡化了一下而已。事實上我也不喜歡直接放源碼項目,這樣大家就不想讀ZXing的源碼了。
下面是我簡化的版本: Zxing簡化
<script type="text/javascript"></script>
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

