Zxing扫描二维码和识别二维码图片

需求说明

前面几篇文章讲述了Zxing自定义扫描界面,但是没有说明如何扫描二维码和识别二维码图片,通过这篇文章希望给大家一些思路。

修改过程

1.识别二维码图片

首先导入知乎的图片选择器

//图片选择
    implementation 'com.zhihu.android:matisse:0.5.1'

在CaptureActivitty中初始化matisse

      Matisse.from(CaptureActivity.this)
                                .choose(MimeType.ofAll())//图片类型
                                .countable(true)//是否显示选择图片的数字
                                .maxSelectable(1)//最大图片选择数
                                //gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size)   显示图片大小
                                .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
                                .thumbnailScale(0.85f)//清晰度
                                .theme(R.style.Matisse_Zhihu)//主题样式
                                .imageEngine(new MyGlideEngine())//图片加载引擎
                                .forResult(REQUEST_CODE);

注意matisse使用Glide的是3.7.X的版本如果你的Glide版本为4.X.X,请修改图片加载引擎,按照matisse的GlideEngine重写出一个Glide 4版本的图片加载引擎,代码如下:

public class MyGlideEngine implements  ImageEngine {


    @Override
    public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Glide.with(context)
                .asBitmap()  // some .jpeg files are actually gif
                .load(uri)
                .override(resize, resize)
                .centerCrop()
                .into(imageView);

    }

    @Override
    public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) {
        Glide.with(context)
                .asBitmap()
                .load(uri)
                .placeholder(placeholder)
                .override(resize, resize)
                .centerCrop()
                .into(imageView);

    }

    @Override
    public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
                .load(uri)
                .override(resizeX, resizeY)
                .priority(Priority.HIGH)
                .into(imageView);
    }

    @Override
    public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
        Glide.with(context)
                .load(uri)
                .override(resizeX, resizeY)
                .priority(Priority.HIGH)
                .into(imageView);
    }

    @Override
    public boolean supportAnimatedGif() {
        return true;
    }
}

读取图片需要Android权限,不要忘记在6.0以上动态获取

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
tv_picture.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe(granted->{
                      if (granted){
                        Matisse.from(CaptureActivity.this)
                                .choose(MimeType.ofAll())//图片类型
                                .countable(true)//是否显示选择图片的数字
                                .maxSelectable(1)//最大图片选择数
                                //gridExpectedSize(120)getResources().getDimensionPixelSize(R.dimen.grid_expected_size)   显示图片大小
                                .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)//图像选择和预览活动所需的方向
                                .thumbnailScale(0.85f)//清晰度
                                .theme(R.style.Matisse_Zhihu)
                                .imageEngine(new MyGlideEngine())
                                .forResult(REQUEST_CODE);//请求码
                      }else{
                                Toast.makeText(CaptureActivity.this,"未获取相关权限,请稍后再试",Toast.LENGTH_SHORT).show();
                      }
                });

      }
    });

在onActivityResult获取选中图片的uri,这里使用了Glide的SimpleTarget,作用是选中图片的bitmap对象

 @Override
  public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (resultCode == RESULT_OK && requestCode == HISTORY_REQUEST_CODE && historyManager != null) {
      int itemNumber = intent.getIntExtra(Intents.History.ITEM_NUMBER, -1);
      if (itemNumber >= 0) {
        HistoryItem historyItem = historyManager.buildHistoryItem(itemNumber);
        decodeOrStoreSavedBitmap(null, historyItem.getResult());
      }
    }

    //获取选中的照片
    if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
      List<Uri> list = Matisse.obtainResult(intent);
      Glide.with(this).asBitmap().load(list.get(0)).into(target);

    }
  }
private SimpleTarget target = new SimpleTarget<Bitmap>() {

    @Override
    public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
      String s = decodeQRCode(bitmap);
       if (s!=null){
        runOnUiThread(()-> {
            Toast.makeText(CaptureActivity.this,s,Toast.LENGTH_SHORT).show();
        });
      }
    }
  };

