Android实现基于ZXing快速集成二维码扫描功能
二维码扫描现在是一直比较多的应用场景,android的开源项目ZXing提供了完整、成熟的解决方案,如果仅仅是出于快速开发的目的,可以根据自己的项目需要,把ZXing官方提供的项目稍加裁剪,就可以快速的集成到自己的项目中。下面详细演示和介绍如何实现基于ZXing官方提供的源码,快速集成二维码扫描功能到自己项目中的解决方案。
(第1步):到ZXing官方主页下载最新的项目代码包,ZXing在github的官方主页:https://github.com/zxing,下载后解压。解压后根目录下有若干项目目录,其中的:android就是我们需要的项目,把它导入到Eclispse中。
(第2步):ZXing的Android项目需要引用两个关键的库文件:android-core-x.x.x.jar和 core-x.x.x.jar,其中x.x.x表示版本号。截止发表本博文时候,版本已经是3.2.0了。这两个关键的android-core-x.x.x.jar 和 core-x.x.x.jar 文件,实际上都可以从第一步下载得到的源代码中自己编译生成,网上也有编译的具体方案,但简单期间,也可以从ZXing的官方直接下载已经编译好的文件,其中android-core的下载链接是:http://repo1.maven.org/maven2/com/google/zxing/android-core/,另外一个ZXing的core下载链接是:http://repo1.maven.org/maven2/com/google/zxing/core/ ,选择最新版本的库文件或者自己需要的版本号,下载后,和其他android项目中导入库文件类似,导入到Android项目中的libs目录下,如果没有libs,新建一个名为libs的目录,把两个库文件放进去即可。
(第3步):这一步作为演示,我们自己新建一个MainActiviy,作为项目的启动器Activity,App将启动我们自己的MainActivity。观察ZXing官方提供的AndroidManifest.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (C) 2008 ZXing authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.zxing.client.android" android:versionName="4.7.3" android:versionCode="103" android:installLocation="auto"> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.FLASHLIGHT"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="21"/> <!-- Don't require camera, as this requires a rear camera. This allows it to work on the Nexus 7 --> <uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera.front" android:required="false"/> <!-- TODO replace above two with next line after Android 4.2 --> <!-- <uses-feature android:name="android.hardware.camera.any"/> --> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <uses-feature android:name="android.hardware.camera.flash" android:required="false"/> <uses-feature android:name="android.hardware.screen.landscape"/> <uses-feature android:name="android.hardware.wifi" android:required="false"/> <!-- This excludes Google TV, which is unfortunately included by virtue of not requiring a camera --> <uses-feature android:name="android.hardware.touchscreen"/> <!-- TODO make this not required again after android.hardware.camera.any is available --> <supports-screens android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:anyDensity="true"/> <application android:icon="@drawable/launcher_icon" android:logo="@drawable/launcher_icon" android:label="@string/app_name" android:allowBackup="true"> <activity android:name=".CaptureActivity" android:screenOrientation="sensorLandscape" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:theme="@style/CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <intent-filter> <action android:name="com.google.zxing.client.android.SCAN"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <!-- Allow web apps to launch Barcode Scanner by linking to http://zxing.appspot.com/scan. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan"/> </intent-filter> <!-- We also support a Google Product Search URL. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="www.google.com" android:path="/m/products/scan"/> </intent-filter> <!-- And the UK version. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="www.google.co.uk" android:path="/m/products/scan"/> </intent-filter> <!-- Support zxing://scan/?... like iPhone app --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="zxing" android:host="scan" android:path="/"/> </intent-filter> </activity> <activity android:name=".PreferencesActivity" android:label="@string/preferences_name" android:stateNotNeeded="true"/> <activity android:name=".encode.EncodeActivity" android:stateNotNeeded="true"> <intent-filter> <action android:name="com.google.zxing.client.android.ENCODE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <!-- This allows us to handle the Share button in Contacts. --> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/x-vcard"/> </intent-filter> <!-- This allows us to handle sharing any plain text . --> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain"/> </intent-filter> </activity> <activity android:name=".book.SearchBookContentsActivity" android:label="@string/sbc_name" android:stateNotNeeded="true" android:screenOrientation="sensorLandscape"> <intent-filter> <action android:name="com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".share.ShareActivity" android:stateNotNeeded="true" android:screenOrientation="user"> <intent-filter> <action android:name="com.google.zxing.client.android.SHARE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".history.HistoryActivity" android:label="@string/history_title" android:stateNotNeeded="true"/> <activity android:name=".share.BookmarkPickerActivity" android:label="@string/bookmark_picker_name" android:stateNotNeeded="true"/> <activity android:name=".share.AppPickerActivity" android:label="@string/app_picker_name" android:stateNotNeeded="true"/> <activity android:name=".HelpActivity" android:label="@string/menu_help" android:screenOrientation="user" android:stateNotNeeded="true"/> </application> </manifest>
其实ZXing官方的项目已经作为为第三方提供集成的代码了,比如其中的关键Activity:.\src\com\google\zxing\client\android\CaptureActivity.Java,在声明中已经提供好了从各种入口访问的Intent的Action。所以在我们自己新建的MainActivity中,直接隐式指定一个Intent的Action,启动之即可:
package com.google.zxing.client.android; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { private final int REQUEST_CODE = 0xa1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(); //隐式指定 intent.setAction("com.google.zxing.client.android.SCAN"); //启动ZXing已经写好、且我们做小量修改后的CaptureActivity。 startActivityForResult(intent, REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); //我们需要的结果返回 if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { //result就是二维码扫描的结果。 String result = data.getStringExtra("RESULT"); Toast.makeText(getApplicationContext(), result, Toast.LENGTH_SHORT) .show(); } } }
因为我们启动ZXing的CaptureActivity不是目的,真正的目的是启动ZXing的CaptureActivity获得二维码扫描结果,因此以startActivityForResult()的方式启动。相应的,我们需要重写:protected void onActivityResult(int requestCode, int resultCode, Intent data),以回调等待传回结果。
(第4步):这一步是重点。在.\src\com\google\zxing\client\android\目录下的CaptureActivity.java中的方法: public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor);此方法是一个回调函数。ZXing项目中写好的扫描模块扫描后返回回调此方法,ZXing官方的原始 public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor)方法是这样的:
/** * A valid barcode has been found, so give an indication of success and show the results. * * @param rawResult The contents of the barcode. * @param scaleFactor amount by which thumbnail was scaled * @param barcode A greyscale bitmap of the camera data which was decoded. */ public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) { inactivityTimer.onActivity(); lastResult = rawResult; ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult); boolean fromLiveScan = barcode != null; if (fromLiveScan) { historyManager.addHistoryItem(rawResult, resultHandler); // Then not from history, so beep/vibrate and we have an image to draw on beepManager.playBeepSoundAndVibrate(); drawResultPoints(barcode, scaleFactor, rawResult); } switch (source) { case NATIVE_APP_INTENT: case PRODUCT_SEARCH_LINK: handleDecodeExternally(rawResult, resultHandler, barcode); break; case ZXING_LINK: if (scanFromWebPageManager == null || !scanFromWebPageManager.isScanFromWebPage()) { handleDecodeInternally(rawResult, resultHandler, barcode); } else { handleDecodeExternally(rawResult, resultHandler, barcode); } break; case NONE: SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (fromLiveScan && prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) { Toast.makeText(getApplicationContext(), getResources().getString(R.string.msg_bulk_mode_scanned) + " (" + rawResult.getText() + ')', Toast.LENGTH_SHORT).show(); // Wait a moment or else it will scan the same barcode continuously about 3 times restartPreviewAfterDelay(BULK_MODE_SCAN_DELAY_MS); } else { handleDecodeInternally(rawResult, resultHandler, barcode); } break; } }
我们将精简此方法,定制自己所需要的内容,为满足我们自己项目中的需求,把此方法修改后的代码为:
public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) { inactivityTimer.onActivity(); lastResult = rawResult; ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult); boolean fromLiveScan = barcode != null; if (fromLiveScan) { historyManager.addHistoryItem(rawResult, resultHandler); // Then not from history, so beep/vibrate and we have an image to draw on beepManager.playBeepSoundAndVibrate(); drawResultPoints(barcode, scaleFactor, rawResult); } //在这里增加我们的代码,目的是:做最小量的修改,仅仅把ZXing提供的CaptureActivity作为一个中间使用的Activity集成到我们自己的项目。 //启动实现二维码扫描,返回一个结果就可以了。 //然后结束这个Activity。 Intent intent=new Intent(); //<key,value>形式存储二维码结果。 //rawResult.getText()即为二维码结果。 intent.putExtra("RESULT", rawResult.getText()); this.setResult(Activity.RESULT_OK, intent); this.finish(); /** 以下是ZXing提供的源码,根据项目需要可以删减使用。 简单期间,我们只需要二维码扫描后返回一个扫描的字符串结果。 所以在次暂时注释掉。 switch (source) { case NATIVE_APP_INTENT: case PRODUCT_SEARCH_LINK: handleDecodeExternally(rawResult, resultHandler, barcode); break; case ZXING_LINK: if (scanFromWebPageManager == null || !scanFromWebPageManager.isScanFromWebPage()) { handleDecodeInternally(rawResult, resultHandler, barcode); } else { handleDecodeExternally(rawResult, resultHandler, barcode); } break; case NONE: SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); if (fromLiveScan && prefs.getBoolean(PreferencesActivity.KEY_BULK_MODE, false)) { Toast.makeText(getApplicationContext(), getResources().getString(R.string.msg_bulk_mode_scanned) + " (" + rawResult.getText() + ')', Toast.LENGTH_SHORT).show(); // Wait a moment or else it will scan the same barcode continuously about 3 times restartPreviewAfterDelay(BULK_MODE_SCAN_DELAY_MS); } else { handleDecodeInternally(rawResult, resultHandler, barcode); } break; } **/ }
(第5步):这一步比较简单,是剩余的收尾工作,修改AndroidManifest.xml文件,把我们的MainActivity作为主Activity启动。把ZXing的CaptureActivity作为一个普通的Activity。
修改后的AndroidManifest.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (C) 2008 ZXing authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.google.zxing.client.android" android:versionName="4.7.3" android:versionCode="103" android:installLocation="auto"> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.FLASHLIGHT"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="21"/> <!-- Don't require camera, as this requires a rear camera. This allows it to work on the Nexus 7 --> <uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera.front" android:required="false"/> <!-- TODO replace above two with next line after Android 4.2 --> <!-- <uses-feature android:name="android.hardware.camera.any"/> --> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> <uses-feature android:name="android.hardware.camera.flash" android:required="false"/> <uses-feature android:name="android.hardware.screen.landscape"/> <uses-feature android:name="android.hardware.wifi" android:required="false"/> <!-- This excludes Google TV, which is unfortunately included by virtue of not requiring a camera --> <uses-feature android:name="android.hardware.touchscreen"/> <!-- TODO make this not required again after android.hardware.camera.any is available --> <supports-screens android:xlargeScreens="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:anyDensity="true"/> <application android:icon="@drawable/launcher_icon" android:logo="@drawable/launcher_icon" android:label="@string/app_name" android:allowBackup="true"> <!-- 新增的我们自己的MainActiviy作为启动Activity --> <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".CaptureActivity" android:screenOrientation="sensorLandscape" android:clearTaskOnLaunch="true" android:stateNotNeeded="true" android:theme="@style/CaptureTheme" android:windowSoftInputMode="stateAlwaysHidden"> <!-- 把ZXing官方提供的作为 main activity启动的CaptureActivity作为一个普通Activiy。 <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> --> <intent-filter> <action android:name="com.google.zxing.client.android.SCAN"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <!-- Allow web apps to launch Barcode Scanner by linking to http://zxing.appspot.com/scan. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan"/> </intent-filter> <!-- We also support a Google Product Search URL. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="www.google.com" android:path="/m/products/scan"/> </intent-filter> <!-- And the UK version. --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="www.google.co.uk" android:path="/m/products/scan"/> </intent-filter> <!-- Support zxing://scan/?... like iPhone app --> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="zxing" android:host="scan" android:path="/"/> </intent-filter> </activity> <activity android:name=".PreferencesActivity" android:label="@string/preferences_name" android:stateNotNeeded="true"/> <activity android:name=".encode.EncodeActivity" android:stateNotNeeded="true"> <intent-filter> <action android:name="com.google.zxing.client.android.ENCODE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> <!-- This allows us to handle the Share button in Contacts. --> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/x-vcard"/> </intent-filter> <!-- This allows us to handle sharing any plain text . --> <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain"/> </intent-filter> </activity> <activity android:name=".book.SearchBookContentsActivity" android:label="@string/sbc_name" android:stateNotNeeded="true" android:screenOrientation="sensorLandscape"> <intent-filter> <action android:name="com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".share.ShareActivity" android:stateNotNeeded="true" android:screenOrientation="user"> <intent-filter> <action android:name="com.google.zxing.client.android.SHARE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <activity android:name=".history.HistoryActivity" android:label="@string/history_title" android:stateNotNeeded="true"/> <activity android:name=".share.BookmarkPickerActivity" android:label="@string/bookmark_picker_name" android:stateNotNeeded="true"/> <activity android:name=".share.AppPickerActivity" android:label="@string/app_picker_name" android:stateNotNeeded="true"/> <activity android:name=".HelpActivity" android:label="@string/menu_help" android:screenOrientation="user" android:stateNotNeeded="true"/> </application> </manifest>
备注:此文仅仅是最简单的一个示例,演示了如何在自己的项目中在ZXing官方项目已提供的完整代码基础上,做最小量的改动,将二维码扫描功能快速集成到自己的项目中为我所用,若需要更多的细节调整,则需要深入定制和改编源代码。