Android Zxing 二维码与相册选取图片扫描

Zxing 是谷歌的一个开源库,网上大多数二维码扫描功能都是基于该库实现功能的,现在来动手简单实现二维码扫描与相册图片读取二维码。
在写这篇文章时,参考了几位大佬的博客相关知识:https://blog.csdn.net/qq_34902522/article/details/78384661 ,他已经实现了封装二维码相册读取图片的功能的库YZxing
还有本次所依赖的大佬的所封装的扫描库
https://blog.csdn.net/qq_23547831/article/details/52037710
老规矩先上图:

二维码扫描录制.gif

由于申请权限代码过于繁琐,除了依赖Zxing ,这里还引入了郭霖大神的PermissionX

    implementation 'cn.yipianfengye.android:zxing-library:2.1'
    implementation 'com.permissionx.guolindev:permissionx:1.2.2'

在Mainifest中声明我们需要的的摄像头权限和Zxing需要的权限:振动器(扫描后解析结果会震动)

 <uses-permission android:name="android.permission.CAMERA"/>
  <uses-permission android:name="android.permission.VIBRATE" />

点击二维码扫描按钮后调用方法checkCamera()
代码如下:

  private void checkCamera() {
        PermissionX.init(this).permissions(CAMERA).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List<String> deniedList) {
                scope.showRequestReasonDialog(deniedList, "扫描二维码需要开启摄像头", "允许");
            }
        }).onForwardToSettings(new ForwardToSettingsCallback() {
            @Override
            public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
                scope.showForwardToSettingsDialog(deniedList, "需要在应用程序设置中手动开启", "OK");
            }
        }).request(new RequestCallback() {
            @Override
            public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                if (allGranted) {
                    startActivityForResult(
                            new Intent(MainActivity.this, ScanQRCodeActivity.class)
                            , SCAN_RESULT);
                } else {
                    Toast.makeText(MainActivity.this, "权限已拒绝", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

在权限检查通过后(PermissionX具体用法参考上面贴出的链接),启动意图跳转到ScanQRCodeActivity二维码扫描界面:

public class ScanQRCodeActivity extends AppCompatActivity {
    private FrameLayout fl_my_container;
    public static final int SCAN_SUCCESS=1111;//扫描成功
    public static final int SCAN_FAIL=1112;//扫描失败

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_q_r_code);
        fl_my_container=findViewById(R.id.fl_my_container);
        initScan();
    }

    private void initScan() {
        try {
            /**
             * 二维码解析回调函数
             */
            CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() {
                @Override
                public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
                    if (!TextUtils.isEmpty(result)) {
                        setResult(SCAN_SUCCESS,getIntent().putExtra("success_result",result));
                        finish();
                    }
                }

                @Override
                public void onAnalyzeFailed() {
                    setResult(SCAN_FAIL,getIntent().putExtra("fail_result","扫描失败"));
                    finish();
                }
            };

            /**
             * 执行扫面Fragment的初始化操作
             */
            CaptureFragment captureFragment = new CaptureFragment();
            // 为二维码扫描界面设置定制化界面
            CodeUtils.setFragmentArgs(captureFragment, R.layout.my_camera);
            captureFragment.setAnalyzeCallback(analyzeCallback);
            /**
             * 替换我们的扫描控件
             */
            getSupportFragmentManager().beginTransaction().replace(R.id.fl_my_container, captureFragment).commit();
        } catch (Exception e) {

        }
    }
}

CodeUtils 是二维码扫描工具类Zxing下的包,根据回调结果响应返回数据给上一个Activity
其中布局activity_scan_q_r_code,my_camera 截图如下:


activity_scan_q_r_code.png
my_camera .png

扫描结果返回到MainActivity 中的 onActivityResult方法,将结果显示在控件TextView 上

  @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (resultCode){
            case ScanQRCodeActivity.SCAN_SUCCESS:
                tv_content.setText(data.getStringExtra("success_result"));
                break;

            case ScanQRCodeActivity.SCAN_FAIL:
                tv_content.setText(data.getStringExtra("fail_result"));
                break;
        }
    }

实现了扫描二维码后,接着来实现通过相册识别二维码
截图如下:


相册识别二维码.gif

由于相册图片是存储在SD卡上的,首先添加WRITE_EXTERNAL_STORAGE,授予APP对系统的读写权限

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

