美文网首页Android开发Android技术知识Android开发
教程:Android使用zxing+cameraview实现自定

教程:Android使用zxing+cameraview实现自定

作者: Chenstyle | 来源:发表于2017-04-26 14:43 被阅读4035次

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

    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;
        }
    }
    

    相关文章

      网友评论

        本文标题:教程:Android使用zxing+cameraview实现自定

        本文链接:https://www.haomeiwen.com/subject/jjbizttx.html