Android扫一扫(ZXing与ZBar整合)

需求

打造一个干净纯粹的扫码模块;App基本都用到扫码功能(条码、二维码),网上资源一大把,搬过来就能用,确实能用,但应该还能更好用,经过几次与大厂对比,索性自己搞一个;

于是采用ZXing相机预览,ZBar解码整合路线;但是存在几个问题:

  1. ZBar中文乱码
  2. ZBar支持的条码类型有限,部分ZXing支持的格式,ZBar不支持;
  3. ZXing默认是横屏扫码

技术

  • Linux下NDK编译
  • Android jni
  • Android Studio 配置CMakeLists

资料

源码

编译工具

android-ndk-r17c-linux-x86_64

交叉编译

Android Studio Electric Eel

Linux环境搭建

操作系统 centos7

Linux环境搭建

解压Linux NDK工具到/android-ndk-r17c/

解压叫餐编译工具到/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/

配置环境变量vi ~/.bash_profile

更新环境变量source ~/.bash_profile

PATH=$PATH:$HOME/bin

export PATH
export ANDROID_NDK=/android-ndk-r17c/
export PATH=$PATH:$ANDROID_NDK
export PATH=/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH

进入/android-ndk-r17c/build/tools,运行命令

./make_standalone_toolchain.py --arch arm --api 28 --unified-headers --install-dir /root/my-android-toolchain/

1、编译libiconv

  1. 下载libiconv源码,上传到Linux,解压;

tar -xvf libiconv-1.17.tar.gz

因为libiconv不是针对Android的项目,所以我们得修改一下构建脚本,进行;在/androidSrc/libiconv-1.17/目录下创建脚本代码 android_build.sh

ANDROID_BUILD=/root/my-android-toolchain/
API_VERSION=28
PATH=$ANDROID_BUILD/bin:$PATH
SYSROOT=$ANDROID_BUILD/sysroot
HOST=arm-linux-androideabi
CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -D__ANDROID_API__=$API_VERSION"
CXXFLAGS="-std=c++11"
LIBDIRS="-L$ANDROID_BUILD/arm-linux-androideabi/lib"
LDFLAGS="-march=armv7-a -Wl,--fix-cortex-a8 $LIBDIRS"
CONFLAGS="--prefix=${SYSROOT}/usr --host=$HOST"
PKG_CONFIG_PATH="$SYSROOT/usr/lib/pkgconfig"

#configure
PKG_CONFIG_PATH=$PKG_CONFIG_PATH CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" LDFLAGS="$LDFLAGS" ./configure $CONFLAGS &&

#make & install

运行脚本./android_build.sh,等会儿就好了;

2、编译ZBar

  • 在android studio创建一个library模块,方便项目可以直接引入使用;
  • 在src/main下创建jni文件夹用于存放C源码,如果创建module有jni目录可跳过;
  • 复制ZBar源码到jni下

  • 复制ZBar-master\android\jni\config.h到src/main/jni下;
  • 复制ZBar-master\java\zbarjni.c到src/main/jni下;
  • 复制刚刚Linux构建完的libiconv源码到jni中

  • ZBar官方有提供了Androd.mk,也可以直接使用它,需要做一下修改,ndk-build;另一种是cmake,在ZBar模块路径下创建CmakeLists.txt用于构建jni;采用一种就可以,需要在build.gradle做配置android.defaultConfig.externalNativeBuild和android.externalNativeBuild

Androd.mk

CmakeLists.txt

Android.mk

MY_LOCAL_PATH := $(call my-dir)

# libiconv
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)
LOCAL_MODULE := libiconv
LOCAL_CFLAGS := \
    -Wno-multichar \
    -D_ANDROID \
    -DLIBDIR="c" \
    -DBUILDING_LIBICONV \
    -DBUILDING_LIBCHARSET \
    -DIN_LIBRARY

