• android 利用ZXING扫描二维码代码分析


    之前给公司做了一个摄影相关的应用,现在要添加二维码扫描的功能,网上找资料后,虽然已经成功集成到app里面,但是总感觉心里没底儿。所以趁这段时间不是很忙,总结一下。

    首先是启动扫描的UI类:

    1,Activity启动,当然是onCreate方法

        private CaptureActivityHandler handler;
        private ViewfinderView viewfinderView;
        private boolean hasSurface;
        private Vector<BarcodeFormat> decodeFormats;
        private String characterSet;
        private InactivityTimer inactivityTimer;
        private MediaPlayer mediaPlayer;
        private boolean playBeep;
        private static final float BEEP_VOLUME = 0.10f;
        private boolean vibrate;
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_capture);
            // ViewUtil.addTopView(getApplicationContext(), this,
            // R.string.scan_card);
            CameraManager.init(getApplication());
            viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
    
            Button mButtonBack = (Button) findViewById(R.id.button_back);
            mButtonBack.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    Scaner.this.finish();
                }
            });
            hasSurface = false;
            inactivityTimer = new InactivityTimer(this);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
            SurfaceHolder surfaceHolder = surfaceView.getHolder();
            if (hasSurface) {
                initCamera(surfaceHolder);
            } else {
                surfaceHolder.addCallback(this);
                surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            }
            decodeFormats = null;
            characterSet = null;
    
            playBeep = true;
            AudioManager audioService = (AudioManager) getSystemService(AUDIO_SERVICE);
            if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
                playBeep = false;
            }
            initBeepSound();
            vibrate = true;
    
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            if (handler != null) {
                handler.quitSynchronously();
                handler = null;
            }
            CameraManager.get().closeDriver();
        }
    
        @Override
        protected void onDestroy() {
            inactivityTimer.shutdown();
            super.onDestroy();
        }
    
        /**
         * 
        * 
        * 
        * @param @param result
        * @param @param barcode
    
        * @author  Administrator
        * @return void
         */
        public void handleDecode(Result result, Bitmap barcode) {
            inactivityTimer.onActivity();
            playBeepSoundAndVibrate();
            String resultString = result.getText();
            if (resultString.equals("")) {
                Toast.makeText(Scaner.this, "Scan failed!",3000).show();
            } else {
                //查询keycode 本地数据库 1,优先查询本地库,2,没有本地库,直接跳到知道链接
                //分析出keyCode
                Log.i("testMain","scan_result=====>"+resultString);
    
                String keyCode="";
                String[] split1;
                if(resultString.lastIndexOf("?")<0){
                    Intent intent = new Intent(this, InnerBrowser.class);
                    Bundle bundle = new Bundle();
                    bundle.putString("result", resultString);
                    //bundle.putParcelable("bitmap", barcode);
                    intent.putExtras(bundle);
                    startActivity(intent);Scaner.this.finish();return;
                }
                String[] attr = resultString.substring(resultString.lastIndexOf("?")-1, resultString.length()).split("&");
                for (String string : attr) {
                     split1 = string.split("=");
                    if(split1[0].equalsIgnoreCase("keycode")){
                        //找到
                        if(split1.length==2){
                            keyCode=split1[1];
                        }
                    }
                }
                Log.i("testMain","keyCode=====>"+keyCode);
                if(!StringUtils.isBlank(keyCode)){
                    AttractionDAO dao=new AttractionDAO(Scaner.this);
                    Attraction a=dao.findAttrByKeyCode(keyCode);
                    Log.i("testMain","a=====>"+a);
                    if(a!=null){
                        Intent it=new Intent();
                        it.setClass(Scaner.this, UIAttractionDetail.class);
                        it.putExtra("a", a);
                        startActivity(it);
                    }else{
                        Intent intent = new Intent(this, InnerBrowser.class);
                        Bundle bundle = new Bundle();
                        bundle.putString("result", resultString);
                        //bundle.putParcelable("bitmap", barcode);
                        intent.putExtras(bundle);
                        startActivity(intent);
                        //this.setResult(RESULT_OK, resultIntent);
                        //使用内置浏览器打开网站内容
                    }
                }else{
                    Intent intent = new Intent(this, InnerBrowser.class);
                    Bundle bundle = new Bundle();
                    bundle.putString("result", resultString);
                    //bundle.putParcelable("bitmap", barcode);
                    intent.putExtras(bundle);
                    startActivity(intent);
                    //this.setResult(RESULT_OK, resultIntent);
                    //使用内置浏览器打开网站内容
                }
            }
            Scaner.this.finish();
        }
    
        private void initCamera(SurfaceHolder surfaceHolder) {
            try {
                CameraManager.get().openDriver(surfaceHolder);
            } catch (IOException ioe) {
                return;
            } catch (RuntimeException e) {
                return;
            }
            if (handler == null) {
                handler = new CaptureActivityHandler(this, decodeFormats,
                        characterSet);
            }
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
    
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (!hasSurface) {
                hasSurface = true;
                initCamera(holder);
            }
    
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            hasSurface = false;
    
        }
    
        public ViewfinderView getViewfinderView() {
            return viewfinderView;
        }
    
        public Handler getHandler() {
            return handler;
        }
    
        public void drawViewfinder() {
            viewfinderView.drawViewfinder();
    
        }
    
        private void initBeepSound() {
            if (playBeep && mediaPlayer == null) {
                // The volume on STREAM_SYSTEM is not adjustable, and users found it
                // too loud,
                // so we now play on the music stream.
                setVolumeControlStream(AudioManager.STREAM_MUSIC);
                mediaPlayer = new MediaPlayer();
                mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                mediaPlayer.setOnCompletionListener(beepListener);
    
                AssetFileDescriptor file = getResources().openRawResourceFd(
                        R.raw.beep);
                try {
                    mediaPlayer.setDataSource(file.getFileDescriptor(),
                            file.getStartOffset(), file.getLength());
                    file.close();
                    mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
                    mediaPlayer.prepare();
                } catch (IOException e) {
                    mediaPlayer = null;
                }
            }
        }
    
        private static final long VIBRATE_DURATION = 200L;
    
        private void playBeepSoundAndVibrate() {
            if (playBeep && mediaPlayer != null) {
                mediaPlayer.start();
            }
            if (vibrate) {
                Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
                vibrator.vibrate(VIBRATE_DURATION);
            }
        }
    
        /**
         * When the beep has finished playing, rewind to queue up another one.
         */
        private final OnCompletionListener beepListener = new OnCompletionListener() {
            public void onCompletion(MediaPlayer mediaPlayer) {
                mediaPlayer.seekTo(0);
            }
        };
    

    从上面代码可以看出,做了三件事儿:加载布局文件;初始化了一个相机管理器;设置按钮监听,初始化了一个InactivityTimer实例;

    然后,最重要的是他实现了一个CallBack函数:具体参见: 

    SurfaceHolder.Callback 译文

    此时,

    surfaceCreated
    

    这个方法会调用然后就初始化相机的一些参数:


    前两个我们好理解,第三个是干嘛的?

    我们先看布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
    
            <SurfaceView
                android:id="@+id/preview_view"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:layout_gravity="center" />
    
            <com.euc.app.scan.view.ViewfinderView
                android:id="@+id/viewfinder_view"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
    
            <include
                android:id="@+id/include1"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                layout="@layout/activity_title" />
        </RelativeLayout>
    
    </FrameLayout>
    

    可以看到里面有一个自定义的View及surfaceView,

    对于我这样的初学者来说,surfaceView 是什么东西?

    csdn上看到这个文章

    Android中SurfaceView的使用详解

    虽然不是很明白,但是大致明白这是个什么东西了。

    了解了生命周期之后,我们来看他执行的方法:

    private void initCamera(SurfaceHolder surfaceHolder) {
            try {
                CameraManager.get().openDriver(surfaceHolder);//配置摄像头
            } catch (IOException ioe) {
                return;
            } catch (RuntimeException e) {
                return;
            }
            if (handler == null) {
                handler = new CaptureActivityHandler(this, decodeFormats,
                        characterSet);//初始化方法里面开启摄像头预览界面。
            }
        }
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
    
        }
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (!hasSurface) {
                hasSurface = true;
                initCamera(holder);
            }
    
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            hasSurface = false;
    
        }
    

    这个surfaceView 创建出来之后,其实也把摄像头的配置信息以及硬件信息初始化好了。

    OK,经过上面一个oncreate以及布局文件的加载,我们已经知道,摄像头预览成功,

    这个自定义的View又是干嘛的?我们继续看源码:

        private final int maskColor;
        private final int resultColor;
    
        private final int resultPointColor;
        private Collection<ResultPoint> possibleResultPoints;
        private Collection<ResultPoint> lastPossibleResultPoints;
    
        boolean isFirst;
    
        public ViewfinderView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            density = context.getResources().getDisplayMetrics().density;
            //将像素转换成dp
            ScreenRate = (int)(20 * density);
    
            paint = new Paint();
            Resources resources = getResources();
            maskColor = resources.getColor(R.color.viewfinder_mask);
            resultColor = resources.getColor(R.color.result_view);
    
            resultPointColor = resources.getColor(R.color.possible_result_points);
            possibleResultPoints = new HashSet<ResultPoint>(5);
        }
    
        @Override
        public void onDraw(Canvas canvas) {
            //中间的扫描框,你要修改扫描框的大小,去CameraManager里面修改
            Rect frame = CameraManager.get().getFramingRect();
            if (frame == null) {
                return;
            }
    
            //初始化中间线滑动的最上边和最下边
            if(!isFirst){
                isFirst = true;
                slideTop = frame.top;
                slideBottom = frame.bottom;
            }
    
            //获取屏幕的宽和高
            int width = canvas.getWidth();
            int height = canvas.getHeight();
    
            paint.setColor(resultBitmap != null ? resultColor : maskColor);
    
            //画出扫描框外面的阴影部分,共四个部分,扫描框的上面到屏幕上面,扫描框的下面到屏幕下面
            //扫描框的左边面到屏幕左边,扫描框的右边到屏幕右边
            canvas.drawRect(0, 0, width, frame.top, paint);
            canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
            canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
                    paint);
            canvas.drawRect(0, frame.bottom + 1, width, height, paint);
    
    
    
            if (resultBitmap != null) {
                // Draw the opaque result bitmap over the scanning rectangle
                paint.setAlpha(OPAQUE);
                canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
            } else {
    
                //画扫描框边上的角,总共8个部分
                paint.setColor(Color.GREEN);
                canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
                        frame.top + CORNER_WIDTH, paint);
                canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top
                        + ScreenRate, paint);
                canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
                        frame.top + CORNER_WIDTH, paint);
                canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top
                        + ScreenRate, paint);
                canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
                        + ScreenRate, frame.bottom, paint);
                canvas.drawRect(frame.left, frame.bottom - ScreenRate,
                        frame.left + CORNER_WIDTH, frame.bottom, paint);
                canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH,
                        frame.right, frame.bottom, paint);
                canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate,
                        frame.right, frame.bottom, paint);
    
    
                //绘制中间的线,每次刷新界面,中间的线往下移动SPEEN_DISTANCE
                slideTop += SPEEN_DISTANCE;
                if(slideTop >= frame.bottom){
                    slideTop = frame.top;
                }
                canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH/2, frame.right - MIDDLE_LINE_PADDING,slideTop + MIDDLE_LINE_WIDTH/2, paint);
    
    
                //画扫描框下面的字
                paint.setColor(Color.WHITE);
                paint.setTextSize(TEXT_SIZE * density);
                paint.setAlpha(0x40);
                paint.setTypeface(Typeface.create("System", Typeface.BOLD));
                canvas.drawText(getResources().getString(R.string.scan_text), frame.left, (float) (frame.bottom + (float)TEXT_PADDING_TOP *density), paint);
    
    
    
                Collection<ResultPoint> currentPossible = possibleResultPoints;
                Collection<ResultPoint> currentLast = lastPossibleResultPoints;
                if (currentPossible.isEmpty()) {
                    lastPossibleResultPoints = null;
                } else {
                    possibleResultPoints = new HashSet<ResultPoint>(5);
                    lastPossibleResultPoints = currentPossible;
                    paint.setAlpha(OPAQUE);
                    paint.setColor(resultPointColor);
                    for (ResultPoint point : currentPossible) {
                        canvas.drawCircle(frame.left + point.getX(), frame.top
                                + point.getY(), 6.0f, paint);
                    }
                }
                if (currentLast != null) {
                    paint.setAlpha(OPAQUE / 2);
                    paint.setColor(resultPointColor);
                    for (ResultPoint point : currentLast) {
                        canvas.drawCircle(frame.left + point.getX(), frame.top
                                + point.getY(), 3.0f, paint);
                    }
                }
    
    
                //只刷新扫描框的内容,其他地方不刷新
                postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top,
                        frame.right, frame.bottom);
    
            }
        }
    
        public void drawViewfinder() {
            resultBitmap = null;
            invalidate();
        }
    
        /**
         * Draw a bitmap with the result points highlighted instead of the live
         * scanning display.
         * 
         * @param barcode
         *            An image of the decoded barcode.
         */
        public void drawResultBitmap(Bitmap barcode) {
            resultBitmap = barcode;
            invalidate();
        }
    
        public void addPossibleResultPoint(ResultPoint point) {
            possibleResultPoints.add(point);
        }
    

    哦,这个就是定义了一个有动态效果的扫描界面


    上面的虽然代码不多,当时我们现在回忆一下步骤:

    1,启动activity,加载布局文件,初始化surfaceView,初始化自定义的View(动态界面),

    2,在初始化surfaceView的时候,同时初始化了摄像头的参数,初始化的handler处理器,启动了摄像头预览。


    问题:那什么时候开始监听扫描二维码的呢?

    初始化handler 的时候就开始监听了,看一下其构造函数:

    public CaptureActivityHandler(Scaner activity, Vector<BarcodeFormat> decodeFormats,
          String characterSet) {
        this.activity = activity;
        decodeThread = new DecodeThread(activity, decodeFormats, characterSet,
            new ViewfinderResultPointCallback(activity.getViewfinderView()));
        decodeThread.start();
        state = State.SUCCESS;
        // Start ourselves capturing previews and decoding.
        CameraManager.get().startPreview();
        restartPreviewAndDecode();
      }
    

    再来一个:上面构造函数new了一个对象,这个对象就是用来监听获取扫描的图像的。

    直到获取了二维码图像,调用回调函数就结束。

    final class DecodeThread extends Thread {
    
      public static final String BARCODE_BITMAP = "barcode_bitmap";
      private final Scaner activity;
      private final Hashtable<DecodeHintType, Object> hints;
      private Handler handler;
      private final CountDownLatch handlerInitLatch;
    
      DecodeThread(Scaner activity,
                   Vector<BarcodeFormat> decodeFormats,
                   String characterSet,
                   ResultPointCallback resultPointCallback) {
    
        this.activity = activity;
        handlerInitLatch = new CountDownLatch(1);
    
        hints = new Hashtable<DecodeHintType, Object>(3);
    
        if (decodeFormats == null || decodeFormats.isEmpty()) {
             decodeFormats = new Vector<BarcodeFormat>();
             decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
             decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
             decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
        }
    
        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
    
        if (characterSet != null) {
          hints.put(DecodeHintType.CHARACTER_SET, characterSet);
        }
    
        hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
      }
    
      Handler getHandler() {
        try {
          handlerInitLatch.await();
        } catch (InterruptedException ie) {
          // continue?
        }
        return handler;
      }
    
      @Override
      public void run() {
        Looper.prepare();
        handler = new DecodeHandler(activity, hints);
        handlerInitLatch.countDown();
        Looper.loop();
      }
    
    }
    

    回调函数:

    public void handleDecode(Result result, Bitmap barcode) {
        inactivityTimer.onActivity();
        playBeepSoundAndVibrate();
        String resultString = result.getText();
        if (resultString.equals("")) {
            Toast.makeText(Scaner.this, "Scan failed!",3000).show();
        } else {  
            //扫描结果的处理。
        }
        Scaner.this.finish();
    }
    

    life日志

  • 相关阅读:
    git学习记录——基础概念和文件的基本操作
    java 之 Properties 类
    java 之 Map类
    java 之 迭代器
    java 之 Collection类
    java 之日期时间操作
    java 之String类
    如何把ASP.NET MVC项目部署到本地IIS上
    [工具]Visual Studio
    [MVC][Shopping]Copy Will's Code
  • 原文地址:https://www.cnblogs.com/tony-jingzhou/p/3716530.html
Copyright © 2020-2023  润新知