最近在用ZXing這個開源庫做二維碼的掃描模塊,開發過程的一些代碼修改和裁剪的經驗和大家分享一下。
建議:
如果需要集成到自己的app上,而不是做一個demo,不推薦用ZXing的Android外圍開發模塊,只用核心的core目錄的代碼就好了。android和android-core的代碼設計的不好,擴展性太差了(我在后期開發新需求的時候改修改了很多CameraManager的邏輯)。只使用core目錄的集成方法很簡單,參考:
中的decode函數,把攝像頭數據轉換成二值化圖像,然后傳入MultiFormatReader解碼。
以下是正文:
我的代碼庫:(基於官方3.2.0)
https://github.com/SickWorm/ZXingDialog
代碼沒有在github維護,所以沒有log。但是所有修改的地方我都加上了“@ch”的注釋,以方便定位
官方源碼:
https://github.com/zxing/zxing
實現功能:
1、功能裁剪(只保留QRCode二維碼掃描功能,去掉條形碼等其他碼掃描功能)
2、移除資源依賴,提供Dialog形式的掃碼功能
3、API兼容(源碼只兼容4.0以上,現兼容至2.1)
4、轉換為豎屏(源碼為橫屏)
5、掃碼速度優化(主要分三點,現只完成了一點)
6、設備兼容(針對低分辨率設備)
本文還會提到:
7、自定義界面
8、優化調試方法
1、建立工程
ZXing源碼並沒有提供一個完整的實例工程給我們使用,構建一個工程我們需要源碼下的三個文件夾的文件:
core/
android-core/
android/
大概步驟如下:
1、創建一個新工程
2、把android目錄下的所有文件覆蓋到新工程(內含有資源文件和AndroidManifest.xml等構建app所需的文件)
3、把android-core所有Java文件拷入到src目錄下(注意!android-core中的src文件夾需要進行一些改動,原來路徑是android-core\src\main\java\com\google\zxing\client\android\camera,我們要把中間的main\java兩層文件夾去掉,不然在Eclipse中無法識別包路徑)
4、把core目錄下的所有Java文件拷入到src目錄下(注意!和步驟3一樣需要去掉main\java兩層文件夾)。這樣ZXing已經可以運行了,我的src目錄是這樣的:
可以直接運行,效果還不錯。如果你遇到一些錯誤,有可能是編譯的JDK版本低於1.7導致的。源碼里使用了ArrayList<>這樣的寫法,1.7以前是不支持的。你可以選擇修改源碼或者提高編譯JDK版本。
但你可能不滿足於這個界面,掃描框太大了,而且是橫屏全屏的,還要求API 15(Android 4.0.3)。下面我們會對這些需求進行修改。
2、代碼優化
1、功能裁剪(只保留QRCode二維碼掃描功能,去掉條形碼等其他碼掃描功能)
我的目標是只保留二維碼識別,不需要其他多余的功能。這一部分的步驟我不打算詳細說明,因為我已經不記得了。。大家可以直接看我的代碼的結果。
可以直接刪掉的是:
com.google.zxing.aztec.** aztec格式的二維碼
com.google.zxing.client.android.book.* Google 圖書相關的功能
com.google.zxing.client.android.clickboard.* 不清楚,復制黏貼?
com.google.zxing.client.android.encode.* 用於生成各種碼
com.google.zxing.client.android.history.* 保存掃碼記錄
com.google.zxing.client.android.result.** 掃碼應用功能相關的功能性代碼
com.google.zxing.client.android.share.* 分享功能
com.google.zxing.client.android.wifi.* WiFi相關,不清楚具體用途
com.google.zxing.datamatrix.** datamatrix格式二維碼
com.google.zxing.maxicode.** maxicode格式二維碼
com.google.zxing.multi.** 貌似是用於多格式支持的?我沒有用到這個包,如果有了解的麻煩告知
com.google.zxing.oned.** one dimension一維碼,也就是條形碼(你去百度搜oned會發現奇怪的東西。。)
com.google.zxing.pdf417.** PDF417格式條形碼
需要修改的是:
com.google.zxing.client.android.CaptureActivity:去掉其他功能的相關代碼,只保留核心功能,即掃碼功能。界面為一個FrameLayout里面包含一個SurfaceView。代碼移除就不詳細說了,直接看上傳的代碼吧,這個文件我參考了http://www.cnblogs.com/keyindex/archive/2011/06/08/2074900.html這個鏈接里的CaptureActivity的修改。
com.google.zxing.MultiFormatReader:這個是指定支持解碼的格式,需要把除QR_CODE以外的格式全部去掉,否則會因為刪掉了解碼包而報錯。具體也請看上傳的代碼。
com.google.zxing.client.result.ProductResultParser:parse函數中,同上。
com.google.zxing.client.android.DecodeThread:構造器中,同上。
另外:
com.google.zxing.client.result 我沒有刪減這個包的代碼,應該也是能優化的
2、移除資源依賴,提供Dialog形式的掃碼功能
經過了第1步的精簡,其實只剩下了2個地方需要修改:
1.掃描界面
2.掃描成功時播放的beep聲音文件
1:去除了其余功能后,對於核心功能我們只需要一個SurfaceView和一個畫界面的View就可以了。代碼如下:
/** * use Java code to build layout instead of xml file * @ch */ private void buildLayout() { requestWindowFeature(Window.FEATURE_NO_TITLE); FrameLayout layout = new FrameLayout(this); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); layout.setLayoutParams(params); surfaceView = new SurfaceView(this); surfaceView.setLayoutParams(params); layout.addView(surfaceView); viewfinderView = new ViewfinderView(this, null); layout.addView(viewfinderView); setContentView(layout); }
ViewfinderView是ZXing自帶的View,如果要修改界面,直接修改它就可以了,我們第7點會提到。
2:由於我最終的目的是能打包成jar包,所以beep文件不能放在res里,而是放在assets里。
//待補充
3、API兼容(源碼只兼容4.0以上,現兼容至2.1)
這部分修改在源碼中標記為//@ch api compatible。
CaptureActivity.java: //@ch api compatible if (VERSION.SDK_INT < 11) { //surfaceview will push buffer automatically surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } //API 11之前需要手動設置,否則會無法顯示。API11之后為默認設置。 CameraConfigurationManager.java: //@ch api compatible if (VERSION.SDK_INT < 13) { theScreenResolution.x = display.getWidth(); theScreenResolution.y = display.getHeight(); } else { display.getSize(theScreenResolution); } //getSize是API 13之后的新API,之前需要用getWidth和getHeight。 AutoFocusManager.java: //@ch api compatible if (VERSION.SDK_INT > 11) { newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { newTask.execute(); } //源碼中這里設置了多線程的模式,THREAD_POOL_EXECUTOR表示這個task最多只有5個線程同時運行,超過5個的就要等待。在低於API 11的版本中,此為默認選項。其實這里只有單線程,所以隨便執行吧。 OpenCameraInterface.java: //@ch api compatible if (VERSION.SDK_INT < 9) { return openWithLowApi(); } ...... /** * for lower than API 9 * @ch api compatible */ public static Camera openWithLowApi() { //If the device does not have a back-facing camera, this returns null Camera camera = Camera.open(); return camera; } //源碼的打開攝像頭是能區分前后攝像頭的,然而API 9之前並沒有前置攝像頭這個概念,所以做了一下處理
就幾個地方,不過也找了我個把小時了。
4、轉換為豎屏(源碼為橫屏)
ZXing默認是橫屏,但是我們一般的APP都會做成豎屏,如果掃碼的時候強制切換成橫屏那樣體驗就不好了。在修改ZXing的豎屏的時候,我按照的是一般APP的豎屏設置方法,結果發現沒有源碼的效果好,需要把碼放到很小才能完成。后面在調試過程中發現掃碼解析的區域和屏幕畫出來的區域不一樣,才知道這部分的修改出了問題。然后我搜索找到一篇前輩的文章,參考了一下發現沒有改完全。附上文件鏈接:
http://blog.csdn.net/aaawqqq/article/details/24804939
其中第五點我沒有修改,文章中的源碼可能比較舊,並不適合替換。第5點不替換替換是沒有問題的。
此外還有一點需要修改的是:
CameraManager.java: //@ch change 240 to 120 for 320x240 machine private static final int MIN_FRAME_WIDTH = 120; private static final int MIN_FRAME_HEIGHT = 120; //@ch change to vertical private static final int MAX_FRAME_HEIGHT = (int) 1080; private static final int MAX_FRAME_WIDTH = (int) 1080; //此處為掃描框最大邊界和最小邊界的界定。但因為我最后做成了正方形,所以這里數值是一樣的。如果為矩形,需要把兩個值交換一下。 //設置最小值是為了保證解碼的成功率,畢竟分辨率太小就沒法識別了。最大值是為了保證解碼速度。其實最大值應該通過插值來重新構圖,不然框的大小不一致體驗就不好了。不過這樣的分辨率起碼是2K屏以上了,所以最大值設定不會有什么影響
5、掃碼速度優化(主要分三點,現只完成了一點)
1.二值化算法優化。ZXing一共提供了兩種二值化算法,一種是HybridBinarizer,另一種是GlobalHistogramBinarizer,默認使用的是HybridBinarizer。我搜集資料的時候發現,HybridBinarizer算法用了更高級的算法,運算要求更高,而總體來講GlobalHistogramBinarizer識別率要更高。在我的實際測試中(我用的Nexus5,Android 5.0.1),GlobalHistogramBinarizer效果確實是要好不少。
這里的修改很簡單,換一個類就可以了:
DecodeHandler.java: private void decode(byte[] data, int width, int height) { ...... //@ch you can use HybridBinarizer or GlobalHistogramBinarizer //but in most of situations HybridBinarizer is shit BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source)); ...... }
2.對焦優化。ZXing中的對焦功能在AutoFocusManager.java中,功能非常簡單,設置自動對焦並2秒對焦一次。但自動對焦可能會帶來一個問題,如下圖:
(圖片源自網絡)
把二維碼當作圖中的那朵花,自動對焦則容易使攝像頭對焦到背景(圖中女性)中去。我在測試中使用三星S4的自動對焦經常對不了二維碼。這里我們可以使用區域對焦的方法(對焦的區域即是掃描框的區域):
//定點對焦的代碼 private void pointFocus(int x, int y) { if (cameraParameters.getMaxNumMeteringAreas() > 0) { List<Camera.Area> areas = new ArrayList<Camera.Area>(); Rect area = new Rect(x - 100, y - 100, x + 100, y + 100); areas.add(new Camera.Area(area, 600));
cameraParameters.setMeteringAreas(areas);
}
mCamera.cancelAutoFocus();
cameraParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(cameraParameters);
mCamera.autoFocus(autoFocusCallBack);
}
區域對焦需要API 14以上。關於對焦暫時沒有更多的想法,但我覺得確實是有優化的余地的。
3.算法優化。算法主要分兩部分,第一部分是二值化,第二部分是提取碼值。第二部分又分為1.尋找定位符,2.尋找校正符,3.轉換矩陣。在測試過程中,影響識別的最大問題就是找不到定位符,即二維碼左上角、右上角、左下角的三個黑白相間的矩形點。比較大的原因可能是二值化部分的問題。這一部分暫時也還沒有深入。
6、設備兼容(針對低分辨率設備)
CameraManager.java中有設置最小掃描框大小的參數MIN_FRAME_WIDTH和MIN_FRAME_HEIGHT。默認是320x240。這對於屏幕分辨率為320x240的設備,掃描框就會變成全屏的。這里我改成了120x120,實際在屏幕分辨率為320x240的設備上也可以掃到二維碼。
7、自定義界面
界面寫在ViewFinderView.java中。掃描框大小由CameraManager決定。修改的時候需要注意和CameraManager的配置關聯起來,否則會出現掃描框和實際解碼的區域不一致。(ZXing的android外圍模塊代碼默認使用的是全屏,如果你想改為非全屏(比如加一個action bar),肯定會造成掃描框區域和實際解碼的區域不一致的問題。這也是文首建議只使用core模塊的原因)
8、優化調試方法
為了應對7可能帶來的問題,我自己做了一個調試的方法,以保證掃描框內容和實際解碼內容一致。首先在CaptureActivity.java初始化cameraManager的地方,把
cameraManager = new CameraManager(getApplication())
改為
cameraManager = new CameraManager(this)
這樣做的目的是:傳入了activity給CameraManager,使得后面在CameraManager中的調試內容可以直接輸出在activity上(dialog也一樣,改為getContext()即可)。但注意這樣會帶來Activity生命周期問題和因為互相引用導致內存泄漏的問題。所以只能在調試階段修改成上面的寫法。
然后在CameraManager.buildLumianceSource中加入:
if (context instanceof Activity) { final Bitmap bitmap = Bitmap.createBitmap(BitmapUtil.createBitmapfromYUV420(data, width, height), rect.left, rect.top, rect.width(), rect.height()); CaptureActivity activity = ((CaptureActivity) context); final ViewfinderView view = activity.getViewfinderView(); activity.runOnUiThread(new Runnable() { @Override public void run() { view.drawResultBitmap(bitmap); } }); }
這樣,屏幕上的掃描框中就會顯示出實際解碼的圖像,你可以通過比對預覽圖像查看是否對齊。
記得調試完改回來getApplication()
版權所有,轉載請注明出處: