Android 开发生僻却实用的知识点 Part 3:从二维码性能优化到 Mockito 测试实战
文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载导读本文是Android 开发生僻却实用的知识点系列文章的第三部分由 WillowTree 团队的 Android 开发者们分享他们在实际项目中踩过的坑与积累的小技巧。文章覆盖七个实战知识点ZXing 二维码大图生成性能优化、adb命令的小细节、TextUtils.concat()的正确使用、父 View 与子 View 的触摸事件拦截博弈、SQLite 事务的提交/回滚陷阱、单元测试中的静态方法限制以及用 Mockito 简化对象依赖的实践。读完本文你将获得可直接复制进工程的高效二维码生成方案、更稳妥的事务与事件分发处理方式以及一套更清爽的测试代码写法。如果你是第一次阅读本系列可以先看看团队在 Part 1Android Studio Image Asset、StrictMode、adjustResize 等和 Part 2WebView 崩溃、RecyclerView position、Collections.emptyList()等中分享的内容。本文在仓库中的位置是 issue-31/Android开发生僻却实用的知识点Part3.md。Tidbit One用 ZXing 高效生成大尺寸二维码如果你正在使用 ZXing 的二维码生成器库来生成二维码会发现直接用它生成大图片比较慢。QRCodeWriter.encode()在指定大尺寸时需要对整个矩阵做逐位填充与渲染尺寸越大耗时越长。核心思路先生成最小矩阵再按需缩放一个被验证可行的优化办法是向库传入0 x 0的图片尺寸此时它会返回一个最小尺寸的BitMatrix矩阵中每一个块对应 1 像素。之后你把这个小矩阵写入BitmapDrawable再将其设为某个 View 的背景由 View 的绘制系统按需缩放展示——这样就把生成大图的昂贵工作替换成了生成小图 缩放的廉价操作。BitMatrix matrix new QRCodeWriter() .encode(content here, BarcodeFormat.QR_CODE, 0, 0); int height matrix.getHeight(); int width matrix.getWidth(); Bitmap bmp Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); for (int x 0; x width; x) { for (int y 0; y height; y) { bmp.setPixel(x, y, matrix.get(x, y) ? Color.BLACK : Color.TRANSPARENT); } } BitmapDrawable qrCodeDrawable new BitmapDrawable(getResources(), bmp); qrCodeDrawable.setFilterBitmap(false); // 关键防止缩放时模糊 imgQrCode.setBackground(qrCodeDrawable);三个关键注意点必须调用setFilterBitmap(false)如果不关闭位图过滤BitmapDrawable在放大显示时会被插值平滑导致二维码的黑白边缘发灰、变得模糊最终影响扫码识别率。所以在把 Drawable 设进 View 之前务必先执行qrCodeDrawable.setFilterBitmap(false)。按需选择位图格式原文示例因二维码背景需要透明使用了Bitmap.Config.ARGB_4444。如果只想要黑白两色、不需要透明背景可以用Bitmap.Config.RGB_565内存占用更小。需要提醒的是ARGB_4444从 API 16 开始已被官方标记为不推荐使用deprecated在新项目里建议优先评估RGB_565或直接采用下面的稳妥方案。对0 x 0参数的兼容性要保持警惕库的实现可能在未来版本中不再接受0 x 0这种非常规参数——毕竟二维码的符号结构决定了其最小尺寸也不可能比10 x 10更小。如果你担心升级库后出现兼容问题可以把创建方法改为显式指定一个较小的尺寸BitMatrix matrix new QRCodeWriter() .encode(content here, BarcodeFormat.QR_CODE, 10, 10);源码层面的依据上述 API 行为直接来自 ZXing 的QRCodeWriter与BitMatrix定义encode(contents, format, width, height)返回用于描述二维码明暗块的矩阵对象matrix.get(x, y)在坐标为true时表示该块为黑色而BitmapDrawable的setFilterBitmap(boolean)正是控制缩放时是否进行双线性过滤的开关。这套小矩阵 关闭过滤 View 背景缩放的组合拳是二维码从生成慢到秒出图的关键。Tidbit Twoadb hell与adb shell结果相同一个看起来匪夷所思、实测却真实存在的小现象在命令行输入adb hell和输入adb shell得到的结果是一样的——adb会进入设备上的 shell 交互环境。这个知识点更多是一个有趣的观察记录adb工具在解析子命令时存在一定的容错行为因此拼写错误这里是缺少s的hell也可能被温柔地接受。不过必须强调两点不要在脚本或 CI 流程中依赖这种容错行为它是未文档化的实现细节任何版本的adb工具更新都可能收紧参数解析规则让这个特性失效日常开发请始终书写完整、正确的命令adb shell避免在自动化场景中埋下不确定性。Tidbit Three用TextUtils.concat()优雅地拼接 CharSequenceTextUtils.concat()是 Android 框架层提供的字符串拼接工具它能把传入的多个CharSequence连接在一起同时保留它们各自的间距Spannable 等格式信息并且返回值仍然是一个CharSequence。这一点和普通的字符串拼接有本质区别a b会把内容降级为纯String若源文本携带Spannable字体、颜色、下划线等富文本样式拼接过程会丢失这些样式TextUtils.concat()则保留原CharSequence中的格式数据适合在需要动态组装富文本如用户名 时间 正文混合展示的场景使用。用法示意CharSequence result TextUtils.concat(title, - , timestamp, - , body);它的返回值仍是CharSequence可以直接传给TextView.setText()或继续参与后续的字符串处理流程是处理富文本拼接时一个值得记住的 API。Tidbit Four父 View 拦截与子 View 的反拦截在 Android 的触摸事件分发机制中子 View 处理的点击事件通常都是由父 View 先拦截、再分发下来的事件从ViewGroup的dispatchTouchEvent()进入经onInterceptTouchEvent()决定是否拦截最终才到达目标子 View。因此如果你需要使用某个子 View 正在处理的点击事件正确做法是使用父 View 的拦截事件方法onInterceptTouchEvent()返回true。但这里存在一个博弈点子 View 可以通过调用requestDisallowInterceptTouchEvent(true)来禁止父 View 拦截自己正在处理的事件序列。如果父 View 出于业务需要必须在这种被禁止的情况下仍然拦截那唯一的办法就是重载requestDisallowInterceptTouchEvent()这个方法本身Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { // 忽略子 View 的请求强制保留父 View 的拦截能力 // super.requestDisallowInterceptTouchEvent(false); }简单总结这套关系子 View 想守住自己的事件 → 调用requestDisallowInterceptTouchEvent(true)父 View 想夺回事件的拦截权 → 重写requestDisallowInterceptTouchEvent()不把请求上交给默认实现。这在实现嵌套滚动如列表嵌套、滑动冲突处理时是绕不开的必修课。Tidbit FiveSQLite 事务的提交/回滚陷阱在执行批量写入等耗时操作时调用SQLiteDatabase的beginTransaction()与endTransaction()能显著提升性能减少磁盘 I/O 次数。但这里有一个极易踩中的坑必须调用setTransactionSuccessful()事务的完整流程是db.beginTransaction(); try { // 批量 insert / update / delete 操作 db.setTransactionSuccessful(); // 标记事务成功 } finally { db.endTransaction(); // 根据标记决定 commit 或 rollback }endTransaction()并不会盲目提交它依据的是此前是否调用了setTransactionSuccessful()。如果你忘了这一行那么在调用endTransaction()时本次事务内做出的所有修改都会被回滚——数据库文件看起来一切正常但数据实际一条都没写进去这种静默失败往往比直接报错更难排查。因此请记住这条铁律beginTransaction()之后、endTransaction()之前务必在业务代码正常执行的路径上调用setTransactionSuccessful()并配合try/finally保证异常情况下也能正确结束事务。Tidbit Six单元测试中的静态方法陷阱如果你正在为应用添加测试用例千万要小心静态方法Android 框架层提供的很多方法在测试单元里根本不能用——它们依赖真实的系统服务、Binder 或底层 native 实现在 JVM 单元测试无设备环境下无法正常执行更麻烦的是由于它们是静态方法你甚至不能用 Mockito 之类的框架去模拟它们——Mockito 基于动态代理/子类化实现 mock只能处理可继承、可覆写的实例方法静态方法是它的盲区。这导致一个常见现象业务代码一旦在关键路径上调用了静态的框架方法如System.currentTimeMillis()、TextUtils.isEmpty()等测试代码就会变得难以编写和控制。应对思路包括在设计上尽量把静态调用包进可 mock 的实例对象依赖注入一个时钟或文本工具包装类必要时引入支持静态方法 mock 的专用框架如 PowerMock 家族但要注意它自身的兼容性与维护成本。结合本仓库中的测试主题文章可见这个约束正是推动面向接口编程 依赖注入的重要动机之一可测试性往往倒逼出更好的代码结构。Tidbit Seven用 Mockito 简化测试中的对象依赖与 Tidbit Six 配套的正面建议是试试 Mockito。它为你在测试用例中创建、配置和验证对象行为提供了简洁的 API能大大简化之前那些测试用例中复杂的对象依赖——不再需要手写一堆 Fake 或 Stub 类只需用mock()、when()、verify()等调用即可完成替身对象的装配。本仓库恰好收录了两篇可直接对照学习的实践文章使用Mockito对异步方法进行单元测试演示了用doAnswer()同步触发回调、以及用ArgumentCaptor捕获回调参数再手动驱动异步逻辑的两种测试方案解决异步函数不好测的经典难题doAnswer(new Answer() { Override public Object answer(InvocationOnMock invocation) throws Throwable { ((DummyCallback) invocation.getArguments()[0]).onSuccess(results); return null; } }).when(mockDummyCollaborator).doSomethingAsynchronously(any(DummyCallback.class)); // 然后正常调用被测方法并 verify 交互Android测试框架Dagger2Espresso2Mockito展示如何用Mockito.when(clock.getNow()).thenReturn(new DateTime(2008, 9, 23, 0, 0, 0))让测试日期不再依赖真实时钟从而断言 UI 显示为2008-09-23。这两个示例分别对应了 Mockito 在单元测试层和UI 测试层的典型用法可以作为你落地 Mockito 时的参考实现。小结本期的七个知识点看似零散实则覆盖了 Android 开发的几个高频痛点知识点核心要点ZXing 二维码用0 x 0生成最小矩阵 setFilterBitmap(false)按需缩放adb 命令adb hell与adb shell结果一致但不可依赖容错TextUtils.concat()保留 CharSequence 样式信息适合富文本拼接触摸事件子 View 用requestDisallowInterceptTouchEvent防拦截父 View 可重写该方法夺回控制权SQLite 事务忘记setTransactionSuccessful()会导致endTransaction()时全部回滚静态方法框架静态方法无法在单元测试中执行也无法 mock需借助设计规避Mockito用 mock/verify 大幅简化对象依赖仓库内有异步测试与 UI 测试两份实战参考这些技巧的共同价值在于它们都源自真实项目中的具体问题规模虽小却能在关键时刻帮你避开一类隐蔽的 Bug 或性能陷阱。更多同系列内容可继续阅读 Part 1 与 Part 2也欢迎翻阅 issue-31 目录下的其他译文依赖注入框架、RxJava 调试、应用架构等扩充你的 Android 知识图谱。赞分享文档教程知识库【免费下载链接】android-tech-frontier【停止维护】一个定期翻译国外Android优质的技术、开源库、软件架构设计、测试等文章的开源项目项目地址https://gitcode.com/gh_mirrors/an/android-tech-frontier点击查看免费下载相关推荐TexturePanner社区贡献指南如何为这个开源Shader项目做出贡献TexturePanner社区贡献指南如何为这个开源Shader项目做出贡献 TexturePanner是一个为Unity3D开发的开源Shader项目主要WeChatQRCode Android二维码识别库深度指南从入门到实战WeChatQRCode Android二维码识别库深度指南从入门到实战 WeChatQRCode是基于OpenCV开源的微信二维码引擎移植的Android扫移动开发计算机视觉Akagi雀魂AI助手5分钟搭建你的智能麻将教练Akagi雀魂AI助手5分钟搭建你的智能麻将教练 你是否在雀魂对局中常常感到迷茫面对复杂的牌局不知如何决策Akagi雀魂AI助手正是为你量身打造的智能麻将桌面应用人工智能上一篇Qwen3-30B-A3B-FP8中小企业大模型本地化部署的转折点下一篇nginx-module-vts高级特性流量限制、过滤器与实时控制完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考