简介:本资源面向Android开发者与移动应用学习者,聚焦在项目中集成ZXing实现二维码与条形码扫描功能,解决条码识别、相机调用与结果解析等常见开发需求。压缩包共1874个文件,以flat、json、xml、dex、class等构建与配置产物为主,另含java源码、jar库、png资源及gradle脚本,整体约18.42MB,结构接近一个可直接参考的Android工程。资源附带说明使用文档.docx,并包含MyApplication示例应用,便于对照学习依赖配置、扫描界面搭建、Intent启动扫描、onActivityResult结果处理、相机权限检查、闪光灯支持与扫描区域提示等关键环节,同时提供错误处理与用户体验优化思路。目前已有2535人学习下载,适合希望快速掌握ZXing集成流程、理解扫描逻辑并落地到实际项目中的开发者参考。
1. 扫码功能从零落地:为什么我建议你从 ZXing 这套源码开始拆
上个月帮一个做仓储盘点的小团队看代码,他们 App 里扫码模块用的是某商业 SDK,授权到期后整个入库流程直接卡死,临时换库又发现接口封装得太深,连个回调都改不动。这种被第三方绑定卡脖子的场景,我在项目里见过太多次。所以当我拿到这份「实现二维码扫描和条形码扫描」的源码包时,第一反应就是:它把 ZXing 这头老黄牛直接摊开给你看,从相机预览、图像裁剪、解码回调到结果处理,整条链路都在你手里。这份资源适合两类人:一类是刚接触 Android 扫码、想搞明白一帧画面怎么变成一串字符的新手;另一类是做过多款 App、但一直用封装库、想自己掌控解码参数和性能调优的老手。它不依赖任何云端识别服务,纯本地解码,离线可用,这一点在弱网仓库、地下车库这类场景里就是刚需。
2. 技术选型与工程结构:ZXing 在 Android 上到底怎么接
2.1 为什么是 ZXing,而不是别的方案
扫码这件事,市面上常见三条路:调用系统相机 + 云端识别、集成商业 SDK、以及本地开源库。云端方案延迟高、有流量成本、隐私数据要出设备;商业 SDK 功能全但授权和包体积是负担。ZXing(Zebra Crossing)是 Java 实现的开源一维/二维条码处理库,核心解码逻辑不依赖网络,Apache 2.0 协议,改起来没有法律包袱。这份源码走的就是 ZXing 本地解码路线,相机预览用 Android 原生 Camera API(部分分支用 CameraX),解码线程独立,主线程只负责 UI 和结果回调。
选型上有个细节值得说:ZXing 官方提供的android-core和android-integration两个模块,前者是给开发者自己写扫描界面用的,后者是 Intent 集成(调起一个现成的扫码 Activity)。这份源码用的是前者,也就是说扫描界面、取景框、闪光灯按钮、权限申请这些都要自己写。对新手来说多了一点工作量,但换来的是完全可控——取景框大小、解码格式白名单、连续扫描间隔,全都能改。
2.2 目录结构一览
拿到源码后先别急着跑,花五分钟把目录过一遍,后面改起来心里有数。典型结构如下(不同分支命名略有差异):
app/ ├── src/main/ │ ├── java/com/example/scanner/ │ │ ├── CameraManager.java // 相机打开、预览、对焦 │ │ ├── DecodeThread.java // 解码工作线程 │ │ ├── DecodeHandler.java // 处理预览帧消息 │ │ ├── CaptureActivity.java // 扫描主界面 │ │ ├── ViewfinderView.java // 取景框自定义 View │ │ └── ResultActivity.java // 结果展示页 │ ├── res/layout/ │ │ ├── activity_capture.xml │ │ └── activity_result.xml │ └── AndroidManifest.xml └── build.gradleCameraManager管硬件,DecodeThread管计算,CaptureActivity管生命周期和 UI,ViewfinderView负责画那个会动的扫描线。职责切得比较干净,二次开发时想换 UI 只动 View,想调解码策略只动 DecodeHandler。
2.3 依赖与权限配置
build.gradle里核心依赖就一个:
dependencies { implementation 'com.google.zxing:core:3.5.2' // 解码核心,纯 Java implementation 'com.google.zxing:android-core:3.5.2' // 相机配置工具类 }AndroidManifest.xml里必须声明相机权限和硬件特性:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />注意autofocus设成false是常见做法,因为部分低端设备没有自动对焦,设成true会导致应用在应用市场被过滤掉。运行时权限(Android 6.0+)在CaptureActivity的onResume里检查,没授权就弹系统对话框,拒绝后给一个引导去设置的提示。
3. 核心链路拆解:一帧预览画面如何变成扫码结果
3.1 相机预览与帧数据获取
扫码的起点是拿到相机的预览帧。这份源码用的是CameraAPI 的setPreviewCallbackWithBuffer,比setPreviewCallback少一次内存分配,在连续扫码场景下更稳。
// CameraManager.java 片段 public void startPreview() { Camera.Parameters params = camera.getParameters(); // 选择预览尺寸,优先选接近 1280x720 的 Point previewSize = findBestPreviewSize(params); params.setPreviewSize(previewSize.x, previewSize.y); // 连续对焦模式,扫码时画面容易糊 params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); camera.setParameters(params); camera.setPreviewCallbackWithBuffer(this); // this 实现 PreviewCallback camera.startPreview(); } @Override public void onPreviewFrame(byte[] data, Camera camera) { // data 是 NV21 格式的原始帧,直接丢给解码线程 if (decodeHandler != null) { decodeHandler.obtainMessage(DecodeHandler.DECODE, data).sendToTarget(); } camera.addCallbackBuffer(data); // 复用缓冲区 }这里有几个参数值得盯:FOCUS_MODE_CONTINUOUS_PICTURE比FOCUS_MODE_AUTO更适合扫码,因为它会持续微调焦距,不会出现对焦一次后画面又糊掉的情况。预览尺寸不是越大越好,1280x720 在多数中端机上解码速度和清晰度平衡得最好,选 1920x1080 反而会因为单帧数据量大导致解码线程排队。
3.2 解码线程与 ZXing 核心调用
解码放在独立线程,避免阻塞主线程导致预览卡顿。DecodeHandler收到帧数据后,把 NV21 转成 ZXing 能吃的PlanarYUVLuminanceSource,再交给MultiFormatReader。
// DecodeHandler.java 片段 private void decode(byte[] data, int width, int height) { long start = System.currentTimeMillis(); // 裁剪取景框区域,减少无效计算 Rect rect = viewfinderView.getScanRect(); byte[] rotated = rotateYUV420Degree90(data, width, height); // 竖屏旋转 PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource( rotated, height, width, rect.top, rect.left, rect.width(), rect.height(), false); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); try { Result result = multiFormatReader.decodeWithState(bitmap); // 解码成功,回传结果 Message.obtain(captureHandler, CaptureActivity.DECODE_SUCCESS, result).sendToTarget(); } catch (NotFoundException e) { // 没扫到是常态,不打印堆栈,直接继续 } finally { multiFormatReader.reset(); } }HybridBinarizer是 ZXing 里对光照不均场景比较友好的二值化器,比GlobalHistogramBinarizer更适合手机摄像头。decodeWithState配合reset是官方推荐的复用写法,避免每次解码都重建 Reader。裁剪取景框区域这一步很关键——整帧 1280x720 有 92 万像素,而取景框可能只占中间 60%,裁剪后计算量直接砍掉近一半,扫码响应肉眼可见变快。
3.3 结果回调与连续扫描控制
解码成功后,CaptureActivity收到消息,通常会做三件事:震动/响铃提示、把结果传给调用方、决定是否继续扫。
// CaptureActivity.java 片段 private Handler captureHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == DECODE_SUCCESS) { Result result = (Result) msg.obj; // 1. 反馈 playBeepSoundAndVibrate(); // 2. 回传 Intent intent = new Intent(); intent.putExtra("SCAN_RESULT", result.getText()); intent.putExtra("SCAN_FORMAT", result.getBarcodeFormat().name()); setResult(RESULT_OK, intent); // 3. 连续扫描模式:延迟 1.5 秒再恢复预览 if (continuousMode) { isDecoding = false; handler.postDelayed(() -> isDecoding = true, 1500); } else { finish(); } } } };isDecoding这个标志位是防重复的关键。扫码枪式的连续扫描如果不加节流,同一个码会被连续识别十几次,业务层收到一堆重复回调。1.5 秒是个经验值,太短容易重复,太长影响连续作业效率。result.getBarcodeFormat()返回的是QR_CODE、CODE_128、EAN_13这类枚举,业务层可以据此判断扫的是二维码还是一维码。
3.4 支持的码制与格式白名单
ZXing 默认支持一大堆格式,但实际项目里往往只需要几种。在DecodeThread初始化时设置白名单,能减少误识别、提升速度:
// DecodeThread.java 片段 Collection<BarcodeFormat> formats = new ArrayList<>(); formats.add(BarcodeFormat.QR_CODE); formats.add(BarcodeFormat.CODE_128); formats.add(BarcodeFormat.EAN_13); hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); // 提高识别率,代价是慢一点TRY_HARDER这个 hint 在光线差、码有磨损时很有用,但它会让解码尝试更多旋转和采样,单帧耗时可能翻倍。我的建议是:正常场景关掉,遇到难扫的码再动态打开,或者干脆给用户一个「增强模式」开关。
4. 跑起来与真机调试:从编译到第一次扫出结果
4.1 环境准备与编译
这份源码是标准 Android Gradle 工程,编译前确认三件事:JDK 版本(建议 17)、Android SDK 版本(compileSdk一般 33 或 34)、以及 Gradle 版本匹配。用 Android Studio 打开后,先Sync Project with Gradle Files,如果卡在下载依赖,检查settings.gradle里的仓库地址是否包含google()和mavenCentral()。
# 命令行编译 debug 包 ./gradlew assembleDebug # 安装到已连接设备 ./gradlew installDebug编译报错最常见的是minSdkVersion太低导致某些 API 找不到。ZXing 的android-core要求minSdk至少 19,但相机 API 的某些方法在 21 以下行为不一致,建议直接设成 21。
4.2 真机调试要点
模拟器扫不了码,必须真机。第一次跑起来如果预览是黑屏,按这个顺序排查:相机权限是否授予、SurfaceView是否在surfaceCreated之后才startPreview、以及相机是否被其他应用占用。日志过滤Camera和DecodeHandler两个 tag,能看到帧回调和解码尝试的节奏。
调试时我习惯在DecodeHandler里加一行耗时日志:
if (System.currentTimeMillis() - start > 200) { Log.w("DecodeHandler", "slow frame: " + (System.currentTimeMillis() - start) + "ms"); }超过 200ms 的帧说明解码压力大,可能是预览尺寸选大了,或者TRY_HARDER开着,或者取景框裁剪没生效。
4.3 常见问题排查
现象:预览画面拉伸变形。原因:相机预览尺寸和 SurfaceView 宽高比不一致。解决:在CameraManager里根据 SurfaceView 的实际宽高比去选最接近的预览尺寸,而不是固定 1280x720。
现象:扫二维码秒出,扫一维码死活扫不出。原因:一维码需要更宽的取景区域和更高的水平分辨率,二维码是二维的,对高度也敏感。解决:给一维码单独调大取景框宽度,或者提示用户把码放远一点让整条码进入画面。
现象:连续扫多个码时,第二个码要等好几秒才识别。原因:解码线程还在处理上一帧,或者isDecoding标志位恢复太慢。解决:检查DecodeThread的消息队列是否堆积,必要时用removeMessages清掉过期帧。
现象:暗光下完全扫不出。原因:没有补光,二值化失败。解决:加闪光灯开关,或者在Camera.Parameters里开FLASH_MODE_TORCH。注意有些设备开闪光灯后预览帧会变亮但解码反而更差,需要实测。
现象:部分手机扫码时画面卡顿。原因:onPreviewFrame里做了耗时操作,或者缓冲区复用没做好导致 GC 频繁。解决:确保onPreviewFrame里只做消息投递,所有计算都在解码线程;addCallbackBuffer一定要在投递后立刻调用。
5. 二次开发改造:从能扫到好用,我踩过的几个坑
5.1 把扫描结果接入业务流的正确姿势
很多新手拿到源码后,直接在CaptureActivity里写业务逻辑,比如扫到码就发网络请求。这是大忌——扫描界面应该只负责「扫出字符串」,业务处理交给调用方。正确做法是通过setResult回传,调用方用startActivityForResult或ActivityResultLauncher接收。
// 调用方 ActivityResultLauncher<Intent> scanLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == RESULT_OK && result.getData() != null) { String code = result.getData().getStringExtra("SCAN_RESULT"); String format = result.getData().getStringExtra("SCAN_FORMAT"); handleScannedCode(code, format); // 业务处理 } }); scanLauncher.launch(new Intent(this, CaptureActivity.class));这样扫描模块可以独立成 library 模块,多个业务页面复用,也方便单独测试。
5.2 性能调优的三个抓手
第一,预览尺寸别贪大。我实测过一台骁龙 6 系的老机器,1280x720 平均解码 80ms,1920x1080 直接飙到 220ms,扫码体验天差地别。第二,取景框裁剪一定要做,而且裁剪区域要跟着ViewfinderView的实际绘制区域走,别写死坐标。第三,解码格式白名单按业务需要开,一个只扫二维码的 App 没必要把CODE_39、PDF_417全打开。
5.3 一个容易忽略的细节:屏幕旋转与帧方向
竖屏 App 里,相机传感器输出的帧默认是横的,必须旋转 90 度才能正确解码。这份源码在DecodeHandler里做了rotateYUV420Degree90,但如果你改了Activity的screenOrientation,旋转逻辑也要跟着调。我见过一个项目把扫描页锁成横屏,结果旋转代码没改,二维码怎么都扫不出,查了一下午才发现是方向问题。从那以后我每次改扫描页的屏幕方向配置,都会强制走一遍竖屏和横屏两种真机测试,确认帧方向和解码区域都对得上。
5.4 扩展方向:从扫码到图像识别入口
ZXing 解码拿到的Result对象里除了文本,还有getRawBytes()和getResultPoints()。前者是原始字节,扫二进制码(比如某些工业条码)时有用;后者是码在画面中的四个角点坐标,可以做 AR 叠加、扫码动画跟随。如果你后续想接 OCR 或物体识别,onPreviewFrame拿到的帧数据可以直接喂给其他模型,这份源码的相机链路就是一个现成的图像采集入口。改造时注意线程模型——别在解码线程里跑模型推理,另开一个线程池,用帧队列做背压,否则预览会卡成幻灯片。
希望这份拆解能帮你少走点弯路,把扫码这个看似简单、实则细节一堆的模块真正吃透。
本文还有配套的精品资源,点击获取