教程:Android使用zxing+cameraview实现自定义二维码扫描

很多图文教程和视频教程都喜欢啰嗦一大堆,才总结性地说一句:废话不多说,下面开始。我跟那些妖艳贱货肯定是要不一样的。开撸。

1. 集成zxing

打开项目build.gradle,加入以下代码:

// zxing 扫描生成二维码和条形码
compile 'com.google.zxing:core:3.3.0'

我们这里之所以只集成了zxing的核心包,核心包里面是解析/生成 二维码/条形码等一系列算法,并且,包足够小。

2. 集成CameraView

点击Google出品的CameraView到项目主页,直接下载或者clone到本地,然后把里面的Library文件夹下面的内容拷贝到自己的项目工程。

项目配置完毕。

3. 工作思路和流程

思路很简单:拍照得到照片,然后把照片交给zxing去解析。我们只需要解析结果。

先上解析代码:

    /**
     * 扫描图片
     *
     * @param bitmap 图片
     * @return zxing扫描初始结果
     */
    private Result scanningImage(Bitmap bitmap) {
        Map<DecodeHintType, String> hints1 = new Hashtable<>();
        hints1.put(DecodeHintType.CHARACTER_SET, "utf-8");
        RGBLuminanceSource source = new RGBLuminanceSource(bitmap);
        BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
        QRCodeReader reader = new QRCodeReader();
        Result result;
        try {
            result = reader.decode(bitmap1, hints1);
            scanResult(result.getText());
            return result;
        } catch (NotFoundException | ChecksumException | FormatException e) {
            // 解析照片上面的二维码失败后,拍照重新解析
            if (mCameraView != null && mCameraView.isCameraOpened()) {
                mCameraView.takePicture();
            }
            e.printStackTrace();
        }
        return null;
    }

不过这里就遇到难题了,因为CameraView默认拍摄的是摄像头所支持的,当前宽高比最高的照片。以目前我正在使用的魅族Pr5为例,2100像素拍出来的照片,随随便便都是8M以上。这么大的照片,即使不会OOM,解析速度也会慢的令人发指。

事实上,在我的手机(魅族Pro5)上面测试,OOM倒是不会,但结果是虚拟机开始频繁GC,然后应用卡死。(其实这就是OOM)

所以,这个时候就要想到去压缩图片,使用到BitmapFactory的一系列方法,对其进行压缩。因为CameraView得到的照片是一个byte数组,我们直接对这个数组进行生成Bitmap,然后再根据其宽高(因为每台手机摄像头都不一样,拍出来的照片也是千差万别)计算压缩比,进行压缩,最后再将压缩后的图片传给zxing进行解析。

流程图:

graph TD
    AA[开始] --> A[拍照]
    A --> B[压缩图片]
    B --> C[发送图片给zxing]
    C --> D[解析图片上的二维码]
    D --> F{Success or failed}
    F --> |Failed| A
    F --> |Success| G[Finish]
    

(图有点抽象,将就着看)

在这个流程之中,比较耗时的地方一般是解析图片。zxing解析图片的过程非常的慢,使用我的Pro5进行解析,压缩400ms以内,解析700ms以内。使用比较低端的测试机,压缩用了500ms(这方面耗时并不多,算法使用的Google的),但是解析却达到了4000ms——5000ms之间。

目前怀疑这个耗时是因为使用了一个将Bitmap转化为zxing所需的Bitmap的问题。zxing所需的Bitmap和我们平常使用的Bitmap格式不同,这一块也没有去看和仔细优化,应该还可以提速一部分。

目前项目只能说可以使用。比较要注意的是让相机开始拍照的时机问题,因为扫描二维码是打开相机后自动扫描,不可能让用户点击拍照按钮再去扫描。我采用的方式是打开相机页面之后,在页面上一个元素中加上postDelay,1000ms后开始获取照片。不过这个也需要加一个判断:CameraView.isCameraOpen()否则会出现打开页面立马退出,倒计时完成后依然会视图去拍照,但是CameraView此时已经被销毁,强行使用被销毁的对象,自然就会导致应用崩溃了。

整个工程完全可以基于CameraView项目中原有的代码开始使用,只需要把代码拷贝过去就行了。我这边也只是集成在了项目之中,目前自己测试没什么问题,但要是真在实际环境中使用的话,还需要大量的测试。等基本完善之后,才会考虑将代码开源出来。

其实也并没有什么开源的地方,代码基本上都是各种copy过来的,只是其中有很多地方需要一点一点去看,去做优化。

