简介这份资源面向 Android 开发初学者与需要快速集成扫码能力的开发者聚焦在 Android Studio 中实现手机扫描二维码这一常见需求。内容围绕相机权限申请、ZXingLibrary 依赖引入、布局与 Activity 跳转等关键环节展开帮助读者理解从点击按钮到调起扫描界面的完整流程并兼顾 Android 6.0 以上动态权限与低版本兼容处理。资源包共 1 个文件为 PDF 格式大小约 44KB篇幅精炼适合作为随查随用的实现参考。目前已有 2819 人学习下载说明该主题在移动开发入门与功能集成中具有较高关注度。通过阅读读者可掌握扫码功能的基本接入思路、权限判断逻辑以及第三方库的调用方式为后续扩展扫码支付、扫码登录等场景打下基础。1. 扫码这件事在 Android Studio 里到底难在哪打开 Android Studio 新建一个空项目想加个「扫一扫」按钮很多人第一反应是找个库、贴几行代码就完事。真动手才发现摄像头预览、权限申请、对焦、光线、识别速度、横竖屏切换每一项都能让一个看似简单的功能卡上半天。手机扫描二维码这个需求在 Android Studio 里从来不是「调个 API」那么轻它是一条从相机硬件到图像解码的完整链路任何一环没接好用户看到的就是黑屏、糊图或者干脆没反应。这篇笔记面向的是手里已经有 Android Studio 工程、想真正把扫码功能跑通的人。不管你是刚学完 Activity 生命周期的新手还是做过几个 App 想补上扫码模块的老手下面这套路径都能直接照着走先讲清楚扫码在 Android 上靠什么实现再落到依赖、权限、预览、解码、结果处理的具体代码最后把那些让人抓狂的坑一条条拆开。核心词就三个——Android Studio、二维码、扫描全文围绕它们展开不绕弯子。2. 选型先定死用 ZXing 还是 ML Kit差别比你想的大扫码功能的第一步不是写代码是决定用哪套识别引擎。Android 上做二维码扫描主流就两条路ZXing 和 Google 的 ML Kit。选错了后面要么包体积爆炸要么识别率上不去返工成本很高。2.1 ZXing 与 ML Kit 的能力边界对比ZXingZebra Crossing是纯 Java 实现的老牌开源库核心只做图像解码相机部分要自己接。ML Kit 是 Google 的机器学习套件扫码只是它众多能力之一底层用模型推理识别率和弱光表现更好但依赖 Google Play 服务。维度ZXingML Kit依赖方式本地 aar/jar可离线依赖 Play 服务部分设备需下载模型包体积增量约 500KB 以内约 2-3MB 起识别速度快纯算法快模型推理弱光/模糊一般依赖对焦较好自定义相机完全可控需配合 CameraX国内设备兼容好无 Google 依赖部分机型缺 Play 服务会失败我一般会这样判断如果 App 面向国内、要控制包体积、相机 UI 要深度定制选 ZXing如果已经在用 CameraX、能接受 Play 服务依赖、想要更省心的识别选 ML Kit。热搜里常出现的「条码扫描」「二维码生成器」这类需求ZXing 生态更成熟生成和解码能共用一套库。2.2 在 build.gradle 里把依赖配到位确定用 ZXing 后第一步是加依赖。ZXing 官方核心库只含编解码相机预览要自己写所以通常再引入一个封装好的journeyapps:zxing-android-embedded它把 CaptureActivity 都封装好了适合快速起步。// app/build.gradle android { defaultConfig { minSdk 21 // ZXing 嵌入式库最低支持 21 targetSdk 34 } } dependencies { // ZXing 核心编解码库 implementation com.google.zxing:core:3.5.3 // 封装好的扫码 Activity省去自己写相机预览 implementation com.journeyapps:zxing-android-embedded:4.3.0 }这里core负责把图像数据转成二维码内容zxing-android-embedded负责相机预览和扫描界面。minSdk设 21 是因为嵌入式库的 CaptureActivity 用到了较新的 Camera API。如果你的项目要支持更低版本就得自己基于 Camera1 写预览工作量翻倍不建议新手这么干。提示zxing-android-embedded4.x 版本默认用 Camera2部分老机型对焦会异常遇到问题可在CaptureActivity的 intent 里强制指定使用 Camera1。2.3 权限声明与运行时申请相机权限是扫码的硬门槛。Android 6.0 以后必须运行时申请只在 Manifest 里声明是不够的。!-- AndroidManifest.xml -- uses-permission android:nameandroid.permission.CAMERA / uses-feature android:nameandroid.hardware.camera android:requiredtrue /// 在启动扫码前检查并申请权限 private val requestCamera registerForActivityResult( ActivityResultContracts.RequestPermission() ) { granted - if (granted) startScan() else showToast(没有相机权限无法扫码) } fun checkAndScan() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { startScan() } else { requestCamera.launch(Manifest.permission.CAMERA) } }registerForActivityResult是现在推荐的权限申请方式替代了旧的onRequestPermissionsResult。uses-feature里requiredtrue会让没有摄像头的设备无法安装如果你的 App 扫码只是可选功能改成false更稳妥。权限被拒后要给明确提示别让用户对着黑屏发呆。3. 从相机预览到解码把扫描链路一段段接起来依赖和权限搞定接下来是真正的核心相机画面怎么变成二维码字符串。这条链路是「相机预览 → 帧数据 → 图像解码 → 结果回调」任何一段断了功能就是废的。3.1 用 CaptureActivity 快速跑通最小可用版本如果不想自己写相机zxing-android-embedded提供的CaptureActivity能让你十分钟内看到效果。它内部已经处理好预览、对焦、解码。// 启动扫码界面 fun startScan() { val options ScanOptions().apply { setDesiredBarcodeFormats(ScanOptions.QR_CODE) // 只识别二维码 setPrompt(将二维码放入框内) // 扫描框下方提示 setCameraId(0) // 0 为后置摄像头 setBeepEnabled(false) // 关闭提示音 setOrientationLocked(true) // 锁定竖屏避免旋转错乱 } val integrator IntentIntegrator(this) integrator.setCaptureActivity(CaptureActivity::class.java) integrator.setScanningOptions(options) integrator.initiateScan() } // 接收扫描结果 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { val result IntentIntegrator.parseActivityResult(requestCode, resultCode, data) if (result ! null result.contents ! null) { val qrText result.contents // 二维码里的字符串 handleQrResult(qrText) } else { showToast(已取消扫描) } }setDesiredBarcodeFormats只留QR_CODE能提升识别速度因为不用去尝试解析条形码。setOrientationLocked(true)很关键扫码界面旋转会导致预览变形、解码失败锁竖屏是最省事的做法。parseActivityResult会把返回的 Intent 解析成IntentResultcontents就是二维码内容为空说明用户按了返回。3.2 自定义扫描界面把 ViewfinderView 换成自己的CaptureActivity自带的扫描框样式比较朴素实际项目里基本都要换。做法是继承CaptureActivity重写布局。// 自定义扫描 Activity class MyScanActivity : CaptureActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 替换默认布局为自定义布局 setContentView(R.layout.activity_my_scan) // 拿到 barcodeView 控制预览 val barcodeView findViewByIdBarcodeView(R.id.barcode_view) barcodeView.resume() } }!-- activity_my_scan.xml -- FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent com.journeyapps.barcodescanner.BarcodeView android:idid/barcode_view android:layout_widthmatch_parent android:layout_heightmatch_parent / !-- 自定义扫描框用半透明遮罩 中间镂空 -- View android:layout_width240dp android:layout_height240dp android:layout_gravitycenter android:backgrounddrawable/scan_frame_border / /FrameLayoutBarcodeView是预览和解码的核心控件resume()在onResume里调用才能启动相机。自定义扫描框用一张带边框的 drawable 叠在中间即可遮罩部分可以用四个半透明 View 拼或者直接画一张带镂空的图。注意别把BarcodeView盖住否则预览被遮挡解码区域也会受影响。3.3 解码参数怎么调识别率和速度的平衡ZXing 的解码行为由DecodeHintType控制调对了识别快调错了要么慢要么漏。// 构建解码提示参数 val hints mapOf( DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE), DecodeHintType.TRY_HARDER to true, // 尝试更复杂的图像 DecodeHintType.CHARACTER_SET to UTF-8 // 中文内容必须指定 ) // 用 MultiFormatReader 解码 val reader MultiFormatReader() reader.setHints(hints) try { val result reader.decode(bitmap) // bitmap 为相机帧转成的位图 val text result.text } catch (e: NotFoundException) { // 当前帧没识别到继续下一帧 }TRY_HARDER开启后会花更多时间分析图像弱光或模糊时有用但会拖慢速度光线好的场景可以关掉。CHARACTER_SET设成 UTF-8 是血泪经验不设的话中文二维码解出来是乱码。POSSIBLE_FORMATS限定成QR_CODE能减少无谓的格式尝试识别更快。注意decode是同步方法别在主线程调用否则预览会卡顿。放在子线程或 Camera 的回调线程里执行。3.4 扫描结果的处理与安全校验拿到二维码字符串只是开始直接拿去做跳转或请求是危险的。二维码内容可能是 URL、JSON、纯文本甚至是恶意构造的。fun handleQrResult(text: String) { when { // 纯 URL先校验协议 text.startsWith(http://) || text.startsWith(https://) - { val uri Uri.parse(text) // 只允许白名单域名跳转 if (isTrustedHost(uri.host)) openBrowser(uri) else showToast(链接来源不可信) } // JSON 格式解析前做长度限制 text.startsWith({) - { if (text.length 4096) { showToast(内容过长); return } parseJsonSafely(text) } else - showToast(识别结果$text) } }isTrustedHost做域名白名单防止二维码指向钓鱼站。JSON 解析前限制长度避免超大内容导致 OOM。这些校验看着啰嗦但线上出过一次问题就知道值。热搜里「可以看到对方名字的最后一个字」这类场景本质也是二维码内容被滥用校验环节不能省。4. 避坑排查扫码功能最常见的 5 个翻车现场扫码功能代码不多但坑特别密。下面这 5 条是我和身边人真实踩过的按「现象 → 原因 → 解决」写清楚。4.1 预览黑屏相机没画面现象进入扫码界面扫描框在但背景全黑没有任何画面。原因相机权限没拿到或者BarcodeView的resume()没在onResume里调用相机没启动。还有一种情况是setCameraId设了前置但设备没有前置。解决先确认权限已授予再检查onResume里是否调了barcodeView.resume()onPause里对应调pause()。相机 ID 用 0后置最稳别乱设。4.2 能识别但一直识别不出框里明明有码现象二维码就在框里画面也清晰就是没反应。原因解码区域和预览区域不匹配或者TRY_HARDER没开导致模糊时放弃。也可能是二维码内容字符集不是 UTF-8解码抛异常被吞了。解决确认BarcodeView的setDecoderFactory用的解码器支持当前格式开启TRY_HARDER指定CHARACTER_SET为 UTF-8。如果还不行打印NotFoundException的堆栈看是不是格式不匹配。4.3 扫描界面旋转后错乱现象手机横过来预览拉伸变形扫描框位置也偏了。原因Activity 没锁方向相机预览的宽高比和屏幕宽高比不一致。解决在 Manifest 里给扫码 Activity 加android:screenOrientationportrait或者用setOrientationLocked(true)。如果必须支持横屏得自己根据屏幕方向调整预览尺寸工作量大新手先锁竖屏。4.4 连续扫描时重复回调现象扫一次码onActivityResult被调了好几次或者同一个码连续触发。原因CaptureActivity默认扫到就返回但如果自己写的解码循环没做去重同一帧会被反复解析。解决在解码回调里加一个标志位识别成功后立即停止解码并返回。用CaptureActivity的话它内部已处理自己写循环就要手动reader.reset()并跳出。4.5 部分机型对焦失败画面糊现象某些手机尤其老机型预览一直模糊对不上焦。原因Camera2 的自动对焦在这些设备上兼容性差或者对焦模式设成了固定焦距。解决在BarcodeView初始化时设置对焦模式为连续自动对焦或强制使用 Camera1。zxing-android-embedded可以通过CameraSettings调整实在不行就换 ML Kit 试试它的对焦策略更宽容。5. 进阶技巧把扫码做成可复用模块的几个习惯功能跑通只是及格线真正让扫码模块好用的是一些不起眼的习惯。我做了几个项目后慢慢固定下来几件事。第一件是封装一个ScanManager把权限检查、启动扫码、结果解析、错误提示全收进去。业务层只调ScanManager.scan(this) { result - ... }换库或改参数时只动一个文件。这样即使以后从 ZXing 换到 ML Kit业务代码一行不用改。第二件是给扫描结果加一层「内容类型」判断。二维码里可能是 URL、WiFi 配置、名片、支付码不同类型走不同处理。我一般写一个QrContentParser用正则先粗判类型再交给对应的处理器。这样新增一种码类型时不用改主流程。第三件是埋点。扫码成功率、平均耗时、失败原因这些数据上线后能告诉你很多事。比如发现某机型失败率特别高就能针对性优化。埋点别贪多就记「开始扫描」「识别成功」「识别失败」「用户取消」四个事件够用了。第四件是测试用例。二维码有各种版本Version 1 到 40、各种纠错等级L/M/Q/H、各种内容编码。我习惯准备一组测试码纯英文、中文、URL、超长文本、低对比度、破损码每次改完解码参数都跑一遍。这比上线后靠用户反馈靠谱得多。// 一个简单的扫码结果封装 data class ScanResult( val rawText: String, val type: QrType, // URL / TEXT / JSON / WIFI ... val timestamp: Long ) enum class QrType { URL, TEXT, JSON, WIFI, UNKNOWN } // 解析入口业务层只关心这个 fun parseQrContent(text: String): ScanResult { val type when { text.startsWith(http) - QrType.URL text.startsWith(WIFI:) - QrType.WIFI text.startsWith({) - QrType.JSON else - QrType.TEXT } return ScanResult(text, type, System.currentTimeMillis()) }这套封装不复杂但能让扫码从「一次性代码」变成「可维护模块」。我吃过亏第一个项目扫码逻辑散在三个 Activity 里后来改个提示文案要翻半天。现在不管多小的项目扫码都单独成包省下的时间远超封装成本。最后说个习惯每次调完解码参数一定在真机上用弱光、反光、倾斜三种场景各扫十次。模拟器永远测不出真实相机的脾气这一步偷懒上线就等着收投诉。希望帮到你。本文还有配套的精品资源点击获取