LOCAL_SRC_FILES := \
	libiconv-1.17/lib/iconv.c \
	libiconv-1.17/libcharset/lib/localcharset.c \
	libiconv-1.17/lib/relocatable.c

LOCAL_C_INCLUDES := \
	$(LOCAL_PATH)/libiconv-1.17/include \
	$(LOCAL_PATH)/libiconv-1.17/libcharset \
	$(LOCAL_PATH)/libiconv-1.17/libcharset/include

include $(BUILD_SHARED_LIBRARY)

LOCAL_LDLIBS := -llog -lcharset

# -----------------------------------------------------

# libzbar
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_LOCAL_PATH)
LOCAL_MODULE := zbar
LOCAL_SRC_FILES := \
			zbarjni.c \
			zbar/img_scanner.c \
			zbar/decoder.c \
			zbar/image.c \
			zbar/symbol.c \
			zbar/convert.c \
			zbar/config.c \
			zbar/scanner.c \
			zbar/error.c \
			zbar/refcnt.c \
			zbar/video.c \
			zbar/video/null.c \
			zbar/decoder/code128.c \
			zbar/decoder/code39.c \
			zbar/decoder/code93.c \
			zbar/decoder/codabar.c \
			zbar/decoder/databar.c \
			zbar/decoder/ean.c \
			zbar/decoder/i25.c \
			zbar/decoder/qr_finder.c \
			zbar/qrcode/bch15_5.c \
			zbar/qrcode/binarize.c \
			zbar/qrcode/isaac.c \
			zbar/qrcode/qrdec.c \
			zbar/qrcode/qrdectxt.c \
			zbar/qrcode/rs.c \
			zbar/qrcode/util.c

LOCAL_C_INCLUDES := \
			$(LOCAL_PATH)/include \
			$(LOCAL_PATH)/zbar \
			$(LOCAL_PATH)/libiconv-1.17/include

LOCAL_SHARED_LIBRARIES := libiconv

include $(BUILD_SHARED_LIBRARY)

CMakeLists.txt

# Set the minimum version of CMake required  
cmake_minimum_required(VERSION 3.4.1)

# Set the project name and version  
project(zbarjni VERSION 1.0)

# Enable C++11 support  
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(JNI_SOURCE ${CMAKE_SOURCE_DIR}/src/main/jni)
# Include directories  
include_directories(${JNI_SOURCE}
        ${JNI_SOURCE}/include
        ${JNI_SOURCE}/libiconv-1.17/libcharset
        ${JNI_SOURCE}/libiconv-1.17/include
        ${JNI_SOURCE}/libiconv-1.17/libcharset/include
        ${JNI_SOURCE}/zbar)

# libiconv library  
add_library(libiconv SHARED
        ${JNI_SOURCE}/libiconv-1.17/lib/iconv.c
        ${JNI_SOURCE}/libiconv-1.17/libcharset/lib/localcharset.c
        ${JNI_SOURCE}/libiconv-1.17/lib/relocatable.c)

target_compile_options(libiconv PRIVATE
        -Wno-multichar
        -D_ANDROID
        -DLIBDIR="c"
        -DBUILDING_LIBICONV
        -DBUILDING_LIBCHARSET
        -DIN_LIBRARY)