还有,自定义扫描二维码界面我就不说了,绘制一个View的事情,不在本教程范围内。

目前基本就这些,谢谢大家。

下面是代码部分:

压缩图片

/**
     * 以字节的格式压缩位图
     *
     * @param bytes 拍照所得字节位图
     * @return 压缩后的Bitmap
     */
    private Bitmap byteForCompressBitmap(byte[] bytes) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        options.inSampleSize = computeSampleSize(options, -1, 1920 * 1080);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
    }


    /**
     * 计算图片样本大小 for google
     *
     * @param options        BitmapOption 记得inJustBounds设置为true
     * @param minSideLength  最小边长 一般为 -1
     * @param maxNumOfPixels 最大像素数
     * @return 返回可缩放倍数
     */
    public int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
        int roundedSize;
        if (initialSize <= 8) {
            roundedSize = 1;
            while (roundedSize < initialSize) {
                roundedSize <<= 1;
            }
        } else {
            roundedSize = (initialSize + 7) / 8 * 8;
        }
        return roundedSize;
    }

    /**
     * 计算图片初始样本大小
     *
     * @param options        开关
     * @param minSideLength  最小边长
     * @param maxNumOfPixels 最大像素数目
     * @return 传入图片初始样本大小
     */
    private int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
        double w = options.outWidth;
        double h = options.outHeight;
        int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
        int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
        if (upperBound < lowerBound) {
            return lowerBound;
        }
        if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
            return 1;
        } else if (minSideLength == -1) {
            return lowerBound;
        } else {
            return upperBound;
        }
    }

上方zxing解析图片中的二维码缺失的RGBLuminanceSource类,如果直接使用zxing包中的RGBLuminanceSource类,因为接收的图片格式不同,会导致不能运行。

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.google.zxing.LuminanceSource;

import java.io.FileNotFoundException;

/**
 * This class is used to help decode images from files which arrive as RGB data
 * from Android bitmaps. It does not support cropping or rotation.
 *
 * @author dswitkin@google.com (Daniel Switkin)
 */
public final class RGBLuminanceSource extends LuminanceSource {
    private final byte[] luminances;

    public RGBLuminanceSource(String path) throws FileNotFoundException {
        this(loadBitmap(path));
    }

    public RGBLuminanceSource(Bitmap bitmap) {
        super(bitmap.getWidth(), bitmap.getHeight());
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        // In order to measure pure decoding speed, we convert the entire image
        // to a greyscale array
        // up front, which is the same as the Y channel of the
        // YUVLuminanceSource in the real app.
        luminances = new byte[width * height];
        for (int y = 0; y < height; y++) {
            int offset = y * width;
            for (int x = 0; x < width; x++) {
                int pixel = pixels[offset + x];
                int r = (pixel >> 16) & 0xff;
                int g = (pixel >> 8) & 0xff;
                int b = pixel & 0xff;
                if (r == g && g == b) {
                    // Image is already greyscale, so pick any channel.
                    luminances[offset + x] = (byte) r;
                } else {
                    // Calculate luminance cheaply, favoring green.
                    luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
                }
            }
        }
    }

    private static Bitmap loadBitmap(String path) throws FileNotFoundException {
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        if (bitmap == null) {
            throw new FileNotFoundException("Couldn't open " + path);
        }
        return bitmap;
    }

    @Override
    public byte[] getRow(int y, byte[] row) {
        if (y < 0 || y >= getHeight()) {
            throw new IllegalArgumentException(
                    "Requested row is outside the image: " + y);
        }
        int width = getWidth();
        if (row == null || row.length < width) {
            row = new byte[width];
        }
        System.arraycopy(luminances, y * width, row, 0, width);
        return row;
    }

    // Since this class does not support cropping, the underlying byte array
    // already contains
    // exactly what the caller is asking for, so give it to them without a copy.
    @Override
    public byte[] getMatrix() {
        return luminances;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 兴奋劲还没过,严苛的军训便如约而至了。 操场上,大家排着队。“八班,王教官。”主任分配着各班的教官,很快就念到了林...
    福气喵阅读 380评论 1 2
  • 如果你的朋友中,已经有为人父母的话,一定会用以下同样的感受: 朋友圈一言不合就晒娃睡觉要晒吃饭要晒去玩要晒而且必须...
    蜘蜘纺阅读 258评论 0 0
  • 同事前天交了辞呈,昨天下午就坐飞机回到了家。问他为什么离开的这么突然,回答,建筑行业太辛苦了,特别是一线管理人员。...
    燕coco阅读 580评论 0 4