
简介面向Android开发者的ZXing扫码实践资源系统讲解条形码与二维码的识别、生成原理及在Android项目中的集成流程适合从零实现扫码功能也可作为已有项目扩展扫码能力的参考。压缩包含390个文件涵盖176个Java源码、201个编译后的class、Android项目配置文件、XML布局、PNG图标以及可直接安装运行的示例APK整体仅1.01MB结构紧凑便于查阅源码与配置说明可辅助理解项目工程搭建。当前已有681人学习下载。内容覆盖ZXing核心组件与依赖配置、通过Intent启动扫描、onActivityResult结果处理、自定义扫描视图、二维码生成、相机权限适配及识别性能优化等关键环节同时提供可运行的Demo工程和常见条码格式的解析核心类既能帮助快速搭建可用的扫码模块也为深入研究编码解码细节和二次开发移植提供了很好的基座。1. 从扫码需求到框架选型为什么最终锁定ZXing说个很常见的场景项目做到一半产品突然丢过来一个需求——App要支持扫码条形码、二维码都要能识别。听起来简单真做起来才发现里面坑不少。我在Android端试过几条技术路线最后稳定跑在生产环境的还是ZXing。先交代一下背景。我是在一个库存管理类的App里接入扫码功能的需求很具体既要扫商品条码EAN-13、UPC-A这类一维码也要扫二维码比如设备序列号信息、出库单二维码。最初也考虑过直接调系统相机然后用第三方扫码SDK但问题是有些定制ROM的扫码能力参差不齐而且很容易被系统裁剪掉部分条码类型后期维护起来很被动。ZXing全称Zebra Crossing是目前Android生态里最老牌的开源条码识别库Apache 2.0协议可以放心商用。它最大的价值不是能扫码这么简单而是对条码格式的支持非常完整——一维码支持EAN-8、EAN-13、UPC-A、UPC-E、Code 39、Code 93、Code 128、Codabar、ITF等二维码支持QR Code、Data Matrix、Aztec、PDF 417。这个覆盖面在同类库里几乎是最全的。选型的时候我还对比过几个方案Google Mobile Vision / ML Kit识别速度确实快但ML Kit需要依赖Google Play服务国内很多设备没有GMS直接劝退。百度OCR / 腾讯云扫码识别精度高但需要联网而且有调用次数限制和费用不适合做离线扫码。ZBar老牌库但对QR Code的支持和ZXing比还是弱一些维护也不太活跃。最终选择ZXing核心原因有三点一是离线可跑不依赖任何网络服务二是条码类型覆盖面全一维码二维码通吃三是社区活跃遇到问题基本都能找到现成答案。我用的版本是3.5.3这个版本对AndroidX适配得比较好API也稳定。如果你项目里还在用旧的支持库可以选3.3.3但强烈建议升级后面讲依赖配置的时候会说原因。2. 依赖配置与相机权限最容易翻车的第一步2.1 依赖引入的正确姿势很多人接入ZXing第一反应是依赖core模块然后自己写相机预览这其实是走弯路。我建议直接用官方的android模块它把CaptureActivity、CaptureManager这些相机管理组件都封装好了省掉大量相机适配工作。Gradle里这样配dependencies { implementation com.google.zxing:core:3.5.3 implementation com.journeyapps:zxing-android-embedded:4.3.0 }注意journeyapps这个封装库它是社区维护的ZXing Android封装不是Google官方出的但是目前Android上集成ZXing的事实标准。它会自动处理相机预览、对焦、扫码框绘制这些脏活累活。有个细节很多人忽略zxing-android-embedded会传递依赖一个core版本建议像我这样显式声明core:3.5.3避免版本冲突。我有一次就是没锁版本被解析到一个老旧的core:3.3.0结果扫码框的取景区域一直有偏移查半天才发现是版本不匹配。2.2 相机权限与运行时申请Android 6.0API 23之后相机权限必须运行时申请。这个不处理好的话扫码页面打开就是黑屏而且不报错非常隐蔽。Manifest里先声明uses-permission android:nameandroid.permission.CAMERA / uses-feature android:nameandroid.hardware.camera android:requiredtrue /注意uses-feature这行它的作用是告诉应用商店这个应用必须有摄像头才能正常工作如果不加某些平板设备上可能出现扫码页面闪退。然后在进入扫码页之前主动申请权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); } else { startActivity(new Intent(this, CaptureActivity.class)); }有个实际经验扫码页尽量做成独立的Activity而不是Fragment因为zxing-android-embedded的CaptureManager和Activity生命周期绑定得很紧放在Fragment里会出现旋转屏幕后预览画面异常的问题。后面有空我会单独写一篇Fragment集成的踩坑记录这次先不展开。2.3 依赖冲突的排查技巧如果项目里还有其他用到zxing的库比如某些图片选择器内部也带了zxing很容易出现Duplicate class报错。我遇到过的情况是项目中同时引入了com.google.zxing:core和某个图片压缩库结果打包时出现重复类。解决方案是在那个库的依赖上排除掉zxingimplementation(com.example:some-image-picker:1.2.0) { exclude group: com.google.zxing exclude module: core }这个坑在大型项目里几乎必踩建议接入ZXing之前先跑一下gradle dependencies把依赖树里所有跟zxing相关的路径都看清楚。3. 核心流程拆解从相机预览到解码输出的完整链路3.1 初始化扫码引擎CaptureManager是入口它负责把相机预览、取景框、解码线程、结果回调串起来。我通常会写一个自定义的CaptureActivity继承CaptureActivity的封装类方便定制扫码框样式和提示文案。最基本的初始化逻辑public class ScanActivity extends CaptureActivity { private CaptureManager captureManager; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); captureManager new CaptureManager(this); captureManager.initializeFromIntent(getIntent(), savedInstanceState); captureManager.setShowMissingCameraPermissionDialog(true); captureManager.decode(); } Override protected void onResume() { super.onResume(); captureManager.onResume(); } Override protected void onPause() { super.onPause(); captureManager.onPause(); } Override protected void onDestroy() { super.onDestroy(); captureManager.onDestroy(); } Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); captureManager.onSaveInstanceState(outState); } Override public boolean onKeyDown(int keyCode, KeyEvent event) { return captureManager.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } }这里有个关键点initializeFromIntent会读取Intent里的扫码配置参数比如是否开启闪光灯、是否启用条码类型过滤所以启动扫码页时通过Intent传参就能灵活控制行为不需要写死。3.2 解码工作线程很多初学者以为扫码是在主线程完成的这是大误解。zxing-android-embedded内部有一个专门的DecodeThread它不断地从相机预览帧里抓取PlanarYUVLuminanceSource然后交给MultiFormatReader去尝试解码识别失败就继续取下一帧识别成功就立刻把LuminanceSource转换成Bitmap并通过Handler回调到UI线程。这个预览帧不断尝试的机制是ZXing识别速度的核心。它并不是等画面静止才识别而是以15-30fps的速度在后台持续扫描每一帧。所以扫码时手稍微抖一下也没关系只要有一帧能捕捉到清晰的条码信息解码就成功了。如果自己实现相机预览加解码最需要注意的是YUV_420_SP格式转换。相机回调默认拿到的是N21格式的字节数组需要先转成PlanarYUVLuminanceSource再用RGBLuminanceSource或者直接在亮度图上解码。zxing-android-embedded已经把这件事封装好了但如果想自己写高性能扫码器这个转换逻辑是绕不开的。3.3 识别结果回调与重复扫码控制扫码成功后会回调onScanResult(Intent data)方法这是通过CaptureManager的setResultCallback注册的。正确姿势是这样captureManager.setResultCallback(new CaptureManager.ActivityResultCallback() { Override public void onScanResult(Intent result) { String content result.getStringExtra(ScanResultContract.SCAN_RESULT); BarcodeFormat format (BarcodeFormat) result.getSerializableExtra(ScanResultContract.SCAN_RESULT_FORMAT); // 处理业务逻辑 handleScanResult(content, format); } });有个细节容易被忽略默认情况下识别成功后扫码页会自动关闭并返回结果。但如果业务上需要连续扫码比如仓库里连续扫多件商品就必须在回调里阻止页面关闭然后重新启动解码。zxing-android-embedded从4.x版本开始提供了ScanOptions和ScanContract这套新API可以更方便地控制扫码行为。连续扫码的场景可以用CaptureManager的restartPreviewAfterDelay方法实现延迟重启解码captureManager.restartPreviewAfterDelay(1000L);这个方法是实际项目中非常高频使用的不处理的话扫完一个条码页面就卡死在结果状态用户体验很糟糕。4. 自定义扫码界面让扫码框匹配你的App气质4.1 扫码框样式定制ZXing的扫码框样式通过Theme定制。先继承zxing_capture_theme主题然后覆盖几个关键的样式项style nameAppScanTheme parentzxing_capture_theme item namezxing_viewfinder_laserdrawable/scan_laser_line/item item namezxing_viewfinder_maskcolor/scan_mask_color/item item namezxing_viewfinder_border_color#00C853/item item namezxing_viewfinder_border_length800/item item namezxing_viewfinder_border_width4dp/item item namezxing_viewfinder_corner_radius8dp/item item namezxing_status_text将条码/二维码放入框内即可自动扫描/item /style这几个属性里zxing_viewfinder_laser是扫描线的Drawablezxing_viewfinder_border_color是扫码框四角的颜色zxing_status_text是底部提示文案。有个小细节zxing_viewfinder_border_length这个值不是必须的它控制的是四角边框每条边的长度默认是800单位是像素的近似值如果扫码框长度不够可以调大这个值。4.2 全屏扫码与沉浸式体验默认的扫码页面会显示标题栏如果你希望扫码界面是全屏沉浸的可以在onCreate里自己控制系统UI但同时要处理好状态栏高度对扫码框位置的影响。一个比较稳的做法是重写getViewfinderView的布局参数把扫码框放在屏幕中间偏上一点的位置因为人手持手机时视线焦点通常偏上这个微调对扫码体验的提升很明显的Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); View view getWindow().getDecorView(); view.setSystemUiVisibility( View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); }但要注意CaptureManager默认是从SurfaceView上取预览帧的如果全屏后预览画面和扫码框对不齐需要在ViewfinderView的onDraw里调整一下坐标变换具体我会在下一节讲。4.3 扫码框与预览画面错位问题这是自定义界面时遇到最多的bug。现象是相机预览画面是正常的但扫码框位置和实际取景区域对不上导致条码明明在框内却识别不出来。根本原因很简单相机预览方向和屏幕方向不一致。Android相机传感器的默认方向是横向的竖屏显示时需要对预览流做旋转。zxing-android-embedded内部虽然处理了旋转但如果你自定义了布局或者全屏切换就可能触发这个错位。解决办法是用DecoratedBarcodeView作为根布局它会自动把ViewfinderView和CameraPreview对齐com.journeyapps.barcodescanner.DecoratedBarcodeView android:idid/barcode_scanner android:layout_widthmatch_parent android:layout_heightmatch_parent app:zxing_scanner_layoutlayout/custom_scanner_layout /custom_scanner_layout里包含ViewfinderView和SurfaceViewDecoratedBarcodeView会负责让它们保持同一个坐标系。我踩过一次坑直接自定义SurfaceView扫码框用的是FrameLayout结果在部分1920x1080分辨率的设备上错位了将近两厘米换回DecoratedBarcodeView后问题消失。一句话总结只要不是特殊需求扫码界面尽量用DecoratedBarcodeView别自己组装相机预览。5. 条码类型过滤与性能调优让识别又快又准5.1 按业务场景缩小解码范围ZXing默认支持几乎所有条码类型但这不一定是好事。解码器会尝试用每一种格式去匹配当前帧一维码和二维码的匹配算法完全不同类型越多单帧的平均解码时间就越长。实际项目中一定要按业务范围过滤。比如只做商品库存场景通常只需要EAN-13、UPC-A、Code 128和QR Code其他的全部关掉ScanOptions options new ScanOptions(); options.setDesiredBarcodeFormats( ScanOptions.ONE_D_CODE_TYPES , ScanOptions.QR_CODE_TYPES ); options.setPrompt(请对准条码); options.setCameraId(0); // 后置摄像头 options.setBeepEnabled(true); // 识别成功提示音 options.setBarcodeImageEnabled(true); // 保存条码截图便于后续展示ScanOptions.ONE_D_CODE_TYPES是一维码的集合常量包含EAN/UPC/Code 128等常见格式QR_CODE_TYPES是二维码的集合常量。这样配置后解码器只需要尝试少数几种格式识别速度会有明显提升。尤其是在低端机型上setDesiredBarcodeFormats和不设这个值的差距是肉眼可见的——不设的时候扫码框经常扫半天没反应设了之后响应立刻快很多。5.2 分辨率与性能的平衡zxing-android-embedded默认会请求相机输出实时预览的最大分辨率但在解码性能上分辨率过高反而会拖慢速度。因为每一帧图像的解码时间跟像素量基本是线性的。我的建议是如果项目里大部分是二维码扫码可以把相机的预览分辨率限制在一个合理范围比如1280x720这个清晰度对QR Code足够但解码速度能快不少。可以在启动扫码页时通过Intent传递这个配置Intent intent new Intent(this, ScanActivity.class); intent.putExtra(SCAN_CAMERA_ID, 0); intent.putExtra(SCAN_WIDTH, 720); intent.putExtra(SCAN_HEIGHT, 1280); startActivity(intent);不过需要说明zxing-android-embedded对设置固定预览分辨率的支持不算完美有些设备上相机会自动调整。所以更通用的做法是解码器侧做降采样——把大分辨率的预览帧先缩小再送进去解码PlanarYUVLuminanceSource本身支持指定裁剪区域你可以只取扫码框对应的那部分区域进行解码这样既保持画面清晰又提升了单帧解码速度。5.3 弱光环境的识别策略条形码和二维码在光线不足时识别率会断崖式下降。ZXing内部有自动曝光和自动对焦的调用但有些设备在暗光下仍然表现不佳。一个实用的兜底方案是启动闪光灯。在扫码页面提供一个闪光灯切换按钮图标用官方推荐的ic_flash_on和ic_flash_offDecoratedBarcodeView barcodeView findViewById(R.id.barcode_scanner); barcodeView.setTorchOn(); // 开灯 barcodeView.setTorchOff(); // 关灯我的实测经验是在仓库、超市货架这种场景开了闪光灯扫码的成功率能提升大概30%-40%尤其是Code 128这种一维码对光线要求很高。但也要注意二维码反光严重时会过曝所以闪光灯按钮一定要给用户手动切换的入口不要强制开启。5.4 识别离焦与斜扫的兼容ZXing对条码不完全水平的容忍度其实挺高的它内部会尝试多个旋转角度去解码。但如果你发现某类设备经常扫不出稍微倾斜的条码可以开启setTryHarderMapDecodeHintType, Object hints new HashMap(); hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); hints.put(DecodeHintType.CHARACTER_SET, UTF-8);TRY_HARDER这个Hint会让解码器投入更多计算资源去尝试更多的解码路径代价是速度变慢、耗电增加。适合的场景是某些质量较差的打印条码、被遮挡了一部分的条码。我一般只在特定模式比如扫描破损条码功能下才开启平时不勾。CHARACTER_SET指定UTF-8很重要特别是二维码里带有中文信息的时候。不指定的话有些设备默认使用ISO-8859-1解码中文直接变成乱码。6. 实战中的坑从线上反馈里挖出来的三个问题6.1 扫码成功后页面卡住或重复跳转这个问题测试阶段很难复现一上线就暴露。现象是扫完一个条码页面有时会连续弹出两次结果或者弹完结果页面不动了。排查下来有两个原因一个是生命周期回调重复绑定。ScanActivity在onCreate里如果同时调用了captureManager.decode()和captureManager.initializeFromIntent()解码器会被初始化两次。解决方法是只保留initializeFromIntent中的解码启动逻辑不要重复调用decode()。另一个是onResume里没有加防重复。如果扫码结果弹出一个Dialog然后用户点击继续扫码而这个Dialog在关闭时又触发了onResume就有可能再次触发解码回调。我的做法是加一个布尔锁private boolean isDecoding false; Override public void onScanResult(Intent result) { if (isDecoding) { return; } isDecoding true; // 处理业务 // 处理完后 isDecoding false; }6.2 部分机型扫码黑屏日志没有明显报错黑屏问题大概率是相机权限没有正确授予或者相机被其他应用占用。但有一种情况很容易忽略设备没有摄像头或者摄像头被禁用了。zxing-android-embedded在CaptureManager.onCreate里会检查摄像头是否可用如果不可用它会尝试调用缺失相机提示对话框。但有些国产ROM的权限策略很激进即使你已经动态申请了权限onResume阶段相机还是打不开。我的兜底方案很土但有效在进入扫码页之前先做一个相机可用性检测public static boolean isCameraAvailable(Context context) { PackageManager pm context.getPackageManager(); return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); }检测不到摄像头时给用户一个友好的提示并直接返回不要进入扫码页。这个在App兼容性测试里很重要——市面上确实有少量不带后置摄像头的平板设备。6.3 扫描中文二维码输出乱码二维码内容如果是中文且编码是GBKZXing默认的UTF-8解码就会出问题。解决方法是解码时同时尝试UTF-8和GBK两种编码MapDecodeHintType, Object hints new HashMap(); ListBarcodeFormat formats new ArrayList(); formats.add(BarcodeFormat.QR_CODE); hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); hints.put(DecodeHintType.CHARACTER_SET, UTF-8); // 有些二维码用GBK编码识别失败时再用GBK尝试一次 try { result reader.decode(bitmap, hints); } catch (NotFoundException e) { hints.put(DecodeHintType.CHARACTER_SET, GBK); result reader.decode(bitmap, hints); }这个兼容逻辑在实际商用扫码需求里很重要。尤其是对接一些老的ERP系统导出的二维码经常是GBK编码不处理就是一堆锟斤拷。7. 从纯扫码到业务闭环ZXing之外的延伸思路扫码只是入口真正让项目出彩的是扫码之后的业务衔接。以我的库存项目为例扫码拿到条码后第一件事不是直接跳详情页而是先做本地缓存和离线校验。我们会把最近扫过的条码和对应的商品信息存到Room数据库里下次没网时再扫同一条码能直接展示缓存信息而不是转圈圈。二维码的情况比一维码更灵活。二维码内容本身可以是一个URL、一个JSON字符串或一个纯文本。我的解析策略是这样的if (content.startsWith(http://) || content.startsWith(https://)) { // 跳转WebView但先做域名白名单校验 openWebPage(content); } else if (content.startsWith({) content.endsWith(})) { // 尝试解析JSON parseJsonContent(content); } else { // 纯文本按业务字段分隔符解析 parsePlainText(content); }这里有个安全提示如果二维码内容是URL千万不要直接跳转。恶意二维码可能指向钓鱼网站或者下载链接。稳妥的做法是先把URL域名和服务端配置的白名单比对匹配才允许跳转否则只显示链接原文让用户自己判断。ZXing本身并不提供识别防伪二维码的能力如果你的业务需要二维码防伪建议在二维码内容里加入签名信息比如时间戳随机数的哈希扫码后到服务端验签。这个属于业务安全范畴但做扫码功能的工程师最好了解因为产品随时可能提这个需求。另外BarcodeFormat里有个QR_CODE和DATA_MATRIX的区别需要清楚Data Matrix主要用在工业小物件表面体积小、容错高但普通用户接触少。我在对接某个军工企业的资产盘点项目时就遇到要用Data Matrix的场景当时条件反射地以为所有二维码都是QR Code结果识别率一塌糊涂。后来加了DATA_MATRIX类型后才解决。做扫码功能强烈建议保留DATA_MATRIX支持成本几乎为零但能防住一批特殊场景。8. 性能压测与稳定性根据我线上项目的实测建议说一个我实际压测的数据在麒麟990处理器的设备上用默认配置连续扫描100个条码混合二维码和EAN-13ZXing平均识别耗时在120ms左右最好成绩约80ms最差约300ms出现在条码严重反光时。在骁龙660级别的老设备上平均耗时大概200ms多一点依然可用。如果你觉得这个速度还不够可以考虑以下优化路径降低解码帧率默认每秒尝试解码所有预览帧可以改成每2-3帧才解码一次减少CPU占用。降低预览分辨率720p的预览帧已经能覆盖绝大多数扫码场景不需要上1080p。界面启动优化扫码页的启动不要做多余IO操作onCreate里只初始化相机和解码器其他全部放到后台线程。内存方面也提醒一下ZXing的单帧解码会创建Bitmap和byte[]对象在低内存设备上频繁扫码可能触发GC抖动。我在项目中加了一个简单的对象池复用解码用的数组性能提升不算特别明显但GC次数确实降下来了。还有一点必须提ZXing的MultiFormatReader不是线程安全的。同一个CaptureManager实例的解码操作都在同一个解码线程串行执行问题不大但如果你把MultiFormatReader暴露给多个线程用会出现偶发的崩溃。我遇到过的问题是自定义了一个Reader包装类去解析相册里的图片结果和相机的解码线程同时跑直接抛了ArrayIndexOutOfBoundsException。解决方案是相册图片解码单独创建Reader实例不要复用。9. 最后再分享一点工程层面的建议前面说了很多技术细节最后聊点工程层面的。如果你接手的是一个老项目接入ZXing之前一定要先梳理现有依赖确认没有重复类冲突。如果你是从零开始建议直接用最新的zxing-android-embedded和core不要因为老项目一直用旧版而排斥升级——旧版在Android 10以后的相机权限适配上有问题部分机型会黑屏。扫码功能虽然看起来简单但它横跨相机、图像处理、编码规范、业务安全好几个领域。做好一个扫码功能不难难的是一套代码在所有Android设备上都能稳定运行。我的建议是接入后务必做一轮覆盖老中青三代设备的真机测试重点看这几个维度低端机的启动速度和识别速度暗光条件下的识别成功率不同屏幕比例下扫码框和预览画面的对齐情况连续扫码100次以上是否出现卡顿或崩溃做完这轮测试你交付出去的扫码功能才算是真正能扛住线上环境的。这套ZXing的方案我已经在两个生产项目上跑了两年多累计扫码量有几十万次除了一些业务侧的接口问题核心扫码引擎本身没有出过大毛病。如果你正在为Android端扫码选型发愁从ZXing入手是性价比很高的选择。本文还有配套的精品资源点击获取