# zbar library  
add_library(zbarjni SHARED
        ${JNI_SOURCE}/zbarjni.c
        ${JNI_SOURCE}/zbar/img_scanner.c
        ${JNI_SOURCE}/zbar/decoder.c
        ${JNI_SOURCE}/zbar/image.c
        ${JNI_SOURCE}/zbar/symbol.c
        ${JNI_SOURCE}/zbar/convert.c
        ${JNI_SOURCE}/zbar/config.c
        ${JNI_SOURCE}/zbar/scanner.c
        ${JNI_SOURCE}/zbar/error.c
        ${JNI_SOURCE}/zbar/refcnt.c
        ${JNI_SOURCE}/zbar/video.c
        ${JNI_SOURCE}/zbar/video/null.c
        ${JNI_SOURCE}/zbar/decoder/code128.c
        ${JNI_SOURCE}/zbar/decoder/code39.c
        ${JNI_SOURCE}/zbar/decoder/code93.c
        ${JNI_SOURCE}/zbar/decoder/codabar.c
        ${JNI_SOURCE}/zbar/decoder/databar.c
        ${JNI_SOURCE}/zbar/decoder/ean.c
        ${JNI_SOURCE}/zbar/decoder/i25.c
        ${JNI_SOURCE}/zbar/decoder/qr_finder.c
        ${JNI_SOURCE}/zbar/qrcode/bch15_5.c
        ${JNI_SOURCE}/zbar/qrcode/binarize.c
        ${JNI_SOURCE}/zbar/qrcode/isaac.c
        ${JNI_SOURCE}/zbar/qrcode/qrdec.c
        ${JNI_SOURCE}/zbar/qrcode/qrdectxt.c
        ${JNI_SOURCE}/zbar/qrcode/rs.c
        ${JNI_SOURCE}/zbar/qrcode/util.c)

target_link_libraries(zbarjni PRIVATE libiconv log)

# Link against Android log and charset libraries  
#target_link_libraries(zbar log charset)

# Enable verbose build output (optional)  
set(CMAKE_VERBOSE_MAKEFILE ON)

# Set the default build type (optional)  
set(CMAKE_BUILD_TYPE Release)

# If you have subdirectories with more CMakeLists.txt files, you can add them here  
# add_subdirectory(subdirectory_name)  

# Finish the CMakeLists.txt file

到这里配置结束,开始编译;理论上是没问题的;

导入java类,ZBar-master\java\net\sourceforge\zbar所有类都cp到module来,注意:包名不要改,这边和zbarjni.c对应着;

3、引入ZXing

Zxingdemo比较多,这边只做扫码,所以简化一下导入;

添加依赖

    api  'com.google.zxing:core:3.3.3'
    api   'com.google.zxing:android-core:3.3.0'

导入java类

导入资源文件

错误提示一个个处理,调用 PreferenceManager.getDefaultSharedPreferences,就全部改为取默认值;

注意:因为是library模块,所以switch(id)得改成用if判断;

完事就构建;

4、修复

ZXing默认横屏改为竖屏;

AndroidManifest.xml-activity -CaptureActivity, android:screenOrientation="portrait"

CameraManager里的 getFramingRectInpreview()方法,增加cameraResolution的判断

 if(cameraResolution.x>cameraResolution.y){  //x大于y 改成竖屏数据,y和x互换 计算截图

        rect.left = rect.left * cameraResolution.y / screenResolution.x;
        rect.right = rect.right * cameraResolution.y / screenResolution.x;
        rect.top = rect.top * cameraResolution.x / screenResolution.y;
        rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;

      }else{

        rect.left = rect.left * cameraResolution.x / screenResolution.x;
        rect.right = rect.right * cameraResolution.x / screenResolution.x;
        rect.top = rect.top * cameraResolution.y / screenResolution.y;
        rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
      }

解码部分,DecodeHandler 的decode数据进行矩阵替换,宽高互换;这边改用c进行转换;代码如下

JNIEXPORT jbyteArray JNICALL Java_net_sourceforge_zbar_DecodeManager_dataHandler
        (JNIEnv *env, jobject thiz, jbyteArray array, jint length, jint width, jint height){


    jbyte* jBuffer = (*env) -> GetByteArrayElements(env, array, 0);
    unsigned char* pbuffer = (unsigned char*)jBuffer;




    jbyteArray resultArray = (*env) -> NewByteArray(env, length);
    jbyte *bytes = (*env) -> GetByteArrayElements(env,resultArray, 0);

    for(int y=0; y<height; y++){
        for(int x=0; x<width; x++){
            bytes[x * height + height - y - 1] = pbuffer[x + y * width];
        }
    }



    (*env) -> SetByteArrayRegion(env, resultArray, 0, length, bytes);

    (*env) -> ReleaseByteArrayElements(env, array, jBuffer, JNI_ABORT);             //释放
    (*env) -> ReleaseByteArrayElements(env, resultArray, bytes, JNI_ABORT);         //释放

    return resultArray;
}

