简介:这份资源面向 Android 开发初学者与需要快速集成扫码能力的开发者,聚焦在 Android Studio 中实现手机扫描二维码这一常见需求,帮助读者理解相机权限申请、第三方扫码库接入与页面跳转的完整流程。资源包共 1 个文件,为 PDF 格式,大小约 44KB,内容以图文与代码片段结合的方式呈现,便于对照阅读和本地留存。讲解覆盖 Android 6.0 及以上动态获取相机权限、在 build.gradle 中引入 zxing-library 依赖、activity_main.xml 中放置触发按钮,以及 MainActivity 中判断系统版本、申请权限并跳转 CaptureActivity 的核心逻辑,同时简要说明 ZXingLibrary 解析二维码的基本原理。目前已有 2819 人学习,适合作为扫码功能入门参考或课程设计、小工具开发时的实现思路来源。
1. 从零在 Android Studio 里跑通扫码:这份资源到底能省你多少事
如果你手上正好有一个「扫一扫」的需求,比如进销存 App 扫商品条码、门禁系统扫访客码、活动签到扫核销码,那这份 Android Studio 实现手机扫描二维码功能的资源包,基本就是拿来即用的起点。它解决的不是「二维码是什么」这种概念问题,而是把相机预览、图像解码、权限申请、结果回调这一整条链路在 Android Studio 里真正跑起来。适合两类人:一是刚接触 Android 相机开发、被 Camera2 和权限搞晕的新手;二是想快速验证扫码方案、不想从零造轮子的熟手。下面我按「资源是什么 → 怎么用 → 坑在哪」的顺序,把这份资源拆开讲透,让你照着就能复现。
2. 扫码方案选型:ZBar、ZXing 还是 ML Kit,先想清楚再动手
2.1 三种主流解码库的边界与取舍
在 Android Studio 里做扫码,绕不开三个名字:ZXing、ZBar、Google ML Kit。这份资源默认走的是 ZXing 的 Android 封装路线,原因很实际——纯 Java 实现、不依赖 Google Play 服务、离线可用、社区资料多。ZBar 解码速度快,但对中文和复杂二维码的识别率不如 ZXing,而且 C 库集成要配 NDK,新手容易在 so 文件上翻车。ML Kit 识别率最高、支持多码同扫,但它是 Google 的服务,国内设备没有 GMS 时直接罢工,这一点在真机测试时是血泪经验。
选型时我一般看三个维度:是否需要离线、是否依赖 GMS、包体积能接受多大。ZXing 的 core 包约 500KB,加上相机预览封装整体增量在 1MB 左右,对大多数 App 可接受。如果你的场景是固定扫一种码、对速度极敏感,可以考虑 ZBar;如果是复杂光照、多码场景且能接受 GMS 依赖,ML Kit 更省心。这份资源选 ZXing,是稳妥且通用的选择。
2.2 依赖引入与 Gradle 配置
资源包里通常已经配好了依赖,但你要知道每一行是干什么的,出问题才能改。核心是zxing-android-embedded这个封装库,它把相机预览和解码线程都包好了,比直接用zxing-core少写几百行。
// app/build.gradle android { compileSdk 34 defaultConfig { minSdk 21 // ZXing embedded 最低支持 21 targetSdk 34 } } dependencies { // 带相机预览的封装库,自动处理 Camera1/Camera2 兼容 implementation 'com.journeyapps:zxing-android-embedded:4.3.0' // 核心解码库,一般由上面间接引入,显式声明便于锁定版本 implementation 'com.google.zxing:core:3.5.2' }minSdk设 21 是因为 embedded 4.x 不再支持更低版本,如果你项目要兼容 Android 4.x,得降到 3.x 版本,但那样相机兼容性会差很多。targetSdk设 34 时要注意 Android 13+ 的相机权限和通知权限变化,后面避坑章节会细说。依赖加完记得 Sync,如果卡在下载,检查一下仓库地址,国内环境常见做法是加阿里云镜像。
2.3 权限声明与运行时申请
扫码必须用相机,Android 6.0 以后相机是危险权限,必须运行时申请。资源里一般会在 Manifest 声明,再在 Activity 里动态请求。
<!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.CAMERA" /> <!-- 声明相机硬件特性,非必需,但能让无相机设备不安装 --> <uses-feature android:name="android.hardware.camera" android:required="false" />// 运行时申请,Android 6.0+ 必须 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQ_CAMERA); }uses-feature里required="false"很关键,设成 true 会让没有相机的平板、模拟器无法安装你的 App,调试时容易误判。运行时申请的回调里要判断用户是否勾了「不再询问」,勾了就得引导去设置页,否则扫码按钮点了没反应,用户以为 App 坏了。
3. 扫码核心实现:从启动相机到拿到解码结果
3.1 用 CaptureActivity 一行启动扫码
ZXing embedded 最省事的地方是自带一个CaptureActivity,你直接用它就能扫,连布局都不用写。
// 方式一:最简单,直接启动内置扫码页 IntentIntegrator integrator = new IntentIntegrator(this); integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE); // 只扫二维码 integrator.setPrompt("把二维码放进框里"); // 底部提示文字 integrator.setCameraId(0); // 0 后置,1 前置 integrator.setBeepEnabled(true); // 扫中响一声 integrator.setOrientationLocked(false); // 不锁横竖屏 integrator.initiateScan();setDesiredBarcodeFormats只留 QR_CODE 能提升识别速度,因为解码线程不用遍历所有格式。setCameraId(0)是后置摄像头,扫码场景基本都用后置。setOrientationLocked(false)让屏幕能旋转,但要注意旋转后预览方向可能错乱,这是 embedded 库的老问题,后面避坑会讲。
3.2 接收扫码结果与解析
启动扫码页后,结果通过onActivityResult回来,这是必须处理的回调。
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data); if (result != null) { if (result.getContents() == null) { // 用户按了返回,没扫到 Toast.makeText(this, "已取消", Toast.LENGTH_SHORT).show(); } else { // 拿到二维码内容 String content = result.getContents(); String format = result.getFormatName(); // 码类型,如 QR_CODE handleScanResult(content, format); } } else { super.onActivityResult(requestCode, resultCode, data); } }parseActivityResult是库提供的静态方法,帮你把 Intent 里的内容解出来,不用自己抠 key。getContents()返回 null 表示用户主动取消,这个分支一定要处理,否则空指针。getFormatName()在只扫二维码时恒为 QR_CODE,但如果你放开了多种格式,它能告诉你扫到的是 EAN_13 还是 CODE_128,做业务分流时有用。
3.3 自定义扫码界面:把扫码框嵌进自己的布局
内置 CaptureActivity 够用但样式固定,很多项目要求扫码框嵌在自定义页面里。这时用DecoratedBarcodeView自己搭。
<!-- activity_custom_scan.xml --> <com.journeyapps.barcodescanner.DecoratedBarcodeView android:id="@+id/barcode_view" android:layout_width="match_parent" android:layout_height="match_parent" app:zxing_scanner_layout="@layout/custom_scanner_overlay" />// 自定义 Activity 里绑定 private DecoratedBarcodeView barcodeView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom_scan); barcodeView = findViewById(R.id.barcode_view); barcodeView.setStatusText("对准二维码自动识别"); // 设置解码回调 barcodeView.decodeContinuous(new BarcodeCallback() { @Override public void barcodeResult(BarcodeResult result) { if (result.getText() != null) { barcodeView.pause(); // 先暂停相机,避免重复回调 handleScanResult(result.getText(), result.getBarcodeFormat().name()); } } @Override public void possibleResultPoints(List<ResultPoint> resultPoints) { // 预览里画识别点,一般不用 } }); } @Override protected void onResume() { super.onResume(); barcodeView.resume(); // 必须,否则回来黑屏 } @Override protected void onPause() { super.onPause(); barcodeView.pause(); // 必须,否则相机不释放 }decodeContinuous是连续解码,相机一直开着,扫到就回调。回调里第一件事是pause(),否则同一张码会连续触发多次,业务上会重复下单或重复签到。onResume/onPause里必须成对调用resume/pause,这是相机资源管理的铁律,漏了就是黑屏或耗电。custom_scanner_overlay是你自己画的扫码框布局,中间镂空、四角画线,纯 XML 就能做。
4. 避坑与排查:扫码翻车的五个真实场景
4.1 现象:扫码页一片黑,相机没启动
原因通常是权限没给,或者onResume里忘了调resume()。还有一种情况是模拟器没有真实相机,用的是虚拟场景,预览是黑的但能扫静态图。解决:先确认权限回调里grantResults是PERMISSION_GRANTED,再检查onResume是否调了resume。真机测试优先,模拟器扫码本来就不靠谱。
4.2 现象:扫到了但回调触发好几次
原因就是回调里没暂停相机,连续解码模式下同一张码每帧都可能命中。解决:在barcodeResult第一行调barcodeView.pause(),处理完业务再决定要不要resume()。如果是内置 CaptureActivity,它自己处理了去重,一般不会重复。
4.3 现象:横竖屏切换后预览方向错乱
这是 embedded 库在部分机型上的老毛病,相机传感器方向和屏幕方向没对齐。解决:扫码页锁定竖屏,setOrientationLocked(true),或者在自己的 Activity 里android:screenOrientation="portrait"。扫码场景锁竖屏体验反而更稳,用户不会边转手机边扫。
4.4 现象:Android 13+ 上相机权限申请了还是打不开
Android 13 对权限做了细分,但相机权限本身没变,问题多半出在targetSdk升到 33+ 后,某些厂商 ROM 对权限回调的处理有差异。解决:申请前先shouldShowRequestPermissionRationale判断,被拒后引导去Settings.ACTION_APPLICATION_DETAILS_SETTINGS手动开。另外别在onCreate里直接申请,等用户点扫码按钮再申请,通过率更高。
4.5 现象:Release 包扫码正常,Debug 包反而报错
常见于混淆配置。ZXing 的某些类被混淆后反射失败。解决:在proguard-rules.pro里加保留规则。
# ZXing 保留规则 -keep class com.google.zxing.** { *; } -keep class com.journeyapps.barcodescanner.** { *; } -dontwarn com.google.zxing.**Debug 包一般不混淆,所以问题常出现在 Release。反过来如果 Debug 报错 Release 正常,那多半是依赖冲突,用./gradlew app:dependencies看有没有重复的 zxing 版本。
5. 进阶技巧:把扫码结果用对,比扫得快更重要
扫码本身跑通只是第一步,真正决定体验的是拿到结果之后怎么处理。我一般会在handleScanResult里做三件事:格式校验、内容清洗、防重复。格式校验是判断getFormatName()是不是你预期的码类型,防止用户扫了别的码进来。内容清洗是去掉首尾空白和不可见字符,二维码里带换行是常事,直接拿去请求接口会 400。防重复是加一个时间戳或内容缓存,同一内容 2 秒内只处理一次。
private String lastContent = ""; private long lastTime = 0; private void handleScanResult(String content, String format) { if (content == null) return; content = content.trim(); // 清洗 long now = System.currentTimeMillis(); if (content.equals(lastContent) && now - lastTime < 2000) { return; // 2 秒内重复,丢弃 } lastContent = content; lastTime = now; // 业务分流:URL 走浏览器,纯文本走搜索,自定义协议走内部路由 if (content.startsWith("http")) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(content))); } else { tvResult.setText(content); } }验证方法很简单:拿同一张码连续扫,看日志里handleScanResult是不是只进了一次;拿一张带换行的码扫,看trim后内容是否干净。我还会用一张模糊的码测识别率,ZXing 对模糊码的容忍度一般,如果业务场景光线差,可以考虑加个手电筒开关,barcodeView.setTorchOn()一行就能开。
从那以后我每次集成扫码,都强制走一遍「真机 + 弱光 + 重复扫 + 横竖屏」这四个用例,少一个都可能在上线后收到用户反馈。希望帮到你。
本文还有配套的精品资源,点击获取