decodeQRcode(Bitmap bitmap)是解析图片二维码的核心方法,通过调用zxing的BinaryBitmap将图片转换成二进制图片对象,再由QRCodeReader解析出信息

  /**
   * 解析图片
   *
   * @param srcBitmap
   * @return
   */
  public static String decodeQRCode(Bitmap srcBitmap) {
    // 解码的参数
    Hashtable<DecodeHintType, Object> hints = new Hashtable<>(2);
    // 可以解析的编码类型
    Vector<BarcodeFormat> decodeFormats = new Vector<>();
    if (decodeFormats.isEmpty()) {
      decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
    }
    hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
    hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
    Result result = null;
    int width = srcBitmap.getWidth();
    int height = srcBitmap.getHeight();
    int[] pixels = new int[width * height];
    srcBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    //新建一个RGBLuminanceSource对象
    RGBLuminanceSource source = new RGBLuminanceSource(width, height, pixels);
    //将图片转换成二进制图片
    BinaryBitmap binaryBitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
    QRCodeReader reader = new QRCodeReader();//初始化解析对象
    try {
      result = reader.decode(binaryBitmap, hints);//开始解析
    } catch (NotFoundException | ChecksumException | FormatException e) {
      e.printStackTrace();
    }
    if (result != null) {
      return result.getText();
    }
    return null;
  }

最终效果如图


最终结果.gif

可以看到读取除了图片中的链接

2扫描二维码

当开启CaptureActivity时设置的action为com.google.zxing.client.android.SCAN时,扫描后会将结果返回


image.png
  @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 0 && resultCode == RESULT_OK) {
            if (data != null) {
                String content = data.getStringExtra("SCAN_RESULT");
                Log.e("result",content);
            }
        }


    }

可能你会问我怎么知道是key是SCAN_RESULT?你可以看一下CaptureActivityHandler中的handlerMessage

 @Override
  public void handleMessage(Message message) {
    if(message.what==R.id.restart_preview){
      restartPreviewAndDecode();
    }else if (message.what==R.id.decode_succeeded){
      state = State.SUCCESS;
      Bundle bundle = message.getData();
      Bitmap barcode = null;
      float scaleFactor = 1.0f;
      if (bundle != null) {
        byte[] compressedBitmap = bundle.getByteArray(DecodeThread.BARCODE_BITMAP);
        if (compressedBitmap != null) {
          barcode = BitmapFactory.decodeByteArray(compressedBitmap, 0, compressedBitmap.length, null);
          // Mutable copy:
          barcode = barcode.copy(Bitmap.Config.ARGB_8888, true);
        }
        scaleFactor = bundle.getFloat(DecodeThread.BARCODE_SCALED_FACTOR);
      }
      activity.handleDecode((Result) message.obj, barcode, scaleFactor);
    }else  if (message.what==R.id.decode_failed){
      // We're decoding as fast as possible, so when one decode fails, start another.
      state = State.PREVIEW;
      cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
    }else  if (message.what==R.id.return_scan_result){
      activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
      activity.finish();
    }else if (message.what==R.id.launch_product_query){
      String url = (String) message.obj;

      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.addFlags(Intents.FLAG_NEW_DOC);
      intent.setData(Uri.parse(url));

      ResolveInfo resolveInfo =
              activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
      String browserPackageName = null;
      if (resolveInfo != null && resolveInfo.activityInfo != null) {
        browserPackageName = resolveInfo.activityInfo.packageName;
        Log.d(TAG, "Using browser in package " + browserPackageName);
      }

      // Needed for default Android browser / Chrome only apparently
      if (browserPackageName != null) {
        switch (browserPackageName) {
          case "com.android.browser":
          case "com.android.chrome":
            intent.setPackage(browserPackageName);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackageName);
            break;
        }
      }

      try {
        activity.startActivity(intent);
      } catch (ActivityNotFoundException ignored) {
        Log.w(TAG, "Can't find anything to handle VIEW of URI");
      }
    }

  }

其中message.what==R.id.return_scan_result,在CaptureActivity中查找该id可以找到如下信息:

   case NATIVE_APP_INTENT:
        // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
        // the deprecated intent is retired.
        Intent intent = new Intent(getIntent().getAction());
        intent.addFlags(Intents.FLAG_NEW_DOC);
        intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
        intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
        byte[] rawBytes = rawResult.getRawBytes();
        if (rawBytes != null && rawBytes.length > 0) {
          intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
        }
        Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
        if (metadata != null) {
          if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
            intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
                metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
          }
          Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
          if (orientation != null) {
            intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
          }
          String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
          if (ecLevel != null) {
            intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
          }
          @SuppressWarnings("unchecked")
          Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
          if (byteSegments != null) {
            int i = 0;
            for (byte[] byteSegment : byteSegments) {
              intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
              i++;
            }
          }
        }
        sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
        break;

注释中说 action 为Intents.Scan.ACTION 时 会发送sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS),返回的结果会封装在key 为SCAN_RESULT;Intents对RESULT 有相关的解释

   /**
     * If a barcode is found, Barcodes returns {@link android.app.Activity#RESULT_OK} to
     * {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
     * of the app which requested the scan via
     * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
     * The barcodes contents can be retrieved with
     * {@link android.content.Intent#getStringExtra(String)}. 
     * If the user presses Back, the result code will be {@link android.app.Activity#RESULT_CANCELED}.
     */
    public static final String RESULT = "SCAN_RESULT";