net.sourceforge.zbar.DecodeManager.java

public class DecodeManager extends ImageScanner {
    public static native byte[] dataHandler(byte[] by, int length, int width, int height);
}

DecodeHandler 的decode方法,调用

data = decodeManager.dataHandler(data, data.length, width, height)

考虑Zbar部分条码类型不支持,解码部分先尝试Zbar,如果失败在用ZXing;

完整decode方法代码:

 private void decode(byte[] data, int width, int height) {
        String resultQRcode = null;
        Result rawResult = null;
//    先用zbar解码 如果失败或者识别不了用zxing
        Image barcode = new Image(width, height, "Y800");
        barcode.setData(data);
        Rect rect = activity.getCameraManager().getFramingRectInPreview();
        if (rect != null) {
                /*
                    zbar 解码库,不需要将数据进行旋转,因此设置裁剪区域是的x为 top, y为left
                    设置了裁剪区域,解码速度快了近5倍左右
                 */
            barcode.setCrop(rect.top, rect.left, rect.width(), rect.height());    // 设置截取区域,也就是你的扫描框在图片上的区域.
        }
        ImageScanner mImageScanner = new ImageScanner();
        int result = mImageScanner.scanImage(barcode);
        if (result != 0) {
            SymbolSet symSet = mImageScanner.getResults();
            for (Symbol sym : symSet) {
                resultQRcode = sym.getData();
                BarcodeFormat str = sym.getSymbolName();//条码格式,转化一下统一结果
                rawResult = new Result(resultQRcode, data, null, str);
                Log.d(getClass().getName(),resultQRcode);
            }

        }
        if (TextUtils.isEmpty(resultQRcode) || rawResult == null) { //如果扫描模式是Zxing
            /*
              因为相机传感器捕获的数据是横向的, 所以需要将数据进行90度的旋转, 用java进行转换在红米三手机测试大概需要 600ms左右
              因此换了C语言, 只需要 35ms左右 速度快了接近 20倍
             */
            data = decodeManager.dataHandler(data, data.length, width, height);
            //Log.d(TAG, "数组转换用时: " + (System.currentTimeMillis() - start));
            int tmp = width;
            width = height;
            height = tmp;


            PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
            if (source != null) {
                BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
                try {
                    rawResult = multiFormatReader.decodeWithState(bitmap);
                } catch (ReaderException re) {
                    // continue
                } finally {
                    multiFormatReader.reset();
                }
            }
            if (rawResult != null) {
                resultQRcode = rawResult.getText();
            }

        }

        long end = System.currentTimeMillis();


        Handler handler = activity.getHandler();
        if (!TextUtils.isEmpty(resultQRcode)) {   // 非空表示识别出结果了。
            if (handler != null) {
                Log.d(getClass().getName(), "解码成功: " + resultQRcode);
                Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
                message.sendToTarget();
            }
        } else {
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_failed);
                message.sendToTarget();
            }
        }

    }

ZBar中文乱码,修改ZBar源码,src/main/jni/zbar/qrcode/qrdectxt.c在63行处,修改如下

//  latin1_cd=iconv_open("UTF-8","ISO8859-1");
//  sjis_cd=iconv_open("UTF-8","SJIS");
//中文乱码
  latin1_cd=iconv_open("UTF-8","GB18030");
  
  sjis_cd=iconv_open("UTF-8","GB2312");

到这里就结束了,可以依赖到项目中去愉快扫码了;

5、优化

界面优化,加入闪光灯,运行时权限参考:Android集成zxing扫码框架

编译参考:Linux下搭建Android交叉编译环境

源码地址

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值