checkStorage()点击调用系统相册,动态申请权限

 private void checkStorage() {
        PermissionX.init(this).permissions(WRITE_STORAGE).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List<String> deniedList) {
                scope.showRequestReasonDialog(deniedList, "读取相册需要该权限", "允许");
            }
        })
                .onForwardToSettings(new ForwardToSettingsCallback() {
                    @Override
                    public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
                        scope.showForwardToSettingsDialog(deniedList, "需要在应用程序设置中手动开启", "OK");
                    }
                })
                .request(new RequestCallback() {
                    @Override
                    public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                        if (allGranted) {
                            Intent intent = new Intent(Intent.ACTION_PICK,
                                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                            startActivityForResult(intent, DEVICE_PHOTO_REQUEST);
                        } else {
                            Toast.makeText(MainActivity.this, "权限已拒绝", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }

相关的请求码和常量如下:

public static final String WRITE_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public final static int DEVICE_PHOTO_REQUEST = 1234;

在onActivityResult 方法中对回调的结果进行操作

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //系统相册返回请求
        switch (requestCode) {
            case DEVICE_PHOTO_REQUEST:
                if (data != null) {
                    Uri uri = data.getData();
                    String imagePath = BitMapUtil.getPicturePathFromUri(MainActivity.this, uri);
                    //对获取到的二维码照片进行压缩
                    Bitmap bitmap = BitMapUtil.compressPicture(imagePath);
                    Result result = setZxingResult(bitmap);
                    if (result == null) {
                        tv_content.setText("识别失败,请试试其它二维码");
                    } else {
                        tv_content.setText(result.getText());
                    }
                }
                break;
        }
    }

通过返回的Uri 获取其图片,优化的话可以对其压缩裁剪,使得Zxing识别更加快速,BitMapUtil已经分开处理判断7.0 uri的异常问题和Bitmap的压缩
工具类代码如下

public class BitMapUtil {
    public static String getPicturePathFromUri(Context context, Uri uri) {
        int sdkVersion = Build.VERSION.SDK_INT;
        if (sdkVersion >= 19) {
            return getPicturePathFromUriAboveApi19(context, uri);
        } else {
            return getPicturePathFromUriBelowAPI19(context, uri);
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private static String getPicturePathFromUriAboveApi19(Context context, Uri uri) {
        String filePath = null;
        if (DocumentsContract.isDocumentUri(context, uri)) {
            // 如果是document类型的 uri, 则通过document id来进行处理
            String documentId = DocumentsContract.getDocumentId(uri);
            if (isMediaDocument(uri)) { // MediaProvider
                // 使用':'分割
                String id = documentId.split(":")[1];

                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = {id};
                filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
            } else if (isDownloadsDocument(uri)) { // DownloadsProvider
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
                filePath = getDataColumn(context, contentUri, null, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            // 如果是 content 类型的 Uri
            filePath = getDataColumn(context, uri, null, null);
        } else if ("file".equals(uri.getScheme())) {
            // 如果是 file 类型的 Uri,直接获取图片对应的路径
            filePath = uri.getPath();
        }
        return filePath;
    }

    private static String getPicturePathFromUriBelowAPI19(Context context, Uri uri) {
        return getDataColumn(context, uri, null, null);
    }


    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        String path = null;

        String[] projection = new String[]{MediaStore.Images.Media.DATA};
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
                path = cursor.getString(columnIndex);
            }
        } catch (Exception e) {
            if (cursor != null) {
                cursor.close();
            }
        }
        return path;
    }


    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }


    /**
     *
     * 对图片压缩
     *
     * */

    public static Bitmap compressPicture(String imgPath) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgPath, options);
        Log.e( "log","未压缩之前图片的宽:" + options.outWidth + "--未压缩之前图片的高:"
                + options.outHeight + "--未压缩之前图片大小:" + options.outWidth * options.outHeight * 4 / 1024 / 1024 + "M");

        options.inSampleSize = calculateInSampleSize(options, 100, 100);
        Log.e( "log" ," inSampleSize:" + options.inSampleSize);
        options.inJustDecodeBounds = false;
        Bitmap afterCompressBm = BitmapFactory.decodeFile(imgPath, options);
//      //默认的图片格式是Bitmap.Config.ARGB_8888
        Log.e("log" ," 图片的宽:" + afterCompressBm.getWidth() + "--图片的高:"
                + afterCompressBm.getHeight() + "--图片大小:" + afterCompressBm.getWidth() * afterCompressBm.getHeight() * 4 / 1024 / 1024 + "M");
        return afterCompressBm;
    }


    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

}

这个方法就是我们这次最主要的功能通过 setZxingResult()方法对优化完后的图片调用二维码库里的类来解析读取,并且返回结果展示

 private static Result setZxingResult(Bitmap bitmap) {
        if (bitmap == null) return null;
        int picWidth = bitmap.getWidth();
        int picHeight = bitmap.getHeight();
        int[] pix = new int[picWidth * picHeight];
        //Log.e(TAG, "decodeFromPicture:图片大小: " + bitmap.getByteCount() / 1024 / 1024 + "M");
        bitmap.getPixels(pix, 0, picWidth, 0, 0, picWidth, picHeight);
        //构造LuminanceSource对象
        RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(picWidth
                , picHeight, pix);
        BinaryBitmap bb = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));
        //因为解析的条码类型是二维码,所以这边用QRCodeReader最合适。
        QRCodeReader qrCodeReader = new QRCodeReader();
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        hints.put(DecodeHintType.TRY_HARDER, true);
        Result result;
        try {
            result = qrCodeReader.decode(bb, hints);
            return result;
        } catch (NotFoundException | ChecksumException | FormatException e) {
            e.printStackTrace();
            return null;
        }
    }

至此,功能已经实现完毕,希望能对各位起到一点作用
在最后附上本项目的GitHub链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341