在handleDecodeExternally中注释掉中的相关代码,该方法的功能是如果获取到二维码则会在viewFinderView中绘制获取到的图片bitmap对象。

 private void handleDecodeExternally(Result rawResult, ResultHandler resultHandler, Bitmap barcode) {

    if (barcode != null) {
       //注释掉
      //绘制结果图片
      //viewfinderView.drawResultBitmap(barcode);
    }

    long resultDurationMS;
    if (getIntent() == null) {
      resultDurationMS = DEFAULT_INTENT_RESULT_DURATION_MS;
    } else {
      resultDurationMS = getIntent().getLongExtra(Intents.Scan.RESULT_DISPLAY_DURATION_MS,
                                                  DEFAULT_INTENT_RESULT_DURATION_MS);
    }

    if (resultDurationMS > 0) {
      String rawResultString = String.valueOf(rawResult);
      if (rawResultString.length() > 32) {
        rawResultString = rawResultString.substring(0, 32) + " ...";
      }
    }

    maybeSetClipboard(resultHandler);

    switch (source) {
      case NATIVE_APP_INTENT:
        // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
        // the deprecated intent is retired.
        Intent intent = new Intent(getIntent().getAction());
        intent.addFlags(Intents.FLAG_NEW_DOC);
        intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
        intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
        byte[] rawBytes = rawResult.getRawBytes();
        if (rawBytes != null && rawBytes.length > 0) {
          intent.putExtra(Intents.Scan.RESULT_BYTES, rawBytes);
        }
        Map<ResultMetadataType, ?> metadata = rawResult.getResultMetadata();
        if (metadata != null) {
          if (metadata.containsKey(ResultMetadataType.UPC_EAN_EXTENSION)) {
            intent.putExtra(Intents.Scan.RESULT_UPC_EAN_EXTENSION,
                metadata.get(ResultMetadataType.UPC_EAN_EXTENSION).toString());
          }
          Number orientation = (Number) metadata.get(ResultMetadataType.ORIENTATION);
          if (orientation != null) {
            intent.putExtra(Intents.Scan.RESULT_ORIENTATION, orientation.intValue());
          }
          String ecLevel = (String) metadata.get(ResultMetadataType.ERROR_CORRECTION_LEVEL);
          if (ecLevel != null) {
            intent.putExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL, ecLevel);
          }
          @SuppressWarnings("unchecked")
          Iterable<byte[]> byteSegments = (Iterable<byte[]>) metadata.get(ResultMetadataType.BYTE_SEGMENTS);
          if (byteSegments != null) {
            int i = 0;
            for (byte[] byteSegment : byteSegments) {
              intent.putExtra(Intents.Scan.RESULT_BYTE_SEGMENTS_PREFIX + i, byteSegment);
              i++;
            }
          }
        }
        sendReplyMessage(R.id.return_scan_result, intent, resultDurationMS);
        break;

      case PRODUCT_SEARCH_LINK:
        // Reformulate the URL which triggered us into a query, so that the request goes to the same
        // TLD as the scan URL.
        int end = sourceUrl.lastIndexOf("/scan");
        String productReplyURL = sourceUrl.substring(0, end) + "?q=" + 
            resultHandler.getDisplayContents() + "&source=zxing";
        sendReplyMessage(R.id.launch_product_query, productReplyURL, resultDurationMS);
        break;
        
      case ZXING_LINK:
        if (scanFromWebPageManager != null && scanFromWebPageManager.isScanFromWebPage()) {
          String linkReplyURL = scanFromWebPageManager.buildReplyURL(rawResult, resultHandler);
          scanFromWebPageManager = null;
          sendReplyMessage(R.id.launch_product_query, linkReplyURL, resultDurationMS);
        }
        break;
    }
  }

MainActivity代码如下:

package com.zhqy.zxingdemo;

import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import com.google.zxing.client.android.CaptureActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this, CaptureActivity.class);
        intent.setAction("com.google.zxing.client.android.SCAN");
        startActivityForResult(intent,0);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 0 && resultCode == RESULT_OK) {
            if (data != null) {
                String content = data.getStringExtra("SCAN_RESULT");
                Toast.makeText(this,content,Toast.LENGTH_SHORT).show();
            }
        }


    }
}

最终结果如下:


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

推荐阅读更多精彩内容