本文是 Android Perfetto 系列的第六篇,主要介绍 Android 设备上 120Hz 刷新率的相关知识。如今,120Hz 已成为 Android 旗舰手机的标配,本文将讨论高刷新率带来的优势和挑战,以及从系统角度解析 120Hz 的工作原理。 在过去的几年中,移动设备的屏幕刷新率经历了从 60Hz 到 90Hz,再到现在普遍的 120Hz 的演进过程。这种提升不仅带来了更流畅的视觉体验,也对系统架构和应用开发提出了新的要求。通过 Perfetto 工具,我们可以更直观地理解高刷新率设备上帧渲染的过程和性能表现。 系列文章目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战 如果您还没看过早期的 Systrace 系列文章,以下是相关内容的传送门: Systrace 基础知识 - Why 60 fps ? 新的流畅体验,90Hz 漫谈 本文使用到的 Trace 文件我上传到了 Github :https://github.com/Gracker/SystraceForBlog/tree/master/Android_Perfetto/微信朋友圈滑动卡顿.perfetto-trace.zip ,需要的可以自取。 基本概念 什么是屏幕刷新率? 屏幕刷新率是一个硬件概念,指的是屏幕每秒钟刷新显示内容的次数,单位是赫兹(Hz)。 60Hz 屏幕:每秒刷新 60 次,每次刷新间隔约 16.67ms 90Hz 屏幕:每秒刷新 90 次,每次刷新间隔约 11.11ms 120Hz 屏幕:每秒刷新 120 次,每次刷新间隔约 8.33ms 屏幕刷新率决定了显示设备能够展示的最高帧率,但屏幕只负责按固定频率显示内容,具体显示什么内容由软件系统决定。 什么是 FPS? FPS(Frames Per Second)是一个软件概念,指的是系统每秒生成多少帧内容提供给屏幕显示。 60FPS:系统每秒生成 60 帧内容,每帧有约 16.67ms 的处理时间 90FPS:系统每秒生成 90 帧内容,每帧有约 11.11ms 的处理时间 120FPS:系统每秒生成 120 帧内容,每帧有约 8.33ms 的处理时间 为了获得最佳的视觉体验,FPS 应该与屏幕刷新率匹配。如果 FPS 低于刷新率,会出现掉帧;如果 FPS 高于刷新率,多余的帧会被丢弃,造成资源浪费。 为了获得最佳的视觉体验,理想情况下 FPS 应该与屏幕刷新率匹配,但实际体验与内容类型和用户感知紧密相关: 内容类型差异: 视频内容:电影(24fps)或视频(30fps)即使在 120Hz 屏幕上也能看起来流畅,这是因为视频内容包含自然运动模糊,且符合观看者对该媒介的预期 交互式界面:而滑动列表、动画等交互场景对帧率要求更高,从 120fps 降到 110fps 都可能被用户感知为卡顿 帧率稳定性:稳定的低帧率(如稳定的 60fps)通常比不稳定的高帧率(如在 90-120fps 之间波动)体验更好 系统行为: 当 FPS 低于刷新率时,显示系统会复用帧或插入黑帧 当 FPS 高于刷新率时,多余的帧会被丢弃,造成计算资源浪费 不同应用场景有不同的流畅度标准,开发者需要根据应用类型选择合适的优化策略。 什么是 Vsync? Vsync(垂直同步)是将软件帧率与屏幕刷新率同步的机制,目的是避免画面撕裂现象。Android 系统中,Vsync 信号被用来触发应用渲染新一帧的时机,确保渲染过程与屏幕刷新周期保持一致。 为什么 120Hz 成为新标准? 市场从 60Hz 到 90Hz,再到 120Hz 的演进有着明确的技术和用户体验驱动因素: 更高的流畅度:120Hz 比 60Hz 提供了两倍的视觉信息,使滑动、动画等交互感觉更加流畅自然。 减少延迟:输入事件到显示结果的延迟从 60Hz 的 16.67ms 减少到 120Hz 的 8.33ms,让用户操作反馈更及时。 硬件支持成熟:现代移动处理器(如高通骁龙 8 系列、联发科天玑系列)已经有足够性能支持 120Hz 的稳定运行。 电池技术进步:更高效的电池和电源管理技术缓解了高刷新率带来的功耗压力。 可变刷新率技术:LTPO 等自适应刷新率技术允许设备在不同场景下智能切换刷新率,平衡流畅度和功耗。 如今,120Hz 不仅是 Android 旗舰机型的标配,连 iOS 设备(iPhone 13 Pro 及以上)也已支持 120Hz 的 ProMotion 技术,标志着高刷新率已成为高端移动设备的基本特性。 系统实现与工作原理 Perfetto 视角下的 120Hz 渲染流程 在 120Hz 刷新率下,Android 系统的渲染流程没有本质变化,主要区别是每一帧的时间预算从 16.67ms 缩短到了 8.33ms ( 当然这里没有讨论 App duration,如果 App Duration 配置大于 8.33ms,那么 App 的 UI + Render 在 App duration 区间完成都是可以的,注意 每个机器的 App Duration 配置不一样)。下图展示了 120Hz 环境下应用渲染的 Perfetto 追踪图: 在 120Hz 设备上,我们可以看到: Vsync 间隔:VSYNC 信号每 8.33ms 触发一次 帧处理流程:每一帧的处理依然遵循 Input → Animation → Traversal 的顺序 时间压缩:所有处理步骤必须在更短的时间内完成,对系统和应用性能要求更高 上图中出现了两个 Buffer 相关的 Trace,这里做一个简单的说明: QueuedBuffer:(例如:QueuedBuffer - VRI[ImproveSnsTimelineUI]#748BLAST#748) 这个 Trace Tag 是在 App 进程中打印的 表示应用完成一帧渲染后,将渲染好的 Buffer 放入队列准备提交给 SurfaceFlinger 在使用 BlastBufferQueue 的系统中,这个时刻标志着 RenderThread 完成渲染并准备将结果传输到系统服务 BufferTX:(例如:BufferTX - com.tencent.mm/com.tencent.mm.plugin.sns.ui.improve.ImproveSnsTimelineUI#47974) 这个 Trace Tag 是在 SurfaceFlinger 进程中打印的 表示 SurfaceFlinger 接收到应用传来的 Buffer 并开始处理的时刻 TX 代表 “Transfer/Transmission”,即缓冲区的传输过程 可以理解在 Android 12 以后,App 完成的 Buffer(指的是 App 的 RenderThread 调用 queueBuffer 的时间点,其实此时 Buffer 还需要 GPU 完成才最终可用, 观察 Trace 中的 GPU completion(表明 GPU 实际完成该 Buffer 内容渲染的时间点) 所对应的 id 即可) 会先进入 App 的 BufferQueue(QueuedBuffer + 1),然后再触发传输到SurfaceFlinger (BufferTX +1 ,QueuedBuffer -1),SurfaceFlinger 拿去合成之后(BufferTX -1)。 Perfetto 同时也提供了 Buffer 追踪的功能,点击 App 上面的 Actual Timeline (这部分知识可以看 Actual Timeline 介绍),就可以看到这个 Buffer 从生产到消费的全过程。 支撑 120Hz 的系统架构优化 要流畅地支持 120Hz 高刷新率,Android 系统架构做了不少调整和改进。这些变化涉及多个组件,也包括了对整个渲染管线的重要优化。下面我们来详细看看几个关键的技术点: 自适应刷新率技术 现代 Android 设备采用多层次刷新率管理策略: 硬件层支持:LTPO(低温多晶氧化物)显示技术允许屏幕在 1Hz 到 120Hz 范围内精确调节刷新率,而非固定档位切换 内容感知算法:系统通过分析屏幕内容类型自动调整刷新率: 静态内容(阅读、图片浏览):降至 10-30Hz 视频播放:匹配视频源帧率(通常 24-60Hz) 滚动和交互:提升至 90-120Hz 游戏:根据游戏引擎输出帧率动态调整 API 支持:Android 提供 Surface.setFrameRate() API,允许应用明确指定其首选帧率,系统会尽可能满足这一请求 1 2 // 应用可以指定首选帧率和刷新率行为 surface.setFrameRate(60.0f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); 120Hz 的优势与挑战 120Hz 带来的体验提升 我拿到第一台120Hz手机的时候,最直观的感受就是: 一切操作都变得更流畅:从桌面滑动、应用切换到刷微博,画面更新更频繁,内容跟随手指移动也更加精准。尤其在快速滑动朋友圈或微博feed流时,文字依然清晰可辨,而不是一片模糊。 游戏体验大幅提升:玩《王者荣耀》或《和平精英》这类竞技游戏时,画面流畅度提升让我的操作精准度也跟着提高。在激烈对抗中,能提前8ms看到敌人动作,虽然时间很短,但确实能带来优势。 眼睛疲劳感减轻:这点可能是个人感受,但长时间盯着120Hz屏幕确实比60Hz舒适,特别是阅读和滑动内容时,眼睛追踪内容的负担减轻了。 触控体验更精准:120Hz不仅是显示更新快,触控采样率通常也会提高,让操作响应更及时,无论是打字还是精细控制都更准确。 120Hz 面临的实际问题 当然,高刷屏幕也带来了一系列技术挑战: 功耗问题:实测中,同一台手机在120Hz模式下比60Hz大约多耗电15-20%。对于本就紧张的手机续航来说,这是不小的压力。 开发门槛提高:原来在60Hz环境下勉强能跑的应用,到了120Hz可能就会显得卡顿。每帧只有8.33ms的处理时间,对开发者的代码效率提出了更高要求。 发热增加:长时间运行高帧率游戏,手机发热明显比60Hz更严重,这不仅影响体验,还可能导致性能降频。 应用适配并不完善:很多应用并未针对高刷做优化,即使在120Hz屏幕上,实际输出帧率可能还是60fps,浪费了屏幕潜力。 思考与展望 按需调整:从 ProMotion 看刷新率的智能管理 随着 120Hz 高刷新率逐渐成为旗舰手机的标配,一个值得思考的问题是:我们真的需要在所有场景下都保持 120Hz 的刷新率吗? 苹果的 ProMotion 技术实际上给出了一个更为合理的答案:只有在真正需要高感知度的动画场景下才激活高刷新率,而在其他场景则可以适当降低刷新率以节省电量。 从上图可以看到,苹果在iOS开发文档中为不同类型的动画场景提供了非常精细的ProMotion帧率推荐配置: 高影响力动画(High-impact animations): 适用场景:全屏转场(如照片应用中点击缩略图展开)、第一人称游戏、Sheet弹出展示等 推荐帧率:80-120Hz,首选120Hz(CAFrameRateRange(minimum:80, maximum:120, preferred:120)) 使用建议:谨慎使用,仅在关键交互场景应用,以减少电量消耗 透明度/颜色过渡和微小移动: 适用场景:开关状态变化、进度指示器旋转、背景模糊效果等 推荐帧率:使用系统默认帧率范围(CAFrameRateRange.default) 使用建议:这类动画不需要过高帧率,视觉效果差异不大 低速小动画: 适用场景:时钟指针移动、缓慢进度条等 推荐帧率:根据动画速度,可选择8-15Hz、15-24Hz或30-48Hz不等 使用建议:低帧率在这些场景下视觉效果已足够好,同时可显著节省电量 其他所有情况: 推荐使用系统默认帧率 这种精细化的帧率管理策略,不仅让系统能够在用户体验和电池寿命之间取得最佳平衡,也为开发者提供了明确的指导。相比于简单粗暴地全局使用120Hz,这种有针对性的帧率调整方案显然更加科学和高效。 电量与体验的权衡 测试表明,将刷新率从 120Hz 降至 60Hz 可节省约 10-15% 的电量。智能地控制刷新率,能在保持良好用户体验的同时,显著延长电池续航时间。 120Hz 的主要价值在于提升交互流畅度和响应速度,而非始终保持高刷新率。更智能的做法是根据实际需求动态调整:在用户感知敏感的场景使用高刷新率,在用户感知不敏感的场景降低刷新率。 开发者的适配策略 应用开发者应当意识到,并非所有内容都需要以最高帧率渲染。通过 Android 提供的 API(如 Surface.setFrameRate()),可以为不同内容类型指定合适的帧率,配合系统的自适应刷新率机制,共同达到最佳的性能与电量平衡。 总之,未来高刷新率技术的发展方向应该是更加智能、更加精细的自适应调节,而非简单地追求更高的数字。真正的技术进步是在用户无感知的情况下,在体验和能效之间找到最佳平衡点。 结论 回顾过去几年高刷屏幕的发展,我认为120Hz确实是手机交互体验的一次重要跃升。虽然它带来了功耗和开发复杂性等挑战,但好处是显而易见的:更流畅的体验、更低的输入延迟、更自然的动画效果。 对于开发者而言,Perfetto这类工具让我们能够看清120Hz下的性能问题,有的放矢地进行优化。虽然从16ms减少到8ms的预算听起来很紧张,但事实上主流处理器已经有足够能力应对这个挑战。只要合理规划UI复杂度、避免主线程阻塞,流畅的120fps体验是完全可以实现的。 从趋势来看,我不认为手机屏幕刷新率会无限攀升。120Hz可能会在相当长的时间内成为标准,而未来的焦点将更多放在如何智能化地调整刷新率,在不同场景下找到体验和功耗的最佳平衡点。毕竟,我们追求的不是数字上的高,而是实际体验的好。 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 移动 OS 设计之性能设计 7 - 扁鹊三兄弟中大哥型优化: 编程范式: 本文探讨了如何通过编程范式来约束应用程序行为,以实现更优的性能表现。通过“扁鹊三兄弟”故事的启发,提出了预防性设计的重要性,并分析了编程范式在操作系统性能优化中的应用。 基于预测模型的 GPU 绘制效率优化方法: 该文章介绍了一种基于预测模型的 GPU 绘制效率优化方法,旨在通过提前预测所需的 GPU 算力来提高绘制任务的执行速度。文章详细描述了绘制流程、影响 GPU 负载的关键指标以及具体的优化方案,包括单 APP 和多 APP 的性能预测。 性能优化:官方文档抓取开机 trace 给 perfetto 分析–Recording traces on Android boot: 这篇文章介绍了如何在 Android 设备开机时智能抓取 trace 以进行性能优化分析,尤其是在无法通过 adb 连接设备的情况下。文章详细讲解了配置文件的准备、推送、启用以及获取 trace 文件的步骤,并提供了相关链接供参考。 记录一次利用 perf 在线定位 BUG 代码位置的处理过程: 本文记录了一次使用 perf 工具在线定位 BUG 代码位置的处理过程。文章详细描述了问题的由来、核心代码、问题出现的情况、分析处理步骤以及最终的结论和建议。主要问题是程序在采样时 CPU 使用率过高,并最终通过 perf 工具定位到一个死循环问题,成功解决。 自适应流媒体(ABR)技术与算法解析: 自适应码率(ABR)流媒体技术能够根据用户的网络状况动态调整视频的质量和码率,以确保视频播放的流畅性和用户体验。本文介绍了 ABR 的背景、协议、目标、挑战以及常用的算法。 [译]AI 算力民主化 第八部分:MLIR 编译器基础设施如何?: 本文讨论了 MLIR 编译器基础设施的起源、发展及其在 AI 算力民主化中的作用。尽管技术上取得了成功,MLIR 未能打破 CUDA 的垄断,原因在于政治博弈、权力斗争和技术妥协。文章还探讨了硬件公司在构建 AI 软件方面面临的挑战。 直播回放:native 程序如何高效 Debug 获取堆栈 perfetto+simpleperf/VSCode/CallStack: 直播回放介绍了如何高效 Debug 获取堆栈的几种方法,包括使用 perfetto、simpleperf、VSCode 和 CallStack。 Android Weekly Issue #671: Android Weekly is a free newsletter that helps you to stay cutting-edge with your Android Development Linux | Misfit task migration: 本文讨论了 Linux 系统中“Misfit 任务迁移”的概念,特别是在异构 CPU 环境下如何优化任务调度以提升性能。文章详细解释了相关的代码实现和设计思路,并探讨了 MTK 对“Misfit”任务的定制化处理。 Android V app 冷启动(9) Activity 生命周期调度: 本文分析了 Android V 应用的冷启动过程,特别是 Activity 生命周期的调度机制。文章详细描述了 Activity 从创建到恢复的整个过程,包括如何通过不同的 Java 代码实现各个生命周期阶段的转换。 【NowInAndroid 架构拆解】(9)重新审视 NowInAndroid 架构设计: 这篇文章对 NowInAndroid 项目的架构设计进行了详细的分析和总结,尤其关注了 ForYou 页面的实现。文章探讨了数据流和控制流在 UI 层和 Data 层之间的传递,描述了模块化和可测试性设计的重要性,并提供了具体的代码实现和设计思路。 MMKV 的源码分析:为什么他的性能更高,为什么他比 SP 好,为什么他的数据更加的精简,比传统 IO 更高效的文件操作方式: 这篇文章分析了 MMKV 的源码,解释了为什么 MMKV 的性能比 SharedPreferences(SP)更高。文章详细讨论了 MMKV 的文件操作、数据格式和数据更新方式的优势,并通过性能测试对比了 MMKV 和 SP 的写入速度。 Android 监听开机自启,是否在前后台,锁屏界面,息屏后自动亮屏,一直保持亮屏: 这篇文章详细介绍了如何在 Android 设备上开发广告应用,尤其是关于开机自启动、锁屏广告以及保持屏幕亮屏的技术实现。文章提供了代码示例,帮助开发者实现这些功能,并解释了相关权限的使用。 Android 应用内存分析与优化 - 理论篇: 内存优化一直是一个很重要但却缺乏关注的点,内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。在你认真跟踪下来可能会发现内存出现问题的地方仅仅只是一个表现的地方,并非深层次的原因,因为内存问题相对比较复杂,它是一个逐渐挤压的过程,正好在你出现问题的代码那里爆了,所以针对应用的内存问题开发者必须多加关注。 IntelliJ IDEA 2025.1 发布 ,默认 K2 模式 | Android Studio 也将跟进: IntelliJ IDEA 2025.1 版本发布,默认启用 K2 模式,同时 Android Studio 也将跟进。K2 模式带来了显著的性能提升,特别是在 Kotlin 代码分析、补全和导航速度上。尽管 K1 模式仍可使用,但未来将不再支持新语言特性和 IDE 优化。K2 编译器采用了模块化架构和并发容忍设计,消除了性能瓶颈,为未来的多线程分析奠定基础。K2 模式支持 Kotlin 2.0 及以上的新语言特性,开发者需注意插件的迁移适配。 Android 副屏录制方案: 本文探讨了在 Android 系统中实现副屏录制的各种方案,分析了现有方案的优缺点,并提出了一种无需权限的终极方案。 Jetpack Compose 的性能优化建议: 本文译自「Performance Optimization in Jetpack Compose」,原文链接 carrion.dev/en/posts/pe…,由 Ignacio Carrión,发布于 2025 年 4 月 8 日。Jetpack Compose 是一个优秀的声明式 UI 框架,对开发者非常友好,可以高效率的撸各种 UI 页面和 UI 元素。但它仍然并不是很成熟,有些事情还做不了,而且渲染性能也略输于原生的 View 方式,毕竟它比原生的 View 多了一层组合树和渲染树。因此,在享受声明式 UI 带来的便捷的同时,就需要深入地了解其内部的工作机制,和学习一些高级技巧,以提升运行时的渲染性能。另外,需要 注意虽然这篇文章是针对 for Android 的 Jetpack Compose,但大部分也适用于 Compose Multiplatform。 The Fourth Beta of Android 16: Android 16 的第四个测试版已发布,这是计划中的最后一个测试版更新,开发者需要确保其应用程序或游戏已准备好适配 Android 16。该测试版为开发者提供了最终的 API 和应用行为,适用于更多设备类型,开发者应尽快进行兼容性测试和更新。文章强调了一些关键变化,如更严格的 JobScheduler 配额、广播和 ART 变化、Intent 安全性增强、16KB 页面大小要求、无障碍功能的更新等。此外,2025 年计划有两次 Android API 发布,Q2 的主要版本将引入行为变化,Q4 的次要版本则专注于功能更新和优化。 Boost app performance and battery life: New Android Vitals Metrics are here: Android Developers Blog 文章介绍了新的 Android Vitals 指标,旨在提升应用性能和电池寿命。文章强调了与顶级 OEM 厂商(如三星)的合作,以利用他们的实际洞察来减少资源消耗。新指标的重点是过度唤醒锁定行为,该行为会导致电池过度消耗。开发者可以通过优化应用的唤醒锁定行为来改善用户满意度。文章还提到这些指标目前处于测试阶段,开发者可以提供反馈以帮助完善指标定义。 ICLR&CVPR 2025 美团技术团队论文精选: ICLR 全称为 International Conference on Learning Representations 国际学习表征会议,是致力于推进人工智能分支中表示学习专业(通常也被称为深度学习)的顶级会议。ICLR 与 ICML 和 NeurIPS 并列为三大机器学习和人工智能会议,在 2025 谷歌学术期刊与会议影响力榜单中排名第 10。 杂记 来美国的两年后: 这篇文章记录了作者在美国生活两年的适应过程,分享了在衣食住行、语言、消费、心态、家庭等方面的体会和挑战。作者探讨了在新环境中如何找到适合自己的生活方式,并反思了身份焦虑和未来规划的问题。 How To Remember Everything You Read With AI: 这篇文章探讨了如何利用人工智能(AI)来加深阅读理解,并强调阅读不仅仅是获取信息,而是通过接触新思想来改变思维方式和身份。文章指出,尽管 AI 可以提供信息,但真正的变化来自于行为和身份的改变,而不是简单的信息积累。 生命不息,折腾不止: 这一篇文章是 joojen Zhou 的个人日志,描述了他在生活和工作中的一些经历和思考。他提到了一些关于网站功能优化、阅读和运动数据统计的方法,以及学习英语的挑战。同时,他分享了如何利用技术工具来提高效率,并探讨了个人成长和自我驱动力的重要性。 技术狂飙下的冷思考:大模型的“科林格里奇困境”: 文章探讨了大模型技术发展中的“科林格里奇困境”,即技术在早期阶段的可控性与成熟阶段的失控性之间的矛盾。文章分析了大模型的现状、伦理治理问题、以及其面临的挑战,包括幻觉、偏见、可解释性和涌现现象等,并强调了技术与伦理治理之间的动态平衡的重要性。 感受即真理的人注定没朋友?: 这篇文章探讨了“拎得清”这个概念,涉及个人感受与群体感受之间的平衡,以及在社交场合中如何处理情绪和礼仪。通过一个具体的案例,文章分析了在群体中如何处理冲突和情绪,以及“我们”和“大家”这两种群体形式的区别。 小微团队是未来趋势: 小微团队是未来的发展趋势,尤其在科技和互联网行业。得益于人工智能和技术的进步,小规模团队甚至个人能够完成过去需要大团队才能完成的工作。这样的团队更易管理,创始人能专注于产品开发,而不必扩张公司规模。小型企业在管理成本和灵活性上具有优势,能够更好地应对市场变化和危机。 Weekly Collections 14: Weekly Collections 是专门针对小报童订阅者发布的内容,主要是以周为粒度跟订阅者分享我读到的有意思的文章,看到的令我有感触的社交发文,比较实用的资源等等。有价值的文章和思考值得反复看,为了方便查找,后面的编号就是本年度的第几周,例如 12 表示今年的第 12 周的 weekly collection。 AI Vibe Coding 技术白皮书:技术平权时代的到来: 本文探讨了“Vibe Coding”(氛围编程)的概念,这是一种利用 AI 工具辅助生成代码的新兴编程方式。文章分析了 Vibe Coding 的优势与挑战,并介绍了其在行业中的应用案例和对软件研发生态的影响,同时探讨了技术伦理与风险防控等问题。 MCP 协议深度解读:技术创新正以前所未有的速度突破: 本文深入解读了 MCP 协议,探讨其作为 AI 应用架构基础协议的技术原理和实际业务影响。MCP 协议通过标准化接口实现了大模型与工具的动态交互,提升了 AI 应用的效率和兼容性,并在多工具协作场景中得到了验证。 做 AI 产品两年,我得出的实操经验: 这篇文章总结了作者在过去两年中开发 AI 产品的实操经验,重点探讨了 AI 产品开发的难点、提示词工程的重要性以及如何构建 AI 产品团队。作者分享了许多具体的例子和经验,以帮助其他 AI 开发者更好地理解和应对 AI 产品开发中的挑战。 3 个重点讲清楚 GPT4.1:OpenAI 的新一代基础模型: OpenAI 最新发布的 GPT4.1 是其新一代基础模型,替换了之前的 4o。相比 4o,GPT4.1 在图像理解和代码能力方面表现更佳,并且价格降低了 20%。此外,GPT4.1 具备 1M 超长上下文能力,在用户实测中表现优异,尤其是在处理超长文本时。令人惊喜的是,GPT4.1 mini 的性能也非常不错,且价格便宜。虽然 GPT4.1 的代码能力超越 GPT4.5,但在推理能力上仍不及 o1 模型。 我是如何高效翻译 65 页 Google 官方提示工程白皮书 PDF 文件的: 这篇文章介绍了作者在翻译 65 页的 Google 官方提示工程白皮书 PDF 文件时的一些高效方法和经验。他主要使用了将 PDF 转成 Markdown 再翻译的方法,并分享了多种工具和技巧来优化翻译流程。 你在为 AI 工具付费?我只花了 100 美元就解锁了一大堆牛逼工具,详细教程在这里: 著名产品 Newsletter 主理人、播客主播 Lenny ,靠自己的人脉关系,联合一大堆牛逼 AI 工具产品搞捆绑销售。 【有嘴就能做开发】Cursor——AI 编辑器 使用详解: 这篇文章详细介绍了 Cursor,一个结合 AI 大模型的代码编辑器。Cursor 不仅支持代码补全、错误定位,还能通过自然语言生成代码,非常适合没有编程经验但有开发想法的人。文章还提到了 Cursor 的发展历程、融资情况以及其功能和使用技巧。 Claude 编程最佳实践指南: 掌握智能体编程的核心技巧,提升工作流效率 鸡血 招聘信息 Android 开发工程师 职位描述: 负责 TikTok 产品 Android 客户端稳定性优化、技术攻关,打造极致用户体验; 参与调查和解决各类影响用户体验的疑难问题,如崩溃、ANR、内存、Native Crash; 参与开发稳定性和 ANR 优化的各类平台工具,提升排查效率; 和 QA、PMO、架构等多个团队共建流程体系,为高质量推出 TikTok 新版本负责; 参与技术预研,并将相关优化方案应用到 TikTok 的产品开发中; 参与厂商技术合作,共同提升客户端在各个厂商设备上的用户体验,同时提高 TikTok 在业界的技术影响力。 Requirements 3 年以上 Android 客户端或 Framework 开发经验; 熟练掌握 Java/Kotlin/C/C++/Rust 开发语言之一; 扎实的编程能力,熟悉多线程和网络编程,了解操作系统原理; 优秀的分析和解决问题能力,对解决具有挑战的问题充满激情; 加分项:有 Framework 开发经验/客户端安全领域/客户端架构/客户端基础技术/跨端 SDK 开发经验优先。 邮箱:zhanglin1@bytedance.com iOS 优化工程师 抖音基建团队 iOS 体验方向招人啦,欢迎大家沟通,全程保密,无论是技术方向还是业务方向都欢迎沟通。base 地上海 【职位描述】 负责抖音系 iOS 用户体验和性能优化,包括但不限于启动速度、流畅度、系统资源优化、安装包大小、端智能、播放体验等方向。 负责性能监控及归因体系建设,包括问题拆解、指标搭建、诊断工具、平台等多个维度全链路建设。 参与设计和开发各类体验优化框架和效率工具,提供高可用通用解决方案,助力业务降低性能瓶颈。 积极跟进业内外先进技术,验证可行性和推进落地,总结相关经验和通用方案,提升技术影响力。 【职位要求】 本科及以上学历,计算机、通信等相关专业,不限定工作年限。 计算机基础扎实,熟悉 OC/Swift/C++ 或者具有快速学习新编程语言的能力,编码习惯良好。 有性能优化、APM、架构等经验者优先,有业务背景但对技术有深度追求者优先。 具备优秀的解决问题和逻辑思维能力、有强烈的责任心和团队精神,善于沟通和合作。 邮箱:chenjiawei.kisson@bytedance.com 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 跨平台框架技术视角看 GapBuffer: 本文从跨平台框架开发者的视角,探讨了 GapBuffer 这一数据结构在 UI 框架中的应用,特别是在 Compose 中的实现。文章详细介绍了 GapBuffer 如何通过插入和删除操作来优化 UI 树的变动,从而提高性能。 移动 OS 设计之性能设计 6 - 技术指标的两类优化目标类型: 本文探讨了移动操作系统性能设计中的技术指标优化,主要分为两类:最优化问题和特定区间内的优化问题。最优化问题追求性能极限,而特定区间内的优化关注在某一性能区间内的稳定性。明确优化目标对于选择合适的技术手段至关重要,避免混用不同类型的策略。 JetBrains Terminal 又发布新架构,Android Studio 将再次迎来新终端: JetBrains 在不到一年内对其终端进行了重大重构,以解决先前版本中的兼容性问题。新的终端架构引入了增强功能,如 AI 驱动的命令生成,但也因兼容性问题收到负面反馈。2025.1 版本将重点放在兼容性和一致性上,确保终端的基本功能不受影响,同时为未来的高级功能铺平道路。 手机系统的 D-Vsync 渲染显示管线优化: 本文介绍了一种智能手机显示渲染优化方案,称为解耦垂直同步(D-VSync)。D-VSync 通过解耦渲染和显示的设计,解决了传统 VSync 的局限性,尤其是在长帧和短帧负载波动时导致的卡顿问题。此方案已在开源鸿蒙和安卓系统中实现,显著减少了掉帧和渲染延迟。 应用预测框架-基于 Android S: 本文介绍了基于 Android S 的应用预测框架,详细分析了其核心机制、实际应用场景及用户体验优化效果,并探讨了如何使用 TensorFlow 进行模型训练和部署以提升预测准确性。 LWN:减少 TLB 压力的措施!: 本文探讨了如何减少 CPU 的 TLB(Translation Lookaside Buffer)压力。TLB 缓存虚拟地址转换结果,显著加速内存访问,但 TLB 失效会导致高昂代价,因此优化 TLB 使用是重要课题。文章讨论了在 Linux 存储、文件系统、内存管理和 BPF 峰会上 Rik van Riel 关于内存管理的主题会议,其中包括减少 TLB 压力的方法,如使用透明大页(THP)和多尺寸透明大页(mTHP),以及如何更好地管理这些大页以提高 TLB 利用率。此外,文章还探讨了用户空间如何帮助减少 TLB 失效,以及如何优化内核机制以更好地支持 mTHP。 对数和自然对数的底: 这篇文章探讨了对数的历史背景及其在数学中的应用,尤其是纳皮尔对数表的制作过程及其在天文学计算中的重要性。文章详细描述了纳皮尔如何在没有幂概念的情况下,通过几何意义和三角公式来发展对数概念。 DRM(Digital Rights Management)生态以及架构介绍: DRM(数字版权管理)技术通过加密、授权管理和安全传输等手段,保护数字内容在分发、存储和播放过程中的安全性。随着数字内容从物理媒介转向在线流媒体平台,DRM 成为数字内容分发的基石。Google 的 Widevine 因其开放的生态系统和灵活的安全等级成为流媒体领域的主流方案。 Linux | 关于 CPU 调频的一些 QA: 本文对 Android 开发过程中关于 CPU 调频的一些常见问题进行了详细的解答,并介绍了与 CPU 调频相关的技术细节和实现方法。 理解 VSync-1-软件 VSync 及节拍器: 本文是 VSync 系列文章的第一篇,主要介绍了软件 VSync 的概念、模型、分类及其产生过程,并详细探讨了 VsyncDispatch 及其子类 VSyncDispatchTimerQueue 的创建与实现。 理解 VSync-2-app,appsf sf 注册回调: 本文详细介绍了在 Android 系统中,应用程序(app)和 SurfaceFlinger(sf)如何向 VsyncDispatch 注册回调的过程。通过分析代码流程,揭示了涉及的类和回调函数的创建与调用关系。 理解 VSync-3-应用添加链接: 本文详细介绍了在 Android 系统中,应用如何与 VSync 信号进行交互的过程,特别是在 Java 层和 Native 层中如何建立连接并处理 VSync 信号。 理解 VSync-4-应用申请与接收 VSync(上): 本文详细分析了应用如何请求和接收 VSync 信号,涵盖从 Java 层到 Native 层的调用链,并解释了 VSyncRequest 的状态转换及其在应用中的实现逻辑。 技术简报 2025 第四期: 这篇文章探讨了技术学习的重点,强调应该专注于概念而非技术本身。文章还详细介绍了多种文件格式的设计和理解,包括 ELF、DEX 等,并分享了一些技术见解和思考。 用魔法打败魔法:互联网大厂虚拟机分析还原: 本文探讨了互联网大厂的虚拟机分析与还原,详细介绍了如何通过逆向工程技术来分析和还原虚拟机保护的应用程序。文章分享了作者的个人分析方法和思考过程,并提供了相关代码示例和工具使用建议。 Dart 单线程异步模型:从原理到工程实践的系统化解析: Dart 的单线程异步模型通过非阻塞 I/O 和事件循环实现“单线程不阻塞”,有效解决传统单线程的同步阻塞问题。文章详细解析了 Dart 的事件驱动模型与多线程模型的区别,介绍了事件循环的双队列架构及其在异步任务调度中的作用,并提供了工程实践中的常见误区及修正方案。通过系统化理解 Dart 的异步模型,开发者可以在 I/O 密集型场景中发挥其高效调度优势,并通过任务分片技术突破 CPU 密集型任务的瓶颈。 Android.bp 中添加条件判断编译方式: 这篇文章讨论了如何在 Android 的构建系统中使用条件判断来适应不同设备和版本的需求,尤其是通过 Android.mk 和 Android.bp 文件进行差异化编译。文章介绍了如何利用条件判断来选择适合的 API 或库,以便在一个公共代码分支中实现对多个产品设备的兼容,减少维护成本。 Android 提升开发测试效率,程序员应该多干了些什么?: 本文讨论了如何提高 Android 开发测试效率,分享了一些实际操作建议和技巧,以帮助程序员提高工作效率,避免低效的沟通和重复工作。 电影电视剧网红广告屏自动轮播介绍视频特效制作,Compose 轻松实现: 这篇文章介绍了如何使用 Compose 框架制作自动横向滚动的广告视频动画。文章详细描述了数据模型的设计、动画的实现逻辑以及具体的代码实现步骤。通过使用 Compose 的不同组件和工具,用户可以轻松创建带有音乐背景的广告视频特效。 Prioritize media privacy with Android Photo Picker and build user trust: 本文讨论了如何通过 Android Photo Picker 提升用户隐私并建立用户信任。Google Play 致力于在处理敏感权限和用户数据时提供更清晰的选择,以便用户可以自信地授予权限而不牺牲应用功能或隐私。 Gemini in Android Studio for businesses: Develop with confidence, powered by AI: Android 开发者博客宣布推出适用于企业的 Android Studio 中的 Gemini,这是一款专为满足小型和大型组织的隐私、安全和管理需求而设计的产品。通过订阅 Gemini Code Assist 标准版或企业版,开发者和管理员可以解锁这些功能和优势。此外,企业可以通过 Google Cloud 控制台直接为组织中的开发者分配许可证。Gemini 提供了多种安全特性和行业认证,确保客户代码和数据的安全。企业还可以通过连接到 GitHub、GitLab 或 BitBucket 存储库,实现代码定制化。Gemini 在 Android Studio 中为整个软件开发生命周期提供智能协助,提高生产力。 Flutter 2025 年产品路线图发布: 每一年 Google Flutter 团队都会发布一份产品路线图,包括 Flutter 框架和 Dart 编程语言,让开发者能够了解官方团队的优先事项,并据此做出自己的计划安排。产品路线图也会随着客户反馈和新兴市场机会的变化而不断发展。开发者们可以通过每季度的调查问卷以及 GitHub 上 issue 的反馈来推进这些工作的优先级。 速学 Android 16 新功能:带有进度的通知类型: 本文介绍了 Android 16 的新功能,特别是 Notification.ProgressStyle 通知类型的应用及其代码实现。这种通知类型允许用户更好地追踪任务进度,并通过自定义设置显示在通知栏中。文中还提供了具体的代码示例,展示了如何使用此功能在导航、外卖等场景中显示实时位置和进度。 Android 应用的 CPU 调度策略优化: 本文探讨了在 Android 应用中优化 CPU 调度策略的方法,包括设置线程优先级、配置实时调度策略、利用 Android 平台特性以及通过 cpuset 控制 CPU 资源分配等。文章详细介绍了如何通过调整线程的优先级和绑定 CPU 核心来提升应用性能。 杂记 为什么我看空英伟达股票 [译]: 本文深入分析了英伟达股票的潜在风险,尽管其在 AI 和深度学习领域占据主导地位,但面临来自新兴技术和竞争对手的多重挑战。作者认为,英伟达的高估值可能无法长期维持,因为市场上出现了新的硬件架构、软件框架,以及更高效的 AI 训练和推理方法,可能削弱英伟达的市场优势。 YC 创始人 Paul Graham 最好的一篇文章: YC 创始人 Paul Graham 的最新博客文章探讨了人生的意义,特别是关于创造的主题。文章认为,单纯的享乐无法带来持久的快乐,真正有价值的是创造新事物。创造不仅能体现人性的智慧,还能带来自我实现的幸福感。无论是创业、写作还是其他形式的创造,关键在于对他人有益,并能在过程中获得成就感。 聊一聊 2025 年 Android 人的求职之路: 阿豪在 2025 年求职 Android 岗位时,发现市场环境变得更加严峻。大部分岗位集中在一线城市,二线城市的机会大幅减少。求职者普遍面临降薪和转岗的风险,外包岗位增多。阿豪建议通过内推增加面试成功率,并强调熟悉简历上的技能和项目。最终,阿豪选择了传音控股的 Android 系统开发岗位,因为其薪资竞争力、市场潜力和良好的工作氛围。 Searching for outliers: 这篇文章探讨了博客文章和其他生活领域中重尾分布的现象。作者分享了个人写作经历,强调在重尾分布中,少数成功的例子往往能够产生重大影响,而大多数尝试可能平淡无奇。文章通过多个例子说明了如何识别和利用重尾分布,以便更有效地寻找和评估潜在的“异类”机会。 独立开发周记 112:3 月数据总结,收入多样: 独立开发者道哥在其周记中总结了 3 月的数据表现,强调了极简日记应用的下载量和收入的显著增长,并分享了多样化收入的探索经历。此外,他也提到了个人生活中的一些小趣事。 The Best Programmers I Know | Matthias Endler: 这篇文章总结了成为优秀程序员的关键特质,包括:深入阅读参考文档,全面了解工具,认真解读错误信息,善于分解问题,勇于动手实践,乐于助人和分享,保持持续学习的热情,不拘泥于地位,建立良好声誉,保持耐心,不怕承认不知道,避免猜测,并编写简单而易维护的代码。 科技爱好者周刊#344:制造业正在“零工化”: 这里记录每周值得分享的科技内容,周五发布。 G̶o̶o̶g̶l̶e̶r̶… ex-Googler.: 这篇文章讲述了一位曾在谷歌工作的员工被公司裁员后的心情。他感到震惊、愤怒和失望,并分享了他在 Chrome 团队的工作经历以及裁员对他的影响。 AI 微软推出 Playwright 官方 mcp server,效果试用!: 微软推出了 Playwright-mcp 服务器,这是一个基于 MCP 协议的工具,旨在增强 AI 应用的实用性。MCP 协议由 Anthropic 公司于 2024 年推出,旨在标准化大语言模型与外部数据源和工具的交互接口。Playwright-mcp 可以与 Vscode copilot 无缝结合使用,帮助用户无代码完成浏览器操作和验证。 AI 编程:从 Copilot 到 Autopilot: 这篇文章探讨了 AI 编程工具的发展及其对专业程序员和非专业用户的影响。文章讨论了 AI 编程工具的演变,从简单的代码补全插件到更智能的独立 IDE 产品,以及它们如何降低编程门槛,使更多人能够实现软件开发。AI 编程被认为是目前唯一盈利的 AI 赛道,因其显著提高了编程效率,并且能够满足长尾需求。文章还讨论了 AI 编程工具的市场竞争格局及其未来发展趋势。 Koin 最新中文文档来了!官方同步,AI 驱动: Koin 的最新中文文档由 Open AIDoc 项目发布,该项目由社区伙伴 El 发起,旨在提供官方同步的中文技术文档,以解决开发者的阅读痛点。通过 AI 自动化翻译,项目提供多种语言版本,并实时更新以保持与官方一致。 Prompt Engineering: 这篇文章探讨了提示工程(Prompt Engineering),即为大型语言模型设计输入提示的艺术和科学。文章指出,撰写有效提示并不需要成为数据科学家或机器学习工程师,但要使提示最有效可能会很复杂。影响提示效果的因素包括所用模型、模型的训练数据、模型配置、用词选择、风格和语气、结构及上下文。提示工程是一个迭代过程,不当的提示可能导致模糊、不准确的响应。文章还介绍了在 Vertex AI 或通过 API 直接向 Gemini 模型提示的技巧和最佳实践,并讨论了提示设计中可能面临的挑战。 Gemini API 最新进展:Gemini 2.5 Flash & Pro、Live API、Veo 2: 本文介绍了谷歌在 Cloud Next 大会上发布的最新 Gemini API 更新,包括 Gemini 2.5 Flash & Pro、Live API 和 Veo 2。这些更新旨在增强开发者的能力,推动人工智能应用的发展。Gemini 2.5 Pro 是迄今为止性能最佳的 AI 模型,具有强大的思考能力。Veo 2 现已面向生产环境开放,能够生成高质量的视频内容。Live API 提供了动态、实时的互动体验,支持多语言和语音活动检测等功能。 Github Copilot 近期重要更新一览 (2025.4.9): 本文介绍了 GitHub Copilot 近期的重要更新,包括推出 Pro+版本、对高级模型请求次数的限制以及在 VSCode 和 JetBrains IDE 中新增的功能。这些更新旨在提高用户体验和功能多样性。 鸡血 Hacker News 上《我认识的最优秀的程序员》的文章,写得很实在,挺有共鸣。作者总结了他见过的那些顶尖程序员的共同点: 死磕官方文档 (Read the Reference): 这点我觉得太对了!遇到问题,别老是第一个就跑去 Stack Overflow 或者问 AI,也别瞎猜。大佬们会直接去看官方文档、去看源码。那才是第一手资料,而且很多时候写得比想象中清楚。 深入理解工具 (Know Your Tools Really Well): 光会用不够,得真正懂你用的技术。不光是知道怎么操作,还要了解它的历史、原理、局限性、生态。这样才能用得好,配置起来也心里有数。 认真读报错信息 (Read The Error Message): 别扫一眼就过!大佬们会仔细看报错,琢磨里面每个词的意思。作者说,很多时候答案就在报错信息里,看懂了就能解决大部分问题。 拆解问题 (Break Down Problems): 遇到难题卡壳了怎么办?高手会把大问题拆成一堆小问题,直到每个小问题都好解决。这基本就是程序员的核心工作:拆解。 不怕“脏活累活” (Don’t Be Afraid To Get Your Hands Dirty): 顶尖的开发者不怕钻研陌生的代码库,不会说“这个我不熟”就不管了。他们就是上手去搞,边搞边学,不知不觉就成了这块的专家。 持续学习,不瞎跟风 (Never Stop Learning): 技术更新快,大佬们会一直学习新东西,但不是盲目追潮流。他们会认真评估新技术的优劣,如果不用,也能清楚说出为啥,以及替代方案是啥。很多大佬年纪不小了,但思维还是很活跃。 不耻下问,也不怕说“我不知道” (Don’t Be Afraid to Say “I Don’t Know”): 承认不知道不是丢人的事,反而是学习的开始。大佬们不怕暴露自己的知识边界,而且能坦诚地跟人交流,哪怕对方是刚入行的新人。 拒绝猜测 (Don’t Guess): 遇到不确定的地方,别凭感觉猜。去查证、去问、去调试。猜对了可能让你养成坏习惯,猜错了直接导致 bug。 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 Perfetto Pin 区域高度调节工具 — 让性能分析更高效: 分析 trace 时,我们经常要把感兴趣的线程给 pin 住,然后在 Perfetto Viewer 默认的 Pinned 区域(固定视图区)里集中查看这些 pin 住的线程。然而有个问题,Pin 区域的高度被谷歌官方给限制死了,限制为浏览器高度的 40%,pin 的线程如果不多还好说,一旦多了我们就会发现,Pin 区域也需要滑动才能查看全部,这就很烦了,明明我们屏幕下方还有好多空间,你为啥不能再扩大点呢。 移动 OS 设计之性能设计 5 - OS 质量提升- 技术指标优化: 本文探讨了移动操作系统性能优化的设计方法,包括如何持续提升操作系统质量、定义技术指标、观测指标以及设定优化目标等内容,同时分析了操作系统与第三方应用之间的资源分配问题及优化挑战。 Android 确定废弃「屏幕方向锁定」等 API ,如何让 App 适配大屏和 PC/XR 等场景: Android 计划逐步废弃限制屏幕方向和大小调整的相关 API,开发者需适配大屏、折叠屏、PC 和 XR 等场景。文章详细介绍了相关变更、适配方法及开发工具。 Android× 鸿蒙 ×AI 技术周刊 - 第 5 期: Android× 鸿蒙 ×AI 技术周刊 - 第 5 期 内核抢占,让世界变得更美好 | Linux 内核: 本文讨论了 Linux 内核抢占的基础知识,介绍了抢占的概念、抢占的类型、内核抢占的时机以及相关配置选项,并分析了抢占发生的条件和执行过程。此外,还探讨了内核抢占的优缺点及其应用场景。 MTK Camera 照片切视频 Systrace 拆解分析: MTK Camera 照片切视频 Systrace 拆解分析文章通过详细的拆解分析,介绍了 MTK Camera 从照片模式切换到视频模式的 Systrace 过程,包括关键流程、阶段拆解及相关技术细节。 开发 PopTranslate 背后的故事: 整整一年没有更新 Blog 了,因为最近发布了一款新产品 PopTranslate,想做一点承前启后的事情。所以在这篇文章里,我想正式介绍一下这款产品,以及产品背后的开发故事,也就是从做完上个产品后的疲倦状态中走出来,然后找到新方向的故事。 Android 耗时统计: 对于 Android 的耗时统计,在 Android 7.0, 增加了一个 Api 可以方便的统计一个 Window 的 View 树的绘制耗时。低于 API 26 可以使用 Choreographer 监听帧时间。 Pixel 8 pro 刷 AOSP 14 源码 详细教程 & 救砖处理: Pixel 8 Pro 刷入 AOSP 14 源码详细教程,涵盖从环境配置、源码下载、驱动安装、编译到刷机的完整步骤,重点强调了避免变砖的注意事项,并提供了解决变砖的方法。 以鸿蒙为例,详解统一渲染技术: 本文以鸿蒙为例,分析统一渲染技术的实现与优化。华为通过引入 RenderService 进程解决分离渲染架构动效卡顿问题,采用统一渲染树管理降低 GPU 负载,同时通过异步流水并行和渲染树拆分技术提升性能。基于 Skia 引擎的 Vulkan 改造,实现帧内高并行 GPU 绘制,并通过 GlobalContext 优化多线程支持,最终显著提升渲染效率。 Android 渲染管线 App 端的性能优化: 文章探讨了 Android 渲染管线的性能优化问题,提出通过将 UI 线程拆分为业务主线程、UI Record 线程,以及 Render 线程的三线程方案来分担负载,从而解决 5%重任务导致的渲染延迟问题,同时强调主线程 UI 布局复杂度可预知,但其他消息事件处理可能引发不可控的卡顿,如四大组件的 ANR 问题。 Linux 内核追踪神器:perf 实现原理剖析: 本文深入剖析了 Linux 内核性能分析工具 Perf 的实现原理、功能特点、安装与使用方法,以及其在性能优化中的应用场景和重要性。Perf 是一款强大的性能分析工具,能够帮助开发者深入理解系统性能问题并进行针对性优化。 LWN:网络代码中着手处理冻结页!: LWN:网络代码中着手处理冻结页!这篇文章讨论了 Linux 6.14 内核引入的冻结页优化技术及其对性能的潜在影响,同时分析了网络子系统在应用该技术时遇到的问题和解决方案。 androidweekly issue-669: androidweekly issue-669 Flutter 教程(十四)动画: Flutter 动画教程介绍了动画的分类及其实现方式,包括补间动画、物理动画、隐式动画、显示动画、Hero 动画和交织动画。文章详细讲解了每种动画的定义、特点及代码示例,帮助开发者在 Flutter 中灵活运用动画技术。 RecyclerView——ItemTouchHelper: 本文详细介绍了 RecyclerView 的辅助类 ItemTouchHelper 的使用及源码解析,包括其功能、构造方法、事件处理、拖拽与侧滑手势处理、以及实现拖动效果的原理。 为什么要慎用 Skia 多线程渲染?: Android 渲染通过 UI 线程和渲染线程的分离实现了多线程渲染,UI 线程负责更新 UI,渲染线程负责完成渲染指令。然而,使用多线程就不得不考虑到线程安全和死锁等问题,因此 Android 也设计了很多保护和限制。对于更新 UI 这项工作,Android 严格限制只能在 UI 线程中进行,如果在子线程中更新 UI 就会抛出一个异常——CalledFromWrongThreadException。对于进行渲染这项工作,Android 没有像更新 UI 那样直接限制成在非渲染线程中就抛出异常,所以开发者需要更加谨慎地使用使用多线程。本文将介绍一个笔者实际遇到的 AOSP bug,引出 Skia 单一使用者原则,分析 Google 如何实现对渲染的线程安全保护。 用于地址空间隔离的页面分配: 这篇文章主要讨论了一种用于内核地址空间隔离的页面分配技术,并分析了其设计理念、性能挑战以及在实际应用中的问题和解决方案。 Android V app 冷启动(8) 动画结束: 本文详细解析了 Android V 应用冷启动过程中的 Transition 结束流程,包括 WMShell 和 WMCore 的工作机制、WallpaperWindowToken 的不可见提交、DisplayContent 的 transition finish 处理、屏幕方向更新与配置更新,以及 Fixed Rotation 的清理与应用。 Flutter 伪 3D 绘制#1 | 三维空间: 该文章介绍了如何使用 Flutter 中的 Canvas 实现伪 3D 绘制,通过投影变换将三维点映射到二维平面,并绘制三维坐标系、空间点和旋转效果。文章详细讲解了数据定义、投影逻辑及绘制方法,并提供了代码示例。 Flutter 伪 3D 绘制#02 | 地平面与透视: 本文介绍了如何在 Flutter 中绘制伪 3D 地平面网格,并通过数学投影逻辑实现透视效果,解决线条绝对平行带来的视觉违和感。通过动态调整参数,可以优化视觉体验,增强三维空间的表现力,为后续深入分析投影映射逻辑奠定基础。 Flutter 伪 3D 绘制#03 | 轴测投影原理分析: Flutter 伪 3D 绘制#03 文章详细解析了轴测投影的原理,介绍了如何将三维空间的点映射到二维平面,包括等轴测投影的数学计算和应用场景。 Kotlin 对 Android 整体编程有什么明显的改进,这几年自身有什么更新: 自 2017 年 Google 宣布 Kotlin 为 Android 官方语言以来,其对 Android 开发的革新体现在编程效率、代码质量、跨平台能力等多个维度,同时 Kotlin 语言自身也经历了重大技术演进。 杂记 程序员的自我修养 - 第二章 编译和链接: 程序员的自我修养 - 第二章编译和链接,详细探讨了从源代码到可执行文件的整个过程,包括预处理、编译、汇编和链接的步骤。文章通过示例代码和工具命令,深入剖析了各阶段的工作原理及其作用,并对编译器的工作流程进行了详细分析。 工作九年程序员的三月小结: 工作九年的程序员拭心分享了三月份的个人工作和生活总结,涵盖了合同续签、AI 项目进展、工具学习、宠物手术、租房搬家以及装修等内容。他对技术的简化趋势进行了反思,同时表达了对未来的焦虑与兴奋。 体验碎周报第 226 期(2025.3.31): 系统的知识来源于对碎片的整理和思考,为了更好地输出系统知识,记录本周我发现的体验设计和思考,为构建系统知识做准备。 自证陷阱能挖多深?: 这篇文章围绕“自证陷阱”展开,作者通过与一位质疑者的对话,分析了如何应对逻辑陷阱,尤其是“自证陷阱”和“动机陷阱”。文章探讨了创作者是否需要证明自己的创作真实性,以及如何避免陷入主观争论。作者最终总结了一些策略来脱离这些逻辑困境,并强调了在面对主观质疑时及时抽身的重要性。 健身是所有人的必修课: 这个文章探讨了健身的重要性以及作者个人的健身体验和感悟,强调健身不仅是一项身体活动,更是长期健康生活规划的重要组成部分。 Python 潮流周刊#96:MCP 到底是什么?: 本周刊由 Python 猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。 草长樱飞的三月月报: 三月工作清闲,作者重新学习 Flutter 并开发了 FyMemos 客户端,同时利用每个周末进行户外活动,包括徒步和露营,感受春天的自然美景。阅读了阿德勒的《自卑与超越》和《读库 2502》中的西游相关内容,收获新视角。此外,开始跑步锻炼身体,改善体能,并经历了一次轻微交通事故。作者总结春天是感受自然的好时机,计划四月继续户外活动并努力提升自己。 AI 闲谈丨一名 AI 体验者的自述: 文章讲述了一位 AI 体验者从最初对 AI 工具的不屑一顾到深刻依赖的态度转变。他分享了 AI 对工作与生活的巨大影响,包括提升开发效率、减少对传统学习媒介的依赖,以及在代码分析和工具开发中的实际应用。同时,他也指出了当前 AI 仍有的一些局限性,并探讨了如何与 AI 共处的最佳方式。 Awesome MCP Servers - Concise List: 该文章提供了一个关于 MCP 服务器的简洁列表,涵盖了多个领域的应用,包括浏览器控制、艺术与文化、云平台、命令行工具、通信、客户数据平台、数据库、开发者工具、数据科学工具、文件系统、金融科技、游戏、知识与记忆、定位服务、营销、监控、搜索以及实用工具等。每个部分列出了相关的 MCP 服务器及其功能和集成方式。 BabelDOC: BabelDOC 是一个功能强大的 PDF 科学论文翻译和双语对比工具,提供在线服务和自部署选项。它支持英语到中文的翻译,具有丰富的命令行选项和 Python API,可用于文档处理和翻译。项目旨在解决 PDF 解析和翻译中的结构保留问题,并通过插件系统实现扩展。 [译] AI 计算民主化 第七部分:如何看待 Triton 与 Python eDSLs?: 本文探讨了 AI 计算民主化背景下,Python 嵌入式领域特定语言(eDSLs)如何弥合易用性与性能之间的差距,重点分析了 Triton 及其优缺点,并对其他类似技术进行了对比。同时讨论了 eDSLs 的局限性、治理问题以及未来 AI 编译器的统一框架 MLIR。 deepseek ai 输入法: 文章介绍了使用 Java 开发安卓输入法并接入 DeepSeek 实现 AI 聊天功能,提供了视频演示和开源代码地址,同时解析了技术细节,包括输入法服务类、布局文件和程序入口等,适合对 Android 开发和 AI 集成感兴趣的开发者参考。 从“首个 AI 软件工程师” Devin 2.0 的系统提示词看提示词工程的奥秘: 文章分析了 AI 软件工程师 Devin 2.0 的系统提示词,深入探讨了提示词工程的原理和应用。提示词不仅为 AI 设定了角色、任务和行为准则,还规定了工具使用、交互结构、安全规范以及特殊场景处理机制。这些提示词设计旨在提升 AI 的效率、安全性和任务执行能力,同时也揭示了提示词工程的复杂性和潜在风险。 为什么靠 AI“什么都懂一点”,却并不能真正变专业?: 现在,很多人都对 AI 抱着一个幻想:我可能不是很专业,但如果借助 AI,不就能轻松变成专业人士了吗?确实,AI 在某些方面给了我们这样的错觉,比如稍微懂一点编程的人,可以轻松地用 AI 写出一个小程序的原型;不怎么懂设计的人,可以通过 AI 快速生成看起来不错的海报。然而,这种“什么都懂一点”再靠 AI 加持就能变专业的想法,其实是一种误解,甚至可能是一种危险的误解。 书籍推荐 Android 性能优化之道 这是一套从 Android 性能优化本质入手,指导读者实现从硬件层到操作系统层再到应用层全面优化的实战方法论。本书由 Android 方向 Google 开发者专家撰写,融合了作者 10 年大厂实战经验,其中不仅包括作者实操过的监控、优化、防劣化等方向的各种典型案例,还包括多个实战小技巧,可以帮助读者解决工作中遇到的 90%以上的能优化问题。 本书内存、速度和流畅性、稳定性、包体积、耗电、磁盘占用、流量、降级这 8 个方向的性能优化内容。这些内容方向均从原理和实战两个维度进行解读。其中,原理部分直指优化的本质,不仅包括相关基础知识,还包括性能优化的底层逻辑;实战部分以指导读者实操为主要目标,以案例为主要讲解形式,深度解读作者精心总结的各种实战案例中用到的技术和原理。本书基于 Android 14 撰写,但也会涉及 Android 14 以外的其他 Android 版本的源码。 jd:Android 性能优化之道 打通 Linux 操作系统和芯片开发 为什么选择写打通操作系统和芯片开发的内容?我们知道计算机是个变化极快的行业,特别是从事互联网行业的朋友,经常面对技术的更新,开发语言的迭代,每天过的都很焦虑,随着新人的入职,技术的变化,老人的技术经验似乎无法得到发挥,这也是为什么都说程序员有 35 岁失业的根本原因。那么技术更新不那么快的行业是不是就好点了呢?的确如此,比如更加底层的嵌入式行业,操作系统行业,芯片行业等都会比互联网行业好很多,特别是同时懂软件和硬件的工程师,甚至随着时间的推移,越老越吃香,而且国家越来越重视底层技术的开发。即便是在互联网行业,如果你对底层技术有着深厚的积累,依然可以很有竞争力,就相当是拥有了武侠片中的内功,一旦有了雄厚的内功,其它武功你一看就明白,一学就会,任何招式你和别人打出去的威力就不是一个级别。这种帮助无论对嵌入式开发者,还是对互联网程序员都是非常明显的。 jd:打通 Linux 操作系统和芯片开发 鸡血 星球 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 Android Vulkan 官宣转正并统一渲染堆栈 ,这对 Flutter 又有什么影响?: Android Vulkan 官宣转正并统一渲染堆栈,这标志着 Vulkan 成为 Android 唯一的 GPU 硬件抽象层(HAL),所有应用和游戏将必须基于 Vulkan 实现。为解决 OpenGL 和 Vulkan 的兼容性问题,ANGLE 被引入作为兼容层。此外,Google 通过 Vulkan 配置文件(VPA)提高一致性,并与 Unity 和联发科合作优化性能。对于 Flutter,Android 的碎片化对 Impeller 的落地带来挑战,但未来可能通过 ANGLE 和 Vulkan 的普及改善这一状况。 移动 OS 设计之性能设计 3 - 专制型资源管理策略至关重要: 文章探讨了移动操作系统在资源管理和服务质量保障方面的设计策略,强调专制型资源管理的重要性,特别是在面对复杂、多任务的移动环境时。文章通过对比安卓和苹果的设计理念,分析了资源分配、优先级管理以及容量规划等关键策略。 移动 OS 设计之性能设计 4 - 服务质量信息的传递: 这篇文章的主要内容是基于第三篇文章「移动 OS 设计之性能设计 3 - 专制型资源管理策略至关重要」的内容。在那篇文章中,我提到移动 OS 相较于以往的操作系统,其计算能力和设备资源都有限。因此,移动 OS 的服务质量需要被明确界定。这种服务质量的体现强调的是可预期的表现。在某些业务场景中,服务并不是越快越好,而是在设备资源的限制下,提供符合用户预期的服务即可。在明确了服务质量的划分后,随之会引发其他问题。实际上,所有的技术方案都是如此。在软件领域中没有所谓的“银弹”,即不存在一个万能的解决方案。每一个方案在解决某个问题的同时,往往会引发新的问题。而这些新的问题则需要通过其他方案来解决,直到这些问题被转换为在其他层面上可接受的问题,或者被等价地转换成用户并不关心的问题。 如何配置 Clion 编写 aosp 的 c++程序: 这篇文章介绍了如何在 Clion 中配置并编写 AOSP(Android 开源项目)的 C++程序,主要适用于无法使用 ASFP IDE 的环境。文章详细说明了使用 AIDEGen 工具生成 CMakeLists 文件的方法,并提供了对 CMake 文件的修改建议及 Clion 索引优化配置步骤。 AI 时代的性能分析:GPU Profiling 初探: 在 CPU 优化的过程中,例如我们遇到 CPU 打满的情况,我们可以通过 perf 等工具进行 Profiling,然后将数据可视化成火焰图等形式进行分析;同样的,在 GPU 的优化过程中,我们也可以通过 Profiling 来进行性能优化。 再次勇闯稳定性岗位!vivo 社招面经——linux 系统工程师(底软稳定性与性能): 今天再一次给大家带来稳定性方向的面经,希望通过我的面经,让更多的人了解这个“神秘”的岗位,让更多的人了解稳定性到底问什么。 利用内存页筛选法手撕内存越界行为: 本文详细介绍了如何利用内存页筛选法定位内存越界问题,结合实际案例分析,探讨了内存踩踏的直接原因、间接原因,以及如何通过调整参数解决问题,并对内存分配器 Scudo 的特性进行了深入讨论。 Flutter 新一代状态管理框架 signals ,它究竟具备什么魔法和优势: Flutter 新一代状态管理框架 signals,通过自动状态绑定和依赖追踪实现了高效的状态管理。文章详细分析了 signals 的工作原理、与其他框架的对比以及其在 Flutter 中的应用方式。 程序员的自我修养 - 第一章 温故而知新: 《程序员的自我修养》第一章通过从底层硬件到上层应用的层层解析,阐述了计算机系统的基本架构、操作系统的核心功能以及多线程与内存管理等关键概念。文章以“温故而知新”为主题,帮助读者理解计算机科学的基础知识,并通过历史发展和现代技术的对比,揭示了技术的演进和核心不变的本质。 高通 Oryon 处理器微架构分析: 高通浓眉大眼又开始搞起了 CPU 设计,以前没什么反响,基本还是用 ARM 公版的核,自从 2021 年收购了 Nuvia,情况变得不一样了,这个公司是苹果 M1 的团队一些成员出走而创立的公司,原本是要搞服务器的,高通收购后又开始搞 PC 和手机端的核,这个公司的收购对高通的发展影响巨大,Oryon 的成功意味着高通不仅挤入了自研 CPU 的第一梯队,还标志着高通具备了 SoC 上所有核心组件自研的能力,并且都是先进水准,比如基带,GPU 这些。为此还和 ARM 对簿公堂,ARM 最后输掉了官司。今天我们看看这款高通“大核”Oryon 的微架构设计。 编译器是什么,它又能做什么?: 编译器是将程序员编写的代码翻译为机器码的工具,在安全性、性能优化和开发体验等方面发挥重要作用。文章通过 Ken Thompson 的“编译器幽灵”事件引出编译器的重要性,详细讲述了编译器在安卓系统中的应用及其优化方法,并探讨了 AI 领域对编译器需求的扩展,强调了编译器在现代计算生态中的核心地位。 聊一聊安卓 WallpaperService 壁纸窗口触摸事件接收原理-第一篇: 在使用 android 手机时候,大家壁纸可能一般都是使用的静态壁纸,静态壁纸一般就是设置一张静态,这种静态壁纸因为是固定的一张图片,所以对壁纸触摸交互这块需求比较少,但是如果设置的是一个动态壁纸,那么这个触摸交互需求就会大大增加。 android studio Debug 安卓 aosp 源码 userdebug 版本看不到局部变量怎么办?: 安卓 aosp 源码 userdebug 版本调试时无法看到局部变量的解决方法,文章分析了问题原因并提供了解决方案,包括使用 eng 版本和修改代码等,同时提出了更彻底的解决方法,通过 eng 版本编译特定模块并覆盖 userdebug 版本的方式来实现局部变量的显示。 Linux 6.10 | CPU 调度: 本文基于 MTK 平台和 Linux Kernel 6.1,围绕 CPU 调度机制进行了分析,重点探讨了唤醒场景下的选核策略、能耗优化方法以及负载均衡等内容。通过多个案例和代码解析,展示了 MTK 平台的调度策略如何在不同场景中实现任务的高效分配,同时也指出了复杂调度策略可能带来的副作用。 LWN:2025 疯狂的 mapcount!: 内核的内存管理子系统必须处理的众多重要任务之一,是跟踪内存页如何映射到系统上运行的进程的地址空间。只要存在到给定页面的映射,就必须保持该页面的存在。事实证明,跟踪这些映射比看起来应该的要困难,并且内存管理子系统内向 folios (页组)的迁移正在增加其自身的复杂性。作为 “mapcount madness” 会议的后续,David Hildenbrand 在 2024 Linux 存储、文件系统、内存管理和 BPF 峰会上发布了 一个补丁系列,旨在改进 folios 的映射计数处理 — 但在某些情况下,精确的跟踪统计仍然难以实现。 我对 Android Looper 的重新设计: 这篇文章探讨了对 Android Looper 的重新设计,提出通过使用 ConcurrentSkipListSet 跳表优先级队列优化消息插入效率,并引入消息分级机制(高优先级、普通、低优先级、闲置消息)来提升系统性能和 UI 流畅性。设计重点包括优先处理 vsync 等关键 UI 消息、通过 barrier 屏障实现异步处理、优化广播和服务的响应及时性,以及避免低优先级消息饥饿问题,同时建议精简线程以减少资源消耗。 任务被唤醒后在哪个 CPU 跑?wake_affine 和 select_idle_sibling: 在 Linux 内核中,若任务 A 唤醒任务 B,则 A 被称为 waker;而 B 被称为 wakee。由于 B 原先是睡眠的,现在醒来,它需要找一个 CPU 来跑它,找哪个 CPU 来跑呢?这里是有学问的。唤醒的同时往往伴随着通信(共享数据的访问),比如很可能是 A 在管道、socket 里面写了数据唤醒 B 去读;或者 A 写了一片数据到共享内存,通知 B 去读。这个时候任务 B 醒来后,迁移到 A 所在的 CPU 或者与 A 所在 CPU 最亲近的 CPU 跑,则可能更容易命中 A 写入的 hot-cache(当然也需要综合考虑 B 以前所在的 CPU、A 所在的 CPU,A 与 B 之间的拓扑关系以及 A 和 B 各自 runqueue 的繁忙程度)。 Media3 1.6.0 — what’s new?: Android 开发者博客:Media3 1.6.0 有哪些新功能? 实战:在 Compose 中优雅地实现提示: 本文详细讲解了如何在 Jetpack Compose 和 Compose Multiplatform 中优雅地实现提示功能,包括背景覆盖、锚点坐标计算、形状剪辑、提示绘制以及提示控制等内容,并提供了完整的代码示例和实现步骤。 Kotlin 中那些无法复现的 Java 写法: Kotlin 对 Java 是 100% 兼容的,正如官方所说。 可即便如此,这就代表 Java 中所有的写法就都能在 Kotlin 中复现出来吗?倒也未必。 今天我就来简单盘点一些我发现的那些在 Kotlin 中无法复现出来的 Java 写法。 地图之 Compose 轻松绘制,可视化带点击事件,可扩展二次开发: 本文详细介绍了如何在 Android 中使用 Compose 实现地图可视化,包括从 SVG 数据到 Vector.xml 的处理、地图大小和边距的调整、点击事件的实现等,并提供了完整的代码示例与封装好的库供开发者使用。 NativeAllocationRegistry—-通过绑定 Java 对象辅助回收 native 对象内存的机制: 本文详细介绍了 NativeAllocationRegistry 类在 Java 和 Native 内存管理中的应用,尤其是在 Java 对象被垃圾回收后如何自动释放相关的 Native 对象内存。文章分为背景、原理、手动释放和自动释放的具体实现,并提供了代码解析和机制说明。同时提到了一些与此机制相关的应用场景,如 Bitmap 内存释放和 LeakCanary 的内存泄漏检测。 利用 bytehook 修复 EGL_BAD_ALLOC 异常: 利用 bytehook 修复 Android 低版本(<=Android 9)中因系统 Bug 导致的 EGL_BAD_ALLOC 异常问题,通过 hook 函数解决空 Surface 引发的 fatal 异常,并提供了相关代码实现与分析。 Flutter 知识集锦 | 获取函数调用栈: 本文介绍了 Flutter 开发中如何获取函数调用栈信息的几种方法,包括异常时的调用栈信息、捕捉异常时的调用栈信息,以及在任意位置主动获取调用栈信息的方式。文章强调了函数调用栈信息在问题定位、源码分析及日志记录中的重要作用。 Make WebViews edge-to-edge: 这篇文章介绍了如何在 Android 应用中确保 WebView 与“边到边”显示兼容,特别是在 Android 15 和 16 中强制实施边到边绘制的情况下。文章详细讨论了如何处理 WebView 的插图(Insets),以及如何根据应用是否拥有网页内容来采取不同的实现方法,包括通过 JavaScript 注入插图或调整容器的填充。还提到了如何处理输入法(IME)插图以确保键盘弹出时内容不会被遮挡。 【笔记】Android 耗时统计: 这篇文章主要介绍了在 Android 开发中如何通过 OnFrameMetricsAvailableListener 和 Choreographer 来监控 UI 性能指标,分析卡顿问题并优化应用流畅度。 使用 Perfetto 进行流畅度分析: 本文详细介绍了如何使用 Perfetto 工具进行流畅度分析,包括抓取 Trace 文件、分析卡顿原因、使用快捷键提升效率,以及结合日志进行可视化分析等内容。 关于 Perfetto 分析 Trace 耗时问题排查: 本文详细分析了 Perfetto 工具中 Trace 耗时问题的排查方法,涵盖了主线程、渲染线程、GL 线程、SurfaceFlinger、Display 及性能问题等多个方面的耗时情况,并提供了原因说明及优化建议。 又解一个 bug - Fragment 异常显示问题: 首页文章分析了一个 Android 开发中关于 Fragment 显示与隐藏的异常问题,并详细探讨了 FragmentTransaction 的 hide 方法实现原理及其潜在的坑点。作者通过案例分享了如何避免类似问题,并提出了设计跨平台框架时的优化建议。 Perfetto 上手指南 2 —— 基础使用: Perfetto 上手指南 2 —— 基础使用,介绍了 Perfetto 的基础操作和界面功能。文章详细说明了如何进入 Perfetto Trace 分析界面、界面基本内容及其操作方法,包括快捷键使用、标记操作、插旗子和 Pin 操作等。文章还对 Trace 内容区的主要元素(如 slice、counter、CPU Sched Slice、thread_state)进行了说明,帮助用户更好地理解和使用 Perfetto 进行性能分析。 开发 PopTranslate 背后的故事: 图拉鼎开发者分享了新产品 PopTranslate 的开发历程与背后故事。这款 macOS 原生工具以 AI 引擎为核心,提供翻译、解释和重写功能,旨在提升用户效率和体验。文章详细介绍了从前一款产品 MarkMark 的疲惫开发状态到探索新方向的过程,并描述了与妻子的合作、设计开发过程以及未来的展望。 lld 链接器:链接驱动和基本使用: 链接是由源代码构建生成可执行文件的最后一个逻辑环节,它的输出就是可执行文件 EXE 或动态链接库 DSO。 lld 链接器的运行流程(1)——主干流程: 接前文(lld 链接器:链接驱动和基本使用) 对 lld 基本情况的介绍,本文和后续文章介绍 lld 实现的 ELF 格式文件的链接流程 lld 中的重定位:原理、流程与数据结构: 接前文对 lld 核心流程(lld 链接器的运行流程)的介绍,本文继续介绍 lld 对链接中一个核心机制的实现——重定位。 Android×AI 技术周刊 - 第 3 期: 本期周刊既有 Android 生态的更新,也有 AI 领域的开源黑科技与实战指南!无论你是想抢先体验 Android 16 Beta 的能力,还是探索 AI 多模态生成、智能体开发的前沿技术,这里都有不容错过的干货。 Android× 鸿蒙 ×AI 技术周刊 - 第 4 期: Android× 鸿蒙 ×AI 技术周刊 - 第 4 期总结 vLLM 深度解析:production stack: 本文资料来自 vLLM Office Hours 第 21 次会议的记录,时间是 2025 年 3 月 6 日,主题是 vLLM 生产堆栈的深度探讨。主讲人包括 Red Hat 的 Michael Goin 和 Yihua Cheng。会议内容涉及 vLLM 的最新动态、production stack 的架构、性能优化、未来路线图等。 vLLM 深度解析:Deekseek and vLLM -1: 本文资料来自 vLLM Office Hours 第 20 次会议的记录,时间是 2025 年 2 月 27 日,主题是要讨论 vllm 对于 deepseek 的更新,内容丰富。 kotlin-weekly-450: kotlin-weekly-450 Issue #666: Android Weekly 第 666 期,真是 6 ~ 杂记 致敬“普通”的工程师——一位软件工程师对“10 倍工程师”神话的质疑与反思: 本文由 Honeycomb.io 联合创始人兼 CTO Charity Majors 撰写,探讨了“10 倍工程师”这一概念的局限性,并强调团队合作和构建支持普通工程师高效工作的系统的重要性。作者认为,软件开发的真正价值在于团队整体的效率,而非个别工程师的超凡能力。 Age is a problem at Apple: 作者批评苹果董事会和领导团队年龄过于老化,平均年龄分别为 68 岁和 60 岁,缺乏年轻视角和多样性,导致决策与年轻用户需求脱节,例如 AI 产品的设计问题。他呼吁苹果引入“新鲜血液”,平衡经验与创新,以应对技术行业快速变化的挑战,并以 Meta 董事会更年轻的年龄结构为对比,强调年轻领导者的重要性。 技术管理思考:工作中人性问题: 所有不符合逻辑事情不要干!整体和端到端审视,一定要逻辑自洽。 科技爱好者周刊#342:面试的 AI 作弊——用数字人去面试: 这里记录每周值得分享的科技内容,周五发布。 2024 年总结: 这篇文章是作者对 2024 年的总结,内容涵盖了写作、社区建设、生活、工作和新年计划等方面的回顾与展望。作者回顾了过去一年的成就与挑战,并展望了未来的目标,强调了身心健康和生活平衡的重要性。 我认识的最差程序员: 衡量开发者生产力最棒的一点是,你能快速识别出差劲的程序员。今天我要和你讲讲我所认识的最差程序员,以及为什么我拼了命也要把他留在团队中。 下一个“N 年思考”: 这篇文章记录了作者在技术领域的职业成长和思考,分享了他在过去几年中围绕技术核心问题所取得的成就,以及对未来技术方向的探索和挑战。他详细描述了在软件工程、DFX 能力建设、跨设备诊断、AI 与软件工程结合等方面的实践与成果,同时提出了一些未解决的问题和未来的努力方向。 AI 时代下的工程领导力:如何打造高效团队 - 来自谷歌工程负责人、Chrome 开发者的宝贵经验分享: 今天偶然读到 Chrome 开发者、Google 工程负责人、著名技术书籍作者 -Addy Osmani 的一篇文章「Leading Effective Engineering Teams in the Age of GenAI」,讲的特别好,对于产品和研发方向如何变得高效,不管你是团队领导者、还是团队成员,都很有价值,分享给朋友们 Leading Effective Engineering Teams in the Age of GenAI: 这篇文章探讨了在生成式人工智能(GenAI)时代,如何有效领导软件工程团队,重点在于技术领导力的演变、AI 工具的使用策略、团队技能提升、以及 AI 技术对工程师职业发展的影响。文章还提供了实际案例研究和未来领导力的方向。 AppStore 首页推荐后,依然月入不足 3000,独立开发两年血泪复盘: 这篇文章是一位独立开发者对其两年独立开发生涯的复盘与反思。尽管曾获得 AppStore 首页推荐并得到用户好评,但因收入不足以维持生计,最终选择放弃全职独立开发。作者分享了失败的原因、独立开发的经验教训,以及对产品推广和商业化的深刻理解,同时也提出了对未来独立开发方向的建议。 谷歌决定闭源 Android?Fake News!: 谷歌将继续开源 Android,但开发流程将更为私密化。媒体误传谷歌将闭源 Android,实际情况是某些模块的开发早已是私有化的。谷歌此举旨在提高效率和版本发布速度,但会对开源贡献者和技术爱好者产生一定影响。 Google 调整 Android 开源政策,核心开发全面转向私有分支,这将对行业产生什么影响?: 谷歌调整了 Android 开源政策,其核心开发全面转向私有分支。这一变化对手机厂商、应用开发者、技术爱好者以及没有与谷歌合作的设备厂商产生了不同程度的影响。主要原因可能包括降低成本、提高效率以及 Android 生态的逐步成熟。 给 IT 年轻人职业建议 5(在工作中找到乐趣): 一个人职业生涯中,工作和兴趣完全匹配几乎不存在。工作中往往是一些枯燥繁琐的事情,如改不完的软件的 BUG,写不完的汇报材料,还有开不完的低效会议,及其周边团队无意义的推卸拉扯。技术人员更喜欢安静的做做设计,写写代码,读读代码,学学新技术等。理想的工作是不存在的,那就需要在日常工作中找到一些乐趣。怎么找到乐趣其实一句话就是学会“上下求索,好奇心”。 AI 关于 DeepSeek 我是怎么研究的(5): 文章详细介绍了 DeepSeek-R1 模型的研究背景、训练过程和性能评估,阐述了其通过结合强化学习和冷启动数据的微调来提升推理能力,并在多个基准测试中表现优异。 4 段超神提示词解锁 Claude 3.7 能力上限: Claude 在升级到 3.7 之后,能力获得了大幅提升,在前端编程方面已经远远超出其他模型,重新定义了 AI PPT 的概念,我已经把所有图表都交给 Claude 3.7 用代码直接写了。最近一直想把方法分享给大家,但苦于没有精力动笔,刚好我的朋友归藏做了一些研究,向阳乔木做了一些延展,写出了这篇很棒的文章。 DeepSeek 赋能 A 股交易:用 AI 让你快人一步!: 用 AI 赋能 A 股交易:DeepSeek 工具解析市场动态,助你快人一步! sonnylazuardi/cursor-talk-to-figma-mcp: 此项目实现了 Cursor AI 与 Figma 之间的模型上下文协议(MCP)集成,允许 Cursor 以编程方式读取和修改 Figma 设计。提供了详细的安装、使用指南以及功能工具列表,用于设计文档操作、元素创建、样式设置、布局调整等。 [译]AI 计算民主化 第一部分:DeepSeek 对 AI 的影响: DeepSeek 对 AI 的影响 探讨了 DeepSeek 技术如何通过创新方法减少对昂贵硬件的依赖,推动 AI 算力民主化。文章回顾了作者 Chris Lattner 的职业生涯,强调了软硬件协同设计、共享基础设施的重要性,并提出解决 AI 算力生态挑战的思路。文章还是系列内容的开篇,核心围绕 CUDA 技术及其影响展开深入探讨。 [译]AI 算力民主化 第二部分:究竟何为“CUDA”?: 本文是“AI 算力民主化”系列的第二部分,深入解析了 CUDA 的技术内涵、历史演进及其在现代 AI 计算中的核心地位。文章详细阐述了 CUDA 作为一个完整生态系统的多层次架构,并探讨了其发展历程及对 AI 领域的深远影响。 [译]AI 算力民主化 第三部分:CUDA 是如何成功的?: 本文深入解析了 CUDA 在 GPU 计算领域取得统治地位的原因,探讨了其技术优势、战略执行、生态系统锁定效应以及 AI 发展的历史机遇。文章还分析了英伟达如何通过软硬件深度耦合、跨代兼容性和开发者生态建设,成功巩固了 CUDA 在深度学习、生成式 AI 等领域的核心地位。 [译]AI 算力民主化 第四部分:CUDA 虽为现有主导者,但它真的完美吗?: 本文探讨了 CUDA 作为 AI 计算领域的主导技术,其优势与局限性。从开发者视角分析了 CUDA 对 AI 工程师、性能优化工程师以及跨平台开发者的影响,同时指出了其复杂性、技术债务和供应商锁定效应对未来 AI 发展的潜在挑战。文章还提及了英伟达如何利用 CUDA 巩固其市场地位,但也面临创新与兼容性之间的矛盾。 Perfetto 快速上手指南 1 —— Trace 的抓取: Perfetto 是 google 从 Android10 开始引入的一个全新的平台级跟踪分析工具。它可以记录 Android 系统运行过程中的关键数据,并通过图形化的形式展示这些数据。Perfetto 不仅可用于系统级的性能分析,也是我们学习系统源码流程的好帮手。 [译]AI 算力民主化 第五部分:CUDA C++替代方案(如 OpenCL)现状如何?: 本文探讨了 CUDA C++替代方案(如 OpenCL)的现状,分析了其技术优势、局限性以及未能成为 AI 计算领域主导平台的原因。文章详细阐述了 OpenCL 的历史背景、技术缺陷、行业竞争与协作的挑战,以及与现代 AI 需求之间的脱节。同时,文章对 NVIDIA 通过 CUDA 与 AI 框架的协同设计所取得的成功进行了对比,总结了成功系统的关键要素,并提出了对未来 AI 编译器发展的思考。 [译]AI 算力民主化 第六部分:TVM 和 XLA 等 AI 编译器现状?: 本文探讨了 AI 编译器(如 TVM 和 XLA)的现状及其在 AI 算力民主化中的角色,分析了其技术优势与局限性,同时对生成式 AI 的兴起如何改变 AI 编译器需求进行了深入剖析。文章还引用了 TVM 和 XLA 的经验教训,并提到了 MLIR 和 Triton 等新兴技术可能带来的突破。 全新 DeepSeek V3 发布,代码能力大幅提升: DeepSeek V3 更新 0324 版本,应该是基于 V3 继续训练的产物。 在 ChatWise 中使用 MCP 工具: MCP (Model Context Protocol),也就是模型上下文协议,是一个让模型访问外部资源的规范,由 Claude 的开发商 Anthropic 提出。开发者可以根据 MCP 规范实现一些服务 (server),然后模型可以通过这些服务获取额外的能力和上下文,比如使用网络搜索、操控外部浏览器等等。 “聊天式编程”让代码听你的话:Cursor 打造极致心流体验: 最近半年深度体验了 Cursor,享受到了很多乐趣,这篇文章将分享对于 Cursor 的一些实践与思考。 闲谈丨一名 AI 体验者的自述: 这篇文章是一名 AI 体验者的自述,分享了 AI 技术对其生活和工作的深刻影响,以及如何通过 AI 提升效率、解决问题。同时文章也探讨了 AI 的局限性以及如何正确使用 AI 的思考方式。 Android 使用 Edge-to-Edge 实现沉浸式状态栏详解: 在 Android 中实现 Edge-to-Edge 布局(内容延伸到状态栏和导航栏下方)并适配不同版本、刘海屏/挖孔屏设备,需要综合处理系统栏的显示、颜色、安全区域和兼容性问题。以下是分步骤的完整实现方案 书籍推荐 Android 性能优化之道 这是一套从 Android 性能优化本质入手,指导读者实现从硬件层到操作系统层再到应用层全面优化的实战方法论。本书由 Android 方向 Google 开发者专家撰写,融合了作者 10 年大厂实战经验,其中不仅包括作者实操过的监控、优化、防劣化等方向的各种典型案例,还包括多个实战小技巧,可以帮助读者解决工作中遇到的 90%以上的能优化问题。 本书内存、速度和流畅性、稳定性、包体积、耗电、磁盘占用、流量、降级这 8 个方向的性能优化内容。这些内容方向均从原理和实战两个维度进行解读。其中,原理部分直指优化的本质,不仅包括相关基础知识,还包括性能优化的底层逻辑;实战部分以指导读者实操为主要目标,以案例为主要讲解形式,深度解读作者精心总结的各种实战案例中用到的技术和原理。本书基于 Android 14 撰写,但也会涉及 Android 14 以外的其他 Android 版本的源码。 jd:Android 性能优化之道 打通 Linux 操作系统和芯片开发 为什么选择写打通操作系统和芯片开发的内容?我们知道计算机是个变化极快的行业,特别是从事互联网行业的朋友,经常面对技术的更新,开发语言的迭代,每天过的都很焦虑,随着新人的入职,技术的变化,老人的技术经验似乎无法得到发挥,这也是为什么都说程序员有 35 岁失业的根本原因。那么技术更新不那么快的行业是不是就好点了呢?的确如此,比如更加底层的嵌入式行业,操作系统行业,芯片行业等都会比互联网行业好很多,特别是同时懂软件和硬件的工程师,甚至随着时间的推移,越老越吃香,而且国家越来越重视底层技术的开发。即便是在互联网行业,如果你对底层技术有着深厚的积累,依然可以很有竞争力,就相当是拥有了武侠片中的内功,一旦有了雄厚的内功,其它武功你一看就明白,一学就会,任何招式你和别人打出去的威力就不是一个级别。这种帮助无论对嵌入式开发者,还是对互联网程序员都是非常明显的。 jd:打通 Linux 操作系统和芯片开发 鸡血 星球 满 500 人了,顺便打个广告。这段时间忙完了,开始要系统性输出了~ 也欢迎加入星球的同学预约一起看 Trace ~ 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
本文介绍了 App 开发者不经常接触到但在 Android Framework 渲染链路中非常重要的一个类 Choreographer,包括 Choreographer 的引入背景、简介、部分源码解析、与 MessageQueue 的交互、在 APM 中的应用,以及手机厂商基于 Choreographer 的一些优化思路。 Choreographer 的引入主要是配合 Vsync,为上层应用的渲染提供稳定的 Message 处理时机。当 Vsync 信号到来时,系统通过对 Vsync 信号周期的调整,控制每一帧绘制操作的时机。目前主流手机的屏幕刷新率已达到 120Hz,即每 8.3ms 刷新一次,系统为配合屏幕刷新频率,相应调整 Vsync 周期。每个 Vsync 周期到来时,Vsync 信号唤醒 Choreographer 执行应用的绘制操作,这正是引入 Choreographer 的主要作用。了解 Choreographer 还可以帮助应用开发者深入理解每一帧的运行原理,同时加深对 Message、Handler、Looper、MessageQueue、Input、Animation、Measure、Layout、Draw 等核心组件的认识。许多 APM(应用性能监控)工具也利用了 Choreographer(通过 FrameCallback + FrameInfo)、MessageQueue(通过 IdleHandler)和 Looper(通过自定义 MessageLogging)这些组合机制进行性能监测。深入理解这些机制后,开发者可以更有针对性地进行性能优化,形成系统化的优化思路。 本文是 Perfetto 系列文章的第五篇,主要是对 Perfetto 中的 Choreographer 进行简单介绍 本系列的目的是通过 Perfetto 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Perfetto 这个图形化的角度,你可以理解的更深入一些。 Perfetto 系列目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 视频(B站) - Android Perfetto 基础和案例分享 如果大家还没看过 Systrace 系列,下面是传送门: Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 主线程运行机制的本质 在讲 Choreographer 之前,我们先理一下 Android 主线程运行的本质,其实就是 Message 的处理过程,我们的各种操作,包括每一帧的渲染操作 ,都是通过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,如下图所示 MethodTrace 图示 Perfetto 图示 演进 引入 Vsync 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定,如下图 MethodTrace 图示 Perfetto 图示 可以看到这时候的瓶颈是在 dequeueBuffer, 因为屏幕是有刷新周期的, FB 消耗 Front Buffer 的速度是一定的, 所以 SF 消耗 App Buffer 的速度也是一定的, 所以 App 会卡在 dequeueBuffer 这里,这就会导致 App Buffer 获取不稳定, 很容易就会出现卡顿掉帧的情况. 对于用户来说,稳定的帧率才是好的体验,比如你玩王者荣耀,相比 fps 在 60 和 40 之间频繁变化,用户感觉更好的是稳定在 50 fps 的情况. 所以 Android 的演进中,最初引入了 Vsync + TripleBuffer + Choreographer 的机制,后来在 Android S (Android 12) 版本又进一步引入了 BlastBufferQueue,共同构成了现代 Android 稳定帧率输出机制,让软件层和硬件层可以以共同的频率一起工作。 BlastBufferQueue 简介 Android S(Android 12) 中引入的 BlastBufferQueue 是 BufferQueue 的一个特殊变体,专门为 UI 渲染优化设计。这是谷歌对 SurfaceFlinger 进行重构的重要举措,主要目的是减轻 SurfaceFlinger 的负担,将缓冲区管理责任更多地转移到客户端 (App) 一侧。 它与传统 BufferQueue 相比效率更高,支持更好的异步处理,减少了主线程阻塞,并为高刷新率设备提供了更好的支持。具体的实现机制和工作原理将在后面的”Choreographer 与 RenderThread 及 BlastBufferQueue 的交互”部分详细介绍。 引入 Choreographer Choreographer 的引入主要是配合 Vsync,为上层应用的渲染提供稳定的 Message 处理时机。当 Vsync 信号到来时,系统通过对 Vsync 信号周期的调整,控制每一帧绘制操作的时机。目前主流手机的屏幕刷新率已达到 120Hz,即每 8.3ms 刷新一次,系统为配合屏幕刷新频率,相应调整 Vsync 周期。每个 Vsync 周期到来时,Vsync 信号唤醒 Choreographer 执行应用的绘制操作,如果每个 Vsync 周期应用都能渲染完成,那么应用的 fps 就是120,给用户的感觉就是非常流畅,这就是引入 Choreographer 的主要作用 当然目前主流旗舰手机的刷新率已达到120Hz,Vsync周期已缩短至8.3ms,上图中的操作要在更短的时间内完成,对性能的要求也越来越高,具体可以看新的流畅体验,90Hz 漫谈 这篇文章(文章虽然讨论90Hz,但同样的原理适用于120Hz) Choreographer 简介 Choreographer 扮演 Android 渲染链路中承上启下的角色 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理)、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作),判断卡顿掉帧情况,记录 CallBack 耗时等 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync);请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 从上面可以看出,Choreographer 在 Android 渲染管线中扮演关键的协调者角色,其重要性在于通过 Choreographer + SurfaceFlinger + Vsync + BlastBufferQueue 这一套完整的渲染机制,确保 Android 应用能够以稳定的帧率运行(60 fps、90 fps 或 120 fps),有效减少帧率波动带来的视觉不适感。 了解 Choreographer 还可以帮助应用开发者深入理解每一帧的运行原理,同时加深对 Message、Handler、Looper、MessageQueue、Input、Animation、Measure、Layout、Draw 等核心组件的认识。许多 APM(应用性能监控)工具也利用了 Choreographer(通过 FrameCallback + FrameInfo)、MessageQueue(通过 IdleHandler)和 Looper(通过自定义 MessageLogging)这些组合机制进行性能监测。深入理解这些机制后,开发者可以更有针对性地进行性能优化,形成系统化的优化思路。 另外,虽然图表是解释流程的有效方式,但本文将更多依赖 Perfetto 和 MethodTrace 工具的可视化输出。Perfetto 以时间线方式(从左到右)展示整个系统的运行状况,涵盖 CPU、SurfaceFlinger、SystemServer 和应用进程等关键组件的活动。使用 Perfetto 和 MethodTrace 可以直观展示关键执行流程,当您熟悉系统代码后,Perfetto 的输出能够直接映射到设备的实际运行状态。因此,本文除了引用少量网络图表外,主要依靠 Perfetto 来展示分析结果。 从 Perfetto 的角度来看 Choreographer 的工作流程 下图以滑动桌面为例子,我们先看一下从左到右滑动桌面的一个完整的预览图(App 进程),可以看到 Perfetto 中从左到右,每一个绿色的帧都表示一帧,表示最终我们可以手机上看到的画面 图中每一个灰色的条和白色的条宽度是一个 Vsync 的时间,对应当前设备的刷新率,如60Hz时为16.6ms,120Hz时为8.3ms 每一帧处理的流程:接收到 Vsync 信号回调-> UI Thread –> RenderThread –> SurfaceFlinger(图中未显示) UI Thread 和 RenderThread 就可以完成 App 一帧的渲染,在最新的 Android 15 中,通过 BlastBufferQueue 机制,渲染完的 Buffer 抛给 SurfaceFlinger 去合成,然后我们就可以在屏幕上看到这一帧了 可以看到桌面滑动的每一帧耗时都很短(Ui Thread 耗时 + RenderThread 耗时),但是由于 Vsync 的存在,每一帧都会等到 Vsync 才会去做处理 有了上面这个整体的概念,我们将 UI Thread 的每一帧放大来看,看看 Choreographer 的位置以及 Choreographer 是怎么组织每一帧的 Choreographer 的工作流程 Choreographer 初始化 初始化 FrameHandler,绑定 Looper 初始化 FrameDisplayEventReceiver,与 SurfaceFlinger 建立通信用于接收和请求 Vsync 初始化 CallBackQueues SurfaceFlinger 的 appEventThread 唤醒发送 Vsync,Choreographer 回调 FrameDisplayEventReceiver.onVsync,进入 Choreographer 的主处理函数 doFrame Choreographer.doFrame 计算掉帧逻辑 Choreographer.doFrame 处理 Choreographer 的第一个 callback:input Choreographer.doFrame 处理 Choreographer 的第二个 callback:animation Choreographer.doFrame 处理 Choreographer 的第三个 callback:insets animation Choreographer.doFrame 处理 Choreographer 的第四个 callback:traversal traversal-draw 中 UIThread 与 RenderThread 同步数据 Choreographer.doFrame 处理 Choreographer 的第五个 callback:commit RenderThread 处理绘制命令 在 Android S (Android 12) 及以上版本中,RenderThread 通过 BlastBufferQueue 向 SurfaceFlinger 提交绘制内容 BlastBufferQueue 由 App 端创建和管理 通过生产者 (BBQ_BufferQueue_Producer) 和消费者 (BufferQueue_Consumer) 模型工作 UI 线程不必等待 RenderThread 完成,可以更早地准备下一帧,减少了主线程阻塞 第一步初始化完成后,后续就会在步骤 2-10 之间循环 同时也附上这一帧所对应的 MethodTrace(这里预览一下即可,下面会有详细的大图) Choreographer 与 RenderThread 及 BlastBufferQueue 的交互 Android S (Android 12) 中,RenderThread 与 SurfaceFlinger 之间的交互发生了重要变化,其核心是 BlastBufferQueue 的引入。下面我们来看看这一机制是如何工作的。 BlastBufferQueue 工作原理 BlastBufferQueue 是一个专为 UI 渲染优化的 BufferQueue 变体,它替代了传统由 SurfaceFlinger 创建的 BufferQueue,转为由 App 端创建和管理,用于 App 与 SurfaceFlinger 之间的缓冲区管理。 更高效的缓冲区管理 在传统的 BufferQueue 中,App 需要通过 dequeueBuffer 获取一个可用的缓冲区,然后渲染内容,最后通过 queueBuffer 将缓冲区提交给 SurfaceFlinger。这个过程中,如果没有可用的缓冲区,App 需要等待,这会导致阻塞。 BlastBufferQueue 通过更智能的缓冲区管理,减少了这种等待,特别是在高刷新率设备上,效果更明显。 RenderThread 与 UI 线程的解耦 在 Android S 之前,UI 线程(主线程)需要等待 RenderThread 完成工作才能继续处理下一帧。而在新的架构中,UI 线程可以更早地完成自己的工作,将渲染任务交给 RenderThread 后即可准备下一帧的工作,不必等待当前帧完全渲染完成。 1 2 3 4 5 6 // 在 ViewRootImpl.performTraversals() 中 // 旧版本需要等待 draw 完成 performDraw(); // 这里会阻塞等待 RenderThread // BlastBufferQueue 引入后 scheduleTraversals(); // 可以更早地准备下一帧 创建机制 BlastBufferQueue 在 ViewRootImpl 的 relayoutWindow 过程中创建: 1 2 3 4 5 6 // 创建 BBQ 的示例代码 if (mBlastBufferQueue == null) { mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); } 与 Choreographer 的配合 Choreographer 仍然是协调这一切的核心。当 Vsync 信号到来时,Choreographer 会触发 doFrame,执行各种回调,其中 CALLBACK_TRAVERSAL 会触发 ViewRootImpl 的 performTraversals(),最终走到 draw 流程。 在 draw 流程中,通过 BlastBufferQueue,RenderThread 可以更独立地工作,而 UI 线程可以更早地返回处理其他任务。 渲染流程变化 以下是引入 BlastBufferQueue 后一个完整渲染帧的简化流程: SurfaceFlinger 的 appEventThread 发送 Vsync 信号 Choreographer.FrameDisplayEventReceiver.onVsync 接收信号 Choreographer.doFrame 开始处理帧 处理 Input、Animation 等回调 处理 Traversal 回调,触发 ViewRootImpl.performTraversals() 执行 measure 和 layout 执行 draw,这里 UI 线程将渲染命令提交给 RenderThread 新变化: UI 线程不必等待 RenderThread 完成,可以更早地准备下一帧 RenderThread 通过 BlastBufferQueue 处理渲染命令 渲染完成后,通过 BlastBufferQueue 将缓冲区提交给 SurfaceFlinger SurfaceFlinger 合成所有图层并呈现到屏幕 相比旧版本,主要变化在于步骤 8-10,BlastBufferQueue 作为客户端 (App) 创建的缓冲区管理组件,使 RenderThread 和 UI 线程解耦,减少了 UI 线程的等待时间,提高了整体效率,特别是在高刷新率设备上。 下面我们就从源码的角度,来看一下具体的实现 源码解析 下面从源码的角度来简单看一下,源码只摘抄了部分重要的逻辑,其他的逻辑则被剔除,另外 Native 部分与 SurfaceFlinger 交互的部分也没有列入,不是本文的重点,有兴趣的可以自己去跟一下。下面的源码基于Android 15的最新实现。 Choreographer 的初始化 Choreographer 的单例初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Thread local storage for the choreographer. private static final ThreadLocal sThreadInstance = new ThreadLocal() { @Override protected Choreographer initialValue() { // 获取当前线程的 Looper Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } // 构造 Choreographer 对象,使用 VSYNC_SOURCE_APP 作为Vsync源 Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); if (looper == Looper.getMainLooper()) { mMainInstance = choreographer; } return choreographer; } }; Choreographer 的构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; // 1. 初始化 FrameHandler mHandler = new FrameHandler(looper); // 2. 初始化 DisplayEventReceiver mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); //3. 初始化 CallbacksQueues mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i WindowManagerImpl.addView(View, LayoutParams) (android.view) -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) public ViewRootImpl(Context context, Display display) { ...... mChoreographer = Choreographer.getInstance(); ...... } FrameDisplayEventReceiver 简介 Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver , 有三个比较重要的方法 onVsync – Vsync 信号回调 run – 执行 doFrame scheduleVsync – 请求 Vsync 信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { ...... @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData eventData) { ...... mTimestampNanos = timestampNanos; mFrame = frame; // Android 15中新增的VsyncEventData传递更丰富的Vsync事件数据 mVsyncEventData = eventData; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame, mVsyncEventData); } public void scheduleVsync() { ...... nativeScheduleVsync(mReceiverPtr); ...... } } Choreographer 中 Vsync 的注册 从下面的函数调用栈可以看到,Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调,通知 UIThread 进行数据处理。 那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下 android/view/Choreographer.java 1 2 3 4 5 6 7 private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null; ...... } android/view/Choreographer.java 1 2 3 public FrameDisplayEventReceiver(Looper looper, int vsyncSource) { super(looper, vsyncSource); } android/view/DisplayEventReceiver.java 1 2 3 4 5 6 public DisplayEventReceiver(Looper looper, int vsyncSource) { ...... mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue, vsyncSource); } 简单来说,FrameDisplayEventReceiver 的初始化过程中,通过 BitTube (本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher,DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FrameDisplayEventReceiver.onVsync,触发开始一帧的绘制,如下图 DisplayEventReceiver 与 SurfaceFlinger 的通信细节 在 Android 系统中,DisplayEventReceiver 通过 JNI 调用 nativeInit 方法来建立与 SurfaceFlinger 服务的通信通道。这个过程涉及多个关键步骤: 创建 NativeDisplayEventReceiver 对象:在 Java 层调用 nativeInit 后,JNI 创建一个 NativeDisplayEventReceiver 实例,用于接收 Vsync 信号。 获取 IDisplayEventConnection:通过 ISurfaceComposer 接口获取 IDisplayEventConnection,这是一个 Binder 接口,用于与 SurfaceFlinger 服务通信。 建立 BitTube 连接:BitTube 是一个基于 socket pair 的通信机制,专为高频、小数据量的跨进程通信设计。它在 App 进程和 SurfaceFlinger 进程之间创建一个高效的通信通道。 文件描述符监听:通过 Looper 监听 BitTube 的文件描述符,当有 Vsync 信号到来时,Looper 会通知 DisplayEventDispatcher 处理事件。 整个通信流程如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 App 进程 SurfaceFlinger 进程 | | |-- 创建 DisplayEventReceiver -------------->| | | || | | || | | | handleEvent -------------| | | |-- 回调 Java 层 onVsync -------------------| | | 这种设计的优势在于避免了使用 Binder 传递高频的 Vsync 事件数据,通过直接的 socket 通信提高了性能和实时性,这对于保证流畅的 UI 渲染至关重要。同时,由于 BitTube 使用了文件描述符,可以无缝集成到 Android 的 Looper 机制中,使得整个系统能够以事件驱动的方式工作。 Choreographer 处理一帧的逻辑 Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中,从下图可以看到,FrameDisplayEventReceiver.onVsync post 了自己,其 run 方法直接调用了 doFrame 开始一帧的逻辑处理 android/view/Choreographer.java 1 2 3 4 5 6 7 8 9 10 11 12 13 public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData eventData) { ...... mTimestampNanos = timestampNanos; mFrame = frame; mVsyncEventData = eventData; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame, mVsyncEventData); } doFrame 函数主要做下面几件事 计算掉帧逻辑 记录帧绘制信息 执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT 计算掉帧逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void doFrame(long frameTimeNanos, int frame, VsyncEventData eventData) { final long startNanos; synchronized (mLock) { ...... long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } } ...... } ...... } Choreographer.doFrame 的掉帧检测比较简单,从下图可以看到,Vsync 信号到来的时候会标记一个 start_time ,执行 doFrame 的时候标记一个 end_time ,这两个时间差就是 Vsync 处理时延,也就是掉帧 我们以 Perfetto 的掉帧的实际情况来看掉帧的计算逻辑 这里需要注意的是,这种方法计算的掉帧,是前一帧的掉帧情况,而不是这一帧的掉帧情况,这个计算方法是有缺陷的,会导致有的掉帧没有被计算到 记录帧绘制信息 Choreographer 中 FrameInfo 来负责记录帧的绘制信息,doFrame 执行的时候,会把每一个关键节点的绘制时间记录下来,我们使用 dumpsys gfxinfo 就可以看到。当然 Choreographer 只是记录了一部分,剩余的部分在 hwui 那边来记录。 从 FrameInfo 这些标志就可以看出记录的内容,后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public @interface FrameInfoFlags {} public static final int FRAME_TIMELINE_VSYNC_ID = 1; // The intended vsync time, unadjusted by jitter public static final int INTENDED_VSYNC = 2; // Jitter-adjusted vsync time, this is what was used as input into the // animation & drawing system public static final int VSYNC = 3; // The id of the input event that caused the current frame public static final int INPUT_EVENT_ID = 4; // When input event handling started public static final int HANDLE_INPUT_START = 5; // When animation evaluations started public static final int ANIMATION_START = 6; // When ViewRootImpl#performTraversals() started public static final int PERFORM_TRAVERSALS_START = 7; // When View:draw() started public static final int DRAW_START = 8; // When the frame needs to be ready by public static final int FRAME_DEADLINE = 9; // When frame actually started. public static final int FRAME_START_TIME = 10; // Interval between two consecutive frames public static final int FRAME_INTERVAL = 11; doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间 1 2 3 4 5 6 7 8 9 10 11 12 13 void doFrame(long frameTimeNanos, int frame, VsyncEventData eventData) { ...... mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos); // 处理 CALLBACK_INPUT Callbacks mFrameInfo.markInputHandlingStart(); // 处理 CALLBACK_ANIMATION Callbacks mFrameInfo.markAnimationsStart(); // 处理 CALLBACK_INSETS_ANIMATION Callbacks // 处理 CALLBACK_TRAVERSAL Callbacks mFrameInfo.markPerformTraversalsStart(); // 处理 CALLBACK_COMMIT Callbacks ...... } 执行 Callbacks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void doFrame(long frameTimeNanos, int frame, VsyncEventData eventData) { ...... // 处理 CALLBACK_INPUT Callbacks doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); // 处理 CALLBACK_ANIMATION Callbacks doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); // 处理 CALLBACK_INSETS_ANIMATION Callbacks doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos); // 处理 CALLBACK_TRAVERSAL Callbacks doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); // 处理 CALLBACK_COMMIT Callbacks doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); ...... } Input 回调调用栈 input callback 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable android/view/ViewRootImpl.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 final class ConsumeBatchedInputRunnable implements Runnable { @Override public void run() { doConsumeBatchedInput(mChoreographer.getFrameTimeNanos()); } } void doConsumeBatchedInput(long frameTimeNanos) { if (mConsumeBatchedInputScheduled) { mConsumeBatchedInputScheduled = false; if (mInputEventReceiver != null) { if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) { scheduleConsumeBatchedInput(); } } doProcessInputEvents(); } } Input 时间经过处理,最终会传给 DecorView 的 dispatchTouchEvent,这就到了我们熟悉的 Input 事件分发 Animation 回调调用栈 一般我们接触的多的是调用 View.postOnAnimation 的时候,会使用到 CALLBACK_ANIMATION 1 2 3 4 5 6 7 8 9 10 11 public void postOnAnimation(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null); } else { // Postpone the runnable until we know // on which thread it needs to run. getRunQueue().post(action); } } 那么一般是什么时候回调用到 View.postOnAnimation 呢,我截取了一张图,大家可以自己去看一下,接触最多的应该是 startScroll,Fling 这种操作 其调用栈根据其 post 的内容,下面是桌面滑动松手之后的 fling 动画。 另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION 1 2 3 4 5 6 7 8 public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); } Traversal 调用栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //为了提高优先级,先 postSyncBarrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } final class TraversalRunnable implements Runnable { @Override public void run() { // 真正开始执行 measure、layout、draw doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 这里把 SyncBarrier remove mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // 真正开始 performTraversals(); } } private void performTraversals() { // measure 操作 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } // layout 操作 if (didLayout) { performLayout(lp, mWidth, mHeight); } // draw 操作 if (!cancelDraw && !newSurface) { performDraw(); } } doTraversal 的 TraceView 示例 下一帧的 Vsync 请求 由于动画、滑动、Fling 这些操作的存在,我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑,在连续的操作,比如动画、滑动、Fling 这些情况下,每一帧的 doFrame 的时候,都会根据情况触发下一个 Vsync 的申请,这样我们就可以获得连续的 Vsync 信号。 看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求) 我们比较熟悉的 invalidate 和 requestLayout 都会触发 Vsync 信号请求 我们下面以 Animation 为例,看看 Animation 是如何驱动下一个 Vsync ,来持续更新画面的 ObjectAnimator 动画驱动逻辑 android/animation/ObjectAnimator.java 1 2 3 public void start() { super.start(); } android/animation/ValueAnimator.java 1 2 3 4 5 6 7 8 9 private void start(boolean playBackwards) { ...... addAnimationCallback(0); // 动画 start 的时候添加 Animation Callback ...... } private void addAnimationCallback(long delay) { ...... getAnimationHandler().addAnimationFrameCallback(this, delay); } android/animation/AnimationHandler.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) { if (mAnimationCallbacks.size() == 0) { // post FrameCallback getProvider().postFrameCallback(mFrameCallback); } ...... } // 这里的 mFrameCallback 回调 doFrame,里面 post了自己 private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); if (mAnimationCallbacks.size() > 0) { // post 自己 getProvider().postFrameCallback(this); } } }; 调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ,这里就会触发 Choreographer 的 Vsync 请求逻辑 android/animation/AnimationHandler.java 1 2 3 public void postFrameCallback(Choreographer.FrameCallback callback) { mChoreographer.postFrameCallback(callback); } android/view/Choreographer.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime scheduleVsyncLocked-> mDisplayEventReceiver.scheduleVsync ->nativeScheduleVsync scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } } 通过上面的 Animation.start 设置,利用了 Choreographer.FrameCallback 接口,每一帧都去请求下一个 Vsync 动画过程中一帧的 TraceView 示例 源码小结 Choreographer 采用线程单例模式设计,与Looper强耦合。每个线程只能拥有一个Choreographer实例,且必须绑定一个有效的Looper对象,因为其内部Handler依赖Looper进行消息分发。在应用中,通常绑定主线程的Looper以确保UI操作的线程安全性。 DisplayEventReceiver 是一个抽象基类,其JNI实现创建IDisplayEventConnection对象作为Vsync信号的监听器。通过此机制,SurfaceFlinger的AppEventThread发出的Vsync中断信号能够被精确传递到Choreographer实例。当Vsync信号到达时,系统回调DisplayEventReceiver的onVsync方法,触发渲染流程。 DisplayEventReceiver 提供scheduleVsync方法用于请求Vsync信号。应用程序需要更新UI时,先通过此方法申请下一个Vsync中断,然后在onVsync回调中执行实际的绘制逻辑,确保渲染与屏幕刷新同步。 Choreographer 定义了FrameCallback接口,其doFrame方法在每次Vsync到来时被调用。这一设计对Android动画系统具有重要意义,使动画能够与屏幕刷新率精确同步,相比早期自行计时的实现,提供了更加流畅、省电的动画体验。 Choreographer 核心功能是接收Vsync信号并触发通过postCallback注册的回调函数。框架定义了五种类型的回调,按照执行优先级排序: CALLBACK_INPUT:处理输入事件,如触摸、按键等交互 CALLBACK_ANIMATION:处理各类动画计算与更新 CALLBACK_INSETS_ANIMATION:处理系统插入动画,如软键盘、状态栏动画等 CALLBACK_TRAVERSAL:处理视图树的测量、布局与绘制 CALLBACK_COMMIT:执行收尾工作,包括组件内存回收(onTrimMemory)和性能监测 ListView 和 RecyclerView 的Item复用机制(ViewHolder模式)在框架层面上的具体实现会涉及到CALLBACK_INPUT和CALLBACK_ANIMATION阶段。在滑动或快速滚动时,Item的初始化、测量与绘制可能在Input回调中触发(如直接响应触摸事件),也可能在Animation回调中执行(如惯性滑动或自动滚动)。RecyclerView通过更高效的复用机制和预取(Prefetch)策略,能够在这两个阶段更智能地准备ViewHolder,减少主线程阻塞,尤其在高刷新率设备上表现更为出色。 CALLBACK_INPUT 和 CALLBACK_ANIMATION 在执行过程中会修改View的各种属性(如位置、透明度、变换矩阵等),因此必须先于CALLBACK_TRAVERSAL执行,以确保所有状态更新都能在当前帧的测量、布局与绘制过程中被正确应用。这种严格的执行顺序保证了Android UI渲染的一致性和可预测性。 APM 与 Choreographer 由于 Choreographer 的位置,许多性能监控的手段都是利用 Choreographer 来做的,除了自带的掉帧计算,Choreographer 提供的 FrameCallback 和 FrameInfo 都给 App 暴露了接口,让 App 开发者可以通过这些方法监控自身 App 的性能,其中常用的方法如下: 利用 FrameCallback 的 doFrame 回调 利用 FrameInfo 进行监控 使用 :adb shell dumpsys gfxinfo framestats 示例 :adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats 利用 SurfaceFlinger 进行监控 使用 :adb shell dumpsys SurfaceFlinger –latency 示例 :adb shell dumpsys SurfaceFlinger –latency com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher#0 利用 SurfaceFlinger PageFlip 机制进行监控 使用 : adb service call SurfaceFlinger 1013 备注:需要系统权限 Choreographer 自身的掉帧计算逻辑 BlockCanary 基于 Looper 的性能监控 新增:Perfetto 工具的强大监控能力 在Android 15中,Perfetto成为主要的性能分析工具,替代了早期的Systrace Perfetto可以捕获更详细的系统性能数据,包括Choreographer的工作细节 使用Perfetto UI可以可视化分析帧渲染过程 利用 FrameCallback 的 doFrame 回调 FrameCallback 接口 1 2 3 public interface FrameCallback { public void doFrame(long frameTimeNanos); } 接口使用 1 Choreographer.getInstance().postFrameCallback(youOwnFrameCallback ); 接口处理 1 2 3 4 5 public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) { ...... postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis); } TinyDancer 就是使用了这个方法来计算 FPS (https://github.com/friendlyrobotnyc/TinyDancer) 利用 Perfetto 进行高级监控 Perfetto 是 Android 新一代的系统跟踪工具,在 Android 15 中成为默认的性能分析解决方案。它提供了比 Systrace 更强大的功能: 更全面的性能数据采集 Perfetto 可以同时收集 CPU、内存、图形渲染、系统服务等多维度的数据 更低的开销 采用高效的跟踪引擎,对系统性能影响更小 更好的 Choreographer 追踪 可以详细跟踪 Choreographer 的工作过程,包括: Vsync 信号的接收和处理 doFrame 方法的执行细节 各类回调的执行时间 UI 线程和 RenderThread 的协作过程 追踪 BlastBufferQueue 能够跟踪新引入的 BlastBufferQueue 的工作过程,帮助开发者理解缓冲区管理机制 使用 Perfetto 跟踪 Choreographer 1 2 3 4 5 6 7 # 开始记录 Perfetto trace adb shell perfetto --txt -c /data/misc/perfetto-configs/chrome-trace.config -o /data/misc/perfetto-traces/trace.perfetto-trace # 完成后获取 trace 文件 adb pull /data/misc/perfetto-traces/trace.perfetto-trace # 在 Perfetto UI 中分析(https://ui.perfetto.dev/) 在 Perfetto UI 中,可以找到名为 “Choreographer#doFrame” 的事件,它展示了每一帧的处理时间和细节。还可以查看 UI 线程和 RenderThread 之间的协作关系,以及与 SurfaceFlinger 的交互。 利用 FrameInfo 进行监控 adb shell dumpsys gfxinfo framestats 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Window: StatusBar Stats since: 17990256398ns Total frames rendered: 1562 Janky frames: 361 (23.11%) 50th percentile: 6ms 90th percentile: 23ms 95th percentile: 36ms 99th percentile: 101ms Number Missed Vsync: 33 Number High input latency: 683 Number Slow UI thread: 273 Number Slow bitmap uploads: 8 Number Slow issue draw commands: 18 Number Frame deadline missed: 287 HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0 ---PROFILEDATA--- Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration, 0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000, 0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000, 0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000, 0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000, 利用 SurfaceFlinger 进行监控 命令解释: 数据的单位是纳秒,时间是以开机时间为起始点 每一次的命令都会得到128行的帧相关的数据 数据: 第一行数据,表示刷新的时间间隔refresh_period 第1列:这一部分的数据表示应用程序绘制图像的时间点 第2列:在SF(软件)将帧提交给H/W(硬件)绘制之前的垂直同步时间,也就是每帧绘制完提交到硬件的时间戳,该列就是垂直同步的时间戳 第3列:在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。 掉帧 jank 计算 每一行都可以通过下面的公式得到一个值,该值是一个标准,我们称为jankflag,如果当前行的jankflag与上一行的jankflag发生改变,那么就叫掉帧 ceil((C - A) / refresh-period) 利用 SurfaceFlinger PageFlip 机制进行监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); mFlinger.transact(1013, data, reply, 0); final int pageFlipCount = reply.readInt(); final long now = System.nanoTime(); final int frames = pageFlipCount - mLastPageFlipCount; final long duration = now - mLastUpdateTime; mFps = (float) (frames * 1e9 / duration); mLastPageFlipCount = pageFlipCount; mLastUpdateTime = now; reply.recycle(); data.recycle(); Choreographer 自身的掉帧计算逻辑 SKIPPED_FRAME_WARNING_LIMIT 默认为30 , 由 debug.choreographer.skipwarning 这个属性控制 1 2 3 4 5 6 7 if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } } BlockCanary Blockcanary 做性能监控使用的是 Looper 的消息机制,通过对 MessageQueue 中每一个 Message 的前后进行记录,打到监控性能的目的 android/os/Looper.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void loop() { ... for (;;) { ... // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } ... } } MessageQueue 与 Choreographer 在 Android 消息机制中,异步消息具有特殊的处理优先级。系统可以通过 enqueueBarrier 方法向消息队列插入一个屏障(Barrier),使得该屏障之后的所有同步消息暂时无法被执行,直到调用 removeBarrier 方法移除屏障。而被标记为异步的消息则不受屏障影响,可以正常处理。 消息默认为同步类型,只有通过 Message 的 setAsynchronous 方法(该方法为隐藏 API)才能将消息设置为异步。在初始化 Handler 时,可以通过特定参数指定该 Handler 发送的所有消息均为异步类型,此时 Handler 的 enqueueMessage 方法会自动调用 Message 的 setAsynchronous 方法。 异步消息的核心价值在于能够绕过消息屏障继续执行,如果没有设置屏障,异步消息与同步消息的处理方式完全相同。通过 removeSyncBarrier 方法可以移除之前设置的屏障。 SyncBarrier 在 Choreographer 中使用的一个示例 scheduleTraversals 的时候 postSyncBarrier 1 2 3 4 5 6 7 8 9 void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //为了提高优先级,先 postSyncBarrier mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } doTraversal 的时候 removeSyncBarrier 1 2 3 4 5 6 7 8 9 void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; // 这里把 SyncBarrier remove mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); // 真正开始 performTraversals(); } } Choreographer post Message 的时候,会把这些消息设为 Asynchronous ,这样 Choreographer 中的这些 Message 的优先级就会比较高, 1 2 3 4 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); 厂商优化 系统厂商由于可以直接修改源码,也利用这方面的便利,做一些功能和优化,不过由于保密的问题,代码就不直接放上来了,我可以大概说一下思路,感兴趣的可以私下讨论 移动事件优化 Choreographer 本身是没有 input 消息的, 不过修改源码之后,input 消息可以直接给到 Choreographer 这里, 有了这些 Input 消息,Choreographer 就可以做一些事情,比如说提前响应,不去等 Vsync 后台动画优化 当 Android 应用退到后台时,如果未被系统终止,其仍可能继续执行各类操作。在某些情况下,应用会持续调用 Choreographer 中的 Animation Callback,即使这些动画对用户不可见,这些 Callback 的执行完全无意义,却会对 CPU 资源造成较高的占用。 因此,系统厂商在 Choreographer 中会针对这种情况做优化,通过一系列策略限制不符合条件的后台应用继续执行无意义的动画回调,有效降低系统资源占用。 帧绘制优化 和移动事件优化一样,由于有了 Input 事件的信息,在某些场景下我们可以通知 SurfaceFlinger 不用去等待 Vsync 直接做合成操作 应用启动优化 我们前面说,主线程的所有操作都是给予 Message 的 ,如果某个操作,非重要的 Message 被排列到了队列后面,那么对这个操作产生影响;而通过重新排列 MessageQueue,在应用启动的时候,把启动相关的重要的启动 Message 放到队列前面,来起到加快启动速度的作用 高帧率优化 现代 Android 设备上的高刷新率(120 Hz)将 Vsync 间隔从 16.6 ms 缩短至 8.3 ms,这带来了巨大的性能和功耗挑战。如何在一帧内完成渲染的必要操作,是手机厂商必须要思考和优化的地方: 超级 App 的性能表现以及优化 游戏高帧率合作 120 fps、90 fps 和 60 fps 相互切换的逻辑 参考资料 https://www.jianshu.com/p/304f56f5d486 http://gityuan.com/2017/02/25/choreographer/ https://developer.android.com/reference/android/view/Choreographer https://www.jishuwen.com/d/2Vcc https://juejin.im/entry/5c8772eee51d456cda2e8099 Android 开发高手课 Perfetto - System profiling, app tracing, and trace analysis 使用 Perfetto 分析 UI 性能 BufferQueue and Gralloc Android 15 图形渲染优化 本文知乎地址 由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面 知乎 - Android 基于 Choreographer 的渲染机制详解 - Perfetto 版 掘金 - Android 基于 Choreographer 的渲染机制详解 - Perfetto 版 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android 性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 记录笔者修复的 userfaultfd_move 的一个内核 bug: 本文记录了作者修复 userfaultfd_move 内核中的一个 bug,该 bug 源于在处理 swap entry 时未考虑到 swapcache 中的 folio,导致 mapping 和 index 未对齐,进而引发内核故障。作者提出的修复方案是在处理 swap entry 时检查 swapcache 并更新其 mapping 和 index,此修复已合入 v6.14-rc6 及其他稳定版本。 系统调用 sync、fsync 和 fdatasync 的区别: 本文探讨了 UNIX 系统调用 sync、fsync 和 fdatasync 的区别。sync 函数将所有修改过的缓冲区排入写队列但不等待实际写入完成;fsync 函数确保特定文件的修改内容同步到磁盘并等待操作完成,适用于需要高一致性的场景;而 fdatasync 仅同步文件的数据部分,减少不必要的元数据同步,从而提高效率。特别是在日志文件写入中,通过固定文件大小使用 fdatasync 可以显著提升性能。 得物 Android Crash 治理实践: 本文详细介绍了得物团队在治理 Android 系统崩溃问题中的实践经验,包括 DNS 解析崩溃、MediaCodec 状态异常崩溃、多线程环境崩溃及小米 Android15 焦点处理空指针崩溃等问题的背景、分析及解决过程。通过这些治理措施,得物显著降低了崩溃率,并为类似问题提供了技术参考。 Flutter 小技巧之通过 MediaQuery 优化 App 性能: Flutter 小技巧之通过 MediaQuery 优化 App 性能,文章介绍了如何合理使用 MediaQuery 来优化 Flutter 应用的性能,特别是在多页面场景下减少不必要的 rebuild 开销。通过将 MediaQuery 的使用位置调整到 Scaffold 内部,或使用 MediaQuery.propertyOf 系列方法,可以有效降低性能损耗。此外,文章还提到从 Flutter 3.10 开始,使用 View.of(context) 替代传统的 WidgetsBinding.instance.window 以适应多 FlutterView 场景。 Koin: 由开发者打造,为开发者服务: Kotzilla 宣布其官方微信公众号上线,旨在为中国开发者提供 Koin 和 Kotlin 的中文学习资源。Koin 是一个轻量级的依赖注入框架,已被全球 35 万款移动应用采用,具备 DSL 声明式注入、模块化组织、作用域管理等核心功能。Kotzilla 由 Koin 创始人创立,专注于 Kotlin 开发工具,并成为 Kotlin 基金会的银级成员。该公司推出的 Kotzilla 平台支持实时性能监测和依赖分析,全面兼容 Kotlin 多平台项目,并提供 Koin 的长期支持版本 (LTS),确保技术升级的稳定性。 Weaver - 基于安全硬件的锁屏密码方案: Android 平台上,GateKeeper+KeyMint 是一种常见的密码解锁方案,基于仅用户知道的密码保护用户的数据。Weaver 是一种基于 Secure Element 等防篡改硬件,旨在增强密码保护用户数据的解决方案,提供了 device-off security threat model 和 brute force password guessing 两个关键特性。device-off security threat model:当设备关闭电源时,用户身份验证和磁盘加密依赖的 secret 存储在安全芯片中,必须使用用户 LSKF (pin/pattern/password)才能获取到该值。brute force password guessing:通过安全芯片内部的安全计时器防止攻击者通过暴力破解的手段推演出用户密码。 Android JankStats 实现解析: JankStats 是安卓 JetPack 里新出的一个专门用来检测帧卡顿的库。并且支持各个安卓版本。我们来分析一下他的实现。 Android Weekly Issue #666: Android Weekly Issue #666 Android 启动框架 EasyLaunch: 这是一个 Android 的启动开源框架, 用于在启动过程中将任务并行, 达到优化启动速度的目的. 彻底搞清 Flow+MVVM+Retrofit+OKHTTP 框架: 本文主要介绍了 Flow 的基本使用方法,结合了 Flow+MVVM+Retrofit+OKHTTP 框架,能够带你理解 Flow 在 ViewModel,Repository,切换线程等方面的细节 。 深入探索 Android Bitmap:从原理到实战: 从功能角度来讲,Bitmap 在 Android 开发中就像是一个 “图像容器”,承载着图像的像素信息,凭借它,开发者能够在应用中轻松实现加载、显示和处理图像的操作。通过 Bitmap 类,开发者可以创建图像对象,在屏幕上展示或者对其进行更深入的处理,诸如缩放、裁剪、旋转等常见的图像操作,都可以借助 Bitmap 来完成。例如,在一个图片编辑应用中,用户可以通过 Bitmap 对图片进行裁剪,选择自己喜欢的部分进行保留;也可以对图片进行旋转,调整到合适的角度;还能对图片进行缩放,使其适应不同的屏幕尺寸。在一个社交应用中,用户上传的照片可能需要进行压缩和裁剪,以适应服务器的存储和传输要求,这时候就可以使用 Bitmap 来实现这些操作。 Now in Android #114: 谷歌 I/O 2025 将于 5 月 20-21 日举办,重点展示 Android、AI 等技术更新;Android Studio 庆祝十周年,回顾其发展历程;Android 16 Beta 推出适配大屏幕的自适应应用、进度通知等新特性;Firebase 集成生成式 AI 模型(如 Gemini 和 Imagen),支持 AI 驱动的用户体验;Google Play 推出“已验证”徽章提升应用安全性;Wear OS 推出儿童应用体验;TrustedTime API 提供可靠时间戳,AndroidX 也发布多项更新。 The Third Beta of Android 16: Android 16 Beta 3 已达到平台稳定性,开发者可立即将面向 Android 16 的应用推送至 Play 商店。本次更新包括新安全和辅助功能,如 Auracast 广播音频支持、轮廓文本增强对比度、局域网保护测试功能,以及多项开发者需关注的行为变化。 Multimodal image attachment is now available for Gemini in Android Studio: Android Studio 的 Gemini 功能现已支持多模态图像附件,开发者可以直接将图片附加到提示中,提升团队协作和 UI 开发工作流程。此功能支持将简单线框图或高保真设计转换为 Jetpack Compose 代码,并可用于快速原型制作、UI 调试以及图表文档化。 Android 开机动画修改指南: 本文详细介绍了 Android 开机动画的修改指南,包括开机动画文件路径、文件结构、加载和播放动画帧、动态颜色特性及调试技巧等内容。文中对相关代码进行了深入解读,并提供了具体的操作步骤和注意事项。 Unit Testing Lifecycle and State in ViewModels: 文章介绍了 ViewModelScenario,这是 Lifecycle 2.9.0-alpha01 引入的工具,用于简化 ViewModel 的单元测试。它解决了直接通过构造函数测试 ViewModel 的局限性,支持触发 ViewModelStore.clear() / ViewModel.onCleared() 以及模拟实例状态的保存与恢复(包括序列化)。通过 ViewModelScenario.recreate(),可以验证 SavedStateHandle 数据的正确保存与恢复,同时支持 Kotlin Multiplatform (KMP)。开发者可通过新 API 更高效地测试 ViewModel 的生命周期和状态管理。 Building excellent games with better graphics and performance: 安卓开发者博客宣布 Vulkan 成为安卓官方图形 API,支持光线追踪和多线程以提升游戏画面,同时通过 ANGLE 兼容 OpenGL;安卓动态性能框架(ADPF)升级,与 MediaTek 合作优化性能,延长游戏时长并提高稳定性;Play Console 新增低内存杀手(LMK)功能,帮助开发者监控内存问题;此外,还推出 PC 游戏移植到移动端的试点计划,简化开发与发布流程,推动更多 PC 游戏加入安卓生态。 鸿蒙(HarmonyOS)性能优化实战-SmartPerf-Host 分析应用性能: SmartPerf-Host 是一款深入挖掘数据、细粒度展示数据的性能功耗调优工具,可采集 CPU 调度、频点、进程线程时间片、堆内存、帧率等数据,采集的数据通过泳道图清晰地呈现给开发者,同时通过 GUI 以可视化的方式进行分析。该工具当前为开发者提供了五个分析模板,分别是帧率分析、CPU/线程调度分析、应用启动分析、TaskPool 分析、动效分析。关于工具使用的更多内容可查看 SmartPerf-Host 调优工具使用指导。本文提供一些性能分析示例,介绍如何使用帧率分析和应用启动分析两个模板采集数据、分析数据,从而发现性能优化点。 Android 子线程更新 View 的方法原理: 本文详细解析了 Android 子线程更新 View 的原理,探讨了 View 更新必须在 UI 线程进行的原因,并提供了多种子线程更新 View 的方法,包括基于独立渲染体系以及基于 ViewRootImpl 渲染体系的实现方式。 [原创]某加固 onCreate 的 vmp 分析: 本文分析了某加固应用的 onCreate 函数实现,利用拦截 JNI 注册函数和 Stalker 跟踪寄存器 x8 的变化,定位关键函数及其调用流程。通过 IDA 动态调试,找到 VMP 解释器和加密 opcode,并针对反调试机制(如 SIGTRAP 信号)提出对抗策略,最终揭示加固逻辑和解密过程。 Jetpack WindowManager 1.4 is stable: 深入理解 Linux 内存优化:如何使用屏障提升性能: 本文深入探讨了 Linux 内存优化中的内存屏障机制,详细分析了其原理、类型、功能以及在多核处理器、设备驱动开发、RCU 机制中的应用,并提供了使用注意事项与性能优化建议。 技术简报 2025 第三期: 本期收集一下各种有意思的软件图,用来看看别人如何图形化展示一个东西。一起体会软件架构的结构美感 鸿蒙(HarmonyOS)性能优化实战-启动分析工具 Launch Profiler: DevEco Studio 内置 Profiler 分析调优工具。其中 Launch 主要用于分析应用或服务的启动耗时,分析启动周期各阶段的耗时情况、核心线程的运行情况等,协助开发者识别启动缓慢的原因。此外,Launch 任务窗口还集成了 Time、CPU、Frame 场景分析任务的功能,方便开发者在分析启动耗时的过程中同步对比同一时段的其他资源占用情况。 simpleperf 的使用技巧: Simpleperf 工具不仅在性能分析领域具有重要作用,还在日常调试中展现了广泛的用途,例如跟踪应用被杀或退出、监控库函数调用、分析内核调用函数以及跟踪 Binder 调用过程等,为开发者提供了强大的调试支持和灵活的应用场景。 Kotlin 的协程,真能提升编程效率么?: Kotlin 协程能通过其结构化并发特性优化代码,提升编程效率。文章主要讨论协程在 Android 开发中的两大应用场景:消灭对称式 API 和回调式 API。通过协程的挂起函数和生命周期管理,开发者可以简化代码逻辑、减少错误风险并提升代码质量。 谈“一切皆文件”的哲学: 这篇文章探讨了 Linux 系统中“一切皆文件”的哲学理念,详细分析了文件句柄(File Descriptor, FD)的抽象本质、分类及其在系统资源管理中的应用。文章还介绍了实现新文件类型的步骤,并解释了文件句柄在多进程间共享的机制。 杂记 我喜欢 Notion 公司的两个信条: 这篇文章通过对 Notion 创始人 Ivan Zhao 的访谈,详细探讨了他的创业经历、产品理念、公司文化以及对工具与人类潜能关系的深刻思考。文章还揭示了 Notion 从早期挣扎到如今成功的关键转折点,以及 Ivan 对于软件设计美学和技术哲学的独特见解。 科技爱好者周刊#341:低代码编程,恐怕不会成功: 这里记录每周值得分享的科技内容,周五发布。 专栏:管理不是派个活 - 管理和管理不一样。: 管理工作分为专业管理和组织管理,两者需要完全不同的技巧,专业管理注重提升组织能力,组织管理则关注构建高效协作环境,而优秀的管理能带来规模效应,推动团队指数级成长。 AI AI 代理可观测性 - 演变标准与最佳实践: 本文探讨了 AI 代理的可观测性及其重要性,特别是在 2025 年 AI 代理成为人工智能领域的重要趋势时。文章详细介绍了 AI 代理的定义、现状、标准化语义约定的建立以及插桩方法的优缺点,同时展望了 AI 代理可观测性的未来发展方向。 【社区说-回顾】全方位 360° 讲解 Gemini 2.0,我家的猫真会后空翻: 为了帮助大家更好地了解和掌握 Gemini2.0 的使用技巧,GDG 社区在 2.27 晚上 7 点在线上举办了「社区说」分享活动,邀请多位资深的 AI 开发专家,从不同角度为大家解读 Gemini 2.0 的最新变化和开发技巧。本文将带领大家详细回顾此次活动特约嘉宾们的精彩分享。 浅谈 Agent、MCP、OpenAI Responses API: 浅谈 Agent、MCP、OpenAI Responses API,文章介绍了 AI Agent、MCP(模型上下文协议)和 OpenAI 最新推出的 Responses API 的功能及应用场景,分析了相关技术的演进与挑战,并展示了开发者如何利用这些工具构建智能体以完成复杂任务。 OpenAI Agents SDK: OpenAI Agents SDK 是一个轻量级且易用的软件开发工具包,用于构建基于代理的 AI 应用。它提供了一些简单的原语,如代理(Agents)、交接(Handoffs)和防护措施(Guardrails),并支持复杂的工具和代理关系表达。该 SDK 具有内置的追踪功能,便于可视化、调试和优化工作流,同时支持模型微调。主要设计原则是功能足够强大但学习曲线平缓,开箱即用且高度可定制。 Manus 发布一天后迅速出现 OpenManus、OWL 等复刻项目,怎么做到的?: Manus 是一款通用的 AI 智能体产品,从用户示例中展示了卓越的用户体验,整体交互给人感觉很不错。但从技术方案上来说,Manus 使用了大量的业内共识的核心基础技术。在那天晚饭过程中,我们讨论了 Manus 的产品形态和技术路线,然后我们整体使用了三个小时左右的时间,开发出来 OpenManus 这个开源项目。 关于 AI 的一些实践和思考(2): 这两天在高强度地进行编程,用 cursor 或者直接在聊天窗口跟不同的 AI 模型对话,解决了同事在业务上的一个问题,将大部分时间用自动化的方式解决,提升了 30 倍的效率,非常有成就感,我也在编程中获得了一些洞见和小启发: Gemma 3 发布:巅峰性能,单 GPU/TPU 即可运行: 我们在全球推出 Gemma 3,一系列基于 Gemini 2.0 模型同源技术打造的轻量级、先进开放模型。它们是 Google 迄今为止在性能、可移植性和社会责任方面表现最出色的开放模型。Gemma 3 专为设备端高效运行而设计,让手机、笔记本或工作站都能轻松驾驭,助力开发者随心打造 AI 应用。Gemma 3 提供 1B、4B、12B 和 27B 等多种规格,适合不同的硬件和性能需求。 招聘信息 联系方式:13928007488 待遇:50-100 年包,优秀的可以谈股票 Linux 内核 1 个 –T3/T4 珠海/广州/西安/深圳 【岗位职责】 负责 Kernel 体系架构、调度、内存管理、文件系统、电源等内核功能问题解决和需求开发; 主导各类底层稳定性问题的攻关,如:定屏、死机、系统奔溃、异常重启等; 【任职要求】 计算机软件、通信、电子相关专业本科以上学历,5 年以上 Linux 内核/驱动相关经验; 熟悉 Linux 内核和驱动开发,对调度/内存/存储 IO/电源/中断/时钟/同步/驱动模型等多个子系统有深刻理解者优先; 熟悉常见的内核分析工具和方法,具备 2 年以上 Android/Linux 平台性能/功耗/稳定性等疑难问题分析处理经验者优先; 熟悉 C/C++, Arm assembly, operating system; 良好的沟通能力和团队合作精神 Linux 系统 1 个:T3/T4 珠海/广州/西安 【岗位职责】 负责统筹 Linux 方案框架设计和集成调试,负责系统级应用的框架设计和子模块功能定义; 负责各子系统间(音视频/input/显示等)对接和调优,承担系统级问题的排查和解决; 承担 Linux 方案技术经理职责,统筹方案规格的落地和技术攻关; 【任职要求】 拥有 5 年以上 Linux 开发经验,承担 Linux 方案架构师或技术经理或相当职位 2 年以上; 至少熟悉 Linux 类 OS(Debian/Ubuntu/Fedora/OpenHarmoy)中的一种,有参与系统应用开发经验者优先; 了解 Socket、pipe、binder 等进程通信机制,有参与开发经验者优先; 熟悉多个 Linux 子系统(显示/编解码/UI/camera/input/休眠/功耗等),有上述 3 个以上经验者优先; 具备良好的团队合作和沟通能力,拥有一定的抗压能力应对突发问题。 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 Android× 鸿蒙 ×AI 技术周刊 - 第 1 期: 周刊的主题为:Android、鸿蒙、AI 相关的科普、技术文、开源项目、近期热点等。 播放器系列 1——总概述: 这篇文章主要讨论了一个播放器项目的核心架构及其实现细节,包括文件读取、解复用、音视频解码、渲染、音效处理以及音视频同步等模块的实现方法,涉及到的技术工具包括 FFmpeg、SDL、QML 等。 移动 OS 设计之性能设计 1 - 应用程序与 OS 之间的边界思考: 移动操作系统设计之性能设计:应用程序与操作系统的边界探讨 记 Android12 上一个原生 BUG 引起的系统重启: 文章分析了 Android 12 系统中一个原生 BUG,该 BUG 在特定的手势操作下会导致系统重启。通过日志分析、代码阅读和复现问题,作者详细探讨了该问题的原因,并指出是因为事件处理过程中 Java 层和 Native 层的异常处理逻辑不一致,导致同一事件触发了两次 sendFinishedSignal。最后提到谷歌已经修复了该问题。 https://androidweekly.net/issues/issue-664: Android Weekly Issue #664 深入聊聊 Flutter 里最接近官方的热更新方案:Shorebird: 本文深入探讨了 Flutter 的热更新方案 Shorebird。介绍了其作为 Flutter 前创始人的商业项目,是最接近 RN code push 的存在。阐述了其实现方式,包括对 Flutter Engine 和 Dart VM 的“魔改”,通过下发“二进制”patch 文件实现热更新,Android 和 iOS 平台的不同表现及原理,还提及了局限和优势,如不能更新 Native 代码、不支持跨版本等,但其版本跟进快,退出机制几乎无损。 揭开 Android View 的神秘面纱:深入探索工作原理: 这篇文章深入探讨了 Android View 的工作原理,包括架构基础、测量、布局、绘制、事件分发机制等环节,还介绍了自定义 View 的常见类型和实战应用,并对其工作原理进行了总结回顾与未来展望,提及新技术如 Kotlin、Jetpack Compose 对 View 开发的影响及发展趋势。 Comprehensive Guide: Get User Location on Android with Jetpack Compose: 通过本文,作者详细介绍了如何在 Android 中使用 Google 的融合位置提供程序(Fused Location Provider)与 Jetpack Compose 来获取用户位置。文章涵盖了融合位置提供程序的优势、权限处理、位置请求参数设置,以及通过回调接收位置更新的完整实现,并提供了一个示例应用程序的代码链接。 Android build structure: Android 项目的构建结构和文件说明,包括最佳实践和文件用途。 揭开 Flutter Slider 中 Shapes 的神秘面纱: Slider 是 Flutter 中使用非常多的一个组件,通常设计师都会对 Slider 做很多的自定义设计,在 Android 中,我们其实是很难通过配置 xml 来改变 Slider 的外观的,而在 Flutter 中,我们可以很方便的组合整个实现,当然,前提是你需要对 Slider 的整体概念有个清晰的认识。 Level Up Your App: Why Android Widgets are a Game-Changer: Android 开发者博客最新文章讨论了在 Android 应用中集成小部件(Widgets)的重要性以及其对应用成功的影响。 257 - Future of AndroidDev in an AI world with Vinay Gaba: Join us as we talk with Vinay Gaba, Android GDE and leading voice in Android development, about the future of the field. Vinay shares insights from interviews with top Android devs on their three-year predictions, and offers his own perspective. We cover AI’s impact, evolving development roles, and crucial future skills. 腾讯 TDF 即将开源 Kuikly 跨端框架,Kotlin 支持全平台: 腾讯即将开源 Kuikly 跨端框架,该框架基于 Kotlin 开发,支持多平台开发(Android、iOS、H5、小程序、PC 等)。Kuikly 采用声明式+响应式开发模式,输出的产物映射到系统原生控件,支持动态化更新,同时具有轻量化特性。Kuikly 的核心设计是通过薄原生渲染层减少两端代码不一致的问题,显著降低开发代码量。是否完全接入 Compose MultiPlatform 仍需等待开源后确认。 Memory ordering: Armv8-A 架构采用弱内存排序架构,允许无依赖的内存访问以不同于程序顺序的顺序完成,从而提高处理器效率。文章重点讨论了内存重排序的行为、限制,以及如何通过屏障指令来强制排序以确保正确性。 再学安卓 - binder 之驱动函数 ioctl: binder_ioctl 是 Binder 驱动中最核心的函数,没有之一,它负责通信两端 IPC 数据的收发以及 Binder 参数的设置。在 IPC 通信过程中,进程调用此函数执行相关命令,达成传输的目的。因此,我们单独用一篇文章的篇幅来分析它。本文内容较多,大致分为基础结构体、C 端流程和 S 端流程三部分,建议分段阅读。推荐前往掘金(PC web 端)获得更好的阅读体验。 Tool and library interdependencies : 这篇文章详细介绍了 Android 构建系统中的工具和库之间的相互依赖关系,包括源代码、库依赖、工具、Gradle 插件、编译器、Android SDK 和 JDK 等。文章解释了语义版本控制、依赖管理、构建关系以及如何优化和扩展构建过程。 随心所动,厂商的 CPU 核管理策略介绍: 随着 CPU 架构的发展,工艺的升级,带来性能提升,能效的提升(同性能下)。但是由于极限性能的增加,也带来了 peak 功耗的增加(大部分情况下,能效比的提升无法抵消这部分),CPU 功耗优化一直是广大 SOC 厂商比较头疼的问题。 CPU 的分支预测: 本文详细讲解了现代 CPU 中的分支预测技术,包括其重要性、基本概念、方向预测和目标地址预测的具体方法,以及相关的硬件设计,如 BTB(分支目标缓冲区)和 RAS(返回地址堆栈)。分支预测对于提高处理器性能至关重要,通过预测分支指令的方向和目标地址,可以减少流水线清空的次数,从而提高执行效率。 WMS 无焦点窗口的 ANR 问题【Android 14】: 这篇文章分析了 Android 14 中 WMS 无焦点窗口导致 ANR(应用无响应)的问题,详细探讨了问题的复现、原因和解决方案,并对相关日志和代码进行了深入剖析。 Dalvik, ART, JIT, and AOT in Android: 这篇文章详细介绍了 Android 中的 Dalvik、ART、JIT 和 AOT 的概念及其演变过程,并分析了它们的优缺点以及如何通过后续优化(如 Profile-guided Compilation、ART Cloud Profiles 和 Baseline Profiles)解决相关性能问题。 大模型时代手机的杀手锏功能思考: 大模型技术的兴起将彻底改变手机的功能和使用方式,作者提出了“杀手锏功能”的概念,即利用大模型技术提升手机的信息消化效率,使其成为整合和处理多模态信息的核心工具。 聊一聊 Android 的消息机制: 本文详细介绍了 Android 平台的消息机制,包括 Looper、Handler 和 MessageQueue 的运行原理及相互关系。通过分析源码和示意图,深入剖析了消息发送、处理、队列管理以及阻塞机制的实现方式,帮助读者理解 Android 消息机制的核心逻辑。 CS61C 系列-计算体系结构的伟大思想: 浮点数的表示 IEEE 754-Lecture 07: 本文深入探讨了 IEEE 754 浮点数表示标准及其在 AI、深度学习等领域的应用,介绍了不同精度类型(FP32、FP16、BF16 等)的特性和应用场景,重点分析了低精度量化技术(如 Q4)对模型效率和性能的优化。文章还展望了未来精度技术的发展趋势,包括动态精度调度和新型数字格式。 kotlin-weekly-449: kotlin-weekly-449 烽火连营——爆杀 Jank 闪烁卡顿: 这篇文章主要探讨了预览高清图切换时出现明显闪烁卡顿的问题。分析表明卡顿由 GPU 计算、频繁 GC 等导致,提出优化策略,如用 FadeInImage 替换 AssetImage,其后台异步解码、缓存复用等特性降低了 CPU 负载和 Jank 频次。还提到若放开图片资源限制,可进行图片压缩和按需加载等优化。 解锁 Linux 共享内存:进程通信的极速引擎: 本文详细介绍了 Linux 系统中共享内存的概念、核心原理、使用方法以及其在实际场景中的应用。共享内存是一种高效的进程间通信方式,允许多个进程通过访问同一块内存区域实现数据共享。文章还分析了共享内存的优势、常见问题及解决方案,并通过多个实际案例展示了其在多进程数据共享和实时处理中的重要作用。 杂记 ColorOS 动画曲线粗浅分析:为何有的地方怪,怪在哪里?: 在体验 ColorOS 15(下文简称「c15」)的过程中,我注意到它的部分动画有一点奇怪,但当时我并没有非常深入地去理解 c15 的曲线设计意图。直到有了官方给出的图例可以参考,作为动画爱好者,我自然是来了兴趣。更加深入地思考加上每天多次刻意的动画系曲线观察,我想我应该也能将我的想法比较通俗易懂地解释出来,带大家些微窥见 c15 设计的一角。 如何看待当前的「前端已死」的论调?: 文章围绕“前端已死”的论调展开讨论,从技术发展、行业现状、AI 影响等多角度分析了前端开发的现状与未来趋势。作者认为前端并未死亡,而是进入了一个稳定发展阶段,并在 AI 时代焕发出新的生机。文章还提出了应对行业变化的策略和发展建议。 程序员阅读清单:我喜欢的 100 篇技术文章(41-50): 程序员们也许是互联网上最爱分享的群体之一,他们不仅喜欢开源自己写的软件,也爱通过写文章来分享知识。从业以来,我阅读过大量技术文章,其中不乏一些佳作。这些佳作中,有些凭借深刻的技术洞见令我深受启发,也有些以庖丁解牛般的精湛手法解释一项技术,让我读后大呼过瘾。作为“爱分享”的程序员中的一份子,我想当一次推荐人,将读过的好文章分享给大家。我给这个系列起名为 《程序员阅读清单:我喜欢的 100 篇技术文章》。 vol34.等待时什么都不要做,只需要等待: 文章探讨了耐心等待的重要性,尤其是在现代社会中,过度干预和急于求成反而可能带来负面结果。同时,文章还涉及了人工智能代理的潜力、产品开发策略、决策瘫痪的应对方法,以及想象力与创造力的区别等内容。 AI 一个提示词 claude 生成一个 app 的 ui/ux: 本文讨论了作者利用 Claude 生成 app 的 UI/UX 的相关实践,分享了不同阶段的操作方法、提示词运用、与设计软件协作方式等内容。 关于 AI 的一些实践和思考: 这篇文章分享了作者在 AI 时代的实践与思考,探讨了 AI 如何改变生活、工作方式以及对个人成长的影响。作者通过使用生成式 AI,提升了编程、写作等多方面的效率,同时也强调了初心和好奇心的重要性,并提出构建数字资产和个人数据库的必要性。 【科普】程序员必看,AI 时代新协议 MCP 正在连接吞噬一切,20+资源全收录!: 文章介绍了新兴的 AI 时代协议 MCP(Model Context Protocol),其核心是为 AI 模型提供标准化的上下文连接方式,类似于 USB-C 接口在硬件中的作用。文章详细阐述了 MCP 的概念、运行机制、核心组件、实际应用场景,以及相关资源和学习资料。 MCP 是何方神圣?——给 AI 世界装上“万能插头”的幕后大佬: MCP 是 AI 世界的“万能插头”,由 Anthropic 于 2024 年底推出,旨在解决 AI 与不同数据源和工具对接的复杂性。它通过统一协议,让 AI 能轻松访问和操作各种资源,从本地文件到云端数据,极大提升了效率和功能扩展性。MCP 的四大组成部分(主机、客户端、服务器、资源库)协同工作,使 AI 能更智能地完成任务,同时保障数据安全。未来,MCP 将让普通人也能轻松指挥 AI,推动智能化生活的普及。 llama.cpp 源码解读–推理流程总览: 本篇文章将会对 llama.cpp 在推理时的流程进行总览介绍,关于 llama.cpp 的概念介绍与 GGML(llama.cpp 框架使用的张量计算库)的相关内容见往期文章。 70% 困境:AI 辅助开发的残酷真相: 这篇文章探讨了 AI 辅助开发的现状及其局限性,提出了“70% 问题”和“知识悖论”,并分析了 AI 对不同经验水平开发者的影响。作者还分享了实际有效的 AI 使用模式,以及对未来 AI 在软件开发中角色的展望。 AIGC Weekly #111: Claude 3.7 和 GPT-4.5 相继发布,前者在编码和网页开发方面有显著改进,后者则提升了知识库和用户意图理解能力。Deepseek 开源周推出多项高效模型训练库,多个 AI 产品和工具更新,展示了 AI 技术的快速发展和应用潜力。 鸡血 书籍推荐 《Android 性能优化之道》——从底层原理到一线实践: 本书围绕 Android 性能优化展开,阐述了其重要性,包括提升程序价值和增强开发者竞争力。讲解了做好性能优化的本质,即充分合理使用硬件资源和取得收益,并从应用层、系统层、硬件层三个维度分析,还指出性能优化的难点体现在知识储备、思考角度、思考方式和优化流程。同时介绍了本书特色、主要内容、读者对象及阅读方式,涵盖内存、速度等全面优化内容,适合不同阶段读者。 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 Flutter 上的 Platform 和 UI 线程合并是怎么回事?它会带来什么?: Flutter 3.29 引入了一个重要调整:在 Android 和 iOS 平台上,Dart 代码将直接运行在应用的主线程上,不再单独使用 Dart UI 线程。这一变更旨在优化 Native 和 Dart 的交互性能,简化线程管理,并减少异步通信带来的问题。通过线程合并,Flutter 可以更高效地处理任务,例如文本输入和平台视图渲染,但也可能带来插件兼容性问题。 秒开率从 18%到 64%,我们对小程序模拟器做了什么?: 小程序是一种运行在快手生态内,无需下载安装、即用即走的轻量级应用。其中,模拟器是快手开发者所使用的工具中最核心的模块之一,但因性能问题收到开发者反馈。为此,24 年 Q2 快手启动了模拟器性能优化专项,从线上数据看:模拟器秒开率从 18%提升至 64%,FCP P90 从 4.4s 提升至 1.9s。本文详细介绍优化措施和成效。 Core 文件损坏还能补救吗?有的兄弟,有的。: 文章主要介绍了 core 文件损坏的相关情况及修复方法。包括 core 文件在 gdb、core-parser 上出现的问题,如截断、无法解析等,还详细阐述了通过 Fakecore 功能进行修复的步骤,如重建 linkmap、校准、重建 Fakecore 等,以及针对特殊情况如线程栈截断的处理方式。 Facebook 开源 Screenshot Tests for Android:视觉回归自动化测试: Screenshot Tests for Android 是 Facebook 开源的一款自动化测试工具,核心功能是在 Android 设备测试过程中生成快速且确定的截图。截图可用于追踪应用界面的变化,从而有效预防视觉回归。视觉回归是指应用界面在更新后出现的不期望的视觉变化,如布局错乱、颜色不一致等问题。通过 Screenshot Tests for Android,可以更容易地捕捉这些问题,确保应用界面的稳定性和一致性。 深入研究 Android 启动速度优化(下): 在上一篇文章深入研究 Android 启动速度优化(上)中,梳理了应用启动的整个过程和问题,启动优化阶段与指标是什么,启动耗时方法的数据统计八种工具与分析,以及一些常见的启动时间问题。可以说是完成了启动优化工作最难的一部分 。 从 input 响应性能差的 issue 演示 perfetto trace 用法: 从 input 响应性能差的 issue 演示 perfetto trace 用法,分析了函数耗时、CPU 频率和调度等问题,并提出了优化思路。 基于 gpu counters 数据的性能优化: 基于 GPU Counters 数据的性能优化,文章探讨了通过 GPU 性能指标分析游戏性能瓶颈并提出优化方向。以三个游戏的性能数据为例,分析了内存读写、顶点和片元指标的差异,并总结了性能优化的通用思路和自动化的重要性。 提升移动游戏体验:性能和功耗的双重优化策略: 与主机和 PC 游戏开发不同的是,由于移动设备的安全限制,游戏开发商并没有足够的系统权限,无法根据手机实际的硬件情况进一步调优。因此,在移动游戏体验的优化上,除了游戏开发者们自己的优化,移动设备生产商的努力也同样至关重要的(这就是为何即便是同一芯片平台的设备不同品牌实际体验也有着明显差别的原因)。本篇文章将以移动设备厂商开发者视角,围绕性能和功耗两个最重要的方面,深入探讨如何优化游戏体验,力求为玩家提供更流畅和持久的游戏体验。需要说明的是,游戏体验是一个非常主观且庞大的话题,本篇只是介绍其中的性能和功耗,其他诸如触控、插帧、超分等不在讨论话题之内。 kotlin-weekly-448: kotlin-weekly-448 不要升级,Flutter Debug 在 iOS 18.4 beta 无法运行,提示 mprotect failed: Permission denied: iOS 真机升级到 18.4 beta 后,Flutter Debug 运行会出现权限不足错误。原因是 iOS 加强内存权限限制,Flutter Debug 运行未签名二进制文件且需动态生成代码,而 release/profile 因代码完全打包成机械码无此问题。Flutter 官方在研究修复,真机未升级则无影响。 Android 策略设计模式的使用:使用设计模式,减少烂代码,让项目更好维护: 这篇文章介绍了在 Android 项目开发中使用策略设计模式解决支付方式代码混乱、难以维护的问题。通过定义接口、创建默认实现类、具体实现类和策略工厂,实现了支付方式的分类管理。还阐述了策略设计模式的概念、组成部分、优点、缺点及应用场景。 深入探索 Android IPC:解锁进程间通信的奥秘: 文章深入探讨了 Android IPC,包括进程与线程的概念及关系、多进程模式、IPC 的基础概念与序列化方式、实现方式(Binder、AIDL、Messenger 等)、多进程模式带来的问题及解决方案,还介绍了 IPC 的应用场景与案例分析,并对其未来发展进行了展望。 理解 Android 中的内存泄漏以及如何让 LeakCanary 来帮忙: 这篇文章介绍了 Android 中的内存泄漏,包括其定义、危害、常见原因,重点讲解了 LeakCanary 检测和修复内存泄漏的作用、工作原理、集成步骤及局限性,还给出了修复泄漏的步骤和结论,强调内存管理的重要性。 2025 年 Android 六边形战士开发趋势,需要掌握哪些技能可自我反省: 本文探讨了 2025 年 Android 六边形战士开发趋势,包括必备技能(如多种语言和技术)、应用层(如原生 XML 布局、Compose 等)、系统层面、性能优化(启动、布局、内存等)、特殊功能(摄像头、音视频处理等)、逆向安全等方面,强调技术学习要与时俱进,多掌握技能。 Android NDK 示例(六)图片内存监控: 这篇文章主要介绍了在 Android 中使用 NDK 实现图片内存监控的方法。指出图片内存占用大,不同 Android 版本对图片处理不同。使用 ShadowHook 库,分别拦截不同版本的图片创建函数,获取宽高等信息。文中详细说明了依赖配置、实现逻辑、外部使用及相关代码示例。 杂记 创业日记 独立开发周记 106:提高转化率: 这篇文章是一位独立开发者的周记,记录了其在开发、优化应用和个人生活中的点滴经历。作者通过量化工作、优化应用功能和尝试新设计来提高产品转化率,同时也分享了生活中的一些趣事和消费经历。 读《黑客与画家(10 万册纪念版)》: 你的职位产生的业绩应该是可测量的,否则你做得再多,也不会得到更多的报酬。此外,你还必须有可放大性,也就是说你做出的决定能够产生巨大的效应。任何一个通过自身努力而致富的个人,在他身上应该都能同时发现可测量性和可放大性。高科技=可放大性 科技爱好者周刊#339:代币是什么: 这里记录每周值得分享的科技内容,周五发布。 Meet the Android Studio Team: A Conversation with Android Developer UX Manager, Dan Dole: 这篇文章介绍了 Android Studio 团队及其对开发者体验的贡献,重点是 Dan Dole 的职业旅程和他对用户体验(UX)的独特见解。他讨论了人工智能(AI)和机器学习如何改变开发者的工作方式,并分享了团队如何通过倾听反馈和技术创新来满足开发者的需求。 Android Studio Ladybug Feature Drop 稳定版已推出: Android Studio Ladybug 🐞 Feature Drop (2024.2.2) 稳定版已推出!借助 Android Studio 中的 Gemini、Wear 功能块的动画预览支持、App Links Assistant 等功能,提高您的工作效率。所有这些新功能都旨在帮助您更快地构建高品质 Android 应用。 02-24~03-02.老胡的周刊(第 179 期): 老胡的信息周刊,记录这周我看到的有价值的信息,主要针对计算机领域,内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 以终为始,面向价值观生活: 理想的人生,应该是随时死去,都不留遗憾。 迷雾: 最近,作者一直在全力开发一个名为 Follow 的项目,主要使用 React Native 编写,但遇到了许多技术挑战,特别是在需要调用 native 方法时。尽管花了大量时间研究 React Native 和 swift 等相关技术,但感到收获有限,这让作者有些郁闷但同时也感到偶尔的喜悦。项目进展方面成功将 Shiro 升级到 Next.js 15,但开源版本暂不更新。 日常中,作者参加了好友 pseudoyu 的婚礼,并与许多远程工作的同事首次见面。此外,作者开始意识到繁重的工作和久坐生活对健康的不良影响,计划关注身体健康。开源方面,作者还开发了一个替代 GitHub Notification 的工具 Linear。 体验碎周报第 221 期(2025.2.24): 系统的知识来源于对碎片的整理和思考,为了更好地输出系统知识,记录本周我发现的体验设计和思考,为构建系统知识做准备。 AI 掌握这些 DeepSeek 提问技巧,开发效率翻倍!: 掌握 DeepSeek 提问技巧,提高开发效率,文章详细阐述了如何在不同开发场景中精准提问以获得高质量回答,包括定位技术问题、制定技术方案、优化代码性能、学习新技术等,并提供了通用提问模板和实用技巧。 多模态人物视频驱动技术回顾与业务应用: 多模态人物视频驱动技术在商业、教育、医疗及文化娱乐等领域具有广泛应用,通过综述关键技术(如口唇驱动、头部驱动和肢体驱动)及其在业务场景中的实践,本文展示了相关技术的最新进展及其商业价值。 Cursor+Claude3.7 的绝杀:从原型到 app,两步完成 app 开发: 最近在 X 上看到了一些人在用 Claude 3.7 Sonnet 生成 app 原型图的尝试,受到启发,发现这么先生成不同界面的原型图再让 Cursor 基于原型图开发 app 会是很好的尝试。尤其是,你也可以不两步直接生成,而是在过程中更可视化地思考你要生产的原型,这对于非专业的产品经理来说,会是好得多的方式。 一个提示词 claude 生成一个 app 的 ui/ux: 产品文档知识库目录中包含关于使用提示词生成应用程序 UI/UX 的相关内容,同时分享了一个名为“流光卡片”的工具及其 API 使用文档,并提供了相关社交平台链接和教程。 【99%的人不知道】Cursor + Claude 3.7 的绝技:从原型到产品,两步完成 app 开发!: 这个视频展示了如何利用 Cursor 编辑器和 Claude 3.7 AI 模型,通过两个简单步骤快速开发一个完整的 APP 原型。这种方法特别适合非专业开发者快速将想法转化为可操作的应用程序。 吹爆 AI ?Flutter 开发在 Cursor & Trae 不一样的 AI 真实体验: 作者分享了在 Flutter 开发中使用 Trae 和 Cursor 的 AI 辅助编码体验,包括将状态管理框架从 redux 迁移到 riverpod 的需求。Trae 思考慢且迁移效果不佳,Cursor 也无法一步完成。尽管如此,它们在某些方面对工作效率有提升,同时还对比了 DeepSeek、Grok 3 和 ChatGPT 等,最后提及了 Anthropic 对 Claude 的发展规划。 关于 DeepSeek 我是怎么研究的(3): 本文介绍了 DeepSeek 涉及的推理机制(Reasoning Schema),包括其定义、组成部分(推理结构、策略和操作)以及工作原理。通过一个 24 点游戏的例子展示了 DeepSeek 的思考过程,并对比了推理(Reason)与推断(Inference)的区别,强调了推理在逻辑性和解释性方面的优势。 关于 DeepSeek 我是怎么研究的(4): 本文详细介绍了 DeepSeek-V3 模型的架构特点,包括基于 Transformer 框架、采用 MLA 和 DeepSeekMoE 架构。重点讨论了细粒度专家划分、共享专家分离、负载均衡策略以及 MLA 和 MTP 技术。此外,还介绍了训练基础设施及 FP8 混合精度训练框架。 AI 辅助编程的崛起意味着什么?: 使用一段时间后,我的整体感受是:AI 辅助编程将会是不可逆转的趋势。越早掌握一款得心应手的工具,越早完成与 AI 的磨合,对程序员的个人发展越有益。 AIGC Weekly #110: Grok3 发布,具备强大功能和上下文处理能力;谷歌 Veo2 视频生成模型开放使用,表现优秀但清晰度有所下降;Figure 推出 Helix 机器人视觉语言模型,实现多机器人协作;AI 在技术面试中的影响显著,需调整面试策略以应对 AI 挑战。 鸡血 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [RSS] 技术文章 Android Fence 同步框架: 本文主要介绍了 Android 的 Fence 同步框架,包括图形显示流程中使用 Fence 确认图像缓冲同步,Fence 类及其成员函数,FenceMonitor 类对 Fence 的直接追踪,追踪 Fence 的完整链路,还概述了 Android 同步框架,指出流程主要在用户空间,内核空间部分未深入分析。 你真的知道 onCreate 和 onResume 耗时操作为什么不会 ANR 吗?: jakeprim 在学习了课程的相关的 ANR 知识点后,针对 Activity 生命周期方法耗时操作的是否 ANR 情况进行了剖析实战。 Google Gemini 如何加速 Android 开发?: 文章主要介绍了 Google Gemini 在 Android 开发中的应用,包括下载配置 Android Studio 预览版以使用 Gemini 、其上手体验(如简单问答、智能诊断、快捷入口等)、代码改进案例,同时指出 AI 目前无法胜任中高级编程任务,使用时要提防其错误,作者后续还会出专门的评测文章。 Flutter 正在推进全新 PlatformView 实现 HCPP, 它又用到了 Android 上的什么黑科技: 文章主要探讨了 Flutter 在 Android 上的 PlatformView 实现,包括此前的几种模式及优劣,重点介绍了全新的 HCPP 实现。它需要特定环境支持,通过 SurfaceControl 构造高层级 Surface 解决混合覆盖问题,目前处于 beta 状态,未来可能成为新的支持模式。 「深蓝洞察」2024 年度最隐蔽的黑手: 在 GEEKCON 2024 上海站,两位选手为大家带来了特别披露议题【买手机送木马?】,披露了某国产手机厂商的恶行:该厂商利用系统权限,在用户不知情的情况下向多个第三方应用植入广告。数百万用户正常体验被破坏的同时,手机厂商却从应用方赚取了数千万的推广费用。 「 深蓝洞察 」2022 年度最“不可赦”漏洞: 但 2022 年,有知名互联网厂商竟持续挖掘新的安卓 OEM 相关漏洞,在其公开发布的 App 中实现对目前市场主流手机系统的漏洞攻击。 案例分享:线程存在 pending exception 导致 java 函数不执行: 文章主要描述了一个 Android 开发中的低概率问题:由于线程中存在 pending 异常,导致 JNI 层调用的 Java 函数未执行。文章分析了问题的根本原因,提供了技术细节,并提出了解决方案。 探索 Java Native Interface(四):JNI 中引用类型: 本文是关于 JNI 中引用类型的读书笔记,介绍了 JNI 支持将类实例和数组类型作为不透明引用,有本地、全局、弱全局三种引用。本地引用自释放,全局和弱全局需主动释放,且三者在生命周期、线程使用等方面有区别。还提及 Java 中的强、软、弱、虚四种引用类型。 华为 - 性能体验设计: 本文主要探讨了如何通过交互流畅体验设计和视觉流畅体验设计,提升移动应用程序的用户感知流畅度。文章涵盖了用户体验设计的原则、流畅评测指标、触屏手势设计、动效优化及完成时延等关键方面,同时提供了具体的优化建议和实践案例。 华为 - 性能最佳实践导读: 性能调优贯穿于鸿蒙应用开发的整个生命周期中,开发前有性能最佳指南等赋能套件让你快速上手学习,开发过程中有性能工具开发套件覆盖应用开发各阶段,应用开发完成上架后有专业的性能测试工具检查测试应用性能指标。本文重点介绍应用开发过程中使用性能工具与性能优化文章定位分析性能问题流程, 分享系统开发小技巧-如何查看进程是否冻结 freeze: 进程冻结技术(freezing of tasks)是将用户进程和部分内核线程置于“可控”的暂停状态。经常在系统开发过程中,分析一些运行问题,诡异的异常功能,或者 anr 等问题时候都会与系统进程的冻结进行打交道,既然与进程冻结打交道,那么我们肯定首先需要知道啥是冻结,冻结意义等。如果系统进程冻结了,有什么依旧是可以证明验证当前进程就是冻结状态。 探索 Android 平台 ARM unwind 技术-5 异步 Unwind: 近期有一些工作涉及到 eBPF 和 simpleperf 性能调测工具不足的思考。其中对涉及的 unwind 的流程做了一些梳理。发现 simpleperf,traced_perf 中 unwind 的方法是一种异步 offline 的方式。这种 unwind 是通过其他进程或者线程来完成,对回栈的目标线程执行流程基本不影响。 【Android V】SkSurface_Ganesh 总结: SkSurface_Ganesh 是 Skia 中基于 GPU 后端实现的 SkSurface,与不同后端(如 GL 和 VK)关联不同的 GrRenderTarget 实例。其创建方式分为三类:WrapBackendRenderTarget、WrapBackendTexture 和非 wrap 类型,不同方式对资源的管理和关联有所不同。在 hwui 中,GL 后端的 SkSurface_Ganesh 实例在每帧结束后会释放,无法统计内存使用信息,而 VK 后端的实例会被持有,导致 GPU 内存使用比 GL 后端多 10-20M。 响应时延的科学研究: 直接设备拖动可感知平均最小时延(PAMTD)是 11ms,点击 PAMTD 是 69ms;间接设备拖动 PAMTD 是 55ms,点击 PAMTD 是 96ms;直接设备拖动时延改进 1 帧(16.7ms)以上,能被大多数用户感知出来,比点击更敏感。 能量感知调度(EAS,Energy-Aware Scheduling)的考古日志系列,连载七: Quentin Perret 在 Morten Rasmussen 工作的基础上完成了 EAS 的开发合入。本文介绍第四个 patch, 将性能域的能量模型在 sysfs 中导出,这样方便用户空间查看,这需要在 CPU 子系统下添加一个 kobject, 并在其下为每个性能域增加一个 kobject。本文基于 kernel5.0-rc1。 能量感知调度(EAS,Energy-Aware Scheduling)的考古日志系列,连载八: Quentin Perret 在 Morten Rasmussen 工作的基础上完成了 EAS 的开发合入。本文介绍第五个 patch,引入性能域(PD)作为调度域的扩展,这样调度器在做任务选核的时候,就可以考虑能耗的影响。这个 patch 第一次将能量模型(EM)和调度器联系起来。本文基于 kernel5.0-rc1。 能量感知调度(EAS,Energy-Aware Scheduling)的考古日志系列,连载九: Quentin Perret 在 Morten Rasmussen 工作的基础上完成了 EAS 的开发合入。本文介绍第六个 patch,是对调度域(sched_domain)的一些优化和改进。新增了一个变量 sd_asym_cpucapacity,它指向调度域层次结构中最低的级别,在该级别上设置了 SD_ASYM_CPUCAPACITY 标志。这个标志表示调度域中的 CPU 具有不对称的计算能力。本文基于 kernel5.0-rc1。 Please register for I/O 2025: Google I/O 大会倒计时现已开始!5 月 20 日和 21 日,欢迎在山景城 Shoreline Amphitheatre 和 io.google 线上观看我们的现场直播。在 I/O 大会上,您将详细了解 Google 在 AI、Android 等领域的最新产品、技术和创新。 探索 Android 动态埋点的新视界:UprobeStats 深度解析: Android15 中 Google 正式引入 UprobeStats,它利用 Linux Kernel eBPF uprobe(用户空间探针)机制来动态获取用户进程中的埋点数据,并将其汇总至 StatsD 模块。 android 系统 SystemServer 进程启动流程分析: 本文详细分析了 Android 系统中 SystemServer 进程的启动流程。SystemServer 是由 Zygote 进程 fork 出来的核心进程,负责管理系统的核心服务。文章从 SystemServer 整体框架、源码分析、binder 驱动与线程池、以及 SystemServer 的具体启动方法等方面展开了深入讲解。 代码与工程之外【2】| 从 CRUD 到商业杠杆:重新理解程序员的核心价值: 代码和编程之外系列源于我对自身及程序员群体的观察思考得来,我们大部分人(包括我),可能陷于各种酷炫高大上技术的盲目崇拜中,可能陷于面向薪资和跳槽编程的功利急躁中,可能陷于应接不暇的繁重工作中,而对于场景、技术本质、价值、动机追求等一些更加不可见,真正决定脚下的路能走多远的主题,却未能了解到。我希望通过我的表达,来帮助大家厘清这些常被忽视的东西,如果能对这些东西有足够的理解,可能会帮助大家走得更快更远 Android Fence 同步框架: 本文主要介绍了 Android 的 Fence 同步框架,包括图形显示流程中使用 Fence 确认图像缓冲同步,Fence 类及其成员函数,FenceMonitor 类对 Fence 的直接追踪,追踪 Fence 的完整链路,还概述了 Android 同步框架,指出流程主要在用户空间,内核空间部分未深入分析。 探秘 React Native:从视图创建到布局渲染的流程解析: 这篇文章主要探秘了 React Native 从视图创建到布局渲染的流程。以 tag 为标识与 ShadowNode 建立映射,通过操作 ShadowNode 进行视图创建和添加。布局属性由 Yoga 引擎管理,通过任务队列在新帧开始时执行操作,自底向上调用 View 的 measure 和 layout 实现布局。 在 Jetpack Compose 中解锁 CameraX 的强大功能: 我们了解到您喜欢 CameraX 和 Jetpack Compose 库提供的强大功能,但希望使用更符合语言习惯的 Compose API 来构建相机界面。今年,我们的工程团队开发了两个新的 Compose 工件,即低层级 viewfinder-compose 和高层级 camera-compose。两者现在均已推出 alpha 版本 。在本系列文章中,我们不仅将为您介绍如何将 camera-compose API 集成到应用中,还将向您展示与 Compose 集成所带来的一些令人愉悦的界面体验。所有令人赞叹的 Compose 功能 (例如自适应 API 和动画支持) 均已与相机预览无缝集成。 系统化掌握 Flutter 开发之 Image:视觉盛宴: 本文系统介绍了 Flutter 开发中 Image 组件,包括基础认知(四种加载方式、关键属性等)、进阶应用(动态加载、滤镜与动画集成等)、性能优化(缓存策略、压缩与格式等)、源码探秘、设计哲学、最佳实践及总结。强调以性能为导向构建高可用、高扩展的图片处理体系。 Compose Performance: 本文总结了 Jetpack Compose 的性能优化方法和工具,强调了减少不必要的重组、有效管理状态以及使用性能分析工具的重要性。文章还介绍了如何通过新技术(如 Strong Skipping 模式、稳定标记和 Gradle 插件)提高 Compose 的稳定性和效率。 杂记 我们对于 GPU 的看法错了 [译]: 我们对 GPU 的看法存在误判。尽管 Fly.io 在构建 GPU 产品上投入了大量资源,但市场需求并未如预期般增长。开发者更倾向于使用现成的 LLM API,而不是自己搭建 GPU 环境。尽管如此,这次尝试让公司在技术、经验和资产管理上有所收获,也验证了创业过程中试错的重要性。 找到心流:通过深度工作和慢生活逃离数字干扰 [译]: 这篇文章探讨了如何在现代数字干扰的时代中,通过深度工作、慢生活和心流状态来提升专注力和生活质量。作者分享了个人经验、具体方法和灵感来源,以帮助读者找到平衡、减少分心并追求更有意义的生活。 第 121 期-偷懒爱好者周刊 25/02/19: 偷懒爱好者周刊 25/02/19 学习周刊-总第 199 期-2025 年第 08 周: 周刊核心为运维周刊,还会侧重 Go 语言生态,Vue 相关技术生态的项目,以及 GitHub 上优秀项目或经验。 审读专家邀请《现代 CPU 性能分析与优化 第二版》: 我们很高兴地宣布,Intel 性能专家 Denis Bakhvalov 的新作 Performance Analysis and Tuning on Modern CPUs-Second Edition 的中文版翻译工作已接近尾声。为确保翻译内容的准确性、专业性和可读性,现面向招募 5 名熟悉计算机体系结构、编译优化和性能优化的技术专家,参与本书的审读工作。 设计欣赏|多巴胺配色 UI: 巴胺配色具有鲜明的特点。其色彩鲜艳、明亮、饱和度高,通常包括红、橙、黄、绿、蓝等充满活力的颜色组合。这些色彩能刺激视觉神经,让人产生愉悦感和兴奋感。在当下快节奏生活中,人们渴望积极的情绪刺激。鲜艳明亮的多巴胺配色能瞬间提升情绪,带来快乐与活力,满足了人们的心理需求。时尚达人、设计师等在社交平台上分享多巴胺配色的穿搭、装饰等,引发大量关注和模仿。 一年收入 27 $ 独立开发的 2024 年终总结: 这是一个关于作者“阿乐去买菜”对 2024 年的年终总结,涵盖了其个人生活、工作、学习和副业的回顾与反思,同时展望了 2025 年的目标。文章记录了作者在独立开发、社交媒体运营、生活体验和职业规划上的成果与不足。 Google 将取消 Kotlin GDE,JetBrains 放弃 为 KMP 定制独立 IDE: Google 将取消 Kotlin GDE,也就是 2025 年开始不再有 Kotlin 类目的 GDE: My Weekly Reading for February 16, 2025: 加州的政策失误、宪法危机辩论以及消费者金融保护局(CFPB)的功能和争议是本周阅读的重点。 潮流周刊-第 210 期 - 震撼烟囱: 记录每周看到的接地气的潮流技术,筛选后发布于此,觉得不错可关注此周刊,方便获取更新通知 36 岁的我为什么想润?为什么是爱尔兰 ireland?: 分享 3 个问题,1 、我为什么要润,2 、未来的规划,3 、为什么是爱尔兰 我的骑行十年-环青海湖: 这篇文章记录了作者与朋友一起骑行环青海湖的经历,包括旅行中的见闻、与陌生人的互动以及一些趣事。作者详细描述了沿途的风景、骑行的挑战和旅途中遇到的有趣人物,同时也分享了骑行结束后的感想。 姗姗来迟的 2024 年终小结和展望: 张鑫旭的 2024 年终总结 科技爱好者周刊(第 338 期):重新思考 6G: 这里记录每周值得分享的科技内容,周五发布。 Google Search Operators: The Complete List (44 Advanced Operators): 本文详细介绍了谷歌高级搜索操作符的完整列表及其在 SEO 中的实际应用。通过学习这些操作符,用户可以更精确地过滤搜索结果,并发现如何利用这些技巧解决 SEO 问题,如竞争对手分析、内部链接优化、内容发现等。 性能周刊 2025-02-23 第 7 期: 文章介绍了 GPU 架构与计算的基础知识,包括 CPU 与 GPU 的区别、GPU 的计算架构、内存架构以及执行模型,并探讨了如何在 GPU 上高效执行 kernel。适合对 GPU 计算和 CUDA 编程模型感兴趣的读者。 Android Weekly - Issue #663: Android Weekly - Issue #663 读《领导力:如何在组织中成就卓越》: 领导力是什么,领导力是创造一种方式让人们成就卓越。——艾伦·基斯。领导者必须致力于解决组织和社会当下面临的重大问题,在于如何动员大家在组织中成就卓越。以身作则,其实就同理心,已所不欲勿施于人,要求别人做到的自己首先必须做的。每个企业和优秀的人都需要一个令人动心,为之奋斗的理想和目标,不满足现状,开放的接受外界的声音,创新的精神。大家有了共同奋斗的目标,一起努力,当有人做了好的成果,用心激励。“信誉是领导力的基石”,深表赞同。在公司中,职务可以任命,尊重要靠赢得,要成为员工中心目中的好的领导,则要靠诚实,信誉,以身作则,洞察力,带领团队取得成功。有很多领导说这漂亮的话,夸大其词,夸夸其谈,但是又做不到,不诚实,在员工心中没有信誉,坏了自己口碑,更重要坏了一个组织,弄垮一个部门,甚至公司倒闭。 AIGC Weekly #109: OpenAI 公布了 GPT-4.5 和 GPT-5 的发布计划,Windsurf 发布了第三波更新,Grok 3 即将发布,Perplexity 推出 Deep Research 能力,AI 语音助手技术快速发展,Meta 研究如何解码语言,新的 AI 视频生成技术和模型不断涌现。 体验碎周报第 220 期(2025.2.17): 系统的知识来源于对碎片的整理和思考,为了更好地输出系统知识,记录本周我发现的体验设计和思考,为构建系统知识做准备。 AI DeepSeek R1 读后感、碳基大模型如何成为“厉害”的人。: 深度学习模型R1-Zero通过强化学习展现推理能力,其核心在于设计奖励机制,尤其是“格式奖励”,促使模型输出“思考”过程。该方法基于统计规律,而非类人类思维,强调计算效率与合理性。模型能力提升需结合高质量数据、监督学习与强化学习,类似于人类学习过程。文章还探讨了如何通过知识储备、专注思考与高质量信息 2025 年不同程序员如何用好 AI 模型: MooreAI 分享了关于 2025 年不同经济实力的程序员如何高效利用 AI 模型的建议,涵盖从产品架构到代码开发的多个层面,并推荐了多种工具与方案。 万字赏析 DeepSeek 创造之美:DeepSeek R1 是怎样炼成的?: 本文详细解析了 DeepSeek R1 的研发过程,探讨了其技术创新、训练方法、模型性能以及对行业的影响。文章从 R1 的发布背景、技术细节、与其他模型的对比,到其在学术界和工业界的反响,全面展示了 DeepSeek 团队如何通过强化学习、推理模型、工程优化等手段推动 AI 发展的边界。 AI 工具出海一个月的所思所想: 接触 AI 工具出海有一段时间了,不过一直处于观望的状态,真正下手去做,也才是最近一个月的事情。真正实践下来发现,它值得投入,但别期望上来就能拿到成绩。有些必要的坑该踩还是要踩的,有些思维方式改转换还是要转换。而最重要的就是思维方式的转换,从一个开发者到一个独立开发者,需要转换的思维有很多。这篇文章记录一下自己这一个月的所思所想。 图解 DeepSeek-R1 的创新训练和推理模型实现原理: DeepSeek-R1 的发布在 AI 发展进程中具有里程碑式的意义,尤其对机器学习研发社区而言意义重大,主要原因有二:开源策略:提供了经过轻量化处理的蒸馏版本;技术透明:公开分享了如何构建类似 OpenAI O1 这样对的推理模型的完整训练方法。接下来,让我们深入了解这个模型是如何构建的。 DeepSeek R1 范式复现笔记: 自 DeepSeek R1 技术报告开放以来,开源社区涌现了多种「复现」工作。本 R1 复现笔记旨在以多个开源项目的再复现以及交叉验证为目标,探索 R1/R1-zero 中强化学习步骤带来的模型效果提升,并尝试展望 R1 技术在未来模型训练与业务落地上的前景 AI 正在推动程序员的进化,而不是灭亡: 这是纽约时报新刊登的一篇 AI 对程序员影响的文章,有人担心 AI 很快会自动取代数百万个工作岗位,文章主要观点还是认为 AI 正在推动程序员的进化,而不是灭亡,创造力、批判性思维、解决问题的能力、沟通能力、共情能力——这些才是人们在未来需要持续培养的技能。当然,还要学会如何管理和使用好这些 AI 工具。 招聘信息 群友内推招聘第三期 - 大量 Android Framework 和 App 岗位需求,找工作的同学看过来 群友内推招聘第四期 - 大量 Android Framework 和 App 岗位需求,找工作的同学看过来 推荐 Monica 国内版 一直在用 Monica 国际版 ,聚合了各种 AI 和工具(不过需要科学上网),基本上上新的 AI 之后,过几天 Monica 就会支持,贼方便 现在 Monica 开始测试国内版本,内置了 Deepseek R1 ,后续国际版的很多功能应该都会挪过来。 我申请了 1000 个内测邀请码:GrackerAI ,给需要的小伙伴:https://monica.cn/invite?inviteCode=GrackerAI 掘金小册:Android 性能优化 原理+实战+进阶,全面掌握 Android 性能优化,作者赵子健,Android GDE,字节跳动资深 Android 开发工程师,现负责飞书客户端性能品质优化工作。熟悉 Android 和 Linux 系统底层原理,擅长性能优化。曾任职于腾讯、阿里巴巴,有丰富的项目性能优化和架构经验。 你会学到什么? 深入底层,全面建立性能优化知识体系; 高手思路,掌握大厂性能调优方法论; 三大模块,实战内存+速度+包体积优化; 玩转“黑科技”,轻松实现性能优化进阶。 连接:https://s.juejin.cn/ds/iPCuKAv3/ 掘金小册:Android 应用稳定性剖析与优化 全方位攻克 Android 应用疑难杂症,实战助力学习前沿优化技术。Pika(陈海亮),Google 开发者专家(Android GDE),掘金移动端签约作者。开源爱好者,多个大厂开源贡献者,擅长性能优化、应用与 JNI 开发。受邀参与多次 Google 活动,如社区说、IO Extented 等。 你会学到什么? 全面技能拓展,囊括前沿热门黑科技实战; 性能优化实践,多个一线开发案例全流程; 源码到优化,从底层到上层逐步贯通; 多维度优化,解决应用开发常见疑难问题。 连接:https://s.juejin.cn/ds/iPCHR7CP/ 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发、AI 等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [竹白] | [RSS] 技术文章 Linux 丨进程僵尸态的实战成因: 文章主要介绍了 Linux 中进程僵尸态的实战成因。一是子进程主线程已退出但有线程卡在 D 态无法退出,导致父进程收不到 SIGCHLD 信号。二是父进程采用 signalfd 处理 SIGCHLD 信号却卡在其他 epoll 事件处理函数中,无法及时处理回收子进程。最后还提及对 AI 与学习方式的观点。 以控制论视角设计移动 OS 中的内存回收策略: 本文从控制论的角度探讨了移动操作系统中内存回收策略的设计方法,提出通过闭环反馈控制(如 PID 控制器)优化内存管理。文章详细分析了如何利用采样、控制器算法及不同内存回收手段来实现高效、响应迅速的内存回收机制,并强调了设计简化和可扩展性的重要性。 kotlin-weekly-446: kotlin weekly 446 期 技术人核心竞争力:超越代码的思考: 本文探讨了软件开发人员在编码之外需要关注的思考和原则,包括如何超越需求、流程、协作、管理以及代码本身,从而提升团队效率和产品质量。对于软件开发人员而言,编码无疑是核心技能,自然也是投入时间和精力最多的领域。然而,在实际的业务研发过程中,需求的多变性、团队的紧密协作、用户的即时反馈、现网的故障排查、频繁的会议沟通以及历史负债的累积等因素,往往使得开发工作变得复杂而充满挑战。在这种背景下,开发人员可能会在“夹缝中开发,焦虑中编码”,尤其是在团队和业务规模不断扩大的时候。 ANR 详细对比分析安卓 13 VS 安卓 10: 文章详细对比分析了 Android 13 和 Android 10 在处理触摸事件时的 ANR(应用无响应)机制的源码差异,重点介绍了两种系统中 ANR 的检测和处理流程,并通过代码和流程图展示了具体实现细节。文章还深入探讨了 ANR 的判断逻辑、触发条件以及从 Native 层到 Java 层的处理过程。 淘宝订单列表 Fragment 转场动画卡顿解决方案: 如何应对产品形态与产品节奏相对确定情况下转变为『在业务需求与产品形态高度不确定性的情况下,如何实现业务交付时间与交付质量的确定性』。我们希望通过混合架构(Native 业务容器 + Weex 2.0)作为未来交易终端架构的重要演进方向,在 Native 容器侧充分发挥原生语言的性能优势、常驻 App 的调控与管控能力、手势识别与交互优势来解决体验问题。本专题《淘宝交易终端架构探索》是我们摸索出的部分实践总结,欢迎大家一起交流进步。 淘宝页面首帧优化的经验和心得: 文章分享了淘宝移动端页面首帧优化的经验和心得,从优化的必要性、衡量标准、性能问题分析到具体优化策略,详细阐述了如何提升用户体验并降低成本,同时提出了防止性能劣化的机制和持续迭代的重要性。 淘宝 App 交易链路终端混合场景体验探索: 如何应对产品形态与产品节奏相对确定情况下转变为『在业务需求与产品形态高度不确定性的情况下,如何实现业务交付时间与交付质量的确定性』。我们希望通过混合架构(Native 业务容器 + Weex 2.0)作为未来交易终端架构的重要演进方向,在 Native 容器侧充分发挥原生语言的性能优势、常驻 App 的调控与管控能力、手势识别与交互优势来解决体验问题。本专题《淘宝交易终端架构探索》是我们摸索出的部分实践总结,欢迎大家一起交流进步。 Weex 购物车长列表横滑操作优化“编年史”: 文章回顾了 Weex 购物车在长列表横滑操作优化上的历程,从基础实现到细节提升,展示了团队如何通过不断迭代优化用户体验,并展望了未来的改进方向。 一句话说透 Android 里面的如何正确管理 Binder 对象的生命周期: 一篇文章深入探讨了如何正确管理 Android 中 Binder 对象的生命周期,避免内存泄漏和线程安全问题,同时提供了详细的代码示例和操作指南。 一文简单了解 Android View 绘制流程: 文章主要介绍了 Android View 的绘制流程,包括 Activity 中 View 的层次和职责,View 显示的流程,以及 View 绘制中 measure、layout、draw 的执行细节。如 measure 中测量模式和子 View 的 MeasureSpec 确定规则,layout 中 ViewGroup 与子 View 的布局递归调用,draw 的六个步骤等。 深入理解 Jetpack Lifecycle(原理篇): 文章深入分析了 Android Jetpack 的 Lifecycle 组件的原理,包括其核心成员 LifecycleOwner 、LifecycleObserver 、Lifecycle 及它们之间的关系,还探讨了 Activity、Fragment 如何实现 LifecycleOwner ,Lifecycle 与 LifecycleRegistry 的关联,生命周期的传递机制,最后总结并表示下篇将聊 LiveData 。 The Second Beta of Android 16: Android 开发者博客发布了 Android 16 第二个 Beta 版本,新增了专业相机功能、图形效果支持、性能框架扩展,以及隐私、安全和后台任务相关的功能改进。此外,还包括一系列开发者工具和 API 更新,帮助开发者优化应用兼容性和用户体验。 Dart 3.7 发布,快来看看有什么更新吧: 在聊 Dart 3.7 发布的之前,就不得不提 Dart 宏功能推进暂停 ,在春节期间,Dart 团队决定,由于宏的性能具体目标还太遥远,团队决定把当前的实现回归到编辑(例如静态分析和代码完成)和增量编译(热重载的第一步)上。关于数据支持,具体在于重新投资 Dart 中的数据实现,因为这也是 Dart & Flutter issue 里请求最多的问题,事实上一开始 Dart 对宏支持的主要动机,也是为了提供更好的数据序列化和反序列化,但是目前看来,通过更多定制语言功能来实现这一点更加实际。另外,Dart 团队目前已经在研究改进 build_runner 性能和推出 augmentations language feature (可能以略有不同的形式)支持,最终目的是找到更直接和方便的方法,来支持建模数据以及处理序列化和反序列化适配。 Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用: 在 Flutter 里,Key 对象存在的目的主要是区分和维持 Widget 的状态,它是控件在渲染树里的「复用」标识之一,这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比》 聊到过,可以说 Key 的存在关乎了 Flutter 的性能,因为它的作用就是提高 Element Tree 的复用效率,例如减少匹配阶段所需的 Widget 比较次数。 记一次无障碍测试引发 app 崩溃问题的排查与解决: 这篇文章主要讲述了 APP 发版前自动化测试中出现的崩溃问题及解决过程。因无障碍测试引发 crash,经排查定位到问题代码,验证后得出两种解决方案,项目采用了其一。最后还进行了思考,提出测试人员应完善日志捕获,开发人员应深入分析根本原因,以提升代码质量和配合效率。 Flutter 3.29 发布,看起来会是一个“大坑”的版本: Flutter 3.29 发布,带来众多更新与变动。包括 Cupertino 和 Material 的更新,Web 相关改进,Engine 方面的优化,新功能如 Backdrop filter 优化等,DevTools 的改进,重大更改和弃用等。此版本变动较大,升级需谨慎评估,文中还提及多窗口支持进展和宏支持停止等内容。 WM 与 SF 的交互机制——Transaction: 文章介绍了 Android framework 中 WM 与 SF 交互的基本机制——Transaction。先讲解了准备知识,包括 SurfaceControl 与 Layer 的关系及通信需求。接着分析了 Transaction 类在 Java 层和 native 层的定义,以 setPosition、apply、merge 方法为例阐述其桥梁作用,最后探讨了设计该机制的优点,如减少 IPC 通信、保证变换同步、具备扩展性等。 TrustedTime API: Introducing a reliable approach to time keeping for your apps: Android 开发者博客最新文章介绍了 TrustedTime API,它是一个提供可靠时间戳的新工具,旨在解决设备本地时间可能被篡改的问题。它通过 Google 的基础设施提供可信的时间源,支持多种应用场景,如金融、游戏、电商等。API 集成简单,适用于 Android 5 及以上版本设备。 技术简报 2025 第二期: 技术简报 2025 第二期文章汇总了多个编程和技术资源链接,涵盖方差计算、C++资料、系统设计思想、自动化复杂性、MESI 协议、ARM 无锁编程、追踪工具、eBPF 资源以及 Linux 内核和用户空间追踪器分析等内容,同时还提到了一些技术竞赛和系统设计案例。 Android V app 冷启动 (1) Activity 生命周期: 文章主要分析了 Android 中从竖屏的 Launcher 冷启动王者荣耀这一过程中 Activity 的生命周期,包括 ATMS 处理启动请求、ActivityStarter 构建窗口层级和启动流程,以及 RootWindowContainer 暂停后台 Tasks 与拉起进程等各阶段的详细步骤和相关代码逻辑。还介绍了进程起来后的 attach app 流程和最终通知 app 端启动 activity 的过程。 Android V app 冷启动(2) 窗口层级的构建: 根据 Android V app 冷启动 (1) Activity 生命周期 分析,在第一阶段启动中,会构建窗口层级。但是,我打算以授人以渔的方式来讲解,即分析框架,而不是分析具体的流程。 Android V app 冷启动(3) 添加启动窗口: 文章围绕 Android V app 冷启动添加启动窗口展开,详细分析了相关代码和流程。从检测是否显示启动窗口,到包装数据,再到创建和添加启动窗口,涉及多个类和方法的交互,最终通过多线程方式为启动窗口创建 View 并通过 WindowManager 添加。 杂记 软技能—代码之外的生存指南 读书笔记: 这篇文章是关于《软技能——代码之外的生存指南》一书的读书笔记,重点介绍了程序员如何从职业发展的角度更好地管理自己的职业生涯。文章总结了书中的核心内容,并结合作者的个人经验,提供了关于职业规划、目标设定、人际交往、面试技巧、公司选择、晋升策略、辞职建议及技术观念的具体建议。 从提问到输出:何谓信息 |上野千鹤子: 本文深入探讨了信息生产的过程,从提问到输出,涵盖了信息的本质、提问的重要性、研究方法、原创性、资料收集、写作风格以及信息传播等多个方面。作者上野千鹤子通过自己的学术经验,分享了如何成为一个有效的信息生产者,并强调了提问能力、原创性和对公共知识财产的贡献。 高通 2025 财年 Q1 财报深度分析:汽车业务增长 60%: 高通 2025 财年 Q1 财报显示,其营收和净利润分别同比增长 17%和 15%。汽车业务增长最快,同比增长 60%,但增速放缓。智能手机和物联网市场需求回暖促使整体业绩上升,但苹果基带业务的不确定性和智能手机市场的低增长预期仍是潜在挑战。高通通过 AI、5G、边缘计算等领域的研发投入,巩固技术优势,同时在汽车和 IoT 业务上展现出强劲增长潜力。 读《智人之上》: 《智人之上 ——从石器时代到 AI 时代的信息网络简史》这本书的读书笔记 芯片,大变局: 文章探讨了全球半导体行业,尤其是中国台湾和韩国在地缘政治与市场竞争压力下的变局。台积电、联电等台湾企业面临美国关税压力和供应链外移挑战,同时也需应对中国大陆代工厂的崛起。韩国半导体企业则在存储芯片领域中受到来自中国和美国的双重压力,需要调整战略以保持竞争力。 vol31.重启你的好奇心: 这篇文章涵盖了多个主题,包括个人成长、技术设计、产品价值定义、AI 应用构建的陷阱,以及如何重新激发好奇心等。它通过理论分析、案例探讨和工具推荐,提供了广泛的知识和见解。 02-10~02-16.老胡的周刊(第 177 期): 老胡的信息周刊,记录这周我看到的有价值的信息,主要针对计算机领域,内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 如何有效做好近 3-6 个月的时间管理——针对多线程任务的动态平衡法: 如何有效做好近 3-6 个月的时间管理,文章提出了一种动态平衡法,通过目标导向和弹性任务池等方法,帮助在多线程任务中找到平衡,提升执行力和掌控感。 DPS 周刊 185 - Kevin Kelly 的旅行建议: 这篇文章分享了 Kevin Kelly 长达 50 多年的旅行经验和见闻,涵盖了旅行中的心得、建议以及作者和朋友们的不同旅行故事。这些内容不仅展示了丰富的旅行体验,还为读者提供了关于旅行计划、文化探索和生活哲理的深刻见解。 技术管理之“做一个四大工程师”: 何为大情怀?一是做任何事情都要有激情,而不是只为了混一口饭吃。二是有大的理想抱负,已经解决了温饱问题时候,多少还是要争取做一些大事,做一些对社会和他人有价值的事情。今天研讨时候大家提到了一个专家脑子僵化的问题。确实 deepseek 目前火爆打破了很多认知,一群刚毕业的学生做出了震惊世界的产品,会让多少人和公司汗颜。众多院士,国内大公司都有那么多专家和人才,怎么都做出来呢。我们这些所谓专家在新 AI 时代如何自处。 AI 个人知识库搭建教程: 本文介绍如何基于 DeepSeek V3 模型+Obsidian 搭建个人知识库,效果就是你可以对 Obsidian 工作区的笔记进行提问,让 AI 帮你梳理和总结。 DeepSeek 高效率使用的指南: 本文详细探讨了如何高效使用人工智能工具(如 DeepSeek 和 ChatGPT),重点在于提问的技巧和思维方式的培养。文章分为多个部分,覆盖了从掌握领域知识到优化提问,再到批判性思维和探索性提问的方法。 最好的致敬是学习:DeepSeek-R1 赏析: 和真格联合推出了一个面向非技术人群(我怎么每次都是面向这个人群)的 DeepSeek R1 创新亮点分享会(由我司首席科学家 Peak 提供 tech review,确保我没有瞎讲)。用通俗易懂的方式带大家过了一次 R1 和 V3 的技术报告。让你可以理解在这之前业界都有哪些问题和疑惑,DeepSeek 是怎样去解题的,以及其中的几个重要闪光点。后半场我们讨论了从中能看到未来 LLM 应用会有哪些新范式和产品可能性。同时对最近一段时间的各种光怪陆离的谣言和伪概念进行一个解释 关于 DeepSeek 我是怎么研究的(1): 抛开纷繁复杂的信息从 DeepSeek 最原始的论文和报告入手研究,避免被外界立场影响,建立自己对 DeepSeek 的认知。这会是一个系列文章,首先研究清楚 DeepSeek 归属的 Reasoning Language Model (RLM)的概念、核心组件及其工作原理,强调了 RLM 结合大型语言模型生成能力和高级推理机制的特点。 关于 DeepSeek 我是怎么研究的(2): 本文探讨了 DeepSeek 如何应用 System 1 Thinking 和 System 2 Thinking 来优化其模型。System 1 Thinking 快速、直观,基于模式匹配;而 System 2 Thinking 缓慢、深思熟虑,基于逻辑推理。通过结合这两种思维方式,DeepSeek 能够在生成推理步骤和评估推理路径方面更高效地解决复杂问题。 性能周刊 2025-02-09 第 6 期: 本文是《性能周刊》第 6 期,涵盖了多篇关于性能分析与优化的文章,内容涉及 Linux 性能分析工具 perf 的使用、火焰图的生成与解读、eBPF 与 bcc 的使用示例、Linux 内核进程管理、Profiling 工具与方法、编译优化技术以及可观测性模型等多个主题,适合对性能优化感兴趣的读者深入学习。 Android Weekly Issue #662: 《Android Weekly》第 662 期涵盖了最新的 Android 开发资讯和教程。主要内容包括:在 Jetpack Compose 中创建拉丝金属 UI 效果,使用 Koin 插件在 IntelliJ 和 Android Studio 中检测 Kotlin 应用的依赖问题,以及分析 Kotlin 网络客户端的线程使用效率,发现 Ktor Client 比 Retrofit 和 Fuel 更高效。除此之外,还介绍了如何在 Jetpack Compose 中实现三点加载动画,优化 Android 应用的过度绘制以提升性能,以及在 MVI 架构中正确加载初始数据的方法。本期还探讨了 Kotlin Multiplatform 移动应用的最佳架构实践,强调模块化和 Clean Architecture 的重要性,并介绍了 Android 16 Beta 2 的新特性,如专业相机增强功能和新的安全 API。 Cursor: 以每秒 1000 个 Token 的速度编辑文件: 一种新的模型和推理方法,可在 1000 tokens/s 的速率下对整个文件进行高精度编辑。 什么是 AI 架构师? — Bret Taylor: 以下内容整理自播客“Latent Space”最近的一期“The AI Architect — Bret Taylor”,邀请到了嘉宾是 Bret Taylor,是一位拥有传奇经历的 Sierra CEO、OpenAI 主席,以及 Google Maps / Facebook Likes 的缔造者,在节目中分享了他对软件工程未来的见解,以及在 AGI 曙光降临之际,如何打造优秀的产品和团队。内容比较长,但是值得认真看看,我对于一些有价值的内容已加粗,如果时间有限,也可以挑重点看看。 课程推荐 掘金小册:Android 性能优化 原理+实战+进阶,全面掌握 Android 性能优化,作者赵子健,Android GDE,字节跳动资深 Android 开发工程师,现负责飞书客户端性能品质优化工作。熟悉 Android 和 Linux 系统底层原理,擅长性能优化。曾任职于腾讯、阿里巴巴,有丰富的项目性能优化和架构经验。 你会学到什么? 深入底层,全面建立性能优化知识体系; 高手思路,掌握大厂性能调优方法论; 三大模块,实战内存+速度+包体积优化; 玩转“黑科技”,轻松实现性能优化进阶。 连接:https://s.juejin.cn/ds/iPCuKAv3/ 掘金小册:Android 应用稳定性剖析与优化 全方位攻克 Android 应用疑难杂症,实战助力学习前沿优化技术。Pika(陈海亮),Google 开发者专家(Android GDE),掘金移动端签约作者。开源爱好者,多个大厂开源贡献者,擅长性能优化、应用与 JNI 开发。受邀参与多次 Google 活动,如社区说、IO Extented 等。 你会学到什么? 全面技能拓展,囊括前沿热门黑科技实战; 性能优化实践,多个一线开发案例全流程; 源码到优化,从底层到上层逐步贯通; 多维度优化,解决应用开发常见疑难问题。 连接:https://s.juejin.cn/ds/iPCHR7CP/ 投稿指南 欢迎投稿分享您的: 技术博客 实践经验 工具推荐 投稿方式: 公众号后台回复”投稿” 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 订阅渠道:[微信公众号] | [知乎专栏] | [掘金] | [竹白] | [个人博客 RSS] 技术文章 详解 DiffUtils、Myers 算法和 Jetpack Compose: 本文详解了 DiffUtils、Myers 算法和 Jetpack Compose。DiffUtils 是 Android 中优化列表更新的工具,依赖 Myers 算法计算最小更改。文中介绍了其工作原理、应用示例和在 RecyclerView 中的作用。Jetpack Compose 基于声明性 UI 原则,由状态驱动 UI 自动重组,内置优化且智能跳过未变元素重组,无需 DiffUtils,可通过带 Keys 的 LazyColumn 等工具实现类似优化,还列举了相关面试问题和结论。 结合源码和 Perfetto 分析 Android 渲染机制: 本文结合源码和 Perfetto 工具,详细解析了 Android 渲染机制,包括渲染流程、关键组件的源码分析、Vsync 信号的处理和 Perfetto 的应用。通过深入的技术分析,文章回答了多个与 Android 渲染相关的问题,并提供了性能优化的建议和参考资料。 GPU 硬件加速提升渲染流畅度: 文章详细分析了 Android 系统中如何通过 GPU 硬件加速提升渲染流畅度,特别是在动画和复杂渲染场景中的优化。文章从影响渲染流畅度的因素入手,深入探讨了 GPU 硬件加速的源码实现、动画优化的机制,以及通过 RenderThread 提升动画流畅度的具体方法,并提供了 Perfetto 工具的对比分析。 Meet the Android Studio Team: A Conversation with Director of Product Management, Jamal Eason: 本次专访中,产品管理总监 Jamal Eason 分享了他在 Android Studio 的职业旅程、独特视角以及对开发者社区的深刻理解。他强调了产品质量、AI 集成(如 Gemini)以及 Crashlytics 和 Play 的应用质量洞察功能对开发者的影响。团队通过开发者反馈、用户研究和与 Android OS 团队的紧密合作,确保工具的不断改进。 Android Weekly Issue #661 Android 字节码处理-使用 AsmClassVisitorFactory 插桩: 这篇文章主要介绍了在 Android 开发中使用 ASM 字节码处理技术,通过插桩的方式修改 Java 类的行为,包括增加、删除和修改字段或方法等操作,同时对相关代码实现进行了详细解读。 深入浅出 Android 事件分发机制: 这篇文章深入浅出地介绍了 Android 事件分发机制,包括基础概念、事件分发起点、View 和 ViewGroup 的分发流程、事件响应及面试场景题。基础概念涵盖 View 和 ViewGroup、MotionEvent 等;分发起点从页面结构和初始分发过程说明;View 和 ViewGroup 的分发流程详细阐述了方法职责和相关代码;事件响应讲解了不同触摸动作的处理逻辑;面试场景题针对常见问题给出答案。 Frida 实现 JNI 方法地址跟踪、反汇编、Patch: 文章主要介绍了使用 Frida 实现 JNI 方法地址跟踪、反汇编、Patch 的过程。包括利用 Frida 的反汇编 API 分析目标进程代码,通过一系列步骤找到并 hook RegisterNatives 方法获取 JNI 方法信息,反汇编 JNI 方法的机器码,以及修改汇编指令实现 Patch 并给出完整代码和示例效果。 Android ANR 系列 1 :理解 Android ANR 设计思想: 本文为 Android App ANR 系列的第一篇,主要是从系统的角度来剖析 Android ANR 的设计思想 Android Perfetto 系列 4:使用命令行在本地打开超大 Trace: 本篇是 Perfetto 系列文章的第四篇,如何使用 trace_processor_shell 在本地打开超过 2G 的大文件。在实际的问题分析过程中,我们经常会碰到非常大的 Trace 文件(大于 2GB),直接扔进 ui.perfetto.dev 是没法打开的,这是因为浏览器内存的限制。这时候我们就需要使用官方提供的 trace_processor_shell 工具来本地打开大文件。 Compose 与原生 UI 混排原理解析: 文章主要介绍了 Compose 在 Android 和 iOS 平台上与原生 UI 混排的原理及使用方式。Android 上通过 AndroidView 实现,包括创建父 ViewGroup 和关联的 LayoutNode 等处理测量、布局、绘制、触摸事件。iOS 上采用“挖洞”方案,添加 InteropContainer 承载 UIView,处理视图显示、背景设置、事件分发和顺序等,同时也指出了 iOS 混排方案的一些限制。 鸿蒙内核论文阅读: 陈海波老师领衔的华为团队在 OSDI24 上发表了鸿蒙内核的论文:Microkernel Goes General: Performance and Compatibility in the HongMeng Production Microkernel,舆论场上不出意料又是神兔大战,很少有人讨论技术本身,出于好奇去学习了下原文,讲笔记整理成问答的形式: Android Gradle 学习(八)- gradle 任务解析: 这篇文章主要介绍了 Android Gradle 中一系列任务的解析,包括构建./gradlew assembleDebug 过程中的众多任务,如 compileDebugAidl、packageDebugRenderscript 等,还提到了任务的创建、重要任务的分析、相关类以及具体的操作过程。此外,文章还阐述了任务的应用,如在任务间插入操作实现自动上传 APK 或添加自定义的清理任务,并对整体内容进行了总结。 使用自定义调度程序暴露并发错误: Jake Hillion 在 FOSDEM 上做了一个演讲,介绍了如何使用 sched_ext(一种在内核版本 6.12 中引入的 BPF 调度框架)来帮助发现难以捉摸的并发问题。他与 Johannes Bechberger 合作,构建了一个调度器,能够在几分钟内揭示测试代码中理论上可能但尚未观察到的并发错误。由于他们的调度器仅依赖于主线内核功能,理论上可以应用于任何在 Linux 上运行的应用程序——尽管由于项目还处于早期阶段,仍有一些需要注意的事项。 安卓 prop/SystemProperties 如何监听值变化: 在安卓开发过程中,无论是系统还是 app 层面都会经常接触到相关的 prop 相关的设置和读取,这个和 settings 数据其实一样的,但是 prop 相比 settings 少了一个时刻监听 prop 的变化,即 prop 变化后要及时通知到其他进程,这样其他进程接受到了 prop 变化后也可以及时进行相关的业务处理,如果 prop 变化后没有相关的通知回调方法的话,那就只能靠不断轮询 getprop,这样即无法保证实时性,也严重损坏性能。所以基于以上的背景,今天带大家实现一下 prop 变化后相关的监听方法及触发 prop 变化回调方法 AI 也能”看懂”图片: 移动端相册 AI 搜图的奥秘: 这篇文章主要介绍了移动端相册 AI 搜图的相关内容。包括 Android 手机相册搜图功能,Clip 模型及其原理、开发 AI 搜图应用的步骤,如通过开源项目 PicQuery 讲解关键组件、预处理、编码器等,还提及关键技术亮点、性能优化策略、技术挑战与解决方案,最后总结了其为图片检索带来的新体验。 lmk 内存压力测试工具 mem-pressure 源码剖析: android 系统开发过程中,经常会遇到一些 low memory kill 的问题,在分析这些系统低内存导致被杀问题时候,经常因为不好复现而成为一个比较烦恼的阻碍。因为这种低内存问题本身就不属于一种功能操作类型的问题,属于一种系统当前可使用的内存少导致的问题,所以分析这类 lmk 低内存被杀的情况迫切需要一种可以帮助我们复现系统低内存的工具,今天马哥就给大家介绍一个内存压力工具 mem-pressure 详细使用和源码剖析。 DeepSeek-R1 论文解析——人工智能领域的 RL LLM 新时代?: DeepSeek-R1 论文解析介绍了一种开创性的开源推理模型,通过强化学习增强大型语言模型的推理能力。文章详细探讨了模型的训练流程、性能表现以及与其他模型的对比。 杂记 2024 年总结 - 持续迭代: 文章总结了作者在 2024 年的生活、工作、家庭、兴趣及个人成长的多个方面,主题围绕“持续迭代”,分享了育儿体验、生活感悟、职业发展、健康管理、投资学习以及输出成果等内容,并展望了 2025 年的目标和计划。 张忠谋两万字访谈:谈黄仁勋、28nm、苹果和其他: 近日,在英伟达 CEO 黄仁勋的牵线搭桥下,美国博客 Acquired 采访了台积电创始人张忠谋。在这个两个多小时的对话中,张忠谋分享了他与英伟达黄仁勋如何结缘,公司与苹果合作的幕后故事,以及他豪赌 28nm 并大获成功的故事。 Deepseek 官方提示库: Why Blog If Nobody Reads It?: 即使没有人阅读你的博客,写博客仍然具有深远的价值。写作迫使你理清思路,提升观点表达能力,记录思想的演变过程,成为未来的时间胶囊。或许某天,一位读者会在恰当的时刻发现你的文字,从而受到启发。持续而有深度的创作比追求一时的流行更有意义。正如街头摄影一样,你记录所见所感,不是为了他人的认可,而是因为那些瞬间值得被捕捉和铭记。 科技爱好者周刊#336:面对 AI,互联网正在衰落: 这里记录每周值得分享的科技内容,周五发布。 技术管理思考:每天 10min 一对一工作讨论 如何提高做事的成功率?: 正如标题写的那样,在过去的一年中,我所认识到最大的收获是,在如何提高做事成功率上有了一些心得和体会。从结论上说:顺势而为 + 减少做事的阻力 创新、内卷与结果主义: DeepSeek 采用的是「蒸馏技术」,这一点毋庸置疑,所以在这里就不再探讨它的行为本身。毕竟它现在和黑神话悟空一样,已经与爱国主义捆绑,吸纳足够的个体,形成了可以对抗一切个体和其他群体的乌合之众。但是蒸馏技术确实导致了我们可以实感的「结果」的发生,这是一个使用低成本芯片制造的 AI,从而在中国掀起了狂热的浪潮。从结果而言,它就是成功的,这一点也毋庸置疑。先说结论,比起质疑结果,最有效的方式是质疑「动机」。 DPS 周刊 184 - 张忠谋的洞见: 最近听了 Acquired 对于台积电创始人张忠谋的采访,里面提到《张忠谋自传》下册也已经出版了。于是我迅速找来上下两册通读了一番,收获颇大。 给 IT 年轻人职业建议 4(“茶壶煮饺子”): 接着给 IT 行业年轻人的职业建议 3 继续聊一个话题,就是关于工作中如何更好的成长。这次建议不要成为一个“茶壶煮饺子”——有东西倒不出来的技术人员。工作中偶尔会遇到这样的技术人,看起来满腹经纶,知识渊博,但解决一些工作难题和创新上看不出来有任何好的表现。为什么这样,我觉得可能还是跟认知有关。 译:AI 正在创造一代文盲程序员: 这是一位开发者分享的经历和反思:他发现自己过度依赖 AI 编程后,coding 能力反而在下降。具体表现为:1) 不再仔细阅读文档,只是复制粘贴错误信息给 AI,2) debug 能力变差,对代码的深度理解也在减弱,3) 如果 AI 五分钟内解决不了问题就会感到烦躁。为了改善这种情况,他提出了一些建议:每周留出”无 AI 日”专注于传统编程方式,在使用 AI 之前先尝试自己解决问题,认真理解 AI 提供的解决方案。 AI 和写作: 本文探讨了 AI 在写作领域的作用和潜力。作者指出,尽管 AI 在编程协作中已取得显著效果,但目前的 AI 写作工具多为文本处理辅助,无法真正促进深度思考。写作被视为思维的外化和放大器,是帮助人们完善想法的关键工具。理想的 AI 写作助手应当是非侵入式的,能够在适当时机提供观点反馈、学术支持和风格建议,协助用户深化思考。然而,由于不同写作类型需求各异,开发这样的 AI 助手存在挑战。作者还强调,AI 无法取代写作中人与人之间的情感连接,优秀作品能够引发读者与作者的共鸣,这是 AI 所不能实现的。 本周讨论 从目前的结果来看,大模型 + 推理 + 网络搜索 产出的效果是最好的,那么是否意味着,持续输出高质量的文章在目前的 AI 时代依然是非常重要的。 你觉得呢? 周六拉了个 AI 相关的讨论群,12 小时内就满 500 人了….大家对 AI 的热情真的是没得说,二群也已经快 300 人了,感兴趣的可以私信我。群里不打广告不割韭菜,纯纯就是之前的 Android 技术群里面的小伙伴们讨论 AI 编程、AI 副业这些内容的。私信:AI 加群 另外最新的技术群也已经满了,照例再建一个新的群,主要还是技术方面的讨论,加了我好友还没加群的可以加(微信号:553000664)。私信:技术群 投稿指南 欢迎投稿本周刊分享您的: 技术博客 实践经验 工具推荐 投稿方式: 本周刊下面留言 发邮件 :dreamtale.jg@gmail.com 微信私信联系:Gracker_Gao 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~ 版权声明 本周刊遵循 CC BY-NC-SA 4.0 协议 转载请注明出处:Android Weekly 第 X 期 欢迎订阅、分享,让更多开发者受益
本篇是 Perfetto 系列文章的第四篇,如何使用 trace_processor_shell 在本地打开超过 2G 的大文件。在实际的问题分析过程中,我们经常会碰到非常大的 Trace 文件(大于 2GB),直接扔进 ui.perfetto.dev 是没法打开的,这是因为浏览器内存的限制。这时候我们就需要使用官方提供的 trace_processor_shell 工具来本地打开大文件。 随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 本系列旨在通过 Perfetto 这个工具,从一个新的视角审视 Android 系统的整体运作方式。此外,它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章,但或许因为难以记住代码或不明白其运行流程,你仍感到困惑。通过 Perfetto 这个图形化工具,你可能会获得更深入的理解。 Perfetto 系列目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace 视频(B站) - Android Perfetto 基础和案例分享 如果大家还没看过 Systrace 系列,下面是传送门: Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 0. trace_processor_shell 工具下载 官方下载地址:https://github.com/google/perfetto/releases ,找到最新的 release 版本,选择自己的平台下载即可: 下载之后里面就会有 trace_processor_shell 工具(以 Mac 平台为例) trace_processor_shell 是 Perfetto 开源项目的核心工具之一,提供高性能的本地 Trace 解析服务。通过 –httpd 参数启动 HTTP 服务器后,它允许: 本地原生加速:绕过浏览器 WASM 的性能限制,直接调用 C++ 实现的解析引擎。基于 Rust/C++ 混合实现的解析引擎,优化了内存布局和并行处理,支持流式解析超大型 trace 文件。 交互式分析:与 Perfetto UI 深度集成,支持动态查询和可视化。 离线调试:无需上传 trace 到云端,保护隐私并支持内网环境。 其他的参数 参数作用示例值 --http-port指定监听端口--httpd :8080 --preload预加载常用数据表--preload sched --num-threads设置解析线程数(默认 CPU 核数)--num-threads 8 1. 使用 trace_processor_shell 打开 Trace 大文件 ./trace_processor_shell –httpd ../jank-航旅纵横-火车票-上下滑动超级卡顿.perfetto-trace 这时候在网页端打开 https://ui.perfetto.dev ,会有下面的弹框 弹框选项功能详解如下 1. YES, use loaded trace 功能:直接复用当前 Trace Processor 已加载的 trace 文件状态(即命令行中指定的 ../jank-航旅纵横-火车票-上下滑动超级卡顿.perfetto-trace)。 适用场景: 若你已通过 trace_processor_shell --httpd 加载了 trace 文件,且希望 UI 直接使用当前进程的解析状态(包括已执行的 SQL 查询、过滤条件等),选择此选项。 优势: 避免重复解析文件,节省时间和内存。 2. YES, but reset state 功能:强制重置 Trace Processor 状态,重新加载当前 trace 文件(或加载新文件)。 适用场景: 需要清除当前 Trace Processor 的所有状态(如临时查询结果、过滤器等),重新开始分析。 想通过同一端口加载另一个 trace 文件(需先停止当前进程或更换端口)。 等效操作: 等同于关闭当前 trace_processor_shell 进程后重新执行命令。 3. NO, Use builtin WASM 功能:完全绕过本地 Trace Processor 服务,改用浏览器内置的 WebAssembly (WASM) 引擎解析 trace 文件。 适用场景: 本地 Trace Processor 服务不可用或存在兼容性问题。 需要支持分享链接、下载修改后的 trace 文件等 WASM 模式专属功能。 代价: 大文件(如 >100MB)解析速度显著下降,且可能因浏览器内存限制崩溃。 如果选择 YES, use loaded trace , 打开 Trace 后,下面这几个功能是不可用的 2. 命令行启动 vs 直接打开 UI 的区别 通过命令行启动 (trace_processor_shell --httpd) 核心机制: 本地启动一个高性能的 C++ Trace Processor 服务(监听 127.0.0.1:9001),提供原生加速的 trace 解析能力。 优势: 性能:原生代码解析速度远超 WASM,尤其适合大型 trace 文件(如 >100MB)。 功能扩展性:支持 SQL 查询、自定义指标计算等高级功能。 状态保持:Trace Processor 的解析状态(如 SQL 临时表)可跨页面会话保留。 限制: 无法直接通过 UI 分享 trace 文件链接或下载修改后的文件。 同一时间仅允许一个浏览器标签页使用加速服务。 直接打开 UI 网页 (ui.perfetto.dev) 核心机制: 完全依赖浏览器内置的 WebAssembly 引擎解析 trace 文件,无本地服务参与。 优势: 便捷性:无需安装或启动本地工具,适合快速查看小型 trace。 功能完整性:支持分享链接、下载修改后的 trace 文件等协作功能。 劣势: 性能瓶颈:WASM 解析速度慢,大文件可能导致浏览器卡顿或崩溃。 功能限制:不支持部分高级 SQL 查询和自定义分析功能。 总结建议 优先命令行启动:处理大型 trace 或需要复杂分析时,使用 trace_processor_shell --httpd 提升性能。 临时轻量分析:直接上传到 ui.perfetto.dev 更方便,但需注意文件大小限制。 3. Mac 权限问题 Mac 上直接运行 ./trace_processor_shell –httpd 会报下面的错误 需要在设置-隐私与安全,点击 Allow 才可以继续运行 参考文档 Perfetto Github 库 Perfetto 官方文档 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文为 Android App ANR 系列的第三篇,主要分享几个 ANR 的案例,系列文章目录如下 Android App ANR 系列 1 :理解 Android ANR 设计思想 Android App ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android App ANR 系列 3 :ANR 案例分享 ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多 Android 系统的设计思想 首先,ANR 属于应用程序的范畴。这不同于 SNR(System Not Respoding),SNR 反映的问题是系统进程(system_server)失去了响应能力,而 ANR 明确将问题圈定在应用程序。SNR 由 Watchdog 机制保证,具体可以查阅 Watchdog 机制以及问题分析; ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度和超时处理 其次,ANR 机制主体实现在系统层。所有与 ANR 相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如 CPU/IO 使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR 对话框,部分 Rom 不显示 ANR 对话框,而是直接闪退到主界面) 然后,ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制,要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入), 如果处理超时,则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作,譬如密集 CPU 运算、大量 IO、复杂界面布局等,都会降低应用程序的响应能力 最后,部分 ANR 问题是很难分析的。有时候由于系统底层的一些影响,导致消息调度失败,出现问题的场景又难以复现。 这类 ANR 问题往往需要花费大量的时间去了解系统的一些行为,超出了 ANR 机制本身的范畴。有一些 ANR 问题很难调查清楚,因为整个系统不稳定的因素很多,例如 Linux Kernel 本身的 Bug 引起的内存碎片过多、硬件损坏等。这类比较底层的原因引起的 ANR 问题往往无从查起,并且这根本不是应用程序的问题,浪费了应用开发人员很多时间,如果你从事过整个系统的开发和维护工作的话会深有体会。所以我不能保证了解了本章的所有内容后能够解决一切 ANR 问题,如果出现了很疑难的 ANR 问题,我建议最好去和做 Framework、驱动和内核的朋友聊聊,或者,如果问题只是个十万分之一的偶然现象,不影响程序的正常运行,我倒是建议不去理它 – From duanqz ANR 常见原因 对于 ANR 的原因,通常要做到 :大胆假设,小心求证 。发现异常的地方提取之后,先假设是这里的问题导致的,然后以这个假设为出发点,看前后的 Log 看看是否能支持自己的假设,如果不能,那么换一个点. 问题出在当前进程 死锁 主线程调用 thread 的 join()方法、sleep()方法、wait()方法或者等待线程锁的时候 主线程阻塞在 nSyncDraw 主线程耗时操作,如复杂的 layout,庞大的 for 循环,IO 等 主线程被子线程同步锁 block 主线程等待子线程超时 主线程 Activity 生命周期函数执行超时 主线程 Service 生命周期函数执行超时 主线程 Broadcast.onReceive 函数执行超时(即使调用了 goAsync ) 渲染线程耗时 耗时的网络访问 大量的数据读写 数据库操作 硬件操作(比如 Camera) service binder 的数量达到上限 其它线程终止或崩溃导致主线程一直等待 Dump 内存操作 大量 SharedPerference 同时读写 问题出在远端进程或者系统 与 SystemServer 进行 Binder 通信,SystemServer 执行耗时 方法本身执行耗时导致超时 SystemServer Binder 锁竞争太多,导致等锁超时 等待其他进程返回超时,比如从其他进程的 ContentProvider 中获取数据超时 Window 错乱导致 Input 超时 ContentProvider 对应的进程频繁崩溃,也会杀掉当前进程 整机低内存 整机 CPU 占用高 整机 IO 使用率高 SurfaceFlinger 超时 系统冻结功能出现 Bug System Server 中 WatchDog 出现 ANR 整机触发温控限制频率 ANR 案例分享 ANR 案例:头条 - 死锁 主要操作是频繁的从侧边栏拉出今日头条进行分屏操作,多次操作之后,应用发生了 ANR,这个会导致手机短暂的在分屏栏中的应用界面黑屏并卡死。不过 4-5 秒之后又正常。 应用发生 ANR 的原因是自身主线程被阻塞导致。 1 2 3 4 5 6 7 8 9 10 11 12 "main"prio=5tid=1Blocked | group="main"sCount=1dsCount=0obj=0x74f9bbe8self=0xe7084400 | sysTid=28210nice=0cgrp=defaultsched=0/0handle=0xe9dcd534 | state=S schedstat=(13454428309928953492) utm=121stm=13core=3HZ=100 | stack=0xff3b6000-0xff3b8000stackSize=8MB | held mutexes= at com.ss.android.common.applog.LogReaper.insertCrashLog(SourceFile:98) - waiting to lock (a com.ss.android.common.applog.LogReaper) held by thread34 at com.ss.android.common.applog.AppLog.uncaughtException(SourceFile:1408 at u.aly.n.uncaughtException(SourceFile:34) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1063) 关键信息 1 waiting to lock (a com.ss.android.common.applog.LogReaper) held by thread34 因此,在下面的 ANR 日志总,查找 tid==34 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 "LogReaper"prio=5tid=34TimedWaiting | group="main"sCount=1dsCount=0obj=0x12fcaba0self=0xcb226e00 | sysTid=28274nice=0cgrp=defaultsched=0/0handle=0xc9f9b920 | state=S schedstat=(77341565269270880) utm=7stm=0core=5HZ=100 | stack=0xc9e99000-0xc9e9b000stackSize=1038KB | held mutexes= at java.lang.Object.wait!(Native method) - waiting on (a java.util.concurrent.atomic.AtomicInteger) at java.lang.Object.wait(Object.java:407) at com.ss.android.action.b.d.a(SourceFile:216) at com.ss.android.newmedia.b.onLogSessionBatchEvent(SourceFile:468) at com.ss.android.common.applog.DBHelper.batchSession(SourceFile:616) - locked (a com.ss.android.common.applog.DBHelper) at com.ss.android.common.applog.LogReaper.switchSession(SourceFile:175) at com.ss.android.common.applog.LogReaper.switchSession(SourceFile:153) at com.ss.android.common.applog.LogReaper.processItem(SourceFile:122) - locked (a com.ss.android.common.applog.LogReaper) at com.ss.android.common.applog.LogReaper.run(SourceFile:632) 关键信息: 1 waiting on (a java.util.concurrent.atomic.AtomicInteger) 应用内代码阻塞引起的 ANR 案例:冻结导致 ANR 搜索 am_anr 查看 anr 发生的时间 1 05-0100:51:39.594 1449 5234I am_anr : [0,2169,com.xxx.weather2,820526660,Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 1.)] 从 ANR 描述可以看到,是当前的 Window 还在处理上一个 Input 事件超时,导致新的事件没有被及时处理,所以发生了 ANR 搜索 ANR in 查看当时的 cpu 信息 1 2 3 4 5 6 7 8 9 05-0100:51:53.974 1449 5234E ActivityManager: ANR in com.xxx.weather2 (com.xxx.weather2/com.xxx.weather.xxxMainActivity) 05-0100:51:53.974 1449 5234E ActivityManager: PID:2169 05-0100:51:53.974 1449 5234E ActivityManager: Reason:Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 1.) 05-01 00:51:53.974 1449 5234 E ActivityManager: Parent: com.xxx.weather2/com.xxx.weather.xxxMainActivity 05-01 00:51:53.974 1449 5234 E ActivityManager: Load: 29.89 / 31.82 / 32.27 05-01 00:51:53.974 1449 5234 E ActivityManager: CPU usage from 4583ms to 12043ms later (2020-05-01 00:51:44.177 to 2020-05-01 00:51:51.637): 05-01 00:51:53.974 1449 5234 E ActivityManager: 18% 1449/system_server: 9.1% user + 9.3% kernel / faults: 7819 minor 1 major 05-01 00:51:53.974 1449 5234 E ActivityManager: 10% 720/surfaceflinger: 6.2% user + 4.4% kernel / faults: 734 minor 26 major 05-01 00:51:53.974 1449 5234 E ActivityManager: 10% 651/android.hardware.audio@5.0-service-mediatek: 9.5% user + 0.8% kernel / faults: 1 minor 4 major 可以看到当时的 cpu 并不繁忙,所以不是系统负载原因导致的 ANR,那么后续分析就要从 Log 中抽取对应的信息,来分析超时的 input 事件发生前后系统在做什么 分析过程如下 找到 ANR 发生时候的具体时间:00:51:39.594 从 5s 前的 Log 开始看,找到有用的信息:00:51:34.478 发现这个时间点并没有什么异常,只是应用没有响应 key_back_down,导致 5s 后报了 anr 从 ANR 之后的 Log 中发现,xxxHansManager : unfreeze uid: 10182 package: com.xxx.weather2 reason: Signal 这句 Log,说明 com.xxx.weather2 之前是冻结状态,这里进行了解冻,那么可以大胆猜想,是不是 com.xxx.weather2 被冻结之后,无法响应事件导致的 搜索冻结 Log,可以看到在 00:51:04.000 的时候,系统把 com.xxx.weather2 冻结了 xxxHansManager : freeze uid: 10182 package: com.xxx.weather2 reason: LcdOff 这个需要让系统看是否是冻结的逻辑问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 由于 LcdOff, 所以启动冻,weather2 被冻结 :com.xxx.weather2 reason: LcdOff 05-01 00:51:03.994 1449 2187 I xxxHansManager : freeze uid: 10245 package: com.tencent.mm reason: LcdOff 05-01 00:51:03.994 1449 2187 D xxxListManagerImpl: com.tencent.mm in autoStart list! 05-01 00:51:03.998 1449 2187 I xxxHansManager : freeze uid: 10246 package: com.xunmeng.pinduoduo reason: LcdOff 05-01 00:51:03.998 1449 2187 D xxxListManagerImpl: com.xunmeng.pinduoduo in autoStart list! 05-01 00:51:04.000 1449 2187 I xxxHansManager : freeze uid: 10182 package: com.xxx.weather2 reason: LcdOff 05-01 00:51:04.002 645 30914 I netd : firewallSetUidRule(4, 10182, 2) 05-01 00:51:04.005 1449 2187 I xxxHansManager : freeze uid: 10187 package: com.heytap.yoli reason: LcdOff 05-01 00:51:04.005 1449 2187 D xxxListManagerImpl: com.heytap.yoli in autoStart list! 05-01 00:51:04.005 1449 2187 I xxxHansManager : isHansImportantCase uid: 10196 pkg: cn.kuwo.player reason: audiofocus 05-01 00:51:04.007 1449 2187 I xxxHansManager : freeze uid: 10234 package: com.tencent.mobileqq reason: LcdOff 05-01 00:51:04.008 1449 2187 D xxxListManagerImpl: com.tencent.mobileqq in autoStart list! 05-01 00:51:04.014 1449 2187 I xxxHansManager : freeze uid: 10235 package: com.tencent.qqlive reason: LcdOff weather2 接收到 KEYCODE_BACK 的 ACTION_DOWN 05-01 00:51:04.471 1449 9842 D xxxPhoneWindowManager: interceptKeyBeforeQueueing:KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x8, repeatCount=0, eventTime=38812125, downTime=38812125, deviceId=-1, source=0x101, displayId=0 } 05-01 00:51:04.471 1449 9842 I sysui_multi_action: [757,803,799,key_back_down,802,1] 05-01 00:51:04.477 1449 1620 D xxxPhoneWindowManager: interceptKeyBeforeDispatching key: win=Window{1b36ae8 u0 com.xxx.weather2/com.xxx.weather.xxxMainActivity} event = KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x8, repeatCount=0, eventTime=38812125, downTime=38812125, deviceId=-1, source=0x101, displayId=0 } 05-01 00:51:04.477 1449 1620 D xxxPhoneWindowManager: interceptKeyBeforeDispatching newEvent keyCode = 4 weather2 接收到 KEYCODE_BACK 的 ACTION_UP 05-01 00:51:34.478 1449 1620 D xxxPhoneWindowManager: interceptKeyBeforeDispatching key: win=Window{1b36ae8 u0 com.xxx.weather2/com.xxx.weather.xxxMainActivity} event = KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x8, repeatCount=0, eventTime=38842126, downTime=38842126, deviceId=-1, source=0x101, displayId=0 } 05-01 00:51:34.478 1449 1620 D xxxPhoneWindowManager: interceptKeyBeforeDispatching newEvent keyCode = 4 KEYCODE_BACK 的 ACTION_UP 5s 没有响应,触发 ANR 05-01 00:51:39.484 1449 1620 I WindowManager: Input event dispatching timed out sending to com.xxx.weather2/com.xxx.weather.xxxMainActivity. Reason: Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 1. 05-01 00:51:39.484 2256 5518 I QUALITY-TOTAL: exp: anr 05-01 00:51:39.594 1449 5234 I am_anr : [0,2169,com.xxx.weather2,820526660,Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 1.)] 05-01 00:51:39.593 1449 1620 D ActivityManager: ANR post Runnable for ProcessRecord{ae0c833 2169:com.xxx.weather2/u0a182} to deal with anr happend at 38847248@#@2169 05-01 00:51:39.593 1449 1620 D ActivityManager: ANR caller(2) = com.android.server.am.ActivityManagerService$LocalService.inputDispatchingTimedOut:19872 com.android.server.wm.ActivityRecord.keyDispatchingTimedOut:2641 com.android.server.wm.AppWindowToken.keyDispatchingTimedOut:2007 com.android.server.wm.InputManagerCallback.notifyANR:111 com.android.server.input.InputManagerService.notifyANR:1822 系统发 Signal 3 给 weather2(SIGNAL_QUIT = 3,给到 Signal Catcher 线程用于输出 Trace) 05-01 00:51:40.868 2169 2202 I oloros.weather: Thread[7,tid=2202,WaitingInMainSignalCatcherLoop,Thread*=0xd9878400,peer=0x138c0250,"Signal Catcher"]: reacting to signal 系统检测到 Signal 给到的 weather2, 所以给 com.xxx.weather2 解冻 05-01 00:51:40.869 1449 9811 I xxxHansManager : unfreeze uid: 10182 package: com.xxx.weather2 reason: Signal 05-01 00:51:40.869 2169 2202 I oloros.weather: 05-01 00:51:40.869 2169 2169 W ViewRootImpl[xxxMainActivity]: Dropping event due to no window focus: KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x8, repeatCount=0, eventTime=38812125, downTime=38812125, deviceId=-1, source=0x101, displayId=0 }, hasFocus:true, mStopped:true, mPausedForTransition:false 05-01 00:51:40.869 2169 2169 W ViewRootImpl[xxxMainActivity]: Cancelling event due to no window focus: KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x28, repeatCount=0, eventTime=38847249, downTime=38812125, deviceId=-1, source=0x101, displayId=0 }, hasFocus:true, mStopped:true, mPausedForTransition:false 可以看到这里 weather2 卡了 36391.9ms,也就是从 00:51:04.471(KEYCODE_BACK 发出) 到 00:51:40.869(当前时间) 这段时间 05-01 00:51:40.869 1449 1620 I InputDispatcher: Window '1b36ae8 com.xxx.weather2/com.xxx.weather.xxxMainActivity (server)' spent 36391.9ms processing the last input event: KeyEvent 05-01 00:51:40.869 2169 2169 V ViewRootImpl[xxxMainActivity]: Sending input event to IME: KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x28, repeatCount=0, eventTime=38847249, downTime=38812125, deviceId=-1, source=0x101, displayId=0 } 05-01 00:51:40.870 2169 2169 I Choreographer: Skipped 2212 frames! The application may be doing too much work on its main thread. ANR 案例:Broadcast 超时导致 ANR 这个案例 Log 缺失,具体就是用户在进行迁移,BroadcastReceiver 的 onReceive 中会起一个线程处理,最终报的是 BroadcastReceiver ANR 1 2 04-0717:03:17.174435 1448 1476V WindowManager: Lookingforfocus: Window{efa2a4f u0 正在升级数据库,数据量较大,请耐心等待}, flags=25296898, canReceive=true1 04-0717:03:17.174459 1448 1476V WindowManager: findFocusedWindow: Foundnewfocus @ Window{efa2a4f u0 正在升级数据库,数据量较大,请耐心等待}2 分析如下: 日历后台广播 ANR 的原因是 onReceive 执行超时,之前没有看出来是因为 onReceive 里面耗时操作是用的 new Thread 的操作,以为在子线程里面做耗时操作,就不会影响后面广播的执行 后面发现 onReceive 里面调用了 PendingResult result = goAsync(); 这句话会在有序广播接收器执行的时候,可以在子线程执行耗时操作,而不会影响 receiver 的生命周期.这个方法非常简单,返回 mPendingResult 并将其设置为 null。 如果我们在 onReceive 方法中调用该方法,这意味着广播处理流程被打断了,当 onReceive 方法执行完毕,由于 mPendingResult 为 null,因此并不会马上回调 AMS.finishReceiver 方法。而且由于 goAsync 返回了 PendingResult,因此我们可以任意时刻、任意线程去调用 PendingResult.finish 去回调 AMS。相当于将一个同步回调变成了异步回调。而在这异步回调过程中,我们可以新起线程进行一些耗时的 IO 操作等等。简单来说,goAsync 提供了一种机制,让我们可以在异步线程中处理广播消息,以防止主线程被阻塞。https://juejin.im/post/5c15fc10e51d454ad55ef9fa 这个案例中,虽然在 onReceive 中使用了线程去处理耗时任务,但是由于调用了 goAsync,所以还是会计算超时时间,如果在规定的时间内没有完成,就算是在子线程,也会触发 BroadcastReceiver 的 ANR 可以看 goAsync 这个方法的官方注释: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 /** * This can be called by an application in {@link #onReceive} to allow * it to keep the broadcast active after returning from that function. * This does not change the expectation of being relatively * responsive to the broadcast, but does allow * the implementation to move work related to it over to another thread * to avoid glitching the main UI thread due to disk IO. * * As a general rule, broadcast receivers are allowed to run for up to 10 seconds * before they system will consider them non-responsive and ANR the app. Since these usually * execute on the app's main thread, they are already bound by the ~5 second time limit * of various operations that can happen there (not to mention just avoiding UI jank), so * the receive limit is generally not of concern. However, once you use {@code goAsync}, though * able to be off the main thread, the broadcast execution limit still applies, and that * includes the time spent between calling this method and ultimately * {@link PendingResult#finish() PendingResult.finish()}. * * If you are taking advantage of this method to have more time to execute, it is useful * to know that the available time can be longer in certain situations. In particular, if * the broadcast you are receiving is not a foreground broadcast (that is, the sender has not * used {@link Intent#FLAG_RECEIVER_FOREGROUND}), then more time is allowed for the receivers * to run, allowing them to execute for 30 seconds or even a bit more. This is something that * receivers should rarely take advantage of (long work should be punted to another system * facility such as {@link android.app.job.JobScheduler}, {@link android.app.Service}, or * see especially {@link android.support.v4.app.JobIntentService}), but can be useful in * certain rare cases where it is necessary to do some work as soon as the broadcast is * delivered. Keep in mind that the work you do here will block further broadcasts until * it completes, so taking advantage of this at all excessively can be counter-productive * and cause later events to be received more slowly. * * @return Returns a {@link PendingResult} representing the result of * the active broadcast. The BroadcastRecord itself is no longer active; * all data and other interaction must go through {@link PendingResult} * APIs. The {@link PendingResult#finish PendingResult.finish()} method * must be called once processing of the broadcast is done. */ public final PendingResult goAsync(){ PendingResult res = mPendingResult; mPendingResult =null; returnres; } 其用法如下 1 2 3 4 5 6 7 8 9 10 11 12 @Override1 public void onReceive(final Context context, final Intent intent){ finalPendingResult result = goAsync(); Runnable worker =newRunnable() { @Override public void run(){ onReceiveAsync(context, intent); result.finish(); } }; mAsyncHandler.post(worker); } ANR 案例:Launcher - Input ANR 搜索 am_anr 找到 ANR 发生的时间点 属于 input dispatch anr 属于应用处理 input 事件超时(而不是 no focus window) 1 19:44:56.815 2515 25056 I am_anr : [0,3365,com.xxx.launcher,819478085,Input dispatching timed out (Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 9.)] 搜索 ANR in 找到 cpu 使用情况 1.从 Load : 0.02 / 0.01 / 0.0 来看,整机的负载并不高,大概率是逻辑导致的 ANR. 2.logd 和 SurfaceFlinger 的 cpu 使用略高,可以当做怀疑的对象重点观察. 1 2 3 4 5 6 7 8 9 10 11 12 1-0119:45:07.753 251525056E ActivityManager: ANR in com.xxx.launcher (com.xxx.launcher/.Launcher) 19:45:07.753 251525056E ActivityManager: PID:3365 19:45:07.753 251525056E ActivityManager: Reason: Input dispatching timed out (Waiting to send key event because the focused window hasnotfinished processing all of the input events that were previously delivered to it. Outbound queue length:0. Wait queue length:9.) 19:45:07.753 251525056E ActivityManager: Parent: com.xxx.launcher/.Launcher 19:45:07.753 251525056E ActivityManager: Load:0.02/0.01/0.0 19:45:07.753 251525056E ActivityManager: CPU usage from0ms to10936ms later (2020- 19:44:56.751to2020- 19:45:07.688): 19:45:07.753 251525056E ActivityManager: 97%546/logd:97% user +0.2% kernel / faults:12minor 19:45:07.753 251525056E ActivityManager: 25%956/surfaceflinger:11% user +13% kernel / faults:151minor17major 19:45:07.753 251525056E ActivityManager: 1.4%1664/media.codec:0.9% user +0.5% kernel / faults:28729minor4major 19:45:07.753 251525056E ActivityManager: 7.5%2515/system_server:2.9% user +4.6% kernel / faults:1818minor8major 19:45:07.753 251525056E ActivityManager: 3.2%3365/com.xxx.launcher:2.1% user +1% kernel / faults:3535minor29major 19:45:07.753 251525056E ActivityManager: 2%3620/com.xxx.persist.system:1.2% user +0.7% kernel / faults:870minor 分析 trace.txt 查看是否有有用的信息,首先看 Launcher 的主线程堆栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 "main"prio=5tid=1Native | group="main"sCount=1dsCount=0flags=1obj=0x72a50cd0self=0x6f7ae10800 | sysTid=3365nice=-10cgrp=defaultsched=0/0handle=0x700123bed8 | state=S schedstat=(37451337214473599138010494263) utm=26967stm=10484core=0HZ=100 |stack=0x7fc0270000-0x7fc0272000stackSize=8192KB | held mutexes= kernel: (couldn't read /proc/self/task/3365/stack) native: #00pc0000000000071a8c /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28) native: #01pc0000000000075710 /apex/com.android.runtime/lib64/bionic/libc.so (__futex_wait_ex(voidvolatile*,bool,int,bool, timespecconst*)+140) native: #02pc00000000000d9744 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_wait+60) native: #03pc00000000002bf5e8 /system/lib64/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::postAndWait()+168) native: #04pc00000000002bf510 /system/lib64/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::drawFrame()+44)12 at android.graphics.HardwareRenderer.nSyncAndDrawFrame(Native method) at android.graphics.HardwareRenderer.syncAndDrawFrame(HardwareRenderer.java:422) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:671) at android.view.ViewRootImpl.draw(ViewRootImpl.java:3983) - locked (a java.lang.Object) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3782) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3085) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1994) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8201) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1085) at android.view.Choreographer.doCallbacks(Choreographer.java:908) at android.view.Choreographer.doFrame(Choreographer.java:835) at android.view.Choreographer$FrameHandler.handleMessage(Choreographer.java:1013) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:238) at android.app.ActivityThread.main(ActivityThread.java:7798) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995) 可以看到这里主线程阻塞在了等待 RenderThread 返回,那么继续查看 RenderThread 的堆栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 "RenderThread"daemon prio=7tid=25Native | group="main"sCount=1dsCount=0flags=1obj=0x13100b00self=0x6f0c698c00 | sysTid=3616nice=-10cgrp=defaultsched=0/0handle=0x6f072e0d50 | state=S schedstat=(35808915205059629728352628704) utm=29226stm=6582core=3HZ=100 |stack=0x6f071ea000-0x6f071ec000stackSize=991KB | held mutexes= kernel: (couldn't read /proc/self/task/3616/stack) native: #00pc00000000000c49f4 /apex/com.android.runtime/lib64/bionic/libc.so (__ioctl+4) native: #01pc000000000007d518 /apex/com.android.runtime/lib64/bionic/libc.so (ioctl+132) native: #02pc0000000000059e58 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+244) native: #03pc000000000005ad94 /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*,int*)+60 native: #04pc000000000005ab38 /system/lib64/libbinder.so (android::IPCThreadState::transact(int,unsignedint, android::Parcelconst&, android::Parcel*,unsignedint)+180) native: #05pc000000000004f000 /system/lib64/libbinder.so (android::BpBinder::transact(unsignedint, android::Parcelconst&, android::Parcel*,unsignedint)+228) native: #06pc000000000007fef8 /system/lib64/libgui.so (android::BpGraphicBufferProducer::dequeueBuffer(int*, android::sp*,unsignedint,unsignedint,int,unsignedlong,unsignedlong*, android::FrameEventHistoryDelta*)+224) native: #07pc00000000000b9880 /system/lib64/libgui.so (android::Surface::dequeueBuffer(ANativeWindowBuffer**,int*)+392) native: #08pc0000000000380540 /system/lib64/libhwui.so (android::uirenderer::renderthread::ReliableSurface::hook_dequeueBuffer(ANativeWindow*, ANativeWindowBuffer**,int*)+104) native: #09pc000000000000989c /vendor/lib64/egl/eglSubDriverAndroid.so (???) native: #10pc0000000000009388 /vendor/lib64/egl/eglSubDriverAndroid.so (???) native: #11pc000000000022cd2c /vendor/lib64/egl/libGLESv2_adreno.so (???) native: #12pc0000000000212b8c /vendor/lib64/egl/libGLESv2_adreno.so (???) native: #13pc0000000000020838 /system/lib64/libEGL.so (android::eglQuerySurfaceImpl(void*,void*,int,int*)+248) native: #14pc00000000002c6794 /system/lib64/libhwui.so (android::uirenderer::renderthread::EglManager::beginFrame(void*)+224) native: #15pc00000000002d414c /system/lib64/libhwui.so (android::uirenderer::renderthread::CanvasContext::draw()+236) native: #16pc00000000002d34e8 /system/lib64/libhwui.so (_ZNSt3__110__function6__funcIZN7android10uirenderer12renderthread13DrawFrameTask11postAndWaitEvE3$_0NS_9allocatorIS6_EEFvvEEclEv$c303f2d2360db58ed70a2d0ac7ed911b+380) native: #17pc00000000002de044 /system/lib64/libhwui.so (android::uirenderer::WorkQueue::process()+228) native: #18pc00000000002ddd24 /system/lib64/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+576) native: #19pc0000000000013654 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+328) native: #20pc00000000000da200 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36) native: #21pc00000000000769d4 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) 可以看到 RenderThread 是卡在了 binder 通信上,调用的方法是 dequeueBuffer,在等待 dequeueBuffer 返回结果。正常情况下主线程是不等待 dequeueBuffer 的,在 nSyncAndDrawFrame 返回后就继续执行了,这里卡住是因为在等 RenderThread 的上一个 draw 任务完成,也就是说上一个 draw 任务卡在了 dequeueBuffer,导致这一帧的主线程卡住导致超时 对帧渲染流程熟悉的话,应该知道 dequeueBuffer 的对端是 SurfaceFlinger,这时候需要查看 SurfaceFlinger 是否有问题(这里如果有 binder info 就可以直接看到 renderthread 在跟 SurfaceFlinger 的哪个线程进行通信,binder info 中可以看到 binder 通信的细节,比如这个案例我们应该可以看到(由于没有 dump 出来 binder info,所以下面的只是一个例子) 1 2 3 4 5 6 7 // 956 是 SurfaceFlinger,这里是 SurfaceFlinger 的 incoming binder 信息 proc956: incoming transaction28350131:0000000000000000from3365:3616to956:1357code2flags10pri0:110r1 node28346208size112:0data0000000000000000 // 3365 是 Launcher,3365:3616 => 956:1356 意思是从 Launcher(3365)的渲染线程(3616)到 SurfaceFlinger(956) 的 Binder:956_4(1357) proc3365: outgoing transaction28350131:0000000000000000from3365:3616to956:1357code2flags10pri0:110r1 这里我们搜 dequeueBuffer,来看 RenderThread 是在与 SurfaceFlinger 的那个 Bidner 线程通信,发现是 Binder:956_4 对应的 sysTid=1357 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 "Binder:956_4"sysTid=1357 #00pc0000000000071a8c /apex/com.android.runtime/lib64/bionic/libc.so (syscall+28) (BuildId:340eb42dd36c6541aec54c306eb4d0ee) #01pc0000000000075710 /apex/com.android.runtime/lib64/bionic/libc.so (__futex_wait_ex(voidvolatile*,bool,int,bool, timespecconst*)+140) (BuildId:340eb42dd36c6541aec54c306eb4d0ee) #02pc00000000000d97e8 /apex/com.android.runtime/lib64/bionic/libc.so (pthread_cond_timedwait+120) (BuildId:340eb42dd36c6541aec54c306eb4d0ee) #03pc0000000000072df0 /system/lib64/libc++.so (_ZNSt3__118condition_variable15__do_timed_waitERNS_11unique_lockINS_5mutexEEENS_6chrono10time_pointINS5_12system_clockENS5_8durationIxNS_5ratioILl1ELl1000000000EEEEEEE+108) (BuildId:5aad1075509f6e517eb78db32da8fbf6) #04pc000000000006f9d8 /system/lib64/libgui.so (android::BufferQueueProducer::waitForFreeSlotThenRelock(android::BufferQueueProducer::FreeSlotCaller, std::__1::unique_lock&,int*)const+788) (BuildId: e017958f19275814d1f2d55ce8b10bfa) #05pc000000000006fd8c /system/lib64/libgui.so (android::BufferQueueProducer::dequeueBuffer(int*, android::sp*,unsignedint,unsignedint,int,unsignedlong,unsignedlong*, android::FrameEventHistoryDelta*)+776) (BuildId: e017958f19275814d1f2d55ce8b10bfa) #06pc000000000007f0a8 /system/lib64/libgui.so (android::BnGraphicBufferProducer::onTransact(unsignedint, android::Parcelconst&, android::Parcel*,unsignedint)+3580) (BuildId: e017958f19275814d1f2d55ce8b10bfa) #07pc000000000004d67c /system/lib64/libbinder.so (android::BBinder::transact(unsignedint, android::Parcelconst&, android::Parcel*,unsignedint)+136) (BuildId: bb7ca5d12323a310f26473506cf070ed) #08pc000000000005a55c /system/lib64/libbinder.so (android::IPCThreadState::executeCommand(int)+1008) (BuildId: bb7ca5d12323a310f26473506cf070ed) #09pc000000000005a0b8 /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+156) (BuildId: bb7ca5d12323a310f26473506cf070ed) #10pc000000000005a898 /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+220) (BuildId: bb7ca5d12323a310f26473506cf070ed) #11pc000000000008093c /system/lib64/libbinder.so (android::PoolThread::threadLoop()+24) (BuildId: bb7ca5d12323a310f26473506cf070ed) #12pc0000000000013654 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+328) (BuildId:594f10db565bb0b9cf0223c7a1990ce5) #13pc00000000000da200 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36) (BuildId:340eb42dd36c6541aec54c306eb4d0ee) #14pc00000000000769d4 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId:340eb42dd36c6541aec54c306eb4d0ee) 可以看到这里是卡在了 waitForFreeSlotThenRelock,其代码逻辑在 frameworks/native/libs/gui/BufferQueueProducer.cpp 中, 感兴趣的可以自己看一下 这里卡在 waitForFreeSlotThenRelock 的意思就是,没有足够的 Buffer,因为每个 Layer 对应的 Buffer 的个数是一定的(2 个或者 3 个或者 4 个,一般是 3 个或者 4 个),如果 4 个 Buffer 都在使用过程中,那么应用调用 dequeueBuffer 去申请 free 的 Buffer 是不会成功的,需要进行等待,这里就是在等待 free 的 buffer 超时,导致了应用的 ANR Log 里面的 SurfaceFlinger、hwui 的部分 Log 也频繁打印来提示问题点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // SurfaceFlinger 的 Log 提示可用 buffer 数量不足 19:44:42.371 956 956 E BufferQueueConsumer: [com.xxx.launcher/com.xxx.launcher.Launcher#0] acquireBuffer: max acquired buffer count reached: 2 (max 1) 19:44:42.371 956 956 E BufferLayerConsumer: [com.xxx.launcher/com.xxx.launcher.Launcher#0] updateTexImage: acquire failed: Function not implemented (-38) // 提示是 dequeueBuffer 失败 19:45:45.127 3365 3616 W OpenGLRenderer: dequeueBuffer failed, error = -110; switching to fallback 19:45:45.138 3365 3616 I OpenGLRenderer: Davey! duration=4015ms; Flags=0, IntendedVsync=21148991192817, Vsync=21148991192817, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=21148991206202, AnimationStart=21148991249587, PerformTraversalsStart=21148991252765, DrawStart=21148991798858, SyncQueued=21148991974796, SyncStart=21152137782451, IssueDrawCommandsStart=21152138724534, SwapBuffers=21156150646824, FrameCompleted=21156152280366, DequeueBufferDuration=0, QueueBufferDuration=0, // MessageQueue 的信息标识是哪个 Message block 19:45:45.142 3365 3365 E ANR_LOG : >>> msg's executing time is too long 19:45:45.142 3365 3365 E ANR_LOG : Blocked msg = { when=-4s2ms what=0 target=android.view.Choreographer$FrameHandler callback=android.view.Choreographer$FrameDisplayEventReceiver } , cost = 4002 ms 19:45:45.142 3365 3365 E ANR_LOG : >>>Current msg List is: 19:45:45.142 3365 3365 E ANR_LOG : Current msg = { when=-4s5ms what=13 target=android.view.ViewRootImpl$ViewRootHandler } 19:45:45.142 3365 3365 E ANR_LOG : Current msg = { when=-4s1ms what=0 target=android.view.ViewRootImpl$ViewRootHandler callback=com.android.launcher3.util.ViewOnDrawExecutor } 19:45:45.143 3365 3365 E ANR_LOG : Current msg = { when=-4s1ms what=0 target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.Editor$1 } 19:45:45.143 3365 3365 E ANR_LOG : Current msg = { when=-3s23ms what=0 target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.ViewFlipper$2 } 19:45:45.143 3365 3365 E ANR_LOG : Current msg = { when=-1s502ms what=6 target=android.view.inputmethod.InputMethodManager$H arg1=74627 obj=android.view.inputmethod.InputMethodManager$PendingEvent@561bec9 } 19:45:45.143 3365 3365 E ANR_LOG : Current msg = { when=+14s859ms what=0 target=android.os.Handler callback=xxxUIEngineProguard.i.b$b } 19:45:45.143 3365 3365 E ANR_LOG : Current msg = { when=+24d20h30m18s414ms what=0 target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.ViewFlipper$2 } 19:45:45.143 3365 3365 E ANR_LOG : >>>CURRENT MSG DUMP OVER (a java.lang.Object) at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696) at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:690) at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299) at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223) at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163) locked (a com.xxxx.video.common.data.DataBaseHelper) at com.xxxx.video.common.data.DataBaseORM.(DataBaseORM.java:46) at com.xxxx.video.common.data.DataBaseORM.getInstance(DataBaseORM.java:53) locked (a java.lang.Class) Binder 数据量过大 1 2 3 4 5 07-2104:43:21.573 1000 148812756E Binder : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling1size388568(data:1,32,7274595) 07-2104:43:21.573 1000 148812756E Binder : android.util.Log$TerribleFailure: Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling1size388568(data:1,32,7274595) 07-2104:43:21.607 1000 1488 2951E Binder : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling1size211848(data:1,23,7274595) 07-2104:43:21.607 1000 1488 2951E Binder : android.util.Log$TerribleFailure: Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling1size211848(data:1,23,7274595) 07-2104:43:21.662 1000 1488 6258E Binder : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$1@770c74f calling1size259300(data:1,33,7274595) Binder 通信失败 1 2 3 4 07-2106:04:35.580[32837.690321] binder:1698:2362transaction failed29189/-3,size100-0line3042 07-2106:04:35.594[32837.704042] binder:1765:4071transaction failed29189/-3,size76-0line3042 07-2106:04:35.899[32838.009132] binder:1765:4067transaction failed29189/-3,size224-8line3042 07-2106:04:36.018[32838.128903] binder:1765:2397transaction failed29189/-22,size348-0line2916 ANR 相关资料分享 西瓜视频稳定性治理体系建设一:Tailor 原理及实践 西瓜视频稳定性治理体系建设二:Raphael 原理及实践 西瓜视频稳定性治理体系建设三:Sliver 原理及实践 西瓜卡顿 & ANR 优化治理及监控体系建设 今日头条 ANR 优化实践系列 - 设计原理及影响因素 今日头条 ANR 优化实践系列 - 监控工具与分析思路 今日头条 ANR 优化实践系列分享 - 实例剖析集锦 今日头条 ANR 优化实践系列 - Barrier 导致主线程假死 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待 Android ANR|原理解析及常见案例 参考资料 https://duanqz.github.io/2015-10-12-ANR-Analysis#1-%E6%A6%82%E8%A7%88 https://duanqz.github.io/2015-10-12-ANR-Analysis http://gityuan.com/2016/12/02/app-not-response/ http://gityuan.com/2017/01/01/input-anr/ https://xiaozhuanlan.com/topic/5097486132 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文为 Android App ANR 系列的第二篇,主要分享 ANR 分析套路和关键 Log 介绍,系列文章目录如下 Android App ANR 系列 1 :理解 Android ANR 设计思想 Android App ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android App ANR 系列 3 :ANR 案例分享 ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多 Android 系统的设计思想 首先,ANR 属于应用程序的范畴。这不同于 SNR(System Not Respoding),SNR 反映的问题是系统进程(system_server)失去了响应能力,而 ANR 明确将问题圈定在应用程序。SNR 由 Watchdog 机制保证,具体可以查阅 Watchdog 机制以及问题分析; ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度和超时处理 其次,ANR 机制主体实现在系统层。所有与 ANR 相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,譬如 CPU/IO 使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR 对话框,部分 Rom 不显示 ANR 对话框,而是直接闪退到主界面) 然后,ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制,要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入), 如果处理超时,则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作,譬如密集 CPU 运算、大量 IO、复杂界面布局等,都会降低应用程序的响应能力 最后,部分 ANR 问题是很难分析的。有时候由于系统底层的一些影响,导致消息调度失败,出现问题的场景又难以复现。 这类 ANR 问题往往需要花费大量的时间去了解系统的一些行为,超出了 ANR 机制本身的范畴。有一些 ANR 问题很难调查清楚,因为整个系统不稳定的因素很多,例如 Linux Kernel 本身的 Bug 引起的内存碎片过多、硬件损坏等。这类比较底层的原因引起的 ANR 问题往往无从查起,并且这根本不是应用程序的问题,浪费了应用开发人员很多时间,如果你从事过整个系统的开发和维护工作的话会深有体会。所以我不能保证了解了本章的所有内容后能够解决一切 ANR 问题,如果出现了很疑难的 ANR 问题,我建议最好去和做 Framework、驱动和内核的朋友聊聊,或者,如果问题只是个十万分之一的偶然现象,不影响程序的正常运行,我倒是建议不去理它 – From duanqz ANR 分析套路 ANR 问题主要有两种原因:应用自身的问题 和 系统异常导致的问题。在分析 ANR 问题的时候,最主要的就是要确定是哪个原因导致的(当然也有一些中间地带,比如代码写的不好,在正常情况下不会暴露,在系统出问题的时候很快就暴露出来)。 ANR 问题一般的分析步骤如下: EventLog 看具体的 ANR 时间(搜索 am_anr),看看是否跟 ANR log 能够对上,以确定 ANR Log 是否有效,如果 ANR Log 有效,分析 ANR Log,提取有用信息:pid、tid、死锁等,遇到 ANR 问题,摆在我们面前的 trace 是不是第一案发现场,如果 ANR 发生的输出的信息很多,当时的 CPU 和 I/O 资源比较紧张,那么这段日志输出的时间点可能会延迟 10 秒到 20 秒都有可能,所以我们有时候需要提高警惕。不过正常情况下,EventLog 中的 am_anr 的输出时间是最早的,也是最接近 ANR 时间的。 看 MainLog(Android Log) 或者 SystemLog 查看 ANR 详细信息(搜索 ANR in),提取有效的信息。 发生 ANR 的时间 打印 ANR 日志的进程 发生 ANR 的进程 发生 ANR 的原因 CPU 负载 Memory 负载 CPU 使用统计时间段 各进程的 CPU 使用率 总的 CPU 使用率 缺页次数 fault xxx minor 表示高速缓存中的缺页次数,可以理解为进程在做内存访问 xxx major 表示内存的缺页次数,可以理解为进程在做 IO 操作 CPU 使用汇总 配合 MainLog(Android Log) 和 EventLog 把 CPU 开始和结束的时间点内的所有有用信息提取出来到一个文件中. 收集关键操作,比如解锁、安装应用、亮灭屏、应用启动等 收集异常和系统关键 Log 系统变慢 :比如 Slow operation、Slow dispatch、Slow delivery、dvm_lock_sample 进程变化 :am_kill、am_proc_died、lowmemorykiller、ANR、应用启动关系等 系统信息 :cpu info、meminfo、binder info(是否满了) 、iowait (是否过高) 收集 ANR 进程的所有关键线程的运行情况、线程优先级等 根据第四步提取出来的关键信息文件,进一步理出系统当时的情况、状态((推荐 vscode 或者 notepad ++ ,有 线索就全局搜索)),比如 是处于低内存频繁杀进程? 重启第一次解锁系统繁忙 还是短时间内多个应用启动系统繁忙 还是应用自己的逻辑等待? 不行就加 Log 复现. 区分是应用的问题还是系统的问题 首先应该分析是否是应用的问题 分析应用的问题的关键是需要理清当时用户的操作是什么,应用在用户这个操作的过程中扮演了什么角色,然后再进行进一步的分析 分析应用是否关键组件中的生命周期中有耗时操作,可能平时没有暴露出来,一旦系统负载上来,就会暴露问题(建议在关键生命周期函数中加上对应的 Log,方便 Debug)。 分析是否出现极端情况,导致应用的逻辑耗时,比如大量的数据处理或者导入,同时运行线程过多等(看应用的 cpu \ io 使用情况)。 分析是否存在死锁。 分析是否是在等待 binder 返回。 分析 Trace 文件中 MainThread 和 RenderThread 是否存在异常。 分析 Trace 文件中 MainThread 跟 WorkerThread 是否存在等待关系。 分析系统的状态 查看 CPU 使用情况(cpu 使用率和 cpu 负载),看看 SystemServer、lowmemorykiller、HeapTaskDeamon、Audio、SurfaceFlinger 这些系统相关的进程或者线程是否占用高 查看是否存在大量 IO 的情况,看 io 负载 faults: 118172 minor(高速缓存的缺页次数)。 major(内存的缺页次数)。 查看系统是否是低内存 看 dumpsys meminfo 的结果,看看是否处于低内存。 kernel log 是否有频繁的 lowmemorykiller。 event log 是否有频繁的应用被系统低内存策略杀掉。 kswapd0 。 应用是否被冻结:应用处于 D 状态,发生 ANR,如果最后的操作是 refriger,那么是应用被冻结了,正常情况下是功耗优化引起的,可以找一下前后是否有 xxxHansManager : unfreeze 这样的 Log;或者在 Systrace 中的 Kernel Callstack 显示 :{kernel callsite when blocked:: “__refrigerator+0xe4/0x198”}。 查看是否存在系统异常,比如自研功能导致系统繁忙,没有及时响应应用 Binder 之类的,这种需要分析 Log 中 SystemServer 的日志输出,查看是否有异常的 Log 输出。 继续分析:应用可以解决还是系统可以解决 ANR 问题如果转给系统,大概率是无解的。 如果应用的代码是正常的,也没有极端的使用场景和数据处理,纯粹是由于系统或者其他的 App 造成的,那么可以转给系统处理。 如果应用代码本身有一定的问题,在非极端场景或者非系统异常的不会暴露出来,那么需要应用这边想一想规避方案。 ANR Log 分析 Log 可由测试使用 Log 工具抓取,可由线上平台获取,也可以自己抓取: 1 adb bugreport log.zip1 ANR Trace Log 所包含的内容如下 线程详细数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 flags=1 obj=0x72c8bbf8 self=0xb400007b0ec10800 | sysTid=5991 nice=-10 cgrp=default sched=0/0 handle=0x7b95f61500 | state=S schedstat=( 807053249 267562324 1494 ) utm=63 stm=17 core=3 HZ=100 | stack=0x7fcccd9000-0x7fcccdb000 stackSize=8192KB | held mutexes= native: #00 pc 00000000000c6418 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) native: #01 pc 0000000000019a9c /system/lib64/libutils.so (android::Looper::pollInner(int)+184) native: #02 pc 000000000001997c /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112) native: #03 pc 0000000000114310 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:339) at android.os.Looper.loop(Looper.java:198) at android.app.ActivityThread.main(ActivityThread.java:8142) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006) 上面 Trace 中 部分字段的含义 线程堆栈 下面是该线程对应的调用堆栈 1 2 3 4 5 6 7 8 9 10 11 native: #00 pc 00000000000c6418 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) native: #01 pc 0000000000019a9c /system/lib64/libutils.so (android::Looper::pollInner(int)+184) native: #02 pc 000000000001997c /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112) native: #03 pc 0000000000114310 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:339) at android.os.Looper.loop(Looper.java:198) at android.app.ActivityThread.main(ActivityThread.java:8142) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006) 分为 Java 和 Native,一般来说,nativePollOnce 是在等待 Message,这是正常的 线程状态 Thread.java 中的状态和 Thread.cpp 中的状态是有对应关系的。可以看到前者更加概括,也比较容易理解,面向 Java 的使用者;而后者更详细,面向虚拟机内部的环境。traces.txt 中显示的线程状态都是 Thread.cpp 中定义的,完整的对应关系如下 art/runtime/thread_state.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // State stored in our C++ class Thread. // When we refer to "a suspended state", or when function names mention "ToSuspended" or // "FromSuspended", we mean any state other than kRunnable, i.e. any state in which the thread is // guaranteed not to access the Java heap. The kSuspended state is merely one of these. enum ThreadState { // Java // Thread.State JDWP state kTerminated = 66, // TERMINATED TS_ZOMBIE Thread.run has returned, but Thread* still around kRunnable, // RUNNABLE TS_RUNNING runnable kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() kBlocked, // BLOCKED TS_MONITOR blocked on a monitor kWaiting, // WAITING TS_WAIT in Object.wait() kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run kWaitingPerformingGc, // WAITING TS_WAIT performing GC kWaitingForDebuggerSend, // WAITING TS_WAIT blocked waiting for events to be sent kWaitingForDebuggerToAttach, // WAITING TS_WAIT blocked waiting for debugger to attach kWaitingInMainDebuggerLoop, // WAITING TS_WAIT blocking/reading/processing debugger events kWaitingForDebuggerSuspension, // WAITING TS_WAIT waiting for debugger suspend all kWaitingForJniOnLoad, // WAITING TS_WAIT waiting for execution of dlopen and JNI on load code kWaitingForSignalCatcherOutput, // WAITING TS_WAIT waiting for signal catcher IO to complete kWaitingInMainSignalCatcherLoop, // WAITING TS_WAIT blocking/reading/processing signals kWaitingForDeoptimization, // WAITING TS_WAIT waiting for deoptimization suspend all kWaitingForMethodTracingStart, // WAITING TS_WAIT waiting for method tracing to start kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish kNativeForAbort, // WAITING TS_WAIT checking other threads are not run on abort. kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code kNative, // RUNNABLE TS_RUNNING running in a JNI native method kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger }; Trace 文件中,线程名的最后一行标识的就是当前线程的状态 正常主线程 Trace 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 flags=1 obj=0x72c8bbf8 self=0xb400007b0ec10800 | sysTid=5991 nice=-10 cgrp=default sched=0/0 handle=0x7b95f61500 | state=S schedstat=( 807053249 267562324 1494 ) utm=63 stm=17 core=3 HZ=100 | stack=0x7fcccd9000-0x7fcccdb000 stackSize=8192KB | held mutexes= native: #00 pc 00000000000c6418 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8) native: #01 pc 0000000000019a9c /system/lib64/libutils.so (android::Looper::pollInner(int)+184) native: #02 pc 000000000001997c /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112) native: #03 pc 0000000000114310 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:339) at android.os.Looper.loop(Looper.java:198) at android.app.ActivityThread.main(ActivityThread.java:8142) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006) 异常主线程 Trace 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 "main" prio=5 tid=1 Blocked | group="main" sCount=1 dsCount=0 flags=1 obj=0x70f65400 self=0xe28dae00 | sysTid=22002 nice=-10 cgrp=default sched=0/0 handle=0xe9674474 | state=S schedstat=( 1943159901 290647362 1661 ) utm=159 stm=34 core=7 HZ=100 | stack=0xff041000-0xff043000 stackSize=8192KB | held mutexes= at com.facebook.cache.disk.DiskStorageCache.e(DiskStorageCache.java:3) - waiting to lock (a java.lang.Object) held by thread 89 at com.xxx.community.util.imageloader.FrescoImageLoader.a(FrescoImageLoader.java:18) at com.xxx.community.util.imageloader.FrescoImageLoader$2$1.run(FrescoImageLoader.java:2) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:254) at android.app.ActivityThread.main(ActivityThread.java:8142) at java.lang.reflect.Method.invoke(Native method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1006) CPU 使用率分析 搜索 ANR in 关键字可以看到 ANR 前一段时间内的 CPU 使用情况,其解析如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ActivityManager: ANR in com.xxx.launcher (com.xxx.launcher/.Launcher)(进程名) ActivityManager: PID: 5991(进程 pid) ActivityManager: Reason: Input dispatching timed out (6a6083a com.xxx.launcher/com.xxx.launcher.Launcher (server) is not responding. Waited 5001ms for FocusEvent(hasFocus=true))(原因) ActivityManager: Parent: com.xxx.launcher/.Launcher ActivityManager: Load: 15.29 / 5.19 / 1.87(Load 表明是 1 分钟,5 分钟,15 分钟 CPU 的负载) ActivityManager: ----- Output from /proc/pressure/memory -----(内存压力) ActivityManager: somavg10=1.35 avg60=0.31 avg300=0.06 total=346727 ActivityManager: full avg10=0.00 avg60=0.00 avg300=0.00 total=34803 ActivityManager: ----- End output from /proc/pressure/memory ----- // 13s 内的 cpu 使用情况 ActivityManager: CPU usage from 0ms to 13135ms later (2020-09-09 02:09:54.942 to 2020-09-09 02:10:08.077): ActivityManager: 191%(CPU 的使用率) 1948/system_server: 72%(用户态的使用率) user + 119%(内核态的使用率) kernel / faults: 78816 minor 9 major ActivityManager: 10% 2218/android.bg: 3.6% user + 6.6% kernel ActivityManager: 30% 5991/com.xxx.launcher: 23% user + 6.4% kernel / faults: 118172 minor(高速缓存的缺页次数) 2 major(内存的缺页次数) ActivityManager: 16% 6174/launcher-loader: 13% user + 2.8% kernel ActivityManager: 3.9% 5991/m.xxx.launcher: 3.1% user + 0.8% kernel ActivityManager: 20% 6549/com.xxx.xxx: 16% user + 3.7% kernel / faults: 3541 minor ActivityManager: 10% 6889/DBCacheManager: 8.7% user + 1.2% kernel ActivityManager: 9.4% 6942/DefaultDispatch: 7.1% user + 2.2% kernel // 1s 内的 各个进程各个线程的 cpu 使用情况 ActivityManager: CPU usage from 246ms to 1271ms later (2020-09-09 02:09:55.188 to 2020-09-09 02:09:56.213): ActivityManager: 290% 1948/system_server: 114% user + 176% kernel / faults: 9353 minor ActivityManager: 32% 5159/LockSettingsSer: 29% user + 2.9% kernel ActivityManager: 25% 8661/AnrConsumer: 8.8% user + 16% kernel ActivityManager: 44% 5991/com.xxx.launcher: 37% user + 7.4% kernel / faults: 5756 minor ActivityManager: 16% 6174/launcher-loader: 13% user + 3.7% kernel ActivityManager: 14% 5991/m.xxx.launcher: 14% user + 0% kernel ActivityManager: 37% 6549/com.xxx.xxx: 28% user + 9.3% kernel / faults: 153 minor ActivityManager: 37% 6942/DefaultDispatch: 28% user + 9.3% kernel ActivityManager: 20% 5962/com.android.phone: 14% user + 5.5% kernel / faults: 1345 minor ActivityManager: 11% 5962/m.android.phone: 7.4% user + 3.7% kernel CPU 负载 1 ActivityManager: Load: 15.29 / 5.19 / 1.871 Load 后面的三个数字的意思分别是 1 分钟、5 分钟、15 分钟内系统的平均负荷。当 CPU 完全空闲的时候,平均负荷为 0;当 CPU 工作量饱和的时候,平均负荷为 1,通过 Load 可以判断系统负荷是否过重 有一个形象的比喻:个= CPU 想象成一座大桥,桥上只有一根车道,所有车辆都必须从这根车道上通过,系统负荷为 0,意味着大桥上一辆车也没有,系统负荷为 0.5,意味着大桥一半的路段有车,系统负荷为 1.0,意味着大桥的所有路段都有车,也就是说大桥已经”满”了,系统负荷为 2.0,意味着车辆太多了,大桥已经被占满了 (100%),后面等着上桥的车辆还有一倍。大桥的通行能力,就是 CPU 的最大工作量;桥梁上的车辆,就是一个个等待 CPU 处理的进程(process) 经验法则是这样的 1.当系统负荷持续大于 0.7,你必须开始调查了,问题出在哪里,防止情况恶化 2.当系统负荷持续大于 1.0,你必须动手寻找解决办法,把这个值降下来 3.当系统负荷达到 5.0,就表明你的系统有很严重的问题 现在的手机是多核 CPU 架构,八核的多的是,意味着 Cpu 处理的能力就乘以了8,每个核运行的时间可以从下面的文件中得到,/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state 中读取的,%d 代表是 CPU 的核。文件中记录了 CPU 从开机到读取文件时,在各个频率下的运行时间,单位:10 ms Memory 负载 1 2 3 4 ActivityManager: ----- Output from /proc/pressure/memory -----(内存压力) ActivityManager: somavg10=1.35 avg60=0.31 avg300=0.06 total=346727 ActivityManager: full avg10=0.00 avg60=0.00 avg300=0.00 total=34803 ActivityManager: ----- End output from /proc/pressure/memory ----- Memory 负载是从 /proc/pressure/memory 中获取的 avg10、avg60、avg300 分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间,以毫秒为单位 some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非 idle 任务同时被阻塞的时间占比,这期间 cpu 被完全浪费,会带来严重的性能问题。我们以 IO 的 some 和 full 来举例说明,假设在 60 秒的时间段内,系统有两个 task,在 60 秒的周期内的运行情况如下图所示: 红色阴影部分表示任务由于等待 IO 资源而进入阻塞状态。Task A 和 Task B 同时阻塞的部分为 full,占比 16.66%;至少有一个任务阻塞(仅 Task B 阻塞的部分也计算入内)的部分为 some,占比 50%。 some 和 full 都是在某一时间段内阻塞时间占比的总和,阻塞时间不一定连续,如下图所示: 具体内容可以参考内核工匠的文章: https://blog.csdn.net/feelabclihu/article/details/105534140 IO 负载 1 ActivityManager: 30% 5991/com.xxx.launcher: 23% user + 6.4% kernel / faults: 118172 minor(高速缓存的缺页次数) 2 major(内存的缺页次数) Minor,高速缓存的缺页次数,指的是 Minor Page Fault(次要页错误,简称 MnPF),磁盘数据被加载到内存后,内核再次读取时,会发出一个 MnPF 信息。 一个文件第一次被读写时会有很多的 MPF,被缓存到内存后再次访问 MPF 就会很少,MnPF 反而变多,这是内核为减少效率低下的磁盘 I/O 操作采用的缓存技术的结果可以理解为进程在做内存访问 Major,内存的缺页次数,指 Major Page Fault(主要页错误,简称 MPF),内核在读取数据时会先后查找 CPU 的高速缓存和物理内存,如果找不到会发出一个 MPF 信息,请求将数据加载到内存可以理解为进程在做 IO 操作 如果有大量的 major,那么说明当时 IO 操作负载比较高 进程负载 1 ActivityManager: 30% 5991/com.xxx.launcher: 23% user + 6.4% kernel / faults: 118172 minor(高速缓存的缺页次数) 2 major(内存的缺页次数) 1.23% user:用户态的 cpu 占用 2.6.4% kernel:内核态的 cpu 占用 CPU 异常进程 SystemServer cpu 占用偏高,系统整体运行会缓慢 kswapd0 cpu 占用率偏高,系统整体运行会缓慢,从而引起各种 ANR。把问题转给”内存优化”,请他们进行优化 logd CPU 占用率偏高,也会引起系统卡顿和 ANR,因为各个进程输出 LOG 的操作被阻塞从而执行的极为缓慢。 Vold 占用 CPU 过高,会引起系统卡顿和 ANR,请负责存储的同学先调查 应用自身 CPU 占用率较高,高概率应用自身问题 应用处于 D 状态,发生 ANR,如果最后的操作是 refriger,那么是应用被冻结了,正常情况下是功耗优化引起的,可以找一下前后是否有 xxxHansManager : unfreeze 这样的 Log;或者在 Systrace 中的 Kernel Callstack 显示 :{kernel callsite when blocked:: “__refrigerator+0xe4/0x198”} CPU 使用率前面的 “+”。部分进程的 CPU 使用率前面有 “+” 号,譬如 cat 和 zygote64,表示在上一次 CPU 统计的时间片段内,还没有这些进程,而这一次 CPU 统计的时间片段内,运行了这些进程。 类似的还有 “-” 号,表示两次 CPU 统计时间片段时,这些进程消亡了 系统关键 Log 介绍 应用冻结 1 xxxHansManager : freeze uid: 10245 package: com.tencent.mm reason: LcdOff 如果冻结逻辑有 Bug,也会导致应用产生 ANR,这一行 Log 比较简单,主要是被冻结的进程信息和被冻结的原因 ActivityManager : Slow operation AMS 在执行关键任务的时候,如果任务耗时超过 50 ms,则会打印对应的 Log frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 1 2 3 4 5 6 7 void checkTime(long startTime, String where) { long now = SystemClock.uptimeMillis(); if ((now-startTime) > 50) { // If we are taking more than 50ms, log about it. Slog.w(TAG, "Slow operation: " + (now-startTime) + "ms so far, now at " + where); } } 对应的 Log 如下,如果系统中频繁打印这种 Log,说明系统目前处于一个比较卡的状态,分析的时候就得考虑到系统的因素 1 2 3 ActivityManager: Slow operation: 138ms so far, now at startProcess: done updating battery stats ActivityManager: Slow operation: 138ms so far, now at startProcess: building log message ActivityManager: Slow operation: 138ms so far, now at startProcess: starting to update pids map Looper : Slow dispatch 1 Looper : Slow dispatch took 418ms main h=android.app.ActivityThread$H c=android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA@e68bdc4 m=0 Looper : Slow delivery 1 Looper : Slow delivery took 361ms android.ui h=com.android.server.am.ActivityManagerService$UiHandler c=null m=53 Looper:Slow Looper 1 W/Looper: Slow Looper main: Activity com.androidperformance.memoryfix/.MainActivity is 439ms late (wall=0ms running=0ms ClientTransaction{ callbacks=[android.app.servertransaction.TopResumedActivityChangeItem] }) because of 3 msg, msg 2 took 268ms (seq=2 running=207ms runnable=15ms late=1ms h=android.app.ActivityThread$H w=110), msg 3 took 171ms (seq=3 running=140ms runnable=5ms io=1ms late=268ms h=android.app.ActivityThread$H w=159) onTrimMemory 1 ClockTag AlarmClockApplication: onTrimMemory: 80 dvm_lock_sample 当某个线程等待 lock 的时间 blocked 超过阈值(比如:500ms),则输出当前的持锁状态. 1 dvm_lock_sample:[system_server,1,Binder_9,1500,ActivityManagerService.java,6403,-,1448,0] system_server: Binder_9 执行到 ActivityManagerService.java 的 6403 行代码,一直在等待 AMS 锁 “-“代表持锁的是同一个文件,即该锁被同一文件的 1448 行代码所持有, 从而导致 Binder_9 线程被阻塞 1500ms. binder_sample binder_sample: 监控每个进程的主线程的 binder transaction 的耗时情况, 当超过阈值(比如:500ms)时,则输出相应的目标调用信息. 1 6628 6628 I binder_sample: [android.view.accessibility.IAccessibilityManager,6,2010,com.xxx.community,100] 进程是 6628,主线程 6628 执行 android.view.accessibility.IAccessibilityManager 接口 所对应方法 code = 6 ( 即 TRANSACTION_addAccessibilityInteractionConnection ) 所花费时间为 2010 ms 该 block 所在 package 为 com.xxx.community 最后一个参数是 sample 比例 查找对应的接口函数,比如上面例子里面 IAccessibilityManager 中 Code = 6 是对应的哪个函数,可以在 cs.android.com 里面 搜索 FIRST_CALL_TRANSACTION ,点击调用,然后查看 out 目录里面的对应的 IAccessibilityManager 文件(一直往下滑,直到可以搜索到 IAccessibilityManager ) 其中对应的 static final int TRANSACTION_addAccessibilityInteractionConnection = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5) Long monitor contention 1 16809 14216 W system_server: Long monitor contention with owner InputDispatcher (17039) at android.content.res.Configuration com.android.server.wm.ActivityTaskManagerService.getGlobalConfigurationForPid(int)(ActivityTaskManagerService.java:1066) waiters=0 in boolean com.android.server.wm.WindowProcessController.hasActivities() for 141ms art/runtime/monitor.cc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 std::string Monitor::PrettyContentionInfo(const std::string& owner_name, pid_t owner_tid, ArtMethod* owners_method, uint32_t owners_dex_pc, size_t num_waiters) { Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); const char* owners_filename; int32_t owners_line_number = 0; if (owners_method != nullptr) { TranslateLocation(owners_method, owners_dex_pc, &owners_filename, &owners_line_number); } std::ostringstream oss; oss PrettyMethod(); oss >> msg's executing time is too long E ANR_LOG : Blocked msg = { when=-32s683ms what=110 target=android.app.ActivityThread$H obj=AppBindData{appInfo=ApplicationInfo{bd8d51e com.android.contacts}} } , cost = 32436 ms E ANR_LOG : >>>Current msg List is: E ANR_LOG : Current msg = { when=-32s672ms what=140 target=android.app.ActivityThread$H arg1=5 } E ANR_LOG : Current msg = { when=-32s671ms what=114 target=android.app.ActivityThread$H obj=CreateServiceData{token=android.os.BinderProxy@f7611ff className=com.android.contacts.xxxAppServicesManagerClient packageName=com.android.contacts intent=null} } E ANR_LOG : Current msg = { when=-32s671ms what=121 target=android.app.ActivityThread$H obj=BindServiceData{token=android.os.BinderProxy@f7611ff intent=Intent { cmp=com.android.contacts/.xxxAppServicesManagerClient }} } E ANR_LOG : Current msg = { when=-31s658ms what=1 target=android.os.AsyncTask$InternalHandler obj=android.os.AsyncTask$AsyncTaskResult@75e517c } E ANR_LOG : Current msg = { when=-29s750ms what=140 target=android.app.ActivityThread$H arg1=10 } E ANR_LOG : Current msg = { when=-29s103ms what=118 target=android.app.ActivityThread$H obj={1.0 460mcc3mnc [zh_CN] ldltr sw360dp w360dp h622dp 480dpi nrml long port finger -keyb/v/h -nav/h appBounds=Rect(0, 0 - 1080, 1920) s.10mThemeChanged = 0mThemeChangedFlags = 0mFlipFont = 0} E ANR_LOG : Current msg = { when=-28s370ms what=118 target=android.app.ActivityThread$H obj={1.0 460mcc11mnc [zh_CN] ldltr sw360dp w360dp h622dp 480dpi nrml long port finger -keyb/v/h -nav/h appBounds=Rect(0, 0 - 1080, 1920) s.11mThemeChanged = 0mThemeChangedFlags = 0mFlipFont = 0} } E ANR_LOG : Current msg = { when=-27s821ms what=122 target=android.app.ActivityThread$H obj=BindServiceData{token=android.os.BinderProxy@f7611ff intent=Intent { cmp=com.android.contacts/.xxxAppServicesManagerClient }} } E ANR_LOG : Current msg = { when=-27s821ms what=116 target=android.app.ActivityThread$H obj=android.os.BinderProxy@f7611ff } E ANR_LOG : Current msg = { when=-27s654ms what=114 target=android.app.ActivityThread$H obj=CreateServiceData{token=android.os.BinderProxy@e23cf1b className=com.android.contacts.xxxAppServicesManagerClient packageName=com.android.contacts intent=null} } E ANR_LOG : >>>CURRENT MSG DUMP OVER null => App WindowManager: Changing focus from Window{b0416d7 u0 com.xxx.launcher/com.xxx.launcher.Launcher} to null,diplayid=0 WindowManager: Changing focus from null to Window{10f5145 u0 com.android.settings/com.android.settings.Settings},diplayid=0 // 从 App 返回桌面,focus 变化 :App => null => Launcher WindowManager: Changing focus from Window{10f5145 u0 com.android.settings/com.android.settings.Settings} to null,diplayid=0 WindowManager: Changing focus from null to Window{b0416d7 u0 com.xxx.launcher/com.xxx.launcher.Launcher},diplayid=0 // 从 App 界面进入锁屏:focus 变化 :App => null => 锁屏 WindowManager: Changing focus from Window{10f5145 u0 com.android.settings/com.android.settings.Settings} to null,diplayid=0 WindowManager: Changing focus from null to Window{82e5f30 u0 NotificationShade},diplayid=0 // 从锁屏界面解锁进入 App,focus 变化 :锁屏 => App WindowManager: Changing focus from Window{82e5f30 u0 NotificationShade} to Window{10f5145 u0 com.android.settings/com.android.settings.Settings},diplayid=0 ANR 相关资料分享 西瓜视频稳定性治理体系建设一:Tailor 原理及实践 西瓜视频稳定性治理体系建设二:Raphael 原理及实践 西瓜视频稳定性治理体系建设三:Sliver 原理及实践 西瓜卡顿 & ANR 优化治理及监控体系建设 今日头条 ANR 优化实践系列 - 设计原理及影响因素 今日头条 ANR 优化实践系列 - 监控工具与分析思路 今日头条 ANR 优化实践系列分享 - 实例剖析集锦 今日头条 ANR 优化实践系列 - Barrier 导致主线程假死 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待 Android ANR|原理解析及常见案例 参考资料 https://duanqz.github.io/2015-10-12-ANR-Analysis#1-%E6%A6%82%E8%A7%88 https://duanqz.github.io/2015-10-12-ANR-Analysis http://gityuan.com/2016/12/02/app-not-response/ http://gityuan.com/2017/01/01/input-anr/ https://xiaozhuanlan.com/topic/5097486132 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文为 Android App ANR 系列的第一篇,主要是从系统的角度来剖析 Android ANR 的设计思想,系列文章目录如下 Android App ANR 系列 1 :理解 Android ANR 设计思想 Android App ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android App ANR 系列 3 :ANR 案例分享 一、ANR 的普遍性与复杂性 在 Android 生态系统中,应用无响应(ANR,Application Not Responding)不仅是开发者面临的常见挑战,更是系统设计哲学的核心体现。虽然 ANR 常被简化为“主线程耗时操作”的代名词,但这种表面化的理解远远不足以揭示问题的本质。实际上,ANR 的根本原因在于 Android 多进程架构、事件分发和资源调度机制之间复杂的协同作用,其实质是系统层面对应用行为实施严格约束与监控的综合体现。 Android 明确将 ANR 定性为应用层问题,这与 SNR(System Not Responding)形成了鲜明对比。SNR 指的是系统进程(如 system_server)失去响应能力,通常依靠 Watchdog 机制通过监控关键系统线程状态来实现;而 ANR 则依托于消息调度机制,系统进程利用精心设计的超时模型追踪应用主线程的响应能力。这种区别反映了系统针对不同层级问题所采取的治理策略: SNR 侧重于确保系统核心服务的生存,采用主动轮询的监控方式; ANR 则聚焦于应用进程的实时响应,通过事件驱动的异步检测机制来进行判断。 从系统架构的角度来看,ANR 机制主要实现于系统层(即 system_server 进程中),其核心在于构建跨进程的事件监控体系。当应用进程通过 Binder 向系统服务发起操作请求(如启动 Activity、处理广播等)时,系统会同步启动超时计时器;而对于输入事件这类异步操作,InputDispatcher 会通过 socket 与窗口建立事件通道,并在事件派发后启动超时检测。这种分层监控设计充分体现了 Android 针对不同任务类型所采用的差异化处理策略。 ANR 机制的深层意义在于平衡开放性与系统可控性。作为一个开放平台,Android 允许应用自由申请硬件资源(如 CPU、IO、内存 等),但必须通过严格的规则防止单个应用的异常行为蔓延至整个系统。当检测到超时事件时,系统会启动多维度熔断机制:首先,通过强制终止问题进程来释放关键系统资源(例如防止其占用 Binder 线程池或文件描述符),从而避免级联故障;同时,系统会冻结进程状态,并采集 CPU 使用率、线程堆栈、内存快照等关键信息,将这些数据写入 /data/anr/traces.txt,为后续问题分析保留现场。更巧妙的是,系统还通过用户可见的弹窗将最终操作权交还给用户,从而既避免了自动化处理可能带来的误判风险,又保持了人机交互的连续性。这种“故障隔离—现场保护—用户决策”相结合的设计,充分展现了 Android 在技术严谨性与用户体验友好性之间的平衡智慧。 二、ANR 的核心设计哲学 ANR 的本质:系统级监控与强制干预 ANR 机制构成了 Android 系统架构中一套深层次的稳定性防御体系,其核心在于通过 跨层协同监控 与 异步决策隔离 构建一个独立于应用状态的全局安全网。这种设计远非简单的超时检测,而是深深植根于 Linux 进程沙箱机制和 Android 组件架构的有机结合中。系统进程(如 system_server)通过 ActivityManagerService (AMS) 与 InputManagerService (IMS) 这两大核心模块,分别对组件生命周期和输入事件流实施全方位监控。正因这种分层架构,监控逻辑得以与业务逻辑解耦,即使目标应用的主线程完全阻塞,系统依然可以依靠独立线程进行超时裁决,从根本上避免了“监控者反被监控对象拖垮”的风险。 在实现层面,ANR 充分体现了事件驱动型系统设计的精髓。例如,在组件类 ANR 的场景中,当 AMS 通过 Binder 向应用进程派发跨进程任务时,系统会同步启动一个倒计时器(例如针对 Service 启动的 20 秒阈值),这一“埋炸弹”机制实质上将异步任务转化为带有超时约束的同步契约。应用进程在完成任务后必须通过 Binder 回调主动“拆弹”,否则系统将介入收集现场信息(如主线程堆栈)并触发用户交互。整个过程由系统进程主导,应用仅作为事件响应方存在,从而确保监控的绝对权威性。 任务派发依赖 Binder 的同步调用来确保原子性,同时 AMS 通过专门的 Handler 将超时检测消息推入消息队列,从而在规定时间内监控任务的执行情况。这种设计既保证了跨进程通信期间任务的完整性,也能在超时后迅速触发熔断处理。 组件类 ANR:异步任务的全局防护逻辑 组件类 ANR 的监控逻辑围绕 ActivityManagerService (AMS) 展开,其本质是通过 任务派发–回调–熔断 的三阶段模型,实现对异步任务生命周期的全链路追踪。当系统通过 Binder 跨进程通信向应用派发任务(例如启动 Service)时,AMS 会同步启动超时检测机制:利用 MainHandler 发送延迟消息实现精确计时。以 Service 启动为例,当 AMS 调用 IApplicationThread 的 scheduleCreateService() 后,会启动对应的超时监控(默认 20 秒)。如果应用在规定时间内未通过 serviceDoneExecuting() 回调通知 AMS,则触发 ANR 判定。 开发者需要特别注意 跨进程回调的时序陷阱:即使异步任务在子线程完成,但若主线程因消息队列堵塞(例如过度调用 runOnUiThread)导致 Binder 回调延迟,系统依然会判定为 ANR。 Android 14+ 引入的 ProcessStateRecord 对进程状态进行了更细粒度的划分,不仅详细记录了主线程消息处理的状态,还实时监控后台任务和挂起状态,从而降低了误判率,并为开发者提供了更丰富的调试信息。 这一设计的关键在于 同步事务与异步熔断的解耦。任务派发依靠 Binder 的同步调用确保原子性,而超时检测则通过 Handler 消息机制异步执行,避免阻塞系统主线程。 当 ANR 触发时,系统会执行多维度的熔断策略: 现场采集 系统会收集主线程堆栈信息、CPU 使用情况、进程状态等关键数据,并将这些数据写入 /data/anr/traces.txt 文件。同时,系统还会利用 ProcessCpuTracker 记录详细的 CPU 使用统计,为后续问题分析提供依据。 资源隔离 系统通过 ProcessRecord 的调度优先级调整机制确保熔断决策的实时性,从而保证即使在系统负载较高的情况下,ANR 处理流程也能得到及时执行。 诊断数据收集 系统提供 ApplicationExitInfo API,允许开发者查询历史 ANR 记录,包括发生时间、进程状态、异常堆栈等详细信息,这些数据对于问题复现和根因分析极为重要。 值得注意的是,Android 15 对后台服务施加了更严格的约束:前台服务必须在 3 秒内完成初始化并调用 startForeground(),否则系统将直接触发 ANR。具体而言,系统通过内部属性(例如 persist.sys.fgs_timeout)以及 API 参数(如 AMS 内部控制前台服务启动超时的参数)来管理这一超时机制。开发者可参照最新的 API 文档了解这些变更,从而在设计服务时确保满足严格的响应时限要求。 系统还提供了多种工具来支持 ANR 问题的诊断和分析: 系统日志收集:开发者可通过 adb 命令获取 ANR 堆栈信息和系统报告,其中包含了问题发生时的详细系统状态。 性能分析工具:Android Studio 的 CPU Profiler 能够实时监控应用性能,帮助开发者发现潜在的性能问题。 系统级分析:Perfetto 提供了强大的系统级性能分析能力,帮助开发者理解复杂的性能问题。 通过这种多层次的监控和防护机制,Android 系统确保了应用响应性能的可靠性,并为开发者提供了完整的问题诊断工具链。开发者需深入理解这些机制,在应用设计中充分考虑性能因素,遵循系统生命周期契约,合理管理主线程负载,确保关键回调的及时响应。 这种设计哲学体现了 Android 平台对应用质量的严格要求:通过明确的超时限制和完善的监控机制,推动开发者构建更可靠、响应更及时的应用。同时,丰富的诊断工具也为开发者提供了必要的支持,帮助他们在遇到问题时能快速定位并解决问题。 Input 类 ANR:输入事件分发的动态熔断体系 输入类 ANR 的监控机制更为复杂,其核心挑战在于如何在 高实时性要求 与 资源高效利用 之间取得平衡。从硬件事件产生到应用主线程处理,输入系统通过 EventHub、InputReader 和 InputDispatcher 三大组件协同构建了一条高效且可控的事件分发链路。 事件读取层 (EventHub) EventHub 利用 Linux 的 epoll 机制监听 /dev/input 设备节点,支持多设备并发监听,并通过事件驱动模型(而非轮询)实现零空闲 CPU 消耗。当硬件中断触发时,系统通过 inotify 接收原始输入数据,并将其封装为 RawEvent。 事件预处理 (InputReader) InputReader 通过特定的 InputMapper 对原始数据进行设备相关的预处理(如触摸校准),并将其转换为标准的输入事件(如 MotionEvent 或 KeyEvent)。同时,根据设备类型和配置进行必要的事件过滤,确保数据质量。 事件分发层 (InputDispatcher) InputDispatcher 的核心职责是确定当前焦点窗口,并通过基于 Unix Domain Socket 的 InputChannel 将事件推送至应用进程。它采用 multiplexing 机制高效管理多个 InputChannel,并依赖 WindowManagerService 获取最新窗口焦点信息,确保事件准确送达目标窗口。 输入 ANR 机制依赖于对事件状态的持续追踪与超时判定,其核心在于 队列状态管理 与 跨线程协作模型 的设计: inboundQueue:存储从 InputReader 接收的待分发事件 outboundQueues:为每个连接维护的输出队列,同时通过 waitQueue 跟踪已分发但未收到完成响应的事件 waitQueue:记录已经分发出去但尚未收到应用端处理确认的事件。 当事件被分发后,系统通过 MonitoredTimeout 机制跟踪其处理状态。默认超时时间为 5000 毫秒(可通过系统属性调整),超时检测采用事件驱动模式,在新事件到达、应用回调完成或周期性心跳检查时触发。一旦检测到超时,系统会通过 WindowManagerService 通知 ActivityManagerService,并收集包括 InputDispatcher 状态及应用进程信息在内的诊断数据,随后可能触发 ANR 弹窗及进程重启流程。 整个输入系统采用了优化的线程模型设计: InputReaderThread 专注于事件读取与预处理 InputDispatcherThread 负责事件分发与超时监控 两者通过无锁队列实现高效的线程间通信,使得即使某个应用进程的主线程阻塞,系统层面的输入处理依然能够正常运行,从而有效防止问题扩散。 对于开发者而言,应特别关注主线程的响应性,避免在输入事件处理回调中执行耗时操作。同时,理解输入系统的分层设计有助于在性能优化时从整体角度提高事件处理链路的效率。 No Focused Window 类 ANR No Focused Window ANR 是输入系统中另一类重要的无响应场景,其本质在于窗口焦点状态异常,导致输入事件无法正确分发。与常规输入超时不同,这类 ANR 反映的是 WindowManager 子系统与输入系统之间的协同问题。 在 WindowManagerService (WMS) 的设计中,窗口焦点管理是一个独立而复杂的子系统。当用户界面发生变化(如 Activity 切换或对话框弹出)时,系统会触发一系列窗口事务操作:首先对旧窗口执行 relayoutWindow 以解除焦点标记,然后为新窗口执行 addWindow 并授予焦点。这些状态变化会通过 WindowManagerPolicy 实时同步至 InputDispatcher,确保输入事件能够路由到当前的焦点窗口。 焦点的获得与丢失由多种系统行为触发。例如: 焦点获得:新 Activity 启动完成并显示第一帧、Dialog 或 PopupWindow 弹出、分屏模式下触碰窗口区域、从后台任务切换器中恢复应用、解锁后前台应用恢复。 焦点丢失:Activity 被全屏 Activity 覆盖、用户按下 Home 键、系统弹出权限请求等关键级别 Dialog、应用进入后台、设备锁屏等情况。 No Focused Window ANR 往往与窗口生命周期管理异常有关。最常见的情况是在 Activity 切换过程中,由于目标 Activity 的 handleResumeActivity 执行延迟,系统在一定时间内无法确定合法的焦点窗口。与输入超时 ANR 不同,输入超时是目标窗口存在但未及时处理事件,而 No Focused Window ANR 则是系统无法找到合适的事件接收者。基于这一区别,系统对这两类情况采取不同的防护策略:对于输入超时,系统会在默认 5 秒后触发 ANR;而对于无焦点窗口情况,如果连续多次事件分发找不到目标窗口,系统会更快地启动 ANR 流程。 从应用开发角度来看,影响焦点切换的代码路径较为有限,主要涉及 Activity 生命周期回调、窗口添加/移除以及输入事件处理等环节。即使这些环节出现问题(如主线程阻塞),通常也会触发常规 ANR,而非 No Focused Window ANR。因此,在遇到此类问题时,更应关注系统整体资源使用状态、 system_server 进程的 CPU 负载以及系统服务间 Binder 调用延迟等系统级指标,而不是单纯聚焦于某个应用的代码优化。这也是为什么 No Focused Window ANR 常被视为系统性能问题而非应用质量问题的根本原因。 系统设计的统一性原则 无论是组件类 ANR 还是 Input 类 ANR,其监控机制均遵循以下核心原则: 状态可追踪性 通过队列(如 waitQueue)和定时器(例如 AMS 中的 Handler)精确追踪任务进度,确保系统始终掌握应用行为的最新状态。 故障隔离性 在超时后迅速终止问题进程,防止局部故障扩散成系统级雪崩。 用户控制权兜底 通过弹窗提示与进程终止机制,确保用户始终拥有最终的操作权,即使应用内部已完全失控。 开发者约束性 强制要求主线程保持轻量与异步设计,促使应用架构更贴合系统设计哲学。 从架构角度来看,ANR 机制是 Android 系统对 开放生态可控性 的最终回答——它既允许开发者自由创新,又通过刚性规则划定行为边界。这种平衡不仅体现在技术实现上,也深刻影响了整个 Android 应用的性能优化文化。 ANR 问题的全局解析与主动防御 ANR 问题的复杂性要求我们在分析框架中同时具备 技术纵深感 与 系统全局观,并通过递进逻辑将碎片化的现象转化为一个可演进的认知体系。这种方法不仅仅是简单的目录分层,而是利用多维视角的交叉验证,建立从微观代码缺陷到宏观系统约束的完整映射关系。 从现象到根源:逐层解剖 ANR 问题 构建纵向分析路径遵循 “现象 → 机制 → 支撑 → 资源” 的链条式逻辑,其目标在于厘清从用户看到的 ANR 弹窗到硬件资源问题之间的完整链条: 机制表象(ANR 弹窗) 作为用户可见的最外层现象,ANR 弹窗实际上是系统对故障的最终裁决——它并不揭示具体根因,而只是展示结果。开发者往往仅停留在查看堆栈日志、寻找主线程阻塞点的层面,但这就如同只观察火山喷发而忽略了地壳运动的根本驱动因素。 系统实现(AMS/InputDispatcher) 深入系统服务层,ANR 弹窗背后隐藏着 AMS 的 appNotResponding 触发流程。AMS 通过 Binder 事务状态机追踪组件的生命周期,而 InputDispatcher 则利用 socket 事件流监控输入响应。此层分析揭示了 超时判定逻辑的差异性:AMS 采用同步阻塞式检测(例如 BroadcastQueue 的超时计算),而 InputDispatcher 则利用基于 epoll 的异步非阻塞模型实现事件循环监控。 底层支撑(Binder/调度器) 系统服务的高效运行依赖于 Linux 内核的核心机制。Binder 驱动通过内存映射实现跨进程通信,其线程池调度策略(例如 BINDER_MAX_POOL_THREADS 阈值限制)直接影响事务处理能力;而系统的公平调度机制则通过动态分配 CPU 时间片决定主线程是否能够及时获得执行资源。此层的关键在于解析资源分配公平性与实时性之间的矛盾——例如,为了保障多任务的公平性,系统可能允许后台进程的 CPU 密集型任务抢占前台应用的响应时间。 硬件资源(CPU/IO/Memory) 最终,所有软件行为都受限于物理硬件。CPU 的乱序执行可能导致锁竞争问题的随机性;磁盘 I/O 延迟会放大主线程在 SharedPreferences 写入时的阻塞;内存带宽争抢则可能使 RenderThread 无法及时获取纹理数据。此层要求建立 硬件指标与软件行为的关联模型,例如利用 perf 工具分析 CPU 缓存命中率与 ANR 触发频率的相关性。 这种纵向深入并非线性递进,而是一个 循环验证 的过程:当硬件层分析发现内存带宽瓶颈时,需要回溯到 Binder 驱动层,检查是否因频繁跨进程通信引发内存拷贝风暴,最终在系统服务层进行数据传输机制的优化。 从被动应对到主动防御:ANR 治理的三步走 方法论的演进路径——“诊断 → 追踪 → 预测 → 设计”,反映了技术认知成熟度的跃迁,具体步骤包括: 堆栈分析 传统 ANR 分析依赖于 traces.txt 中的线程堆栈,这本质上是故障发生时的静态快照。当问题由偶发竞争条件(如 Binder 线程池瞬时饱和)引起时,堆栈可能显示为正常的 NativePollOnce 状态,而无法揭示真实的资源争抢过程。此时,需要引入 多时间点堆栈对比技术,通过比较 ANR 前后 5 秒内的堆栈变化,识别线程状态迁移模式。 动态追踪 利用 systrace 和 perfetto 等工具提供的毫秒级事件追踪能力,可以监控主线程 Looper 的事件处理周期,量化 dispatchMessage 的执行耗时;同时结合 Binder 驱动中的 binder_transaction 事件,可以绘制跨进程调用的热力图。动态追踪的核心价值在于 揭示隐藏的时间相关性,例如发现输入事件延迟往往紧随 SharedPreferences 磁盘写入操作出现。 机器学习预测 当 ANR 的根因涉及多个子系统交互(如 CPU 调度、内存回收和 I/O 负载的耦合效应)时,传统方法难以处理高维数据。通过收集线程状态、Binder 交互数据以及 CPU 争夺情况等 20 多项指标,利用机器学习算法建立分析模型,可以自动识别 ANR 类型(例如主线程阻塞、IPC 死锁或资源竞争)。Google 已在 Android Vitals 中应用类似技术,实现了 ANR 根因的云端聚合分析。 架构预防性设计 终极目标是从代码设计阶段就内化系统约束,例如: 通信拓扑约束:限制跨进程调用层级,避免 A → B → C 的链式调用,改用事件总线广播模式。 资源预算管理:为每个业务模块分配 Binder 事务配额,超出阈值时自动降级。 异步边界强化:利用 HandlerThread 和 Executor 严格隔离同步与异步操作,防止线程模型出现混乱。 这种从被动应对到主动防御的方法论进化路径,不仅为系统从根源上预防 ANR 提供了有效策略,也为开发者提供了丰富的诊断工具和优化思路。 ANR 相关资料分享 反思|Android 输入系统 & ANR机制的设计与实现 西瓜视频稳定性治理体系建设一:Tailor 原理及实践 西瓜视频稳定性治理体系建设二:Raphael 原理及实践 西瓜视频稳定性治理体系建设三:Sliver 原理及实践 西瓜卡顿 & ANR 优化治理及监控体系建设 今日头条 ANR 优化实践系列 - 设计原理及影响因素 今日头条 ANR 优化实践系列 - 监控工具与分析思路 今日头条 ANR 优化实践系列分享 - 实例剖析集锦 今日头条 ANR 优化实践系列 - Barrier 导致主线程假死 今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待 Android ANR|原理解析及常见案例 参考资料 https://duanqz.github.io/2015-10-12-ANR-Analysis#1-%E6%A6%82%E8%A7%88 https://duanqz.github.io/2015-10-12-ANR-Analysis http://gityuan.com/2016/12/02/app-not-response/ http://gityuan.com/2017/01/01/input-anr/ https://xiaozhuanlan.com/topic/5097486132 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 本周刊可以通过微信公众号、知乎专栏、掘金专栏、个人博客、竹白等平台订阅和阅读。 技术文章 Arm64 中 B 跳转汇编的使用是如何实现的: 这篇文章主要讨论了 ARM64 汇编中关于跳转指令(branch)的使用和实现,以及在不同场景下的代码示例和分析。文章通过 C 代码与汇编的对比,详细解释了 bl(跳转并返回)和 b(直接跳转)指令的功能、实现细节及其在不同情况下的应用。此外,还提到了一些特殊场景下的寄存器跳转实现方式。 从 0 到 1 掌握 Flutter(一)Flutter 与移动端跨平台: 这篇文章主要介绍了 Flutter 这一跨平台开源框架。包括其定义、优势,如提高开发效率、统一代码开发多平台、创建美观定制的用户体验等。还阐述了跨平台技术的进化史,对比了原生开发、Web 混合开发、React Native 等方案的特点,并强调 Flutter 通过创新架构解决了性能瓶颈,成为跨平台开发的重要选择。 系统化掌握 Dart 编程之异常处理(一):筑基之旅: 异常(Exception)是指程序执行过程中发生的意外情况,可能导致程序崩溃或无法正常工作。Dart 提供了强大的异常处理机制,帮助开发者优雅地捕获和处理这些异常,确保程序的稳定性和可靠性。为了系统化地掌握 Dart 的异常处理,我们将从理论基础、具体实现、实践应用到最佳实践四个层面进行详细讲解。 系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路: 本文系统化讲解了 Dart 编程中的异常处理,从基础防御到全局设计,涵盖了异常分类、资源管理、异步操作(如 Future、Stream 和 Isolate)的异常处理,以及全局监控与业务逻辑解耦的设计思想。通过“三层金字塔”模型(底层语法防御、中层业务规则、顶层全局监控),构建多层次的异常防御体系,并强调将异常处理视为代码核心架构的一部分,以实现对不确定性的有效管理和持续优化。 Now In Android 精讲 6 - UI Layer: 文章主要讲解了 Android 中 UI 层的相关内容,包括界面层的组成、UI state 的定义和管理、事件的类型与理解、状态容器与状态管理(如业务逻辑状态容器 ViewModel 和界面逻辑状态容器),以及状态、状态容器和事件如何串联,强调了遵循单向数据流原则和避免在 viewModel 构造中使用异步方法等要点。 Android Weekly En Issue #660: Android Weekly En Issue #660 2025 年 Android 开发趋势全景解读: 2025 年的 Android 开发者,正在经历从”代码工人”到”智能场景架构师”的转型。那些能快速掌握 Compose、设备端 AI、车载开发三大核心技能的程序员,将在新一轮技术浪潮中占据先机。记住:在这个 AI 生成代码的时代,架构设计能力和硬件理解深度将成为不可替代的竞争力。 How I Use AI: Meet My Promptly Hired Model Intern: Armin Ronacher 在文章中分享了他如何利用多种 AI 工具(如 Open WebUI、ChatGPT 和 Ollama)提升生产力,尤其在内容创作和编程中,通过语法检查、代码生成和调试等方式优化工作效率。他强调 AI 是协作的增强工具而非替代品,并主张以批判性思维使用 AI,同时对“低质量 AI 生成内容”持保留态度,但认为其仍可作为灵感来源。他呼吁在创意和知识产权问题上展开建设性对话,充分挖掘 AI 的潜力。 解决 Gradle 依赖下载问题方案汇总: 在使用 Gradle 处理项目构建时,常常会出现构建需要的依赖下载失败的问题。这篇文章就介绍一下如何解决这一类的问题。 Daily Productive Sharing 1162 - Thoughts on AGI: OpenAI 的 o 系列模型加速了 AI 在编程和科学领域的优化与自动化进程,但其未来发展仍受限于计算能力和人类社会因素。 Flutter 新春第一弹,Dart 宏功能推进暂停,后续专注定制数据处理支持: 在去年春节,Flutter 官方发布了宏(Macros)编程的原型支持, 同年的 5 月份在 Google I/O 发布的 Dart 3.4 宣布了宏的实验性支持,但是对于 Dart 内部来说,从启动宏编程实验开始已经过去了几年,但是从目前的推进趋势看,完全的宏功能支持并不理想,结论大概是:能用是能用,但是质量和性能都达不到一开始的预期。 Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年: Android Studio 1.0 宣发于 2014 年 12 月,而现在时间来到 2025 ,不知不觉间 Android Studio 已经陪伴 Androider 走过十年历程。 Deepseek R1 可能找到了超越人类的办法: 我本想写一篇关于 DeepSeek R1 的科普文,但发现很多人仅仅把它理解为 OpenAI 的复制品,而忽略了它在论文中揭示的“惊人一跃”,所以,我决定重新写一篇,讲讲从 AlphaGo 到 ChatGPT,再到最近的 DeepSeek R1 底层原理的突破,以及为什么它对所谓的 AGI/ASI 很重要。作为一名普通的 AI 算法工程师,我可能无法做到非常深入,如有错误欢迎指出。 Testing | Jetpack Compose Tips: Previews allow quickly verifying components during development, which can be automated with the preview screenshot testing Gradle plugin to create visual regression tests. Behavior tests can use DeviceConfigurationOverride, a new testing API to simulate different device configurations to avoid needing multiple emulators to run a full test suite. AI 编程智能体深度剖析:Cursor 与 Cline 实战对决,谁更胜一筹?: 这篇文章对闭源和开源的 AI 编程智能体进行了深度剖析,包括 Cursor、Cline 等工具。介绍了 AI 编程智能体的概念、核心组成部分,对比了其与 AI 问答、人工编码的区别,并阐述了它们在客户端开发不同工作中的影响。还详细对比了多种 AI 编程工具的优劣势、使用成本、适用场景、任务处理结果等,同时介绍了 Cursor、Trae、Windsurf 等工具的安装、使用、配置等方面的内容。 非技术文章 专栏:职场不用喝咖啡 - 认知升级书目:《好战略,坏战略》: 《好战略,坏战略》强调战略不同于目标或愿景,真正的战略是微观、系统且可执行的,书中通过反面教材揭示企业常见错误,推荐深入学习以提升认知和实践能力。 2025 年英语进阶:打开海外新机遇的最佳资源: 来领新年第一份礼物了!免费放出我精选的学习英语资源:“2025 年英语进阶”。每个资源都包含了我的简要点评、价格信息、适合的英语水平以及链接,方便你快速了解并筛选出最适合自己的学习工具和计划。 创业日记 独立开发周记 101:让极简更极简: 极简时钟作者的周记 工作承载不了太多意义,但也不要陷入工作虚无主义: 工作就像人生的一个维度:它很重要,但不是唯一。它需要投入,但要有度。它值得认真,但别太执着。工作是为了更好的生活,而不是让生活成为工作的附属品。我们的目标是:认真工作,快乐生活。既不做工作的奴隶,也不做生活的逃兵。工作和生活就像代码和注释,缺一不可,但要比例适当。 一场关于 DeepSeek 的高质量闭门会:比技术更重要的是愿景: 关于 DeepSeek 的高质量闭门会讨论了其技术细节、组织文化以及未来发展愿景,强调了 DeepSeek 在人工智能领域的创新及其对行业的影响。文章指出,DeepSeek 的成功不仅在于技术,更在于其独特的愿景和团队文化。 近期测试的几个软件: 近期收到或抽到了一些软件,都挺有趣,放在一起聊聊:Juchats、Tooboo、Photoncam、Piecelet、SteveFans AI 辅助编码的残酷真相:它能帮你完成 70%的工作,但最后 30%令人非常沮丧: 在过去几年深入参与 AI 辅助开发的过程中,我注意到一个非常有趣的现象:尽管许多工程师都表示自己在使用 AI 时生产力显著提升,但我们日常使用的软件却并没有明显变好。到底发生了什么?我想我知道原因,而其中揭示了一些软件开发的根本事实,值得我们认真思考。让我来分享我所学到的内容。 自洽的程序员—好的伴侣可以帮你消化工作上的负面情绪: 好的伴侣关系, 不是形式上的两个人住在一起, 而是灵魂上的相互理解和支持。工作是生活的重要部分, 而不是需要隐藏的秘密。 适度分享不仅能帮助消化负面情绪, 更能增进彼此的理解和信任。分享工作中的酸甜苦辣, 也是让感情升温的催化剂。 在互相理解和支持中, 两个人都能获得成长。 工具 羊毛薅起来:SiliconCloud 基于优秀的开源基础模型,提供高性价比的 GenAI 服务。作为集合顶尖大模型的一站式云服务平台,SiliconCloud 致力于为开发者提供更快、更便宜、更全面、体验更丝滑的模型 API。 可以直接在线对话(自定义 Prompt)也可以生成 API 在各个本地 or 在线的工具(比如 Cursor)里面使用,这个很方便~ 您每邀请一位好友成为 SiliconCloud 新用户,您与好友均可获赠 2000 万 Tokens(14 元平台配额) 邀请链接:https://cloud.siliconflow.cn/i/zGUmxkbg 邀请码:zGUmxkbg 杂记 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 本周刊可以通过微信公众号、知乎专栏、掘金专栏、个人博客、竹白等平台订阅和阅读。 技术文章 深入 Flutter 和 Compose 的 PlatformView 实现对比,它们是如何接入平台控件 : 本文深入对比了 Flutter 和 Compose 在 PlatformView 实现上的差异,重点分析了它们接入平台控件的方式、技术实现和适配场景的不同。文章详细展示了 Flutter 的三种模式(VD、HC、TLHC)的演变及其优缺点,同时探讨了 Compose 如何通过 AndroidView 将传统 View 集成到其 UI 渲染树中,并分析了两者在 SurfaceView 支持上的关键区别。 Now In Android 精讲 5 - Data Layer : 文章主要精讲了 Android 中的 Data Layer,包括学习前需解决的一系列问题,通过代码示例讲解了 Repository 的使用及设计原则,阐述了 Repository 与 DataSource 的关系,介绍了离线优先业务,如模块设计、读写策略、同步设计等,最后总结鼓励对照代码学习并应用。 DeepSeek-R1 发布,性能对标 OpenAI o1 正式版 : DeepSeek-R1 发布,性能媲美 OpenAI o1 正式版,并开源模型权重。DeepSeek-R1 采用 MIT License,支持模型蒸馏,API 和应用同步上线,性能在数学、代码、自然语言推理等领域表现优异。小模型蒸馏版本也已开源,协议调整为更宽松的 MIT License,明确支持用户进行模型蒸馏。API 服务定价透明,用户可通过官网或 App 体验新功能。 内核空间内存 profiler: memprofiling : 内核空间内存使用情况剖析器 memprofiling 由 Kent Overstree 和 Google 的 Suren Baghdasaryan 开发,能够直观展示内核内存的分配情况,包括分配者及分配对象数量。 Android 15 内存追踪利器:ProfilingManager! : Android 15 内存追踪利器:ProfilingManager 两个 display driver 导致的问题 : 两个 display driver 导致的问题,第一个问题是 MTK 平台的 PQ 功能存在 bug,关闭 PQ 后解决了 settings 界面滑动花屏的问题。第二个问题是项目在开启 doze_suspend 后出现黑屏和 SF 的 DMA Buffer 泄露,原因是驱动在适配 doze suspend 功能时屏幕上下电逻辑存在 bug。 案例分享:数据库 sqlite3 访问 SIGBUS 崩溃问题 : 案例分享:文章探讨了一个与数据库 sqlite3 访问相关的 SIGBUS 崩溃问题,分析了其发生原因、定位方法及解决过程,最终确认是由于多线程并发操作导致文件锁丢失引发的数据库访问错误。 AGI 前夜的思考 [译] : 这篇文章探讨了人工通用智能(AGI)即将到来的影响,分析了技术发展、社会变革、潜在风险及应对策略。作者认为我们正处于历史性时刻,未来将充满惊人的可能性,也伴随着巨大的挑战。文章呼吁每个人发挥作用,共同塑造一个积极的未来。 快让 Appium 自动化测试你的 App 吧 : 文章主要介绍了 Appium 自动化测试流程,包括环境配置(安装 appium、驱动,配置环境变量,选择 Python 语言等),使用方法如打开目标 Activity、查看布局元素、尝试点击事件,还提及相关工具及可能遇到的问题。 一些“小模型”的使用案例 : 在 Hacknews 上有一个讨论很火,就是大家都用小参数的语言模型做什么,有没有什么好的使用案例。我把这些案例整理汇总了一下,大约有六类:(1)文本分类与信息提取、(2)办公与生产力辅助、(3)对话/消息处理与辅助回复、(4)网页/应用集成与自动化、(5)娱乐、创作与游戏、(6)模型部署、技术瓶颈与思考。 Episode 212: Happy birthday, Android Studio! : In this episode Chet, Romain and Tor chat with Xav and Jamal from the Android Studio team to talk about the history of Android’s IDE. Android 车机 Car 模式原理 : 文章主要介绍了 Android 车机的 Car 模式原理,包括车机系统 Android Automotive OS 的特点,Car 模式中的各个模块如 Car API、Car Service、Vehicle HAL 的功能、使用方法、类图等,还讲解了 MCU 报文通讯流程及相关开发维护工作。 Android 字节码处理-ASM 入门开胃菜 : 文章主要介绍了 Android 字节码处理相关内容,包括编译流程、字节码修改阶段、ASM 框架(简介、两种模式、执行模型、核心组件)、类结构及描述符等,还提到 ASM 学习曲线陡峭,建议查阅官网和示例代码。 Now in Android: 113 - Android 16 Developer Preview 2, Android XR, Android Studio Ladybug, and more! : Welcome to Now in Android, your ongoing guide to what’s new and notable in the world of Android development. In this episode, we’ll cover updates on the Second Developer Preview of Android 16, Android XR, Spotlight Week on Android Camera and Media, Android Studio Ladybug Feature Drop and more! Android WebView 中网页被劫持的原因及解决方案 :这篇文章探讨了 Android WebView 中网页被劫持的原因及解决方案。原因包括 JavaScript 重定向、恶意网页等多种情况。解决方案有使用 HTTPS、验证 URL 等措施,并提供了相应代码案例。还通过案例深入分析了问题,得出网页被劫持可能由多种因素导致的结论。 Kotlin 技术月报 | 2025 年 1 月 :2025 年 1 月的 Kotlin 技术月报涵盖了最新动态、精选博客和社区活动等。最新动态包括 IDE 支持、Kotlin K2 编译器新特性、Gradle 支持情况等。精选博客有 KMP 发展回顾、KMM 跨平台原理、MMKV 封装思路等。社区活动是 Kotlin 中文开发者大会视频回放。 带着问题学,Compose 附带效应(Side Effect)一探究竟 :文章以“Jetpack Compose 附带效应(Side Effect)”为主题,介绍了其定义,包括在组合函数中对输入输出范围外状态或系统产生影响的操作,如网络请求等。还列举了处理副作用的 API 及其用途场景、特别之处,如 SideEffect、LaunchedEffect 等。探讨了如何确保重组时应用状态与外部系统一致,以及副作用 API 的最佳实践和常见误区,最后总结了实际开发中的关键要点。 非技术文章 SP05.愿意主动承受痛苦,才是热爱的证明 : 文章探讨了“热爱”与“痛苦”之间的关系,指出真正的热爱往往藏在愿意主动承受的痛苦背后。通过作者自身的经历以及身边人物的故事,阐述了如何辨别兴趣与热爱,并强调主动选择承受痛苦的重要性。 读《吸引力法则:如何利用心理暗示实现愿望》的记录与思考 : 这篇文章是作者对《吸引力法则:如何利用心理暗示实现愿望》一书的读后感和思考。文章从吸引力法则的定义、理论体系到具体实践方法,进行了系统的梳理和个人化的解读。作者强调吸引力法则的核心是“相似者互相吸引”,通过关注感受和调整磁场,达到与愿望和谐的状态,并分享了多种提升想象力和正向思维的具体方法。 月刊(第 28 期):AI 没有体验世界的能力 : 本篇是对二〇二四年十一月至十二月的记录与思考。 EBook - 自洽的程序员 : 但这本书的真正用意是想解决工作过程中碰到的焦虑、倦怠、迷茫、抑郁等情绪,聚焦于解决具体问题,通过改变认知将我们从负面情绪的泥淖中走出来,做到更坦然,真诚的面对自己的内心,成为一个自洽的程序员。 33 岁在重庆,程序员,在 2024 年拥有的面试经历 : 这篇文章记录了一位 33 岁的程序员在 2024 年于重庆的求职经历及反思。他分享了多次面试的过程、心得以及对未来的展望,同时总结了求职过程中需要注意的事项和心态调整的重要性。 《奔跑吧,程序员:从零开始打造产品、技术和团队》 : 这篇文章是对创业公司、技术选择、设计原则、招聘策略以及学习技巧等主题的深入探讨。通过实际案例和理论分析,文章介绍了如何在不确定的环境中快速增长,并提供了许多实用建议。 技术简报 2025 第一期 : 本文章主要整理了一系列技术相关的文章和主题,包括搜索引擎架构、Linux 上下文切换、GPU 计算、数据库分区、创造性思维等内容,旨在分享编程技术洞察和经验。 读《黄仁勋:英伟达之芯》 : 这篇文章探讨了黄仁勋及其领导下的英伟达如何通过创新和坚持,成为人工智能领域的领导者。文章详细描述了黄仁勋的管理风格、他对技术的深刻理解,以及他如何利用第一性原理重新定义 GPU 的应用场景,推动 AI 革命。 一个伪独立开发者的独白——4399 小游戏开发 : 作者回顾了自己作为伪独立开发者的经历,分享了开发 4399 小游戏的经验和心得。 【Netflix】极简主义:记录生命中的重要事物 官方双语字幕 Minimalism : 这部纪录片访问的对象,都努力抗拒认为事物能带来幸福的美式理念,展现少即是多的真谛。 被动收入的一些思考与实践(1) : 这篇文章讲述了作者在构建被动收入过程中的实践经验、心得体会以及一些关键洞见。通过一次小的尝试,作者逐步摸索出如何将自己的技能和资源转化为可持续的收入来源,同时也总结了在过程中学到的核心理念。 工具 个人工具箱 : 自己一直是个工具控,也一直信奉着“工欲善其事,必先利其器”的理念,总是不断折腾和优化自己的硬件与软件,针对自己的一个特定需求会试图找到最优解,现在也慢慢找到了最适合自己使用习惯的解决方案。因为工作、学习和个人兴趣,设备经过很多次迭代,在这个时间节点作一下记录,后续也会不断更新,希望能够对其他人有所参考。 对标 Cursor 和 Windsurf,Trae 如何成为中文开发者的首选? : Trae 是字节跳动推出的一款专为中文开发者优化的 AI IDE,旨在解决现有工具在中文语言支持上的不足,同时通过整合 Claude 3.5 和 GPT-4o 等主流大模型,提升开发效率并提供智能化支持。Trae 通过本地化设计、便捷的功能迁移和多样化的 AI 功能,重新定义了中文开发者友好型 IDE 的标准。 杂记 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 本周刊可以通过微信公众号、知乎专栏、掘金专栏、个人博客、竹白等平台订阅和阅读。 技术文章 深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比 : 这篇文章对比了 Flutter 和 Jetpack Compose 在 UI 渲染刷新时差异更新(Diff)的实现机制。Flutter 采用线性对账算法,通过最小化查找将 Widget 配置信息更新到 Element 上。Jetpack Compose 则使用基于 Gap Buffer 的 SlotTable 数据结构,在重组完成后才将差异部分更新到对应的 LayoutNode。 计算体系结构的伟大思想:虚拟内存 2-Lecture 33 : 虚拟内存是现代计算中隐藏的重要技术,为性能优化、安全性和灵活性提供强大支持。它通过地址映射和分页机制,为每个程序提供独立的虚拟地址空间。硬件和软件协作完成地址翻译和页面交换,实现有效的内存管理。虚拟内存在云计算、GPU 计算、数据库等领域广泛应用,并将随着硬件进步不断优化和发展。 【音视频】视频播放卡顿问题的分析和解决丨音视频实战经验 : 本文主要分析了视频播放卡顿问题的原因及解决思路。文章首先指出视频播放卡顿的主要原因可能是网络波动、解码渲染能力不足以及缓冲策略不合理导致的数据断供。针对这些问题,文章提出了一种基于分层架构的播放器设计方案,包括播放控制层、缓冲管理层、网络数据层和解码渲染层。并重点介绍了核心的三级缓冲策略和动态调整机制。最后,文章还给出了一些其他的优化建议,如网络优化、解码优化、渲染优化、监控优化和体验优化等。 【直播回放】快手团队的 KMP 鸿蒙落地实践| 2024 Kotlin 中文开发者大会 : 自研 OS 时代,以鸿蒙为首的各类新型操作系统应运而生,这对快手这样以“超大规模工程量级移动端应用”为主要业务载体的公司,带来了巨大的效能挑战。本次分享将结合快手的 KMP 鸿蒙落地实践,从降低 KMP 业务接入成本的角度出发,介绍快手是如何通过建设 KMP 鸿蒙易用性基础设施促进业务落地并提高研发效能的。希望快手的技术方案选型和渐进式落地推广思路,能给同样需要从 0 到 1 落地 KMP 的应用提供参考与帮助。 Android 15- 16kb 页对齐适配大扫盲 : 文章主要介绍了 Android 15 中 16kb 页对齐的适配。包括需要适配的两种情况:未进行 16kb 对齐的 so 及 native 代码中固定硬编码 4kb 部分系统调用异常。讲解了适配过程,如环境准备、so 本身的 16kb 对齐方法,还通过 shadowhook 实战举例,最后总结了三个适配相关的小结论。 Matrix 源码阅读笔记 —— TracePlugin(中) : 文章是关于 Matrix 的 TracePlugin 功能的介绍,包括对 SignalAnrTracer 和 FrameTracer 的分析。SignalAnrTracer 主要监控 ANR 信号,获取系统的 trace 文件,涉及多种语言的代码实现和处理逻辑。FrameTracer 则包括 Android 7 及其以后和以前版本的帧率检测,前者直接使用 API,后者通过 Hook Choreographer 计算帧率,并详细阐述了相关代码的执行逻辑和任务顺序。 彻底掌握 Android14 Vsync 原理 : 本文主要介绍了 Android14 Vsync 原理,包括 Vsync 是什么及作用、HW-Vsync 计算模型建立、软件 Vsync 信号计算过程与校准、App 和 SF 申请 Vsync 信号的流程等。指出 Vsync 用于同步画面,软件 Vsync 基于 HW-Vsync 计算,有特定参数和计算方式,且存在校准场景和相应流程。 Android 系统性能优化之重排,你知道几个? : Android 系统性能优化之重排解析 Android 系统多方面采用重排技术优化性能,包括 Java 层 multidex、字节码 aot/jit 转汇编、native C++编译优化以及图形栈重排指令、CPU 和 GPU 底层的机器码重排等。 重排既可以提升性能,但同时也增加了复杂度和可能引入 bug。作者曾遇到过 art 虚拟机中由于指令重排导致的 bug。 对于 Android app 的 looper 消息循环,可以参考 iOS 的 runloop 做分类分级,而不是一刀切。这是一个值得探索的优化方向。 能量感知调度(EAS,Energy-Aware Scheduling)的考古日志系列,连载五 : 本系列 patch 为 EAS 相关,主要记录 EAS 从提出讨论,经过多个版本迭代,最终合入主线内核的过程。Quentin Perret 在 Morten Rasmussen 工作的基础上完成了 EAS 的开发合入。本文介绍第二个 patch,schedutil 调频器为适配 EAS 所做的一些准备。本文基于 kernel5.0-rc1。 能量感知调度(EAS,Energy-Aware Scheduling)的考古日志系列,连载六 : 本系列 patch 为 EAS 相关,主要记录 EAS 从提出讨论,经过多个版本迭代,最终合入主线内核的过程。Quentin Perret 在 Morten Rasmussen 工作的基础上完成了 EAS 的开发合入。本文介绍第三个 patch, 为 EAS 引入一个能量模型管理框架,这是一个相对独立的框架模型,可用于其它子系统。有了能量模型, EAS 可以在满足性能的条件下,选择能效比最高的 CPU 运行。本文基于 kernel5.0-rc1。 案例分享:图片内存泄漏快速定位方法 : 本文探讨了 Android 平台内存泄漏快速定位的一种新方法,利用对图像内存占用的可视化分析。 android app 渲染流水线图解 : 刷到 Google io 2018 的讲解的 app 渲染流程视频,虽然视频有点老了,新版本 android 可能变化了,但内容不错,讲得很好。整理出来分享给大家。 Jetpack Compose Optimization Checklist : 这篇文章总结了 Jetpack Compose 中优化性能的关键做法,包括利用可跳过性(skippability)、状态管理和延迟组合等高级概念来确保 UI 渲染的流畅性,适用于复杂应用场景。该清单涵盖了懒加载列表、动画和布局等多方面的最佳实践,可以帮助开发者提高 Jetpack Compose 应用的性能表现。 AI 编程蓝皮书 : 作者介绍了使用 AI 编程的流程和工具。 再学安卓 - binder 之驱动加载 : 本文主要介绍了 Android 内核中 Binder 驱动的初始化过程, 包括 initcall 机制、设备创建、文件系统挂载等。通过对代码的分析和踩点, 全面地展示了 Binder 驱动的启动流程。 perfetto 高阶使用:编译浏览器 UI : 这篇文章主要介绍了 perfetto 编译浏览器 UI 的相关内容。包括参考学习文档、核心步骤、实战步骤、报错处理等。核心步骤为获取源码、执行 tools/install-build-deps –ui 和 ui/run-dev-server 等命令。实战中需满足前提条件,如翻墙、使用特定系统等,并列举了多种报错情况及处理方法,还提到了取巧的离线使用办法。 Android 15 新特性,预测性返回手势 : Android 15 新增了预测性返回手势功能,可以在返回上一个界面之前预览即将返回到的界面。这个功能从 Android 13 开始引入,但直到 Android 15 才默认启用。预测性返回手势可以提升用户体验并降低返回操作的误操作率。但由于需要各 App 进行适配,所以这个功能的推广进度比较慢。 这样代码命名,总不会被同事蛐蛐了吧 : 本文主要探讨了如何进行良好的代码命名,从而提高代码的可读性和可维护性。 手机上跑大模型,酷不酷 : 本文介绍了在手机上运行大语言模型的解决方案 mlc-llm 项目。这个项目可以让大语言模型直接运行在手机、电脑等设备上,无需依赖网络。这带来了诸多可能性,比如在手机上与 AI 助手进行交互、避免数据隐私泄露、为游戏等硬件实现智能对话等。此外,作者还分享了在探索 mlc-llm 项目中发现的 conda 软件环境管理工具的使用心得。 Android15 调试专题第九弹:编译 Pixel 内核源码 :这篇文章介绍了如何编译 Pixel 系列手机的内核源码 PMU, AMU 的区别 : 文章简要内容概括:本文对比了 Arm 架构中的 Performance Monitoring Unit (PMU)和 Activity Monitoring Unit (AMU)的区别。它们都有性能计数器用于监控和分析系统性能,但在用途、编程模式等方面有所不同。PMU 主要用于性能分析和调试,AMU 主要用于系统管理和监控,尤其是功耗和性能管理。 技术管理:目标与对象视角下的洞察与实践 : 技术管理的核心目标是为业务成功提供全方位、深层次的技术支撑与保障,通过技术创新与规划助力商业目标的实现,同时在满足技术人员精神层面(如个人技术进步、能力提升)和物质层面(如薪资、奖金等)核心诉求的基础上,激发其工作热情与创造力。优秀的技术管理者应注重培养团队成员的技术领导力,打造具备持续进步能力的卓越团队,并在与非技术管理者争取团队利益时,勇于突破心理束缚,积极为团队争取资源与支持。 Android anr 排查之 sp 卡顿 : 这篇文章主要分析了 Android 开发中 SharedPreference 引起的主线程卡顿和 ANR 问题,并提出了解决方案。SharedPreference 的 apply 操作会在主线程执行写入操作,可能导致主线程卡顿,而页面退出时,ActivityThread 会阻塞等待 SharedPreference 写入完成,从而进一步引发 ANR 问题。为了解决这一问题,文章提出通过反射替换 SharedPreference 的内部实现,避免主线程卡顿和 ANR,并对这一优化方案进行了详细说明。 音视频基础能力之 Android 音频篇 (五):史上最全的音频渲染总结 : 这篇文章是音视频基础能力之 Android 音频篇的第 5 篇,主要介绍了 Android 平台下音频渲染的几种方式。包括 AudioTrack 的初始化、开始渲染、停止渲染的步骤及相关参数设置,OpenSL 的初始化、播放、引入等流程,AAudio 的关键流程及播放控制,Oboe 的引入方式。还提及了硬件音视频能力与操作系统的关系及系列文章的相关链接。 2025 稳定性业务研讨问题 : 这篇文章从能力、管理和流程三个方面探讨了应用稳定性业务的改进方向。在能力方面,需解决三方应用的内存泄露、无响应故障等“硬骨头”问题,并利用图像检测、事件旅程检测等技术突破,同时提升 AI 深度遍历测试、自动化测试生成能力以及竞品对比测试和逆向能力;系统稳定性能力已较完善,可通过小幅改进进一步优化。在管理方面,需更好地协同管理芯片厂家导入基线、Google GKI 升级变化等外部不可控因素,以及三方应用升级引发的问题。在流程方面,需加强应用市场上架测试流程和自研需求开发质量的流程管理,以全面提升稳定性业务的效率和效果。 CUDA 内存管理器 : 内存管理器提供了内存分配的抽象接口,供 CUDA 驱动程序的其他单元或通过 cuda runtime API 的用户层 API 使用。它还提供了在主机(Host)和设备(Device)之间共享已分配内存的抽象接口。CUDA Driver API 以内存对象(Memory Objects)的形式看待内存分配。一个内存对象表示分配的一块内存。它抽象了内存管理中的所有层次,并为 CUDA 驱动程序的其他单元提供 API,用于 malloc/free 和 mmap/unmap 内存,以及其他的辅助功能。为了避免内存碎片化,内存对象是从一个更大的固定大小的内存块(Memory Block) 中分配的。每个内存块都有一个内存描述符(Memory Descriptor),其中包含与内存块关联的所有内存属性。内存管理器负责维护给定 CUDA 上下文中分配的所有内存块。 非技术文章 讨论胶水工作 : 这篇文章讨论了在软件工程团队中出现的”胶水工作”(glue work),即那些不直接产生代码却对项目成功至关重要的工作,例如帮助他人提高效率、更新路线图、与用户沟通等。文章描述了一位优秀的”胶水工作”者如何由于团队和组织没有认识到这种工作的重要性而无法获得晋升,最后不得不考虑转向更加”可量化”的非技术性工作。 成长创业笔记 6:2025 年稳步前行,家政小程序迭代新版本,继续出发 : 该文章主要介绍了作者独立开发者小张对其家政小程序的迭代更新与反思。 vol28.信息过载自救指南 : 本文从信息过剩、信息茧房、 FOMO(害怕错过)和注意力涣散等多个原因分析了信息过载的产生,并提出了一些应对措施,包括保持无聊、找到自己的母题、构建信息获取系统等。文章还提供了一些优质内容的链接推荐。 为什么韩国明明是发达国家却总是一副吃不起肉的样子? : 回答和评论都是精华:大学四年所有课都可以不去,也要牢记楼主的这篇创作,这教会了我们如何思考,以及对于人生的规划,将大问题拆解成可以践行的小问题 新疆二十日(上):雪域沙海,我的北疆探索之旅 : 本次新疆的行程全程自驾,从北疆到南疆的 20 天里大概行驶了超过 5000 公里。我们从上海直接飞抵伊宁,途径赛里木湖、克拉玛依、喀纳斯、乌鲁木齐、吐鲁番、哈密、库车、阿克苏、塔县,最终在喀什返程。一路上,我们穿越了阿禾公路,独自畅游在无际的雪山中;也走过了沙漠“大海道”,看到了独特的雅丹地貌;还有惊无险地通过了塔莎古道,重温了当年西游玄奘之路。在北疆,我们看到了冬季的白雪皑皑;在南疆,我们也领略了晚秋的胡杨林,即便现在已踏上归途,仍旧让我们流连忘返。 新疆二十日(下):古道高原,南疆的冰与火之歌 : 本文是《新疆二十日(上):雪域沙海,我的北疆探索之旅》的后续章节,记录了我们从北疆驶向南疆的旅程。如果说北疆的雪山、湖泊和荒野赋予了这段旅途以雄浑壮丽,那么南疆则以胡杨林、沙漠和古道为我们展开了一幅多彩而深邃的画卷。在南疆,我们将继续追寻丝路的遗迹,穿越塔克拉玛干沙漠,领略胡杨林的秋末辉煌,也将重走玄奘的西游足迹,踏上塔莎古道的荒凉与壮美。 对父母的和解 : 在我年纪轻轻、见识稍浅之时,我的父亲曾给我些许建议,至今仍萦绕在我的心头。他说:“当你想要批评某人时,你要记住,这个世界上并不是所有人都拥有你的那些条件。” 人到中年,减肥后维持体重不反弹一年的感悟 : 中年人如何在减肥后稳定体重并防止反弹 为了帮助你回忆旅行足迹,我开发了「山河旅记」 : 「山河旅记」是一款专注于记录个人旅行经历的应用。它提供了简单高效的各种记录功能,如自动生成旅行足迹、支持批量导入照片、建立旅行图谱等,帮助用户轻松保存旅途中的点点滴滴,方便日后回忆重温。应用采用完全本地存储的设计,确保数据安全性,并计划持续完善更多实用功能,让每个旅行者都能拥有一本独一无二的旅行手记。 如何优雅地开启个人博客? : 最近陆陆续续收到了一些朋友关于想要开启个人博客的想法,有些是想通过公开输出,倒逼自己一把;有些是本来就有记录的习惯,希望拥有自己的一个独立博客;有些则是希望通过书写、记录,去发现探索自己的内心。各有目的和需求,但是最终都会碰到一个开启博客问题:如何写呢,如何分类自己的思想呢,如何去搭建呢,如何跨越自己的羞耻心公开教育自己呢,如何去持续稳定输出呢,想想就有一堆问题,有些是心理上的,有些是技术上的,有些是写作输出上,各有不同,这些问题都会导致在开启个人博客这件事上碰到诸多阻碍,而长期停滞不前,随后不了了之。 打点鸡血 关于胶水工作: : 推广 纯银的产品分析专栏,主要是为了扩展视野: 📊 产品分析的重要性 在产品分析中,关注用户在产品中的行为比关注产品设计更为重要,因为用户行为能准确反映产品设计的效果。 🔍 用户使用场景分析 产品分析应涉及用户使用场景、需求定位和市场分析,以确保产品结构和逻辑符合实际需求。 📖 系列产品分析 纯银系列的产品分析包括多季的内容,涵盖广泛的话题和不同的市场需求探讨,适合对产品策略有兴趣的读者。 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 本周刊可以通过微信公众号、知乎专栏、掘金专栏、个人博客、竹白等平台订阅和阅读。 技术文章 Tools, not Rules: become a better Android developer with Compiler Explorer : 该文章介绍了一个名为 Compiler Explorer 的工具,它可以帮助 Android 开发者了解编译过程中的优化机制。该工具允许开发者探索 Java 和 Kotlin 代码是如何转换为汇编指令的,以及 ART 编译器在此过程中进行的各种优化。文章通过几个具体的代码示例,展示了 Compiler Explorer 如何帮助开发者理解编译器的工作原理,从而更好地编写高效的 Android 代码。 几乎是当下最详细的 AOSP 编译与调试运行指南 : 这篇文章是关于 AOSP 编译与调试运行的详细指南,包括 Android 13 源码在 Ubuntu 环境下的全量下载与编译,源码导入 AS 的两种方式及相关操作,介绍了源码目录结构、编译产物信息、代码搜索和单独模块编译,还阐述了系统调试方法,如源码修改编译运行、断点调试系统代码(Java)。 闲谈丨像福尔摩斯一样去解 Bug : 解决 Bug 时,要像福尔摩斯一样收集充分的数据和工具,深入了解原理而非表象;要拥有广博的视野,洞见问题的本质;要对已解决的问题保持学习和思考,不断积累经验。同时还要客观认识 Bug 的性质和影响,优先处理高风险 Bug,考虑用折衷方式应对无法根治的问题。 CPU 使用率高就一定有效率吗? : 这篇文章讨论了 CPU 使用率高并不一定意味着效率高的问题。作者通过具体实验分析了线程并发计数时悲观锁(synchronized)和自旋锁(CAS、spin_lock)的性能差异,并对自旋锁进行了优化。最后总结了内存墙是导致性能问题的核心原因,提出了几个相关的案例供读者参考。 AI Agent(智能体)技术白皮书(Google,2024) : 本文介绍了基于生成式 AI 的”Agent”的基本架构和工作原理。Agent 是一个能够利用工具和编排层来扩展语言模型能力的应用程序,可以执行复杂任务并与外部系统交互。文章详细探讨了构成 Agent 的三大核心组件:模型、工具和编排层。其中工具是连接模型和外部世界的关键,包括 Extensions、Functions 和 Data Stores 等形式。此外,文章还介绍了通过上下文学习、基于检索的学习和微调等方式来提升 Agent 性能的方法。最后,文章给出了基于 LangChain 快速构建 Agent 原型的示例代码。 LLM-Powered GUI Agents in Phone Automation: Surveying Progress and Prospects : 本文详细介绍了 vivo AI Lab 联合香港中文大学 MMLab 等团队发布的关于大模型驱动的手机自动化智能体的综述论文。论文长达 48 页,覆盖了 200 余篇文献,系统总结了基于大模型的手机自动化技术的发展历程、技术框架、应用场景及未来挑战。文章首先回顾了传统手机自动化技术的局限性,如通用性差、维护成本高、意图理解能力弱等,随后介绍了大语言模型(LLM)如何通过自然语言理解、多模态感知和推理决策能力,推动手机自动化的智能化发展。论文还详细探讨了手机 GUI 智能体的框架设计、模型选择与训练、数据集与评估方法,并指出了未来研究的方向,如数据集多样性、设备端部署效率和安全问题等。 Android 性能优化:内存优化(理论篇) : 这篇文章主要介绍了 Android 内存优化的理论知识,包括内存基础知识、内存分配、单进程内存上限、垃圾回收算法、引用类型、优化必要性及思路等。指出内存优化可避免 OOM 等问题,提升系统效率和用户体验。优化思路涵盖数据收集分析、借助工具排查问题、针对问题优化及制定长效治理策略。 Android 性能优化:内存优化(实践篇) : 这篇文章是关于 Android 性能优化中内存优化的实践总结。先介绍了内存构成和获取内存信息,接着列举了常用内存检测工具及优缺点。然后阐述了 OOM 常见问题如 Bitmap 内存占用、内存泄露等,给出优化方式和监控方法。还提到了业务优化,包括设备分级和业务降级,以及线上监控和告警体系搭建。最后总结了内存优化的成果和不足。 让您的应用为 16 KB 页面大小的设备做好准备 : 为 16 KB 页面大小的设备做好应用准备 Getting Started with CameraX in Jetpack Compose : 本文概述了如何使用 Android 的新 CameraX 和 Jetpack Compose 库在 Compose 中创建摄像头预览。 鸿蒙应用签名实操及机制探究 : 本文介绍了鸿蒙单框架应用的签名机制,拆解了每一步的实操过程和背后的实现原理,并对源码进行了分析整理签名的校验机制。从中探究了鸿蒙系统的安全设计思路,希望能给从事鸿蒙研发的同学提供一些借鉴。 译:我是如何利用 LLM 进行编程的 : 本文作者分享了利用大语言模型(LLM)进行编程的个人经验和未来展望。作者主要通过自动补全代码、搜索技术问题、对话式编程三种方式利用 LLM,并认为这些方式都为提高编程工作效率带来积极影响。文章还分析了利用 LLM 编写可读测试、小型化包结构等新的编程趋势,并提出了 sketch.dev 这样专门针对 LLM 编程的 IDE 概念。 谈功耗是什么 : 这是一篇关于移动设备功耗分析和优化的文章,功耗是硬件器件消耗电池能量的量度。我们需要从软件角度理解和估算功耗,通过分析软件结构和对硬件的使用情况来分解和定位功耗问题。从软件角度看功耗主要包括 CPU 负载、传感器使用、屏幕显示等各个方面。我们需要从进程、线程级别逐步深化到函数、任务级别来分析和定位功耗问题。在任务级别上,Android 应用的功耗主要体现在 Looper 消息处理、Binder 调用、线程回调等关键结构上,可以通过代码标注的方式将其与功耗情况关联起来。 SPE profiling 及其使用 : 本文介绍了 Arm Statical Profiling Extension(SPE),相比传统的性能监测单元(PMU)方式,SPE 可以更精确地采集 CPU pipeline 执行过程中的指令信息,包括指令地址、访问数据地址、延迟时间等关键指标,对于性能分析和优化非常有用。 Android 性能优化之绑定 RenderThread 到大核 CPU : 本文从以下几个方面介绍了如何在 Android 中绑定任意线程到任意 CPU 上: 介绍了 sched_setaffinity 函数的用法,用于设置线程与 CPU 的亲和性。 介绍了如何获取手机 CPU 的频率信息,并根据频率将 CPU 划分为大中小三类。 介绍了 Android 系统中的 RenderThread 线程,以及如何找到并绑定它。 介绍了如何获取任意线程的 tid (内核线程 ID)。 给出了绑定线程到 CPU 的代码实现。 提供了 JNI 接口,供 Java 层调用绑定线程的功能。 性能周刊 2025-01-04 第 3 期 : 2025 年的第一期性能周刊 TargetSdk 30 升级后的 ARSC 压缩方案探索 : 本文主要介绍快手如何在 TargetSdk 30 上 重新对 resources.arsc 进行压缩,取得巨大包体收益的探索 我在性能团队的这两年 : 这篇文章主要介绍了一个前端性能团队在实践过程中所遇到的一些问题及解决方案。包括性能指标的定义与细化、线上线下的防劣化机制、性能优化的具体手段等。整体来说,文章涵盖了性能优化的全生命周期,对于想要深入了解性能优化工作的同学来说是非常好的参考。 一文彻底搞懂 Android 广播的所有知识 (上)–四大组件系统 : 一篇详细介绍 Android 广播机制知识的文章,从广播机制的原理、分类,到广播接收者、广播发送者、广播分发中心的原理和实现,全面系统地阐述了广播机制的各个方面。 一文彻底搞懂 Android 广播的所有知识 (下)–四大组件系统 : 文章详细介绍了 Android 广播的发送和接收流程,包括广播发送者、广播分发中心及广播接收者之间的交互机制。文章从广播的发送、接收、队列管理等方面全面解析了 Android 广播的运作过程。 非技术文章 橙子的 2024 年终总结:追寻幸福 : 这篇文章是作者 2024 年的年终总结,概括了他这一年在学习英语、跑步、旅行、人际关系以及工作等方面的思考和实践。作者从寻找幸福和财富的角度出发,探讨了复杂世界中如何平和地对待生活中发生的事情。他在各个领域的经历和感悟为读者提供了有价值的见解和启发。 控糖革命-为什么吃了红薯之后会犯困 : 这篇文章探讨了控制血糖的重要性,分析了各种食物对血糖的影响机制,并提出了一些有效的血糖管理技巧。作者分享了自己的亲身经历,通过对血糖曲线的持续监测和调整饮食结构,成功地改善了身体状况。文章强调平稳的血糖曲线对于健康的重要性,并提出了三个需要注意的关键点:1.葡萄糖并非唯一影响因素;2.评判食物优劣需考虑替代选择;3.建议都有科学依据支持。 英语学习过程中的一些思考 : 这是作者关于自己学习英语半年来的一些思考和总结。作者在开始认真学习英语大约半年时间后,取得了一些进步,主要阐述了自己在寻找学习素材、阅读、听力以及背单词等方面的具体做法和一些心得。 vivo X200 Pro 使用体验 : 这是一篇关于作者对 vivo X200 Pro 手机的使用体验的文章。作者从购买选型、实际使用、系统操作、影像系统和电池续航等方面对这款手机进行了全面的评测和点评 2025 年的一些做与不做 : 这篇文章讲述了作者对生活简单化的追求,并提出了 2025 年的一些”减法”和”加法”的想法,比如减少手机时间、对他人的评价等,增加写作、阅读、运动等方面的时间。作者提倡通过”20+20”原则来选择和保留家中的物品,鼓励断舍离,保留最喜欢的东西。 对时间管理的一些思考 : Rolen 分享了他关于时间管理和内容输出的经验 我们在 2024 年从大型语言模型中学到的事 : 这篇文章回顾了 2024 年大型语言模型 (LLM) 领域的重大进展,主要包括:LLM 模型性能的持续提升突破 GPT-4 水平、LLM 效率和价格的大幅提升、多模态 LLM 的崛起、基于提示词的应用生成成为常见功能,以及新兴的”可扩展推理”模型等。同时,文章也提出了一些亟需解决的问题,如 LLM 的使用难度上升、知识分布不均、模型批评等。 创业日记 独立开发周记 85 :2024 年终总结 工具 电子书 - 大语言模型 : 该文章概述了大语言模型技术的发展历程及其在学术界和工业界的现状。作者指出,大模型技术的核心原理和关键技术细节往往难以完全解密,这限制了学术界的研究进展。同时,作者也对大模型技术的开放性表示乐观,认为随着更多核心技术的公开和共享,大模型技术的”透明化”将进一步提高。 Windsurf:面向未来的 AI 编程工具详解 : Windsurf 是一款面向未来的 AI 编程工具,它采用先进的上下文感知、多模型 AI、实时协作和高效代码管理功能,为开发者提供全面的编程支持,提升开发效率和代码质量。它通过创新性的 Flows 模式和 Cascade 功能,为 AI 与人类开发者的协作提供了全新的范式,并在团队协作、项目管理等方面表现出色。该工具在分析代码库、检测和修复错误、自然语言编程等方面也有广泛应用,可有效优化开发流程。随着 AI 技术的进步,Windsurf 有望进一步提升其智能化水平,为开发者带来更优质的编程体验。 杂记 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 掘金 - Gracker:https://juejin.cn/user/1816846860560749 知乎 - Gracker:https://www.zhihu.com/people/gracker 个人博客 - Android Performance : 写东西的地方 个人介绍 - 欢迎加微信群组多多交流 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) 本周刊 Newsletter 订阅:https://androidweekly.zhubai.love/ ,支持微信和邮箱订阅 微信公众号 Android Performance : Android 性能优化知识星球 : 个人运营的一个知识星球,欢迎加入,多谢支持~
Android Weekly 是一份专注于 Android 技术生态的周刊,每周一更新。本周刊深入挖掘 Android 系统架构、性能优化、跨平台开发等领域的高质量技术内容,为开发者提供持续的知识更新与技术洞察。 您可以通过微信公众号、知乎专栏、掘金专栏、个人博客、竹白等平台订阅和阅读。 本期作为 2025 年的第一期,我将为大家呈现去年精心收集的精华内容,涵盖技术与非技术领域,涉及个人开发者、团队建设、公众号运营以及个人博客等多个方面,力求为读者带来更多启发与价值。内容可能有点多,大家可以收藏起来慢慢看。 技术文章 大型 APP 的性能优化思路:本文主要讲述了大型 APP 的性能优化思路。与中小型 APP 不同,大型 APP 需要有一个专门的团队来从全局的视角进行性能优化,重点包括如何管控业务对资源的使用、如何度量业务对资源的消耗、以及如何让业务在资源紧张时做出更优的策略。文章以速度、流畅性优化和内存优化为例具体阐述了这些思路。 使用 Perfetto 定位应用启动性能的瓶颈:这篇文章主要介绍了如何使用 Perfetto 工具定位 Android 应用程序启动性能的瓶颈。文章详细讲解了应用启动时间的两个核心指标 TTID 和 TTFD、Perfetto 工具的使用方法,以及通过 Perfetto 分析应用启动过程中可能出现的性能问题,如启动时触发 GC、主线程耗时操作、OpenDexFilesFromOat 耗时过长以及连续多帧绘制超时等。 Android 线程性能优化方法总结: 本文主要总结了 Android 线程性能优化方法,包括进程和线程的概念、常见问题及优化手段。常见问题如进程内存隔离、进程与线程 ID 关系等。优化手段涵盖降低主线程任务量、提高线程执行效率,如避免使用 AQS、减少线程创建、锁消除等,还介绍了非常规优化及其他性能方面的注意事项。 浏览器内核创新技术演进及实践 - U4 5.0 : 文章介绍了 UC 内核团队研发的最新一代 Web 引擎 U4 5.0,从渲染引擎、JS 引擎、多媒体技术、基础技术等方面,详细阐述了内核引擎技术的演进思路和应用实践。通过一系列技术优化,如直接光栅化、直接合成、InRenderer GPU、Code Cache、JS AOT、U4 Snapshot 等,可以让 Web 应用实现 App 级的优秀性能体验。 Android Kotlin 协程初探 | 京东物流技术团队:该文章是关于 Android Kotlin 协程的介绍,包括协程的概念、与其他编程语言中协程的关系、协程解决的问题,以及协程的工作原理等。 如何正确地理解应用架构并开发:本文探讨了应用架构的相关概念,包括模块与包的区别、分层架构设计、CQRS 模式、六边形架构和洋葱圈架构等,并针对一些常见的工程实践问题给出了建议和最佳实践。 Evaluating Performance:这个文章思路写的特别好,简单总结了这个文章的思路:Google 看待性能方式,还是非常全面和独到的。性能问题一类是容量问题,一类是噪声问题。容量决定了最大负载,提高系统容量的方法,包括调频 CPU/GPU frequency,CPU cores online,温控测量,组件优化,降负载;jitter 是随机噪音,那些措施可以减少随机噪音,如文件预读,应用反复启动;性能优化测量方法,如 TouchLatency 测试应用,如何在低频设置下,找到 jitter,如 block D 状态位置等 大公司如何做 APP:背后的开发流程和技术:本文主要讲述了大公司在开发 APP 时常见的研发流程和技术特点。涵盖了产品需求、研发流程、技术实现等多个方面,并对一些常见的技术问题如组件化开发和大前端开发进行了深入分析。 Android 居然还能这样抓捕和利用主线程碎片时间:本文通过分析 Android 应用开发中主线程卡顿问题,提出了利用主线程空闲时间来执行业务逻辑的解决方案。方案包括帧率耗时监控模块、空闲时间切片模块、耗时任务拆分模块和子任务智能调度模块。通过这种方式,可以有效地利用主线程的空闲时间,提高主线程资源的利用率,从而减少主线程的卡顿,给用户带来更好的操作体验。 西瓜 Android 子进程优化治理实践:该文章描述了西瓜视频客户端团队在针对启动阶段子进程优化治理方面的实践。主要包括 push 进程、小程序进程、downloader 进程、Sandboxed 进程等子进程的优化方案及其实践效果。 WWDC 2021: Avoid hitches and discover the Render Loop:这篇文章概括地介绍了 WWDC 2021 上关于”避免卡顿和探索渲染循环”的三个视频内容 Android 卡顿与 ANR 的分析实践:本文主要介绍了 Android 中卡顿和 ANR 的分析实践,包括产生原理、监控工具 LooperMonitor 的实现和优势,以及通过实际案例分享了利用工具分析和解决问题的过程。 Perfetto 数据流架构故障分析:带你研究 trace 为何丢失:这篇文章介绍了 Perfetto 这个跨平台系统跟踪和分析平台的数据编码与传输(Data Flow)架构设计,并从这个角度解释了数据丢失可能出现的原因,并提出了相应的解决或规避建议。文章还介绍了 Perfetto 的基本架构,包括数据生产者、消费者、以及 IPC 通信的模型。同时还分析了 Perfetto 在平衡观测开销与传输可靠性、数据编码协议等方面的权衡与设计。最后给出了一些如何降低 Perfetto 使用故障的具体建议。 Android 和 iOS 渲染架构差异对比,孰胜孰负?:文章对比了 Android 和 iOS 的渲染架构差异,指出两者上层都非直接通过 CPU 绘制,真正负责绘制的是 Android 的 SurfaceFlinger 和 RenderThread 及 iOS 的 render server。强调 Metal 和 Vulkan 等底层 API 对渲染性能影响更大,还比较了 Metal 和 Vulkan 的特点及使用差异。 Android 中基于 DWARF 的 stack unwind 实现原理:本文介绍了 Android 中基于 DWARF 标准的 stack unwind 实现原理。首先介绍了基于 frame pointer (FP) 的栈回溯方法,并分析了编译器优化对 FP unwind 的影响。随后深入介绍了 DWARF 标准中 .eh_frame 和 .debug_frame 等 section 的作用和内部格式,并举例说明了 readelf 工具如何解析这些信息以实现栈回溯。最后简单介绍了 simpleperf 中如何利用 DWARF 信息进行基于事件的栈回溯。 指尖上的科技:智能手机触摸屏技术与功耗优化:本文介绍了触摸屏的工作原理,包括触摸屏的分类、工作模式以及关键参数,并分析了常见的触摸屏功耗问题和优化策略。文章重点介绍了电容式触摸屏的工作原理,并阐述了触摸屏的基本工作流程。同时,文章还介绍了不同工作模式下的功耗特点,以及针对常见的功耗问题提出了相应的优化策略。此外,文章还提供了一些功耗调试的方法,帮助用户解决触摸屏功耗相关的问题。 一颗像素的诞生:该文章主要阐述了浏览器内核如何将网页内容渲染成像素,整个渲染流程包括解析 HTML、处理 CSS 样式、布局排版、绘制、光栅化、合成、上传等步骤。文章详细介绍了每个步骤的工作原理和优化方案。 ART 虚拟机中的栈上替换编译(OSR):本文主要介绍了 Android Runtime (ART) 虚拟机中的栈上替换编译(OSR)技术。ART 虚拟机中有三种编译策略:kBaseline、kOptimized 和 kOSR。当解释执行方法时,如果方法的执行频率过高,就会触发 kBaseline 编译。然而 kBaseline 编译出来的机器码只能从方法入口处开始执行,无法从中间指令处开始执行。为了解决这个问题,ART 引入了 OSR 编译。OSR 编译会在方法的循环回边处构造一个优化后的机器码执行栈,从而实现从中间指令处开始执行。文章详细介绍了 OSR 编译的实现机制,包括栈帧结构、寄存器分配等关键技术。 全方位剖析内核抢占机制:全方位剖析了 Linux 内核中的抢占机制(preemption),包括其原理、实现细节、对系统性能和用户体验的影响。重点介绍了 Linux 内核中 3 种抢占模型的异同、内核抢占的实现机制、如何通过内核抢占解决优先级反转等问题。并针对具体案例分析了内核中不当使用抢占接口导致的实时性下降问题。 内核内存稳定性新特性:Page Table Check 机制解读:内核引入了 Page Table Check 机制来检测内存损坏问题。这是为了解决内核中长期存在的一类内存引用计数 bug。 内核调度客制化利器:SCHED_EXT:本文介绍了 Linux 内核的 SCHED_EXT 可扩展调度器,它提供了一个支持 eBPF 程序修改调度策略的调度类。SCHED_EXT 的目标是让开发者更易于实验和探索新的调度策略,并支持满足特殊应用场景的深度定制化调度。文章详细介绍了 SCHED_EXT 的基本框架、扩展支持接口、核心数据结构和调度逻辑,并以 Central Scheduler 示例说明了 SCHED_EXT 的 bpf 调度器应用。 抖音 Android 端图片优化实践:本文从抖音 Android 端图片优化历程着笔,主要介绍字节自研 BDFresco 图片框架及其在抖音的最佳实践、经验沉淀、业务价值。通过分享业务视角遇到的一些问题和我们的解决思路,希望能抛砖引玉,为遇到类似困扰的伙伴们提供有价值的参考。 速度优化:充分利用 CPU 闲置时刻:文章主要介绍了充分利用 CPU 闲置时刻提升效率的优化方案。包括通过 proc 节点文件下的 CPU 数据和 times 函数判断 CPU 是否闲置。proc 文件方案中,介绍了总 CPU 消耗和进程 CPU 消耗的计算方法及相关数据含义,还提到了 CPU 闲置通知的采集时间范围和使用率等。times 函数方案在高频判断场景中性能更好,通过计算 CPU 速率判断闲置,实现更简单。 淘宝页面首帧优化的经验和心得:本文分享了淘宝移动端页面首帧优化的经验和心得。主要包括以下内容: 首帧优化的重要性,如建立用户好感度、提升转化率、节省成本等。 首帧的定义和衡量标准,包括 Loading 图、主体内容呈现、页面可交互等阶段。 分析和排查性能问题的方法,包括了解现状、原理分析、使用性能分析工具等。 常见的优化方案和策略,如预加载/缓存、延迟初始化、并行处理、View 渲染优化等。 线上验收和防劣化机制的重要性。 持续迭代优化和复杂度管理的建议。 GPU 性能原理拆解:该文章从 GPU 的架构角度分析了常见的一些 GPU 性能优化「迷思」,并提出了正确的分析方法,包括分析 GPU 的隐面剔除策略、顶点着色器执行效率、带宽瓶颈、分支预测等。同时还介绍了移动端 GPU 独有的一些特点,以及验证性能优化效果的一些测量指标。 抄 Apple Intelligence 作业的思路:文章主要探讨了 Apple Intelligence 相关内容,包括其诞生的技术背景、特点、启示以及对终端模型的预言等。技术背景涉及规模扩展、价格竞争及存在的问题。Apple Intelligence 特点为强大、直观、集成、个人向,有写作工具等功能,存在隐私风波等情况。其启示包括注重产品使用体验和用户情绪价值、面临生态罗生门等。此外还探讨了抄其作业需考虑信息和处理能力来源,以及对终端模型部署的预测。 vivo X200 系列手机做了哪些性能优化:该文主要介绍了 vivo X200 系列手机在性能优化方面做出的努力。主要包括: 采用高通最新的 CPU 芯片,优化缓存结构,调度算法优化,内存管理优化,触控系统优化,以及通信信号优化等方面。文章从多个角度分析了 vivo 在这些方面的创新和改进,为用户带来更出色的手机性能体验。 Android 系统的锁优化:锁 Lock 的使用和优化在并发的软件系统开发中,是个典型的场景,除了可能导致性能开销外,严重的甚至出现影响系统稳定的死锁问题。本文通过 android 系统源码的具体案例,来简单介绍下常见的锁优化,包括:忘加锁,多加锁,加错锁,锁小了,锁大了等情况。大锁转小锁,小锁转无锁等案例。 启动优化:盘点那些《不常规》的黑科技:该文章介绍了启动时间优化的一些方法,包括梳理冗余逻辑、设计启动框架、线程梳理、优化闪屏页、利用 Baseline Profile 技术,以及一些”非常规”的优化策略,如 Apk 资源重排、主动触发 dex2aot、启动阶段抑制 GC、以及保活策略等。 各个手机发布会上的游戏帧率能准确对比出谁家的手机性能更流畅么?:文章总结了如何更准确地评估手机游戏的性能指标,不仅仅看平均帧率。文章指出,平均帧率并不能完全反映游戏的流畅性,需要从多个方面了解游戏帧率的性能。 利用 ADPF 性能提示优化 Android 应用体验: Android Dynamic Performance Framework(ADPF)是 google 推广的一套用于优化散热以及 CPU 性能的动态性能框架。本文主要介绍其中的 performance hint 的部分。 技术剖析 | Java 内存屏障:从理论到实现:内存屏障(Memory barrier)是一组处理器指令,使 CPU 或编译器对屏障指令前后发出的内存操作强制实施排序约束,通常意味着在屏障之前发出的操作保证在屏障之后发出的操作之前执行。本文将深入探讨 Java 中内存屏障的概念、应用以及在 JVM 中的具体实现,从理论到实现阐述这一主题。 关于 Android15 GKI2407R40 导致梆梆加固软件崩溃:文章主要讲述了因 Android15 GKI2407R40 导致梆梆加固软件崩溃的问题。大量 App 闪退,经分析是访问无效地址导致段错误。通过多种手段分析,发现根本原因是 7r40 缺少相关补丁,解决办法是回退版本或应用后面正常版本。文中还介绍了问题分析过程和相关调试方法。 Tools, not Rules: become a better Android developer with Compiler Explorer: 这篇文章介绍了 Compiler Explorer 这个工具,它可以帮助开发者深入了解 Android 编译器的工作原理。文章展示了如何使用 Compiler Explorer 分析 Android Java 和 Kotlin 代码的编译输出,并介绍了一些编译优化技术,如返回值合并、常量折叠等。此外,文章还讨论了代码混淆和基线配置文件对编译过程的影响。总的来说,这篇文章鼓励开发者使用工具而不是死记硬背规则来提升代码效率。 移动平台的 GPU 性能分析:这篇文章介绍了在移动平台上分析 GPU 性能的方法。作者介绍了通常分析移动 GPU 性能的目的、使用的工具以及相关的性能指标,包括实时开销、带宽开销和 Shader Core 性能等方面。文章还讨论了在分析游戏场景数据时需要关注的一些指标。总的来说,这篇文章为移动游序渲染性能分析提供了一个全面的参考。 结合源码和 Perfetto 分析 Android 渲染机制:这篇文章主要介绍了 Android 渲染机制的原理和流程,结合 Perfetto 工具从多个角度分析 Android 渲染过程,并解答了一些渲染相关的常见问题。 Reddit improved app startup speed by over 50% using Baseline Profiles and R8:Reddit 通过使用 Baseline Profiles 和 R8 编译器优化大幅提高了 Android 应用的启动速度和渲染性能。该应用还采用 Jetpack Compose 重写了旧版 UI,在用户体验和开发体验上都有所改善。 物格而后知至:WALT 调度器之 RTG:RTG(Related Thread Group)是高通引入的一个特性,目的是将关联的进程加入到同一个 group 组中,用于提升性能。比如显示相关的主线程和 render 线程就可以加入到相同的 RTG 组中,根据实际需求,对其进行一些特殊处理。可以将其调度到同一个 CPU 簇上,使得它们可以共享相同的 cache 资源,减少 cache miss;也可以聚合 RTG 组里的任务负载,提升 CPU 的频率,达到性能提升的目的。RTG 对于选核也会产生一定的影响。除此之外,在 RTG 的基础上引申出来很多其它的特性,如优先调度等。 不做 Linux 内核代码的复读机:这篇文章讨论了学习 Linux 内核的正确方式。作者认为很多人只是在简单地复读 Linux 内核代码,而没有真正理解其背后的原理和思想。作者提出了七个关于调度器的问题,这些问题都需要内核开发者解决。 【Android 原生问题分析】夸克、抖音划动无响应问题【Android14】:这篇文章主要分析了 Android14 中夸克、抖音划动无响应的问题。通过多次添加 log 分析,发现问题出在 DisplayManagerService 通知机制上。一个 App 多个进程共享 uid 时,通知记录创建方式有误,导致部分进程无法收到通知。最终 Google patch 解决了此问题,将通知记录由一个 uid 对应一个改为对应一组。 Android 图形架构缺陷:Android 图形架构缺陷分析及优化方案 芦半山的文章: 经验 | 向 AOSP 贡献虚拟机的优化:文章主要讲述作者向 ART 主线提交虚拟机优化改动的经历,包括缘起、确定方案、开发各环节(下载代码、配置环境、开发、测试、测算效果、优化代码格式和性能)、提交与回撤,以及针对国内 App 生态的相关影响和后记。整个过程曲折,涉及诸多技术细节和问题解决。 问题 | Debuggable app 在 Android 14 上运行卡顿:富途开发者反映 debuggable 版本 app 在 Android 14 上卡顿,经探究发现是解释器执行模式改变所致。谷歌改动原因、性能劣化原因被分析,提出修复方案,经性能验证效果明显。此问题国内沟通不畅,作者愿做“传声筒”,还申请公众号同步更新文章。 【思考】学习源码的三重境界:文章主要讲述学习源码的三重境界。第一重是理解知识细节“知其然”;第二重是跳出细节,从宏观和历史角度思考,理解设计意图“知其所以然”;第三重是在实践中学习,通过主动写代码来深入理解。作者还举例说明了后两重境界,并在后记中强调看源码对自身能力提升有益。 【Android ART】回退到解释执行:文章介绍了 Android 虚拟机中的 deoptimize 概念,即从 AOT/JIT 回退到解释执行。以数组越界为例说明其作用,能使编译代码更精简优化。还阐述了切换过程的核心工作,如 Shadow Frame 重建、程序流向控制等,非调试原因的 deoptimize 通常只处理最上面一帧。 Java Hook 的实践之路:文章介绍了基于 ART 的 Java Hook 方案,包括原理选择倾向入口替换及解决其问题,确定了 API 设定和依赖库,阐述了实现过程中 Trampoline、Target Method 和 Hook 的设计,还补充了增强兼容性和稳定性的设计细节,最后指出方案仍有缺陷且项目暂无法开源。 【Android 15】内存分配器 Scudo 在这些年的优化:文章主要介绍了 Android 11 引入的内存分配器 Scudo 面临的困境及优化。困境在于其安全目标拖累性能和内存表现而被冷落。优化包括申请内存时用 Cache 和 TSD 应对问题,释放内存时采取措施缓解归还空闲页的性能损耗,高效使用内存时采用折中方案减少碎片化。最后指出优化思路源于生活。 【Android ART】Heap 的内存布局:本文基于 Android 15 介绍了 ART 中 Heap 的内存布局。Heap 由众多不同功能的内存组合而成,通过 Space 管理,文中先阐述内存布局。Heap 内存分为 5 种 Space:Image space 加载特定文件,对象不可移动和回收;Zygote space 由 zygote 启动过程中的重要对象构成,不可移动和回收;Non-moving space 对象不可移动但可回收;Large object space 管理特定对象,不可移动但可回收;Main space 是内存分配和回收的主要场所,对象可移动和回收。 Android Native | 信号的底层逻辑:本文基于 Android 15,围绕信号的底层逻辑展开,包括数据访问异常产生信号的过程、信号的形式及发送方式、线程检查处理信号的流程,还详细阐述了从异常状态切换到信号处理结束返回用户态的各阶段,涉及内核、CPU 等知识。 案例 | 奇怪,为什么 Hook 不生效?:大厂一兄弟在通过代理替换 hook 方法时遇到问题,目标是替换 Choreographer 中 mHandler 字段的两个方法,结果 release 包中一个生效一个不生效。经分析,hook 未生效是因机器码获取 ArtMethod 固定,不受代理对象影响,还探讨了相关优化、生效情况及原因,强调要具体情况具体分析。 实践 | 解决 GDB 无法调试 Android Coredump 的问题:文章主要讲述用 GDB 调试 Android Coredump 时遇到的问题及解决过程。因 LLDB 功能和可视化体验不足,作者尝试用 GDB 但遇到共享库无法加载的问题。经调试和分析,问题根源是一笔改动导致内存中读取的 program header 异常,修复方案是更正返回逻辑,也提到了之前版本的 workaround 方案。 Android“引用们”的底层原理:这篇文章深入剖析了 Android 中四种引用的底层原理,包括 WeakReference、SoftReference、PhantomReference 和 FinalizerReference。分别阐述了它们的特点、回收机制、处理逻辑等,如 WeakReference 如何欺骗 GC 及 get 方法的处理,SoftReference 的回收时机,PhantomReference 与 ReferenceQueue 的配合,FinalizerReference 与 finalize 方法的关联等,最后作者分享了研究此内容的心得。 解读 HWASan 日志:这篇文章主要介绍了 HWASan 相关内容,包括从 Android 14 开始其使用变得简单,错误提示类型及原因,如 tag-mismatch、invalid-free 等,还讲解了错误地址、内存块信息、根因判断逻辑、调用栈存储等,最后提到了寄存器记录等,作者虽知受众不多仍精心梳理。 Android | 拨开“类加载”的迷雾:本文主要围绕 Android 中的类加载展开,指出“双亲委派模式”译名易造成误解,阐述类加载的过程,包括查找是否已加载、开辟空间、填充数据、寻找父类和接口、方法和字段链接、完成初始化等步骤,并提及 PreloadClasses 环节作用衰减,被 Boot Images 取代,后者会提前加载和初始化部分类。 闲谈|关于工作选择这件事:文章围绕工作选择展开,通过回乡见闻及自身经历探讨形势变化下的工作现状。提到女司机兼职、钓鱼人增多、少儿培训招生难等,强调副业重要。还阐述对工作路线选择的思考,认为管理路线需等待机会,技术路线深度和广度最终会汇合,应重视个人时间和价值。 闲谈丨技术思维的桎梏:这篇文章探讨了技术思维的桎梏,指出程序员偏爱确定性,导致性格保守、遵守规则,丧失机会,还存在自负,互相轻视,造成人际关系紧张和视野局限。作者以自身经历为例进行阐述,最后表示写此文是为思考提纯和方便回顾,若能给他人带来感悟则荣幸。 赵俊民的文章 案例分享:NIO channels 导致一个内存泄漏:这是一篇探讨 NIO channels 内存泄漏问题的技术文章。作者分析了 SystemServer 进程中某个线程持有未释放的内存区域的原因,并找到了导致问题的关键代码位置。针对这个问题,作者与开发人员讨论并提出了具体的修复方案。 案例分享:一个奇怪 StackOverflowError 问题:这篇文章讨论了一个奇怪的 StackOverflow 异常问题的分析和解决方案。 技术管理思考:关于软件工程团队问题改进 1. 技术管理思考:关于软件工程团队问题改进 2 技术管理思考:关于软件工程团队问题改进 3 一个踩内存问题的折腾 谈软件质量管理 架构设计基本素养 Google 在 LLM 时代下软件工程 SimplePerf 系列 Simpleperf 理论与实践指南(前言篇) - 知乎 Simplepref 操作实践篇 - 知乎 火焰图(Flame Graph) - 知乎 Simpleperf 理论篇(七)- 查看结果 - 知乎 Simpleperf 理论篇(六)- 脚本参考 - 知乎 Simpleperf 理论篇(五)- 可执行命令 - 知乎 Simpleperf 理论篇(四)- Android 平台分析 - 知乎 Simpleperf 翻译篇 3-Android 应用分析 - 知乎 Simpleperf 翻译篇 2-幻灯片 - 知乎 Simpleperf 翻译篇 1-概述 - 知乎 如何从 Wall/CPU time 理解多线程程序的并行效率 - 知乎 小二哥的文章 [085]SW VSYNC 模型更新与校准 [086]VSYNC 研究-最后的窗户纸 [089]图解 Binder 应用篇-补课篇 [090]unsignaled-buffer-latch 功能 [093]SurfaceSyncer 的致命缺陷 [095]Binder 调用的优先级降级 [096]图解 HWC 的合成策略 [097]SurfaceSyncGroup 的细节 [098]vnd binder 和 binder 的共存问题 非技术文章 程序员海外工作 — 语言篇:本文主要讲述了作者在提高英语听力、口语、阅读和写作方面的一些经验。 So Long, and Thanks for All the Bytes:Chet Haase 撰写的告别文章。他在谷歌 Android 团队工作了近 14 年,是该领域的资深人士。然而,他决定离开科技行业,重新投身于自己热爱的喜剧剧本创作。他打算前往芝加哥攻读喜剧编剧专业的硕士学位,开启人生的新征程。 中外程序员差异:这篇文章对比了中外程序员的一些差异,总结了几个方面的差异,包括:关键要点、细节把握程度、文字表达能力、个人职业目标、业余时间安排 程序员如何提升个人技术影响力|得物技术:本文探讨了程序员如何提升个人技术影响力的方法和实践。从输入知识、输出项目贡献、写技术文章、做行业演讲、出书等方面提供了详细的建议和步骤。文章强调了持续学习和输出的重要性,力求帮助技术同学建立自己的影响力。 降本增效的本到底是什么?:最近几年所有公司都在强调降本增效,降本增效的措施从裁员,优化再到 AI, 方法五花八门,但回归到问题本质,我们付出的最高成本真的是在人力,设备,推广这些显性的投入上吗?不是的,我认为恰恰相反,成本最高的是我们的决策,特别是管理者的决策,决策失误带来的成本才是最高的,而且这个问题也最难解决。观察了那么多优秀团队,自己也带团队多年,一个 NB 的团队一定会有一个特征:有着极高的决策效率,决策效率则包括两个方面,决策速度和决策成功率。 我们如何学习?:该文章探讨了如何融合引导式学习和沉浸式学习的方法,以提高学习效果。作者认为,通过利用人工智能技术,可以在实际项目中为学习者提供个性化的指导和支持,帮助他们全身心投入并将所学知识牢固掌握。同时,作者还讨论了记忆系统和练习方法,以及聊天机器人导师的局限性。 读书笔记-超越百岁:这篇文章主要概括了《超越百岁》一书的核心内容,介绍了长寿的科学原理和实践方法。文章从医学发展的 3.0 时代说起,指出现有医疗模式对抗”四大骑士”(心脏病、癌症、2 型糖尿病、阿兹海默症)的方法存在问题,并提出了新的健康管理理念。文章重点阐述了影响健康和寿命的 5 个关键因素:运动、营养、睡眠、情绪和药物,从中筛选出最关键的锻炼和饮食两个杠杆。同时还总结了百岁老人和长寿基因的研究进展。 第一性原理思考:解决问题的通用框架:这篇文章介绍了一个基于第一性原理的通用问题解决框架,主要包括 4 个步骤:信息收集、信息建模、信息判断和策略迭代。文章以减肥和提高 iOS 开发水平作为具体案例,详细阐述了每个步骤的具体操作。最后,文章也指出了该框架的三个局限性。 第一性原理思考:解决问题的通用框架(续):这篇文章介绍了一种解决问题的通用框架,重点讨论了信息判断这一核心步骤。作者列举了几种有用的信息判断方法,如 28 原理、谬误推导、终局思维等,同时也分析了一些常见的信息判断误区,如把相关性当因果、从众心理、现状即是真理和情绪等。 聊聊未来技术趋势:本文提供了一篇关于未来技术趋势的分享内容。作者从 AI、投资、下一代技术等方面,结合市场报告和资料,分享了一些自己的理解和见解。文章涉及了 SpaceX 火箭回收、OneKey 硬件钱包、区块链和智能合约等主题。 AI 和写作:写作 AI 和辅助创作的思考 十年学会编程:该文提出了学习编程的一些建议,提倡用十年时间来深入学习编程,而不是仅仅通过几天或几小时的速成课程学习。作者认为,要成为一名优秀的程序员需要长期的实践和经验积累。 OpenAI o1 模型的前世今生: OpenAI 发布的 o1 模型在推理能力上取得了巨大进步,在 STEM 领域的表现远超 GPT-4 等前代模型。o1 的关键在于在预训练和训练后两个阶段采用了创新性的方法,如使用大量包含推理过程数据进行预训练、引入强化学习和自举生成思维链等。o1 的出现标志着大语言模型进入”后训练”时代,未来可能引发模型性能大幅提升,同时也带来了一些安全隐患需要关注。 Follow —— 信息获取的另一种形态:Follow 是一款新型的信息获取和浏览工具,它改变了普通用户获取信息的方式。它通过对 RSS 信息呈现方式的优化、与 RSSHub 的深度集成以及自定义信息源的功能,为用户打造了一个个性化的 “数字花园”,提升了信息获取的效率和体验。 【0101】技术的定位:程序员是这个时代的手艺人:这篇文章探讨了程序员的职业定位,分析了技术红利的发展趋势,指出了程序员应该如何管理自己的心态、专注于技术能力的提升来迎接未来的挑战。 全网最深度解读:自研 CPU,如何成为高通骁龙的最后一块拼图?:聊聊高通自研 CPU 背后的本质逻辑和深度技术梳理 AI 时代的独立开发之路:本文介绍了作者从文学转向编程,并分享了他开发的两个 AI 应用程序的成功经验。作者从不会编程到自己开发应用,探索了在 AI 时代独立开发的可能性。文中主要讨论了作者开发 AI 美食推荐应用和陌生人闹钟的过程和思考。 程序员焦虑症之「没用过」和「不知道」,码农的「拧螺丝」之道:这篇文章探讨了程序员常见的焦虑症状——“没用过”和”不知道”新技术的焦虑。作者认为这源自于我们只是在被动”拧螺丝”而非主动理解技术本质。他建议,我们应该尊重自己的能力,不要被框架和工具的更新所束缚,而要去理解它们的原理和工作机制,从而真正提升技术和解决能力。 编程十年的感悟:这篇文章是作者分享了自己 10 年来从事编程工作的感悟。主要包括:持续学习新技术、重视英语能力提高、培养独立思考能力、关注产品搭建而非过于追求代码优化、选择合适的工具技术而非追求最新流行、重视与优秀同事的交流学习、保重身体健康等方面。作者认为编程的核心竞争力在于解决问题的能力,而非局限于某一种具体技术。 我在性能团队的这两年:作者从 2 年前加入飞书文档性能团队开始介绍了性能指标的定义、细化、优化过程以及防劣化机制的各个环节。包括线下通过自动化脚本检测劣化、线上通过大盘数据监控、针对性能劣化进行追踪和复现等方面的经验。最后还分享了团队在滚动防劣化、块加载优化、内存优化等具体场景下的实践。 有序退网:该文章作者分享了他渐进退网的决定和过程。作者认为互联网和社交媒体已经变得过于”上瘾”,并对个人生活和工作产生了负面影响。通过阅读相关心理学书籍以及亲身体验”闭关”的积极效果,作者决定开始有意识地控制自己的网络使用时间,将精力集中于工作、健身、学习、开源项目等其他更有价值的方面。 个人博客内容更新 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View 2023 年的方方面面 Android App ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android App ANR 系列 3 :ANR 案例分享 Android 开发者们的动态 我使用 Follow 创建了一个 Android 开发者们动态的 List,目前免费,欢迎使用 Follow App 作为知识输入源的各位订阅。 关于 Follow App 的介绍可以看这篇文章:Follow —— 信息获取的另一种形态:Follow 是一款新型的信息获取和浏览工具,它改变了普通用户获取信息的方式。它通过对 RSS 信息呈现方式的优化、与 RSSHub 的深度集成以及自定义信息源的功能,为用户打造了一个个性化的 “数字花园”,提升了信息获取的效率和体验。 目前 Follow 已经取代了我的大部分内容获取软件,新闻、动态、独立博客更新、视频、音频、包括社交网络都可以订阅。 打鸡血 这幅图展示了如何有效记忆和保留知识的策略,助你成为学习高手: 费曼技巧: 选择主题:深入理解一个主题。 简单解释:用简单的语言或对孩子解释。 识别知识空白:找出并填补理解中的漏洞。 精炼解释:提高解释的清晰度和准确性。 自我测试:通过测试进一步简化解释。 莱特纳系统: 通过逐步减少复习频率,将难题转化为易题。 每周三次复习难题、两次中等题、一次易题。 主动回忆: 主动提取信息,加强神经连接,而非被动吸收。 六大记忆保持技巧: 多样化资源:使用视频、书籍和便利贴等多种学习材料。 建立联系:用类比将新信息与熟悉概念关联,便于理解和记忆。 深入理解:关注背后的原因,加深理解,使信息更有意义。 关注健康:优先保证睡眠、营养和锻炼,维持大脑健康。 间隔学习:分散学习时间,避免认知过载。 信息分块:将复杂信息分解成小块,逐步学习以防止压倒性负担。 关于作者 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 个人博客: 写东西的地方 博主个人介绍 :里面有个人的微信和微信群链接。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 微信公众号: 知乎:https://www.zhihu.com/people/gracker 邮箱订阅:https://androidweekly.zhubai.love/ 掘金:https://juejin.cn/user/1816846860560749
本篇是 Perfetto 系列文章的第三篇,前两篇介绍了 Perfetto 是什么以及 Perfetto Trace 怎么抓,本篇主要是在网页端打开 Perfetto Trace 之后,面对复杂的 Perfetto 信息该怎么看。 随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 本系列旨在通过 Perfetto 这个工具,从一个新的视角审视 Android 系统的整体运作方式。此外,它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章,但或许因为难以记住代码或不明白其运行流程,你仍感到困惑。通过 Perfetto 这个图形化工具,你可能会获得更深入的理解。 Perfetto 系列目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace 视频(B站) - Android Perfetto 基础和案例分享 如果大家还没看过 Systrace 系列,下面是传送门: Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 Perfetto View 界面 抓到 Perfetto Trace 之后,一般是在 ui.perfetto.dev 中打开(如果用官方提供的脚本,则会在抓去结束后自动在这个网站上打开,想看看怎么实现的话可以去看看脚本的源码)。打开后界面如下: 可以通过 Open trace file 或者直接把 Perfetto Trace 拖到白色区域来打开 Trace。 Perfetto Trace 界面 打开 Perfetto Trace 之后界面如下: 大致上 Perfetto Trace 界面可以分为四个区域: 最右边的操作区:这里最主要的是 Current Trace 这一栏下面的那几个会经常用到。 Show timeline :显示当前 Trace,切到了别的界面之后,再点这个就会回到 Trace View 界面 Query:写 SQL 查询语句和查看执行结果的地方 Metrics:官方默认帮你写好的一些分析结果,可以选择直接打开 Info and stats :当前 Trace 和手机 App 的一些信息 上方的信息和操作区域:最主要就是看时间。 中间的 Trace 内容区:操作最多的区域,Trace 内容都在这部分,最上面的几部分是从功能的角度来划成一个区域的,比如 CPU 区(可以查看当前 Task 跑在哪个核心上,频率是多少,跑了多长时间、被谁唤醒)、Ftrace event 区等;下面的就是以 App Process 为单位展示的(包括 App 的各种线程、Input 事件、Binder Call、Memory、Buffer 等信息)。 最下方的信息区:这个区域主要是展示各种信息、我们选中了某个 Task 段之后,这里就会展示这个 Task 相关的信息(如果你加了 Log,这里也会显示 Log;ftrace event 同理)。 Perfetto 界面最初看的时候会觉得很乱,花里胡哨的,但是用习惯了之后,真香~ 基本操作 Perfetto Trace 界面的操作是非常顺滑的,这是相比 Systrace 的一个巨大的优势,Systrace 打开稍大的 Trace 就会卡卡的,但是 Perfetto Trace 打开 500Mb 的 Trace 依然操作很顺滑。 操作看 Trace 的快捷键跟 Systrace 很像,w/s 是放大/缩小,a/d 是左右移动,鼠标点击是选择。官方左下角的文档有详细的操作说明,忘记了的话可以随时去看看,熟能生巧: 其他快捷键里面用的比较多的: f 是放大选中 m 是临时 Mark 一段区域(与 Systrace 一样), 用来上下看时间、看其他进程信息等。临时的意思就是你如果按 m 去 mark 另外一个区域,那么上一个用 m mark 出来的 Mark 区域就会消失。退出临时选中:esc ,或者选择其他的 Slice 按 m,当前这个 Slice 的选中效果就会消失 shift + m 是持续 Mark 一段区域(如果你不点,他就不会消失),主要是用来长时间 Mark 住一段信息,比如你把一份 Trace 中所有的掉帧点都 Mark 出来,就可以用 shift + m,这样就不会丢失。 点击小旗子,就可以看到这段区间内的执行信息 删除持续 Mark 点击你选中的那个 Slice 的最上面那个三角 下面选择 Tab:Current Selection 点击最后边的 Remove ,就可以把他 Remove 掉了 q :隐藏和显示信息 Tab,由于 Perfetto 非常占屏幕,熟练使用 q 键很重要,看的时候快速打开,看完后快速关闭。 插旗子:Perfetto 还可以通过插旗子的方法来在 Trace 上做标记,Perfetto 支持你把鼠标放到 Trace 最上面,就会出现一个旗子,点击左键即可插一个旗子在上面,方便我们标记某个事件发生,或者某个时间点 取消插的旗子:与退出持续选中一样,点击旗子,右下角有个 Remove ,点击就可以把这个旗子干掉了,就不插图了 Perfetto 使用技巧 查看唤醒源 我们可以通过查看某一个 Task 的唤醒源,来了解 App 和 Framework 的运转流程,Systrace 和 Perfetto 都可以查看唤醒源,不过 Perfetto 在这方面做的更丝滑一些。 在 Android Systrace 响应速度实战 3 :响应速度延伸知识 这篇文章中,有讲 Systrace 是如何查看唤醒源的,其实略微还是有些麻烦的。 Perfetto 中查看唤醒源则非常方便且操作很顺滑: 比如我们想看下图中, RenderThread 是被谁唤醒的,我们可以有好几种方法: 点击 Runnable 状态 与 Systrace 操作一样,直接点击 Running 前面的 Runnable,就可以在下面的信息区看到 Related thread states: Waker:唤醒源 Previous state:这个 Task 的前一个状态 Next state:这个 Task 的后一个状态 点击他上方的 Running 状态,查看连续唤醒信息: 或者我们可以点击 Running 状态,点击小箭头直接跳到对应的 CPU Info 区域,这里可以看到更详细的信息,也可以连续点击 Task,来追踪唤醒源,并可以通过信息区的小箭头来回在 CPU Info 区域和 Task 区域跳转 点击 RenderThread 上方的 Running 状态,通过小箭头跳转到 CPU Info 区域 RenderThread 是被 MainThread 唤醒的 再点击 MainThread 可以看到他是被 SurfaceFlinger 唤醒的,下方信息区还有对应的唤醒延迟分析 查看 Critical Path(Task) Critical Task 指的是与当前我们选中的 Task 有依赖关系的 Task,比如我们的 Task 是 e,e 要等 d 执行结束后才能执行,d 要等 c,c 要等 b,b 要等 a,那么 e 的 Critical Task 就是 a、b、c、d。 Perfetto 上就可以查看某一个 Task 的 Critical Task,鉴于 Critical path lite 是 Critical path 的子集,我们这里只介绍 Critical path: 点击 Running 状态,然后点击在下面的信息区点击 Critical path 稍等片刻就可以看到我们选择的 MainThread 对应的 Critical path: 放大来看,可以看到我们选择的 MainThread 的边缘,第一个 Critical Task 是唤醒他的 sf 的 app 线程 再往左看 sf 的 app 线程是被 sf 的 TimerDispatch 线程唤醒的,这里就不贴了。 其实可以看到,Perfetto 提供的 Critical Path 其实就是把连续唤醒的 Task 都聚集到一起了,方便我们来看各个 Task 之间的关系。 Pin (固定到最上面) 在每个 Thread 的最左边,有一个图钉一样的按钮,点击之后,这个 Thread 就会被固定到最上面,方便我们把自己最关注的 Thread 放到一起。 比如下面是我 Pin 的从 App 到 SF 的流程图,放到一起的话就会清晰很多,看掉帧的话也会更方便。 CPU Info 区域 Task 高亮 在 CPU Info 区域,鼠标放到某一个 Task 上,就会这个 Task 对应的 Thread 的其他 Task 都会高亮。 我们经常会用这个方法来初步看某些 Thread 的摆核信息 查看 Buffer 消耗情况 App 的 Buffer 消费者是 SurfaceFlinger,通过 App Process 这边的 Actual Timeline 这行,我们可以看到 Buffer 具体是被 SurfaceFlinger 的哪一框消费了。 快速查看 App 执行超时 由于 Android 多 Buffer 机制的存在,App 执行超时不一定会卡顿,但是超时是需要我们去关注的。 通过 Perfetto 提供给的 Expected Timeline 和 Actual Timeline 这两行,可以清楚看到执行超时的地方。 点击 Actial Timeline 红色那一段,底下的信息栏会显示掉帧原因: 在 Perfetto 上查看 Log 在信息栏上切换到 Android Logs 这个 Tab,鼠标放倒某一行上,Perfetto 就会把对应的 Timeline 拉一条直线,可以看到这个 Log 所对应的时间 同样切换到 Ftrace events tab 也可以查看对应的 ftrace 的 event 和对应的时间线 分析 Thread 的 Running 信息 可以通过鼠标左键按住滑动,选中一段区域来进行分析,比如选中 CPU State 这一栏的话,就可以看到这一段时间对应的 Running、Runnable、Sleep、Uninterruptible Sleep 的占比。 这在分析 App 启动的时候经常会用到。 总结 上面分享了 Perfetto 基本的界面和操作,以及分享了一些比较常用的 Perfetto 的技巧。Google 目前在积极推广和维护 Perfetto,很多新功能指不定哪天就蹦出来了,到时候觉得有用我也会更新上来。 至此 Perfetto 基础篇就结束了,后续就是通过 Perfetto 这个工具,来了解 Android 系统运行的基本流程,以及使用 Perfetto 以及 Perfetto SQL 来分析遇到的性能、功耗等问题。 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
上一篇文章 Android Perfetto 系列 1:Perfetto 工具简介 介绍了 Perfetto 是什么,这篇简单介绍一下 Perfetto 的抓取。 随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 本系列旨在通过 Perfetto 这个工具,从一个新的视角审视 Android 系统的整体运作方式。此外,它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章,但或许因为难以记住代码或不明白其运行流程,你仍感到困惑。通过 Perfetto 这个图形化工具,你可能会获得更深入的理解。 Perfetto 系列目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace 视频(B站) - Android Perfetto 基础和案例分享 如果大家还没看过 Systrace 系列,下面是传送门: Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 正文 使用 Perfetto 分析问题跟使用 Systrace 分析问题的步骤是一样的: 首先你需要抓取 Perfetto 文件 在ui.perfetto.dev 中打开 Trace 文件进行分析或者使用命令行来进行分析 这篇文章就简单介绍一下使用 Perfetto 抓取 Trace 文件的方法,个人比较推荐使用命令行来抓取,不管是自己配置的命令行还是官方的命令行抓取工具,都非常实用。 1. 使用命令行来抓取 Perfetto(推荐) 基本命令 - adb shell perfetto 对于之前一直用 Systrace 工具的小伙伴来说,命令行抓取 Trace 非常方便。同样,Perfetto 也提供了简单的命令行来抓取,最简单的使用方法与 Systrace 基本一致。你可以直接连到你的 Android 设备上使用/system/bin/perfetto命令来启动跟踪。例如: 1 2 3 4 5 6 7 8 //1. 首先执行命令 adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 20s \ sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory // 2. 操作手机,复现场景,比如滑动或者启动等 // 3. 将 trace 文件 pull 到本地 adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace 这个命令会启动一个 20 秒钟的跟踪,收集指定的数据源信息,并将跟踪文件保存到/data/misc/perfetto-traces/trace_file.perfetto-trace。 执行 adb pull 命令把 trace pull 出来,就可以直接在ui.perfetto.dev 上打开了。 进阶命令 adb shell perfetto with config file 这里就是 Perfetto 与 Systrace 不同的地方,Perfetto 可以抓取的信息非常多,其数据来源也非常多,每次都用命令行加一大堆配置的话会很不方便。这时候我们就可以使用一个单独的**配置文件(Config)**,来存储这些信息,每次抓取的时候,指定这个配置文件即可。 对于在 Android 12 之前和之后版本上使用 Perfetto 的配置文件传递,以下是详细的指南和对应的命令行示例。 在 Android 12 及之后的设备上 从 Android 12 开始,可以直接使用/data/misc/perfetto-configs目录来存储配置文件,这样就不需要通过 stdin 来传递配置文件了。具体命令如下: 1 2 adb push config.pbtx /data/misc/perfetto-configs/config.pbtx adb shell perfetto --txt -c /data/misc/perfetto-configs/config.pbtx -o /data/misc/perfetto-traces/trace.perfetto-trace 在这个例子中,首先将配置文件config.pbtx推送到/data/misc/perfetto-configs目录中。然后,直接在 Perfetto 命令中通过-c选项指定配置文件的路径来启动跟踪。 在 Android 12 之前的设备上 由于 SELinux 的严格规则,直接通过文件路径传递配置文件在非 root 设备上会失败。因此,需要使用标准输入(stdin)来传递配置文件。这可以通过将配置文件的内容cat到 Perfetto 命令中实现。具体命令如下: 1 2 adb push config.pbtx /data/local/tmp/config.pbtx adb shell 'cat /data/local/tmp/config.pbtx | perfetto -c - -o /data/misc/perfetto-traces/trace.perfetto-trace' 这里,config.pbtx是你的 Perfetto 配置文件,首先使用adb push命令将其推送到设备的临时目录中。然后,使用cat命令将配置文件的内容传递给 Perfetto 命令。 Config 的来源 Config 我建议使用 ui.perfetto.dev 的 Record new trace 这里进行选择定制,然后再保存到本地的文件里面,不同的场景就加载不同的 Config 即可,文章最后一部分有详细讲到这部分,感兴趣的可以看一下。 官方也提供了 share 按钮,你可以把你自己的 config share 给其他人,非常方便。同时我也会建了一个 Github 的库,方便大家在分享(进行中)。 官方代码库也有一些已经配置好的,各位可以下下来自己使用:https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/ 注意事项 确保 adb 正常:在使用这些命令之前,请确保你的设备已经启用了 USB 调试,并且已经通过adb devices命令确认设备已经正确连接。 Ctrl+C 中断: 当使用adb shell perfetto命令时,如果你尝试使用 Ctrl+C 来提前结束跟踪,这个信号不会通过 ADB 传播。如果你需要提前结束跟踪,建议使用一个交互式的 PTY-based session 来运行adb shell。 SELinux 限制: 在 Android 12 之前的非 root 设备上,由于 SELinux 的严格规则,配置文件只能通过cat config | adb shell perfetto -c -的方式传递(其中-c -表示从标准输入读取配置)。从 Android 12 开始,可以使用/data/misc/perfetto-configs路径来存储配置文件。 在 Android 10 之前的版本, adb 没法直接把 /data/misc/perfetto-traces pull 出来. 你可以使用 adb shell cat /data/misc/perfetto-traces/trace > trace 来替代 2. 使用 Perfetto 提供的官方脚本抓取(强烈推荐) Perfetto 团队还提供了一个便捷的脚本tools/record_android_trace,它简化了从命令行记录跟踪的流程。这个脚本会自动处理路径问题,完成跟踪后自动拉取跟踪文件,并在浏览器中打开它。本质上这个脚本还是使用的 adb shell perfetto 命令,不过官方帮你封装好了,使用示例: On Linux and Mac: 1 2 3 4 curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace chmod u+x record_android_trace ./record_android_trace -o trace_file.perfetto-trace -t 10s -b 64mb \ sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory On Windows: 1 2 3 curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace python3 record_android_trace -o trace_file.perfetto-trace -t 10s -b 64mb \ sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory 同样的,这里也可以通过 -c 来指定配置文件,比如 1 2 3 4 curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace chmod u+x record_android_trace ./record_android_trace -c config.pbtx -o trace_file.perfetto-trace -t 10s -b 64mb \ sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory 这将会记录一个 10 秒的跟踪,并将输出文件保存为trace_file.perfetto-trace。 执行后会自动抓取 Trace, 自动在浏览器自动打开,非常方便 脚本内容可以直接访问:https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace 来查看, 3. 使用手机上的开发者工具来抓取 当然有时候会没有办法连接到电脑上,或者测试内容不能插 usb,这时候就可以使用 Android 上的自带的系统跟踪应用(System Tracing App)来抓取 Trace。这个应用内置于开发者选项中,可以让你通过几个简单的步骤来配置和启动性能跟踪。 启动系统跟踪应用 启用开发者选项:首先,确保你的设备已经启用了开发者选项。如果你的设置里面没有开发者选项,你需要在关于手机那里,找到编译编号,然后连续点击 7 次,就可以打开开发者选项。 打开开发者选项:在设置菜单中找到并打开开发者选项。 启动系统跟踪:在开发者选项中向下滚动直到找到“系统跟踪(System Trace)”或类似的选项。点击它,将打开系统跟踪应用。 大概长下面这样(每个手机可能界面或者文字会有差异,但是功能是一样的) 系统跟踪应用提供了一系列的配置选项,包括但不限于: 跟踪时长:你可以指定跟踪的持续时间,例如 10 秒或更长时间。 数据源:选择你想要收集数据的来源。这可能包括 CPU、内存、网络等多种不同的数据源。 输出文件位置:指定跟踪文件保存的位置。 启动和停止跟踪 配置好所有需要的参数后,你可以通过点击“录制跟踪记录”按钮来启动跟踪。再次“录制跟踪记录”按钮就可以结束抓取,完成抓取后,通常会有一个提示告诉你抓取已经完成,并提供查看或分享跟踪文件的选项。就可以将跟踪文件导出到电脑上,使用 Perfetto 网页 UI 进行更深入的分析。 4. 使用网页端来抓取 网页端抓取的功能比较迷,很多时候你都会抓取失败,比如连不上 adb、连上之后说你需要执行 kill。所以我更推荐大家使用配置好的命令行来抓取,网页端更适合进行 Config 的配置。 Perfetto 还提供了一个强大的 网页端工具(ui.perfetto.dev),允许开发者通过浏览器配置和启动跟踪。你只需要访问 网站,点击“Record new trace”,然后根据需要选择数据源和配置参数。确保你的设备通过 ADB 连接到电脑,并且在网页端选择“Add ADB device”。之后,点击“Start Recording”即可开始收集跟踪数据。 这里选好你想抓取的信息源之后,可以点击 Recording command 来查看,这里可以看到你选好的 Config 的具体内容,你可以分享或者保存到本地的文件里面,用命令行抓取的时候使用。 选取 Config 的时候,Android apps 那一栏里面的 Atrace userspace annotations、Event log (logcat)、Frame timeline 建议都选上(command + a) 另外如果想看调用栈,可以把 Stack Samples 这里的 Callstack sampling 勾选上(注意需要最新版本的 Android 才可以,而且所 debug 的进程得是 debugable 的) 至于其他的有啥用,可以自己探索,后续的 Perfetto 也会介绍到每个部分和他在 Trace 上的呈现,帮助大家更快入手 Perfetto。 从网页端提取参数 前面提到网页端的可以图形化选择 Config 这个很方便,选好之后,点击 Recording command 这里,就可以看到已经选好的 Config,你在保存的时候记得把下面这几行去掉就可以了 参考文档 https://perfetto.dev/docs/quickstart/android-tracing https://perfetto.dev/docs/concepts/config https://developer.android.com/tools/releases/platform-tools?hl=zh-cn https://mp.weixin.qq.com/s/nsqc51L5T4mrTUVsPgkj6A https://juejin.cn/post/7344983784549400613 https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/ 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本篇是 Perfetto 系列文章的第一篇,主要是简单介绍 Perfetto 工具,包括 Perfetto 的历史、发展,以及 Perfetto 能做什么。 随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 本系列旨在通过 Perfetto 这个工具,从一个新的视角审视 Android 系统的整体运作方式。此外,它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章,但或许因为难以记住代码或不明白其运行流程,你仍感到困惑。通过 Perfetto 这个图形化工具,你可能会获得更深入的理解。 Perfetto 系列目录 Android Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace 视频(B站) - Android Perfetto 基础和案例分享 如果大家还没看过 Systrace 系列,下面是传送门: Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 正文 2019 年开始写 Systrace 系列,陆陆续续写了 20 多篇,从基本使用到各个模块在 Systrace 上的呈现,再到启动速度、流畅性等实战,基本上可以满足初级系统开发者和 App 开发者对于 Systrace 工具的需求。通过博客也加了不少志同道合的小伙伴,光交流群就建了有 6 个。这里非常感谢大家的支持。 随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 所以就有了这个系列,我也有在星球里面写了几条为什么要更新 Perfetto 系列的原因(之前一直觉得 Systrace 系列就够了): 目前 Oppo、Vivo 这些手机厂商内部,都已经切换成 Perfetto 了,不管是抓 Trace 还是看 Trace,都在使用 Perfetto ,很多新人接触的都是 Perfetto 而不是 Systrace ,守着之前的老 Systrace 系列会流失这部分读者 之前的 Systrace 系列,对应的 Code 已经比较老了,全新的 Perfetto 系列可以使用 Android 14 的 Code 来进行更新 个人对 Perfetto 的使用也没有很深入,有些高阶功能目前还只是浅尝辄止。可以通过重写 Perfetto 系列来进行这部分内容的强化 Perfetto 是个很强大的工具,他的背后是整个 Android + Linux 系统,所以在写这个系列的时候,应该是以他背后的这个 Android + Linux 为主,而不是仅仅局限于 Perfetto 这个工具。工具只是我们观测 Android + Linux 的方式,理解整个 Android 系统运行的规律,思考其运行的原理,通过工具挖掘问题,通过问题思考本质,这才是对开发者来说有意义的 很多 Android 系统运行相关的内容,Perfetto 的官方文档还是没有讲,这部分我这边可以作为补足;另外官方文档是英文版本的,中文博客可以补充这方面。 Perfetto 可以拿到 Google Dev Fest 上作为演讲内容~。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 性能分析为什么需要上帝视角 在介绍 Perfetto 之前,我们需要了解为什么性能分析需要 Systrace 和 Perfetto 这样的工具:以 Android 系统为例,影响性能的因素是非常多的:App 自身质量、系统各个模块的性能、Linux 的性能、硬件性能,再加上各个厂商的策略、厂商定制的功能、系统自身的负载、低内存、发热、Android 各个版本的差异、用户的使用习惯等。这都不是通过分析某一个 App 或者某一个模块就能知道原因的,我们需要一个上帝视角,从更高的纬度来看 Android 系统的运行情况。 而 Perfetto 工具就提供了这样的一个上帝视角,通过上帝视角我们可以看到 Android 系统在运行时的各个细节,比如 Input 事件是怎么流转的 你正在使用的 App 的每一帧是怎么从 App 产生到上屏的 CPU 的实时频率、负载、摆核、唤醒等 系统中的各个 App 是怎么运行的 …. App 开发者和 Android 系统开发者也都会在重要的代码逻辑处加上 Trace 点,打开部分 Debug 选项之后,更是可以得到非常详细的信息,甚至一个 Task 为什么摆在某个 cpu 上,都会有详细的记载。通过这些在 Perfetto 上所展示的信息,我们能初步分析到性能问题的原因,接下来继续分析就会有针对性。 同样为了说明性能优化的复杂性,可以看看 这本书中对于性能的描述,具体来说就是方法论,非常贴合本文的主题,也强烈推荐各位搞性能优化的同学,把这本书作为手头常读的方法论书籍:系统性能工程是一个充满挑战的领域,具体原因有很多,其中包括以下事实,系统性能是主观的、复杂的,而且常常是多问题并存的** 性能是主观的 技术学科往往是客观的,太多的业界人士审视问题非黑即白。在进行软件故障查找的时候,判断 bug 是否存在或 bug 是否修复就是这样。bug 的出现总是伴随着错误信息,错误信息通常容易解读,进而你就明白错误为什么会出现了 与此不同,性能常常是主观性的。开始着手性能问题的时候,对问题是否存在的判断都有可能是模糊的,在问题被修复的时候也同样,被一个用户认为是“不好”的性能,另一个用户可能认为是“好”的 系统是复杂的 除了主观性之外,性能工程作为一门充满了挑战的学科,除了因为系统的复杂性,还因为对于性能,我们常常缺少一个明确的分析起点。有时我们只是从猜测开始,比如,责怪网络,而性能分析必须对这是不是一个正确的方向做出判断 性能问题可能出在子系统之间复杂的互联上,即便这些子系统隔离时表现得都很好。也可能由于连锁故障(cascading failure)出现性能问题,这指的是一个出现故障的组件会导致其他组件产生性能问题。要理解这些产生的问题,你必须理清组件之间的关系,还要了解它们是怎样协作的 瓶颈往往是复杂的,还会以意想不到的方式互相联系。修复了一个问题可能只是把瓶颈推向了系统里的其他地方,导致系统的整体性能并没有得到期望的提升。 除了系统的复杂性之外,生产环境负载的复杂特性也可能会导致性能问题。在实验室环境很难重现这类情况,或者只能间歇式地重现 解决复杂的性能问题常常需要全局性的方法。整个系统——包括自身内部和外部的交互——都可能需要被调查研究。这项工作要求有非常广泛的技能,一般不太可能集中在一人身上,这促使性能工程成为一门多变的并且充满智力挑战的工作 可能有多个问题并存 找到一个性能问题点往往并不是问题本身,在复杂的软件中通常会有多个问题 性能分析的又一个难点:真正的任务不是寻找问题,而是辨别问题或者说是辨别哪些问题是最重要的 要做到这一点,性能分析必须量化(quantify)问题的重要程度。某些性能问题可能并不适用于你的工作负载或者只在非常小的程度上适用。理想情况下,你不仅要量化问题,还要估计每个问题修复后能带来的增速。当管理层审查工程或运维资源的开销缘由时,这类信息尤其有用。 有一个指标非常适合用来量化性能,那就是 延时(latency) Perfetto 介绍 Perfetto 是一个高级的开源工具,专为性能监测和分析而设计。它配备了一整套服务和库,能够捕获和记录系统层面以及应用程序层面的活动数据。此外,Perfetto 还提供了内存分析工具,既适用于本地应用也适用于 Java 环境。它的一个强大功能是,可以通过 SQL 查询库来分析跟踪数据,让你能够深入理解性能数据背后的细节。为了更好地处理和理解大规模数据集,Perfetto 还提供了一个基于 Web 的用户界面,使你能够直观地可视化和探索多 GB 大小的跟踪文件。简而言之,Perfetto 是一个全面的解决方案,旨在帮助开发者和性能工程师以前所未有的深度和清晰度来分析和优化软件性能。 谷歌在 2017 年开始了第一笔提交,随后的 6 年(截止到 2024)内总共有 100 多位开发者提交了近 3.7W 笔提交,几乎每天都有 PR 与 Merge 操作,是一个相当活跃的项目。 除了功能强大之外其野心也非常大,官网上号称它是下一代面向可跨平台的 Trace/Metric 数据抓取与分析工具。应用也比较广泛,除了 Perfetto 网站,Windows Performance Tool 与 Android Studio,以及华为的 GraphicProfiler 也支持 Perfetto 数据的可视化与分析。 我们相信谷歌还会持续投入资源到 Perfetto 项目,可以说它应该就是下一代性能分析工具了,会完全取代 Systrace。 如果你已经习惯使用 Systrace,那么切换到 Perfetto 会非常顺滑,因为 Perfetto 是完全兼容 Systrace 的。你之前抓的 Systrace 文件,可以直接扔到 Perfetto Viewer 网站里面直接打开。如果你还没有适应 Perfetto ,你也可以从 Perfetto Viewer 一键打开 Systrace。 下图是 Perfetto 的架构图,可以看到 Perfetto 包含了三大块: Record traces :即数据抓取模块,可以看到抓取的内容和来源非常丰富,Java、 Native 、Linux 都有涉及到,相比 Systrace 要丰富很多。 Analyze traces :主要是 trace 分析模块,包括 Trace 解析、SQL 查询、Metrics 分析等,这部分有专门的命令行工具提供,方便大家直接调用或者在工具链里面去调用。 Visualize Traces:Trace 的呈现、抓取等 这几个模块在后续的系列文章中都会详细介绍 Perfetto 的核心优势和功能亮点: 通过长时间的使用和对比,以及看各种分享,总结了一下 Perfetto 的核心优势和功能两点 支持长时间数据抓取: Perfetto 通过后台服务支持长时间数据抓取,利用 Protobuf 编码存储数据。 数据来源与兼容性: 基于 Linux 内核的 Ftrace 机制,记录用户空间与内核空间的关键事件。 兼容 Systrace 的功能,并有望最终取代它。 全面的数据支持: 支持 Trace、Metric 和 Log 类型的数据。 提供多种数据抓取方式,包括网页、命令行工具、开发者选项以及 Perfetto Trigger API。 高效的数据分析: 提供数据可视化网页,支持大文件渲染,优于 Systrace。 Trace 文件可转换为 SQLite 数据库文件,支持 SQL 查询和脚本执行。 提供 Python API,允许将数据导出为 DataFrame 格式,为深入分析提供便利。 支持函数调用堆栈展示。 支持内存堆栈展示。 支持 pin 住你感兴趣的行到最上面,不用一直上下翻(通过脚本可以一打开就自动 pin) 支持可视化 Binder 调用和跳转 支持很方便的查询唤醒源 支持 Critical Task 的可视化查询 Google 的持续更新: Google 的工具团队在持续更新 Perfetto,版本 Release 和 Bugfix 都非常及时,可以在 Github 上观察。 这里专门提一下 SQL,Perfetto 可以使用 SQL 这是一个巨大的改进,他在解析 Trace 文件的时候,会内建许多 SQL 表和图,方便使用 SQL 语句进行查询,比如下面这几个查询,就是非常实用的(图来自内核工匠)。 另外他的官方文档里面,介绍到对应的部分,也会把对应的 SQL 以及查询结果示例贴出来。 有了这个,就再也不怕老板说你没有数据了,分分钟 SQL 查出来,表格转图标,一份高质量的 Report 就出来了:优化前后,xxx 指标下降了 xx%,属实是非常方便的。 相比 Systrace 没那么好用的地方 Vsync-App 没那么直观 Vsync-App 在 Perfetto 相对来说没那么直观,比如你看习惯了 Systrace 中 Vsync-App 那条贯穿整个 Trace 的竖线,你再看 Perfetto 就没有这个,就会觉得怪怪的: Perfetto 中,你可以把 Vsync-App Pin 到最上面来看 Vsync 信息 Systrace 中,Vsync 以竖线的方式贯穿整个 Trace,很容易辨别: 当然 Perfetto 取消这个也是有理由的:Vsync-App 其实并不能说明 App 有性能问题,Perfetto 使用了另外一个方式来展示,如果你用 Perfetto 命令抓的 Trace,就会有下面这个信息,记录了 App 一帧的 Expected Timeli 和 Actual Timeline 。相比 Vsync-App,这两指标更能说明问题:原文档 预期时间线每个切片代表应用程序用于渲染帧的时间。为了避免系统出现卡顿,应用程序需要在这个时间范围内完成。开始时间是调度 Choreographer 回调的时间。 实际时间线这些切片代表了应用程序完成帧的实际时间(包括 GPU 工作)并将其发送到 SurfaceFlinger 进行合成的时间。开始时间是应用程序开始运行的时间。这里的切片的结束时间代表的是。后处理时间是应用程序的帧被发布到 SurfaceFlinger 的时间。 通过看 Expected Timeli 和 Actual Timeline 的差异,我们可以很快速定位到卡顿的点(红色标识的 Actual Timeline 那一帧就是卡顿) 其计算方式如下,看了图你就知道为什么这两个是更准确的(包含了 GPU 执行时间) 相对应的,SurfaceFlinger 也有这两个指标 折叠功能比较烂,比较废屏幕 如果你是普通的宽屏,打开 Perfetto 随便 Pin 几个关键线程到最上面,你下面的可操作空间就很小了,如果碰到某个关键线程堆栈比较长,那就更是顶级折磨了,而且他这个堆栈还不能折叠(Systrace 可以) 解决办法: 少 Pin 几个关键线程 (那还有啥乐趣) 把显示器竖起来(宽度就打折了) 最终我们找到了完美的方案:换成 LG 那个魔方屏幕,16:18 ,看 Perfetto 简直是绝配(办公室已经被我安利有三台了) 不差钱:LG 28MQ780 - 3599 平替:[联合创新 28C1Q - 2999](https://item.jd.com/100058985199.html) 参考文档 Perfetto Github 库 Perfetto 官方文档 内核工匠 - Perfetto 进阶 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
随着 Google 宣布 Systrace 工具停更,推出 Perfetto 工具,Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto,许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼,希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。 所以就有了这个系列,我也有在星球里面写了几条为什么要更新 Perfetto 系列的原因(之前一直觉得 Systrace 系列就够了): 目前 Oppo、Vivo 这些手机厂商内部,都已经切换成 Perfetto 了,不管是抓 Trace 还是看 Trace,都在使用 Perfetto ,很多新人接触的都是 Perfetto 而不是 Systrace ,守着之前的老 Systrace 系列会流失这部分读者 之前的 Systrace 系列,对应的 Code 已经比较老了,全新的 Perfetto 系列可以使用 Android 14 的 Code 来进行更新 个人对 Perfetto 的使用也没有很深入,有些高阶功能目前还只是浅尝辄止。可以通过重写 Perfetto 系列来进行这部分内容的强化 Perfetto 是个很强大的工具,他的背后是整个 Android + Linux 系统,所以在写这个系列的时候,应该是以他背后的这个 Android + Linux 为主,而不是仅仅局限于 Perfetto 这个工具。工具只是我们观测 Android + Linux 的方式,理解整个 Android 系统运行的规律,思考其运行的原理,通过工具挖掘问题,通过问题思考本质,这才是对开发者来说有意义的 很多 Android 系统运行相关的内容,Perfetto 的官方文档还是没有讲,这部分我这边可以作为补足;另外官方文档是英文版本的,中文博客可以补充这方面。 Perfetto 系列写好了,可以拿到 Google Dev Fest 上作为演讲内容~。 Paul Graham 说:要么给大部分人提供有点想要的东西,要么给小部分人提供非常想要的东西。Perfetto 其实就是小部分人非常想要的东西,那就开始写吧,欢迎大家多多交流和沟通,发现错误和描述不准确的地方请及时告知我,我会及时修改,以免误人子弟。 本系列旨在通过 Perfetto 这个工具,从一个新的视角审视 Android 系统的整体运作方式。此外,它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章,但或许因为难以记住代码或不明白其运行流程,你仍感到困惑。通过 Perfetto 这个图形化工具,你可能会获得更深入的理解。 Perfetto 系列目录 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 4:使用命令行在本地打开超大 Trace 视频(B站) - Android Perfetto 基础和案例分享 待更新 欢迎大家在 关于我 页面加入微信群或者星球,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容 Systrace 系列 另外 Systrace 工具尽管已经不更新了,但是之前的 Systrace 系列文章,内容依然没有过时,还是有很多公司在使用 Systrace 来分析各种系统问题,Systrace 工具是分析 Android 性能问题的利器,它可以从一个图形的角度,来展现整机的运行情况。Systrace 工具不仅可以分析性能问题,用它来进行 Framework 的学习也是很好的。 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 产生与工作机制解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
今年偷个懒,找了个模版,主要从以下几个方面回顾过去一年:健康 / 锻炼、工作 / 职业、友情 / 社交、个人生活 / 家庭、学习 / 知识管理、旅游 / 文化、兴趣 / 创造、情绪 / 精神状况、财务状况。 内容更多是自己对于 2023 年的一个记录,算不上总结,文采也不好。不过很多事情,如果你不记录,就慢慢消失了。希望每次我翻看这篇记录的时候,都会感慨 2023 年真是丰富多彩的一年:有难忘的瞬间、有低谷、有朋自远方来、有去看大山大川;也会感慨有些事情做得很糟糕,如此这般其实可以做得更好;也会责备自己的懒惰,明明知道怎么做是对的,却因为懒惰没有去坚持。 各位看官一笑而过即可~ 健康 / 锻炼 今年由于公司有骑行活动比赛,持续一整年,再加上成都环城绿道去年贯通,今年一整年的活动就是骑行。年初在闲鱼收了一个美利达瑞克多 4000,准备今年好好骑车。年初的目标是一圈(97km)骑到 3h 内,挑战目标是 2h 50min,没想到最终都达到了(总共骑了 35 圈绿道,最快用时是 2h 47min),如愿以偿在公司内部比赛中拿了第一名。同时把媳妇也带进了坑,一起骑了几圈。 自身叠加的那些 Debuff:哮喘、鼻炎,并没有明显的改善;不过最值得开心的是年底由于肺炎+甲流,住了 4 天院,打了四天吊瓶之后,鼻炎导致的味觉消失也被治好了,同时血氧也恢复到了 96% 以上(之前 always 在 94 以下,给我焦虑得不行)。成都的空气质量真是一言难尽,连华西的医生都直摇头,这一点想移居成都的同学可以慎重考虑一下。 新的一年还是得 骑车+跑步 一起抓,报了个都江堰半马,希望能完赛。减肥依然是主旋律,管住嘴迈开腿很重要,睡前保持饥饿感~ 工作 / 职业 工作方面,今年不算很顺利,做的东西很杂,很多东西也没有最终落地,自己总结了一下,还是因为缺乏规划能力和执行力。不过也算积累了很多知识和做事方法,调研了很多技术方案,与客户也合作完成了几个项目。另外公司内部卧虎藏龙,需要虚心向每一个人学习。 AI 今年算是整了个大新闻,不管是公司还是个人,工作流中都插入了 AI 相关的内容:利用 AI 提升工作效率、创造 AI 相关的工具。不过由于公司性质,AI 还没有产生那种颠覆性的影响,不过我们都知道这一天很快就会来临。 “一种新技术一旦开始流行,你要么坐上压路机,要么成为铺路石。 —— Stewart Brand” 友情 / 社交 今年最重要的一次社交可能就是从成都去深圳参加 深圳 GDG DevFest 技术嘉年华,见到了几位老朋友和各位大佬,吃到了心心念念的砂锅粥和牛肉火锅,跟群里的小伙伴面了基,听了各位 GDE 的主题演讲,可谓是收获满满。 剩下的时间就是跟公司的几个小伙伴骑车了,能让大家周五晚上 12 点还在绿道上疯狂拉扯的,只有骑完之后那一顿火锅/麻辣烫了。@James @Hanzo @天宇 @宣总 @王 @陈大 @祥大 @果哥 @鹏哥 新的一年继续拉扯~ @福滕 和 @小陈 有了小宝宝,@周岩 的新房居然提前交房了(不过依然没有女朋友,狗头~)~,@老崔 跟 @和泉 都在深圳安家了。 交流最多的还是跟微信群里的小伙伴们,基本都是通过微信公众号或者博客添加的,基本都是 Android App 开发者或者国内的系统开发者,5 个群大概 2000 多人吧(虽然 90% 都是资深潜水员)。通过微信群认识了很多业内的大佬,手机圈本身就小,多个朋友多个家(狗头~),同时也感叹真是山外有山,人外有人,啥都别说了学就是了。 最大的感受是:最好跟在同一个频道上的人沟通,会减少很多沟通的成本。 个人生活 / 家庭 小宝宝橘橘快三岁了,橘橘妈也换了新的组(跟我做相同的工作:系统性能优化),一家人开开心心平平安安,就是最大的心愿了。今年由于各种原因,在家里陪宝宝和家人的时间有点少,出游计划也多被搁置。新的一年要提升效率,把时间多用在陪伴家人。 今天读到一个博主的 2023 年终总结,其中一段话让我感触很深,这才是生活的最终目标,不要本末倒置: 我希望多丰富一些体验,做一些没做过的事情,看一些没看过的风景。我希望 有一天可以把时间用在自己身上,而不是用在工作上,不是在那些不重要的人,或者事身上。更具体一点,我希望家人身体健康、平平安安,有爱,有陪伴,我也希望有一天可以走遍中国、走遍世界,看那些没看过的风景,吃那些没吃过的美食。 当我想到这里,我豁然开朗,我知道我终其一生追求的是什么,剩余的,都不重要的。那么,我怎样才可以?我需要时间,我需要钱。 从此开始,我所有的事情都围绕着这个目的展开,只要某件事情,对我的人生最终目的是有推进作用,无论多么艰难,我都必须做,是的,必须,没有任何商量的余地。 学习 / 知识管理 今年学习方面一败涂地,英语学习基本上停滞,最后这个月才重新拾起来;技术学习更多停留在读,写和思考总结都比较少;文化学习,比如阅读,书架上的书很多,读完的很少;博客也只更新了几篇文章,费曼学习法那种输出倒逼输入,道理都懂,还是没有克服拖延症。 工作中倒是养成了一个习惯:在公司内部使用 Typora 记录每天的工作内容、技术专项、技术调研、Bug 记录等;新的一年切换到 Loop 之后,我会在上面更详细地记录和思考,以及组内分享,带动组内的技术氛围(今年年初吹下的牛皮年底了最终还是没有实现)。 从我自己的学习经历来看,学习有三大障碍: 如何获取优秀的输入源:互联网如此发达的年代,如何获取优秀的输入源很重要,这就需要在长期的学习过程中,记录那些优秀的人的博客、优质的 Github 库、高质量的周报、专注在技术输出上的技术团队等(话说我这篇文章:Android 性能优化必知必会 本意也是搜集这些内容的,欢迎大家推荐和自荐)。 静下心来把一个知识点吃透:短视频类的快消品在争夺人的注意力的同时,也把我们的耐心消耗殆尽,静下心来阅读和思考一篇文章、一个知识点、一本书,都成了一个很不容易的事情。这个只能自己锻炼自己,远离社交网络和短视频,重新夺回注意力,把心思放在真正需要的地方。 克服拖延症:我们常说,道理都懂,依然过不好,拖延症在里面功不可没。今天看到张朝阳分析治疗拖延症的方法,分析出来与君共勉:拖延症的关键关键就在于拖延本质上是因为不熟悉这件事情想要逃避,因而产生恐惧和焦虑,那战胜它的方法就是在想要拖延时,先在脑子里过一遍要做的事情,梳理好脉络,构想出细节,让自己的神经元兴奋,立即行动。这样可以战胜拖延,并感受到正反馈 年初看了李自然的一个视频:人生如逆旅,我亦是行人,视频主要是讲了下面几个点,当时感触很深,也分享给大家:【李自然说】人生如逆旅,我亦是行人 不给自己设限,能快速融入到更有潜力的行业 向顶级的人拼命地学习 拼命地打开自己的视野和格局 拥抱变化,甚至主动去求变 一直坚持做,就会有一个长期的口碑 提高容错率 旅游 / 文化 今年去了四个地方:四姑娘山,厦门,烟台+威海,乐山。 厦门:团队旅游目的地,看海,吃沙茶面,吃姜母鸭,不得不说厦门这天气太让人羡慕了,环海路也可以骑车,市内还有步道(可惜没去)。 烟台+威海:烟台+威海,先去参加 @小婉 的婚礼,然后回母校去看看见了几位老师和老同学,韩餐和海鲜都吃到了,环海路依然风景漂亮,学校后面的沙滩落日很美~ 四姑娘山:开了一天车去到处于川西的四姑娘山,路上要经过海拔 4800 的垭口,宝宝和媳妇高反,没有怎么玩,很快就回来了。我们去的那天晚上下了雪,第二天到了景区后拍照很漂亮,我录了视频在这里:https://www.bilibili.com/video/BV1H84y1Q7oM ,漂亮是真漂亮,值得再去一趟~ 乐山:看乐山大佛和吃小吃~甜皮鸭和炸串真好吃~ 带着宝宝出去玩虽然很折腾,但是还是很值的,宝宝在一路上会认识很多新的东西,交新的朋友,吃到新的食物。2024 依然会继续带着宝宝折腾~ 兴趣 / 创造 终于来到轻松一点的环节了,今年主要兴趣是骑车,购置了一台大疆 Action4 来拍骑车时候的视频,要技术没有,都是一镜到底,创造力这玩意是真得有天赋才行。 情绪 / 精神状况 自我感觉良好,除了稍微有一些焦虑。自我感觉良好很大程度上得益于家庭的支持,有爸妈在家帮忙看宝宝,我和媳妇双 it,很多决定媳妇都会坚定支持。成都美食比较多,比较杂,想吃哪种都可以吃到,玩的地方也比较多,川西、古镇、都江堰青城山,去重庆也不远;天府新区高新区这边也不怎么排外,呆着很舒服(除了雾霾,这个真的是负分) 2024 继续保持吧,心态要好,睡眠要好。 财务状况 房贷还剩余比较多,每个月工资大半都交给银行了,除了夫妻两个的工资基本没有其他的收入,这也是我目前比较焦虑的点,一旦遇到被辞退或者其他变故,周转就会有困难。所以最终目标还是要管好自己的钱袋子,控制花销。看了一个说法,分别是穷人、中产阶级、富人的资产负债情况: 穷人疲于奔命,收入被衣食住行消耗殆尽。 中产阶级深陷「老鼠赛跑」的陷进,为各种账单、贷款奋斗半辈子。 富人买入并持有优质资产,形成「钱 → 资产 → 钱」的良性循环。 所以最终还是要持有 优质资产,我理解这里的优质资产可以是房产,也可以是知识。结合个人的情况,我思索再三,又重新开启了 知识星球,简介在这里 The Performance 知识星球简介,我并没有做宣传,只在博客中有介绍。我觉得目前最有效的两种学习方法:费曼学习法和公开学习法,知识星球都可以满足我,我会把日常的一些思考、案例、学习的进度、工具等分享到星球上;如果在这个过程中,有些知识能帮助到你,那么我觉得就值了。 最后三项 最骄傲的成就 今年可能最值得拿出来说的就是去了 电子科技大学 ,给研究生们上了三天的课,蹭的当然是公司跟企业合作这个便利,不过过程还是很值得一说的:从接到这个任务,到开始备课(其实就是学习 @鹏哥 去年准备的 PPT),再到每个知识点的学习,再到真正去讲课,还是充满了挑战的,毕竟是第一次,还好没有翻车。 今年好像还是我,这次就没那么紧张了,不过去年的内容要好好更新一下,讲课的方式需要再优化优化,知识点该深入的还是要继续深入,毕竟 Android 从最上层到最底层都讲清楚也不是那么容易的. 最大的挑战 工作方面的就不说了,除了工作方面,最大的挑战应该还是英语口语了,今年有好几次需要用到英语口语的时候,结果都不是那么好,这也刺激我要好好学英语。这个感觉要放到 2024 年最重要的 Task 里面,目前也有一些小的规划在实施中。 明年的目标和愿望 多陪陪家人,做一些之前没有做过的事情,去一些之前没有去过的地方。 至少做一次 Google Dev Fest 级别的公开分享 出一次国 坚持把知识星球做大做强 坚持锻炼身体,一次马拉松完赛 多看书 坚持每 2 周更新博客,提早把 Perfetto 系列写完 录视频 文笔有限,借用 aoxiang 的话来做个结尾吧: 很感谢对我的认可,你花时间看到这里,我很希望这篇文章对你有价值。同时,我也很希望你能想清楚「你想要过什么样的人生」,当你有了这个判定标准,你任何一个抉择,都会无比轻松。 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
目前星球为付费模式,星球收入主要是是赚个博客的服务器费用以及给家里的豆子(猫)买个猫粮,同时也是我更新博客的动力。如果觉得内容还不错,就加入星球支持一波吧~ 非常感谢~ 知识星球名为 The Performance,一个分享 Android 开发领域性能优化相关的圈子,主理人是博主自己,国内一线手机厂商性能优化方面的一线开发者,有多年性能相关领域的知识积累和案例分析经验,可以提供性能、功耗分析知识的一站式服务,涵盖了基础、方法论、工具使用和最宝贵的案例分析。 随着 Android 的发展,性能优化成了所有 Android 手机厂商和 App 厂商的重中之重。然而性能优化又是一个非常宽泛的话题,涉及到的知识非常多,从底层 Kernel 到 Framework 再到 App,每一个环节都有大量的知识点需要了解。所以笔者团队建立了这个专门分享和讨论 Android Performance 相关的技术圈:提供高质量的技术分享和技术讨论,提供系统性的 Android 性能优化相关知识的学习。 所有加入星球的同学,都可以畅所欲言,分享知识和案例,提问或者解答他人的问题,以问题分析带动学习,共同学习,共同进步,所有人都可以分享和讨论下面话题相关的内容,或者享受相关的权益。 目前星球的内容规划如下(两个 ## 之间的是标签,相关的话题都会打上对应的标签,方便大家点击感兴趣的标签查看对应的知识),由于改为个人经营,精力有限,划线部分是暂时删除。 Trace 分析 - 加入星球后,可以提供 1v1 的 Trace 教学和分析(Systrace、Perfetto、SimplePerf 等,需要提前预约),另外也会提供各种 Trace 相关的案例分析。 #The Performance# — 可以提早阅读「Android 性能优化 - 系统性课程」的电子书,每周会放出已经写好的章节。「Android 性能优化 - 系统性课程」 是我们规划的一本讲 Android 性能优化的电子书,目前开发者社区有相当多高质量的性能优化理论知识和实践文章和开源库,但是目前市面上缺乏一个完整的、系统性的、包含了性能优化原理、工具、实践等内容、面向初级开发中和中级开发者、面向 App 开发者和系统开发者,且持续更新的 Android 性能优化工具书。书的大纲 (暂定) 我们已经基本上列好了,预计会花费一年左右的时间来完成,在星球中会放出写好的章节,让大家提前看到,也欢迎大家一起进行 Review #性能工具# — 分享 Android 开发中使用到的性能分析工具以及其使用方法,同时也提供 1V1 的 Systrace、Perfetto 等性能工具的视频指导。性能工具的使用,最好还是以视频的方式展示会直观很多,文章是静态的,很多地方比较难讲清楚,1V1 的视频会议指导也算是一个学习的方法。 #案例分析# — 典型案例分析思路总结、球友提供的案例分析与讨论。案例分析是学习的一个很重要的途径,阅读大量的实际性能案例对以后自己分析和解决性能问题是非常有帮助的,同时也欢迎大家提供案例和解决方法,怕泄露信息的话,我们会对关键信息进行打码。 #经典解读# — 经典方案、课程重读,例如优秀的三方库解析、Android 开发高手课重读等。比如可以对方案进行深度的剖析,横向对比等;对 Android 开发高手课进行重读和查漏补缺。 #知识分享# — 优秀文章、博客、工具分享。业界有很大大牛的博客、经过实际业务考验的开源方案、各种性能工具等,我们会寻找这些优秀的内容,分享给大家。 #知识沉淀# — 微信群聊精华、微信问答、博客留言解答等。 #性能面试# — Android 性能相关的面试题搜集和解答,也算是刚需了吧。 #编程语言# — 编程语言相关的使用技巧分享。 #效能提升# — 效能提升分享,包括开发者开发效能、工作效能提升方法、工程效率、工具推荐等,磨刀不误砍柴工嘛。 #行业动态# — 性能相关新技术第一时间解读报告,包括但不限于下面的内容。 行业峰会、学术峰会新思路解读报告 论文、行业、书籍介绍、视频 Android 大版本性能相关介绍 Android 新硬件性能相关内容介绍 Android 性能相关开源项目解读 #大咖分享# — 每月定期邀请行业大咖进行经验分享、案例分析。 #工作内推# — 各大厂商内推工作机会介绍。 付费星球二维码 微信扫码加入即可(iOS 用户最好用微信扫码) 免费星球二维码 当然星球还有一个免费的版本,定位是知识分享,感兴趣的也可以加入 微信公众号 当然也欢迎关注微信公众号~ 微信群 新的微信交流群,没有加之前微信群都可以加这个,方便平时随时沟通和大家相互之间讨论 微信群旨在讨论 Android 及其相关的技术话题,包括但不限于 Android 性能优化课题(响应速度、流畅度、ANR、Crash、内存、耗电、性能监控等)Android App 开发、Framework 开发、Linux、大前端、面试分享、技术招聘等话题 鉴于群里的各位大佬们时间都很宝贵,大家聊天的内容尽量与上面的主题相关,禁止吹水、装机、购机、手机厂商优劣讨论……否则群会被贴上水群的标签,希望大家共同维护群氛围 另外还有 闲聊吹水群、跑步群、读书群、GPT 群,里面大家就随意发挥了,可以私我拉进去 如果群满 200 或者二维码失效,可以加我微信拉各位进去(553000664) 个人其他信息 博客地址:https://www.androidperformance.com/ 免费知识星球:https://t.zsxq.com/ZZ337Am 付费知识星球 :https://t.zsxq.com/Fuvvf6y 知乎地址:https://www.zhihu.com/people/gracker 即刻:https://okjk.co/pJbjFa Android Weekly :https://androidweekly.zhubai.love/ 微信公众号:AndroidPerformance 掘金 :https://juejin.cn/user/1816846860560749
1 Origin I am embarking on a new series of articles addressing various considerations in OS architecture design. Indeed, these considerations are not limited to OS but are applicable to the design of any large-scale software. I am limited by my capabilities and knowledge and bring a highly subjective view, and there are undoubtedly inadequacies. I am eager to hear different thoughts and perspectives, and through the collision of ideas, we can achieve a deeper understanding. In my opinion, the core differences between Android and iOS from the OS perspective are primarily manifested in: The IPC mechanism between applications and core services Platform development environment, including programming languages, IDE tools, and the construction of the developer ecosystem Application lifecycle management mechanisms and strategies The runtime organizational structure of the kernel and core services Why do they adopt different strategic decisions? It relates to the factors considered during architectural design. A software architectural decision is a selection of the most suitable decision for the present and foreseeable future amidst a series of current considerations; it is a collection of decisions. Thus, there’s no absolute right or wrong in architectural design, or rather, rational or irrational. Different projects and decision-makers face different considerations and prioritize different aspects. If architects of similar skill levels switch scenarios, they are likely to make similar decisions. This indicates that architectural design is an engineering process and a technical craft, learnable and following certain patterns. The challenge in architectural design lies in accurately understanding the environment the organization operates within, current and foreseeable considerations, and finding the most suitable methodologies or tech stacks from existing engineering practices. It is evident that considerations play a significant role. What factors need to be considered in software architectural design? These include, but are not limited to, testability, component release efficiency, development efficiency, security, reliability, performance, scalability, etc. Experienced architects, especially those with operational experience in similar businesses, are better at discerning what to focus on at different times, what must be adhered to, and what can be relaxed or even abandoned. Considering the law of diminishing marginal returns, the consideration of influencing factors and decision-making behavior will permeate the entire lifecycle, introducing another art of decision-making. We can summarize that: Under different stages and constraints of various considerations, software architectural designs on different projects may differ. During the entire cycle of software product iteration, such design decisions are always evolving. Interestingly, one consideration may conflict with another, leading to a situation where one cannot have the best of both worlds. For example, improving component development efficiency might impact program performance. So, what were the considerations for the designers of Android and iOS in the context of mobile OS design? To answer this question, we first need to explain the relationship between the OS, application programs, and the kernel. 2 Mechanisms and Policies First, it should be noted that the OS is part of the software stack above the hardware. It, along with application programs, constructs the complete software program stack, utilizing the hardware capabilities to provide services to users. From the hardware’s perspective, regardless of the OS or application programs, both are software; however, the OS has higher privileges, allowing it to operate in the CPU’s high-level modes, for tasks such as direct interaction with hardware and executing interrupt handling routines. From the software developer’s perspective, however, the OS and application programs are entirely different entities. Application programs utilize the capabilities provided by the OS to meet their business requirements or use hardware capabilities for tasks like playing music or storing data. At a higher level, from the user experience perspective, application programs, OS, and hardware are all part of the same entity. When issues arise in their collaboration, ordinary consumers might simply feel that the device is less than ideal. Therefore, all three parties are obligated to cooperate and facilitate each other; only when the consumer is well-served can these entities sustainably profit. In discussing the OS, it’s crucial to recognize that the vast majority of modern OSes consist of a kernel and system services. In macOS/iOS, the kernel is Darwin, formed from the combination of XNU and the MACH microkernel. Its system services are provided by an array of daemon services, encapsulating kernel capabilities, data management, and higher-level APIs like display. In Android, the kernel is the Linux kernel, with system services comprising C++ written daemon services (e.g., SurfaceFlinger) and Java written daemon services (e.g., SystemServer). They too encapsulate kernel capabilities, data management, and higher-level APIs concerning display rendering and composition. Current mainstream operating systems include macOS, Windows, and various derivatives of Linux. Clearly, Android belongs to the Linux derivatives, with another renowned version in the developer community being Ubuntu. Due to the complexity and path-dependent nature of a complete OS, derivatives are often deployed on different hardware and application scenarios. For a while, Linux was lauded for its extensive device radiation and wide application scenario spectrum. However, being able to run an OS and running it well—connecting hardware and application programs to offer an optimal user experience—are two different things. An increasingly common understanding is that the OS’s mechanisms, policies, and closely associated application programs vary greatly depending on the application scenario. For instance, even if based on the same Linux kernel, the use and system services built upon it for embedded devices, smartwatches, smartphones, large servers, or even smart cars are distinctly different, as are the operating strategies of application programs. The Linux kernel and device drivers can be seen as the bridge between hardware and system services—a standard bridge—but the vehicles and pedestrians traversing it are entirely distinct. This leads to another classic OS design concept: mechanism and policy. The “separation principle” noted in UNIX programming specifies the separation of policy from mechanism and interface from the engine. Mechanisms provide capabilities; policies dictate how those capabilities are used. In this context, the memory management, process management, VFS layer, and network programming interfaces that Linux provides are mechanisms. Memory allocation and release mechanisms, process schedulers, frequency and core allocators, and different file systems are policies. Going further, identifying which processes interact with users and which are background tasks, synchronizing this information to the scheduler to determine the optimal process for the next scheduling window is fundamentally a policy built upon the Linux process management mechanism. Similarly, notifying future computational task requirements to the CPU frequency allocator for dynamic adjustment (DVFS) involves a policy and a mechanism. For instance, all modern OSes support memory compression capabilities, but different OSes use this mechanism according to their own characteristics to best meet business needs. iOS exhibits process-level compression, while Android relies on Linux’s ZRAM. Though OS mechanisms might be similar, policies are diverse. The extent of their differences depends on the OS designers’ understanding of their own business—meaning the application programs running on the OS and the kind of experience and services they aim to provide users. Early Android was essentially a desktop architecture, but modifications, especially by domestic manufacturers, have made it increasingly resemble iOS, aligning more with the system capabilities required by mobile device operating systems. The OS for smartphones and smart cars probably faces a similar scenario—they cannot be directly transplanted. One interesting aspect of policy is that implementing one policy often brings up another issue, necessitating the introduction of an additional policy. When one policy compensates for another, a chain forms, eventually creating a closed-loop mechanism. In other words, all policies must be in effect simultaneously to maximize the system’s benefits. When learning about a competitor OS’s policies, remembering this aspect is essential; otherwise, we might only grasp the superficial aspects, leading to “negative optimization” once the features are launched. Now, narrowing it down to Android and iOS, where do their strategy designs originate? 3 Butts Decide Heads Apple has released a series of operating systems including macOS, iOS, iPadOS, watchOS, and tvOS. The distinctions between them are not merely in brand names but are characterized by specific strategy variations. While they largely share underlying mechanisms, their strategies are distinctly different. For instance, the background running mechanism on iOS is vastly different from that on macOS. iOS resembles a “restricted version of multitasking,” while macOS offers genuine multitasking. Hence, it’s not that iOS can’t implement multitasking but rather a deliberate design decision. Android, as we refer to it, is actually a project open-sourced by Google, known as AOSP (Android Open Source Project). Device manufacturers adapt AOSP and integrate their services based on their business models and understanding of target users. Apple is singular, but there are numerous device manufacturers, each with their own profit models and interpretations of user needs. They modify AOSP accordingly, and the market decides which version prevails. From a technical perspective, AOSP is rich in mechanisms but lacks in strategies. Google has implemented these strategies within its GMS services. Users outside mainland China, like those using Pixel or Samsung phones, would experience Google’s suite of services. Although the ecosystem is considered subpar in China due to the proliferation of substandard apps, the situation is somewhat mitigated overseas, but still not comparable to Apple’s ecosystem. Given Google’s less-than-ideal strategic implementation, domestic manufacturers in China have carved out space for themselves. The intense competition and the sheer volume of phone shipments in mainland China have led manufacturers to prioritize consumer feedback and innovative adaptations of AOSP. The most significant difference between iOS and Android stems from their respective strategies, rooted in their initial service objectives and developmental goals. Books like “Steve Jobs” and “Becoming Steve Jobs” touch upon the development of the iPhone and the discussions around AppStore. Jobs was initially resistant to allowing third-party app development on mobile devices due to concerns about power consumption, performance, and security. The initial intent was to create a device that offered an unparalleled user experience, not necessarily catering to every user demand. As Apple had written the first batch of apps themselves, they amassed a wealth of insights on designing excellent embedded device applications, leading to the creation of effective API systems. This comprehensive approach from hardware to software was not for the sake of exclusivity, but a necessary path to crafting the best user experience. Contrastingly, during 2007-2008, Android was focused on getting the system up and running. Android’s initial aim was to accommodate a vast array of app developers, leading to its favoring of Java, a popular language among developers and in the embedded device domain. Although Android later shifted to Android Studio, improving the development experience, it still lagged behind Apple’s Xcode in terms of application development and debugging tools. Apple’s strong control over its app ecosystem, partly attributed to its powerful IDE tools, aids developers in solving problems rather than imposing constraints. Further, initiatives like LLVM, Swift, and SwiftUI underscore Apple’s commitment to facilitating superior app development to enhance the user experience. The purpose of designing an OS is profit-oriented, and it should facilitate app developers in crafting quality programs. Apple has showcased that offering quality developer services can be instrumental in achieving optimal device experiences. A summary of insights gleaned from Apple’s approach includes: Building an OS is a means; delivering a complete and excellent experience is the end goal. Both the OS and device manufacturers may need to put in extra effort to achieve this objective. Serve app developers well, assist them in improving app quality, and even identify and diagnose app issues. Provide faster and more user-friendly APIs to efficiently meet the needs of app developers. An excellent IDE tool can serve developers well, enabling the development of superior apps, and ensuring the OS’s survival. While Apple exercises absolute control, it also offers software services that are significantly above industry standards. Offering an OS is merely a means; understanding the nature of the relationship with developers and providing developer services, such as IDE, is a more profound consideration at the cognitive level. 4 Strategy of “Overload Protection” The greatest feature of mobile devices is their portability, enabled by battery power. Besides, as handheld devices, they primarily rely on passive cooling since they don’t have an active cooling mechanism (exceptional cases of gaming phones and attachable fans aside). Currently, there are two trends: one, the transistor fabrication process is inching closer to its physical limit, and two, more functionalities are being integrated into a single chip. This increase in the number of active transistors (or their area) leads to a corresponding rise in heat emission, although it wasn’t a primary concern during the early days of smartphones. Now, the balance between power consumption and performance has become a significant challenge for smartphones. More active threads mean the CPU remains busy, resulting in reduced CPU time slices allocated to user-related programs, thus impacting performance. Therefore, the design of mobile device OSes naturally leads to restrictions on resource utilization by applications. If left unrestricted like servers or desktop computers, it would be impossible to maintain a balance between performance, power consumption, and heat dissipation. The more constrained a device is in terms of performance and power consumption, the stricter the control over application programs, as is the case with smartwatches. Both Android and iOS have their resource protection mechanisms. In Android, the most common is the OOM (Out Of Memory) mechanism. When the heap memory usage of a Java application exceeds a certain threshold, the system terminates it. Although Android has a mechanism to detect excessive CPU usage, it is somewhat rudimentary and only monitors the CPU usage of regular applications, not system or native thread (written in languages other than Java). In contrast, iOS has a plethora of mechanisms ranging from CPU, memory, to even restrictions on excessive IO writes, including: Termination when the device overheats Termination of VoIP class applications when there are excessive CPU awakenings Termination during BackgroundTask execution if CPU use exceeds a threshold Termination if BackgroundTask is not completed within the specified time Termination if a program’s thread exceeds CPU use threshold Termination if a program’s disk write volume exceeds a threshold Termination if program’s inter-thread interactions within a unit time exceed a threshold Termination if a program’s memory usage is exceeded Termination under excessive system memory pressure Termination if a program opens too many files Termination during PageCache Thrashing iOS outlines these behaviors in developer documentation to clarify the reasons for unexpected application exits. Google’s lax approach to Android’s design has provided ample room for domestic manufacturers to introduce their overload protection strategies (similar to iOS’s, with minor variations) to ensure phones are not compromised by substandard applications. However, the issue lies in the lack of transparency about system termination behaviors. Developers are often in the dark about why their applications are terminated. Even if they are aware of the reasons, the lack of debugging information during termination impedes improvement efforts since no manufacturer releases this information. Consequently, application developers resort to various “black technologies” to keep their applications alive and bypass the system’s detection mechanisms. What should have been a collaborative ecosystem building effort has turned into a battleground. In the end, both parties suffer, with consumers bearing the brunt of the damage. In an ideal world: Overload protection mechanisms should be documented and explained in application development guides. Debugging information context should be saved when the system executes overload protection, and developers should have access to this information (with specific permissions, scope, and validity to be determined). Manufacturers should provide convenient and user-friendly debugging tools for developers to fix issues locally during development. Developers should be mandated to fix issues when they exceed the quality standards set by the manufacturers, failing which their applications should be delisted. Manufacturers and developers should be partners. Manufacturers may need to do more to assist developers, as many capabilities are exclusive to them. Blaming developers solely for poor quality is not a competitive approach for manufacturers. The fault, in this case, is at the cognitive level. 5 Strategy on “Lifecycle Management” Different device forms pursue varied user experience requirements, leading to diverse OS design necessities. In desktop OS, the lifecycle of an application is entirely under its control, aiming to maximize the program’s potential. This design is viable because desktop computers are not constrained by power consumption and heat dissipation and rarely face performance bottlenecks. Their primary concern is exploiting the machine’s capabilities to the fullest. On the contrary, smartphones are a different story due to their limitations in power consumption and heat generation. Similarly, smartwatches also suffer from these restrictions but to a more stringent degree. No one desires a watch that heats up their wrist and cannot last a day on a full charge. Moreover, their performance and memory limitations mean that too many apps can’t remain active in the background, necessitating a centralized management module to uniformly implement services for most common applications, known as a hosted architecture. While smart cars aren’t constrained by performance, power, or heat, they require high stability. Unless completely powered down, core system services must remain operational, emphasizing the importance of system anti-aging design. A core strategy in smartphone OS design revolves around lifecycle management, determining the entire journey of an application from its inception to termination. Android leans towards desktop system design, offering a “looser” strategy and more room for developers to maneuver. In contrast, iOS imposes more restrictions; an application relegated to the background only has about 5 seconds to perform background tasks before entering the Suspend state. In this state, the application is denied CPU scheduling, rendering it “quiet” when in the background. Chinese manufacturers, after obtaining AOSP code, have replicated a mechanism similar to iOS’s Suspend. However, due to the lack of native support in AOSP, compromises were made, resulting in an implementation not as thorough as iOS. Android interprets this running strategy as the developers’ responsibility to create well-crafted applications – a notion I find naive and impractical. By this logic, human societal development would never have required laws, an idea that contradicts human nature. Fortunately, Google might have realized this issue, gradually enhancing the so-called “freezing” strategy in their annual updates, albeit less effective than improvements made by domestic manufacturers. The progress in AOSP is slow, and substantial changes in this area aren’t expected in the next two to three years. So, if an application is Suspended in the background on iOS, how can it perform required background computations? iOS introduced the BackgroundTask mechanism, allowing applications to request permission for background task execution, with the system intelligently scheduling these tasks. Hence, iOS offers a strategy for application background operation but places the final decision in the system’s hands. This allows the system to schedule background tasks based on the phone’s current status, avoiding task execution during high system load periods to reduce overall load. The system also assigns daily quotas to each application, incorporating execution frequency and duration as crucial factors. Generally, tasks are allowed about 30 seconds of execution before being terminated by the system. However, background tasks aren’t limited to computations. How are requirements like playing music or location tracking addressed? Applications needing these services must declare them explicitly in the IDE, with the App Store checking for a match between the application and requested permissions – a mismatch leads to rejection. The App Store is central to iOS’s lifecycle management mechanism, enabling quality control during the application’s listing and operational phases. Applications identified as subpar are flagged for the developers to fix, facing delisting otherwise. Post-Suspend, the system may also terminate applications as part of overload protection. The most common reason is memory reclamation, especially given the expense of memory chips; without opting for larger memory, terminating applications is the only way to free up more memory. So, if the application isn’t even running, how are background tasks executed, and messages received? Thanks to BackgroundTask design, even if an application is terminated, the system will automatically restart it to execute background tasks when conditions are met. Message reception is achieved through notification mechanisms, with two kinds: one displaying detailed content in the notification bar, activating the application only upon user interaction; the other is for VoIP class applications, capable of actively restarting terminated applications. Android possesses a similar mechanism but requires the integration of its GMS service. Due to uncertain reasons, this service is inaccessible in China, forcing domestic apps to rely on various “dark arts” and commercial collaborations to keep their programs alive in the background for message reception. This has led to a grotesque scenario where head applications, often used by users, are greenlit by manufacturers, who, upon realizing this trend, keep intensifying various services, treating the phone as their playground and squeezing every bit of system memory. Could manufacturers offer a notification service akin to this? They could, but the construction and operational costs are disproportionately high compared to their sales profits, leading to the only option of increasing memory capacity, passing the price pressure onto consumers. The overall cost of a complete machine has an upper limit; bolstering memory means cutting corners elsewhere. For domestic manufacturers to break into the high-end market, recognizing the issues in the entire loop and co-building the ecosystem with applications is the sole breakthrough. Looking at iOS’s design, compared to macOS, it restricts application freedom but isn’t a one-size-fits-all solution. It offers various “windows of opportunity” or “unified solutions” to cater to different developers’ needs. The objective is to allow developers to operate within reasonable boundaries, not to drain users’ battery and performance. Summarizing the principles beyond the technology: Mobile devices have many constraints; therefore, application “freedom” must be restricted but not completely cut off, requiring corresponding solutions. Common tasks among applications should be provided uniformly by the system, saving overall system load, especially crucial for devices with many constraints. The final execution power of a program should be determined by the system, which, after synthesizing various information, schedules uniformly, benefiting the ultimate user experience protection. At this point, it seems like a clash between two regimes: one valuing freedom and individual priority, and the other advocating unified arrangement and scheduling. Regardless of the regime type, the ultimate objective must be considered. If the aim is to offer the best device user experience, evidently, the latter regime has been proven right by the market. 6 Above Design Looking back at the history of electronic consumer products, the development has mainly followed two themes: the democratization of professional equipment and the integration of multifunctionality (N in 1 style). The reliance on CPU computation is gradually being replaced by Domain Specific Architecture (DSA). Upon DSA, domain-specific programming languages and compilers are constructed, with GPU and Shader Language in the graphic processing domain serving as prime examples. The era where software reaps the benefits of CPU performance enhancement is drawing to a close, and DSA appears to be the opportunity for the next “great leap” in the coming decade. M1 epitomizes the dividends brought by regular microarchitecture and manufacturing process, but its impact is magnified due to the subpar performance of competing products. When a product’s core components are supplied by specific manufacturers, its developmental ceiling is essentially predetermined. This underscores the oft-repeated adage that core technologies must be self-controlled. Besides its CPU capabilities, M1 excels in multimedia processing, especially in video stream processing scenarios, outperforming Intel chips substantially. These performance enhancements are attributed to the processor’s performance uplift in specific scenarios. However, this doesn’t signify the end of the road for performance enhancements based on CPUs. As CPU performance enhancements stagnate, precise understanding of demands and optimizations of matrices and architectural designs to boost performance on existing CPUs become imperative. Profound insights into hardware, compilers, algorithms, and operating systems (both frameworks and kernels) are increasingly crucial. After optimizing business codes to a certain extent, focus inevitably shifts towards the underlying layers. Accumulated experience from numerous failures is essential to anticipate issues and design optimal architectures and optimization matrices proactively. An optimization matrix refers to the necessity of an ensemble of complementary technologies, not just an OS, to deliver an exceptional experience. This includes IDEs, cloud collaboration, and accurate cognition. Offering a supreme experience is a daunting task, but the more one learns, the more possibilities become apparent. By the same token, maintaining a perpetual “awareness of one’s unawareness” is equally pivotal. However, all these are contingent upon the designers’ ability to keep pace with their cognition. Charlie Munger once articulated that investment isn’t merely about scrutinizing financial statements and trend charts. Psychology, sociology, political science, and even biology are intricately linked to it. Only by dismantling the barriers between disciplines and integrating contents from multiple fields without reservations can one perceive a world invisible to others. While I haven’t attained such an enlightenment, Munger’s insights offer invaluable lessons worthy of our learning. Deliberate cross-disciplinary and cross-field practice, coupled with reflective thinking, significantly augments the learning process. “I leave my sword to those who can wield it.” - Charlie Munger About Me && Blog About Me: I am eager to interact and progress together with everyone. Follow me on Twitter Blog Content Navigation Record of Excellent Blog Articles - Essential Skills and Tools for Android Performance Optimization An individual can move faster, a group can go further.
本文是之前星球里 Yingyun 大佬的文章,由于星球已经关闭,所以把这个关于 OS 性能设计的系列文章发到博客上 Yingyun 是资深性能优化专家,他对于系统优化有非常深入的见解,本身在国内各个手机大厂都呆过,他本人的博客还在休整中,等休整好了我再发出来,目前他在我们的微信群里很活跃,对本文有什么建议或者意见,或者说想咨询问题的可以加我们的微信群(加我微信 553000664,备注博客加群,我会拉你进去) 1 缘起 新开系列文章,OS 架构设计中的各种考量因素。其实不止 OS,在设计任何大型软件都涉及到此类内容。 能力与知识面有限,而且还带了非常主观的看法,肯定有不足之处。希望听到不同的思路与观点,通过观点的碰撞进而达到更进一步的认知。 我认为,Android 与 iOS 在 OS 角度看最核心的差异主要表现为: 应用与核心服务之间的 IPC 机制 平台开发环境,包括编程语言、IDE 工具、开发者生态建设 应用生命周期管理机制与策略 内核与核心服务的运行时组织结构 他们为什么会有不同的策略决策呢?这跟架构设计时的考量因素有关。一个软件架构设计决策是在当前一系列考虑因素中选择了对当前与可见的未来选择的最合适的决策,是一系列决策的集合。所以,架构的设计上没有绝对的对与错,或者说合理与不合理。因为不同项目、不同决策者所面临的的考虑因素、追求的侧重点是都不尽相同。 如果架构师的水平差不多,把他们互换下情境,那大概率上所做出的决策是差不多的。这说明架构设计是一个工程,是一个技术手艺,它是可以被习得且有规律可循的。 架构设计中的挑战,可能更多的是在于准确理解组织所处的环境、当前与可见未来所要满足的考量因素,并从已有工程实践中找出最合适的方法论或者技术栈。由此可见,考虑因素就起了重要作用,在软件架构设计里要考虑哪些因素呢?包括但不限于,可测试性、组件发布效率、组件开发效率、安全性、可靠性、性能、扩容性等等。有经验的架构师,特别是对类似业务有操盘经验,他更能把握好不同时期应该注重什么,哪些是必须坚持的,哪些是适当放开甚至舍弃的。考虑到边际收益递减,影响因素的考量与决策行为会贯穿整个生命周期,这又是另一个「决策艺术」了。 我们可以总结出: 不同阶段与不同考量因素的限制下,不同项目上软件架构设计可能是不同的。 软件产品迭代的整个周期内,这种设计决策是随时会发生。 有意思的是一个考量因素可能会与另外一个有冲突,会造成鱼和熊掌不可兼得局面。比如提高了组件开发效率但有可能会影响到程序性能。那针对一个移动 OS 的设计,当初 Android 与 iOS 的设计者们的考量因素是什么呢?为了回答这个问题,首先要解释 OS 与应用程序以及内核之间的关系。 2 机制与策略 首先要说明的是 OS 是硬件之上的软件栈的一部分,它与应用程序一道构建了完整的软件程序栈,通过发挥硬件的能力为用户提供服务。从硬件的角度看,甭管 OS 还是应用程序,它们都是软件,只是 OS 的权限比较高,可以在 CPU 的高级别模式下运行,比如用于直接跟硬件打交道、执行中断处理程序等。但是在软件开发者角度来看,OS 与应用程序,那可是完全不一样的。应用程序利用 OS 提供的能力,实现自身的业务需求、或者使用硬件的能力,比如播放音乐、存储数据等。 再拔高一个层次,在用户体验角度来看,应用程序、OS 以及硬件,都是一回事情。 当他们协作出现问题,普通消费者可能就觉得这台设备就不够理想。因此,这三方都有义务互相配合好、互相为彼此提供便利,只有把消费者伺候舒服了,这三家才有可持续的利润可赚。 当我们说到 OS,绝大部分现代 OS 是由内核(Kernel)跟系统服务组成。 在 macOS/iOS 中,它的内核是 Darwin,而 Darwin 又是由 XNU 与 MACH 微内核组而成。它的系统服务,是由一大堆 daemon 服务提供,他们封装了内核的能力与数据管理、界面显示等级别的 API。 在 Android 中,它的内核是 Linux 内核,它的系统服务既有 C++ 编写的 daemon 服务(如,SurfaceFlinger)、又有 Java 编写的 daemon 服务( 如,SystemServer),他们同样也是封装了内核的能力与数据管理、界面渲染与合成等级别的 API。 现在主流的操作系统有 macOS、Windows 以及基于 Linux 的各种衍生版本。显然,Android 是属于基于 Linux 的衍生版本,当然还有个在开发者圈子中更有名的延伸版本,那就是 Ubuntu。 由于一个完整的 OS 的复杂性与路径依赖特性,往往会将一个延伸版本部署到不同的硬件与不同的应用场景上。有一阵子 Linux 就是以此为标榜,即它辐射到了多少台设备,应用场景有多广之类。能运行一个 OS 跟是否运行得好,即把硬件、应用程序连接起来,提供了最优的用户体验,完全是两码事情。 一个越来越普遍的认知是,不同的应用场景下,所需要的 OS 的机制与策略以及与之紧密配合的应用程序,是完全不一样的。 比如,即使是基于同一个 Linux 内核,用于嵌入式设备、智能手表以及智能手机、大型服务器甚至是智能汽车,它们使用 Linux 的方式与构建在它之上的系统服务均是不一样的,当然应用程序的运行策略也不尽相同。Linux 内核与设备驱动可以理解为硬件与系统服务之间的桥梁,是一个标准的桥梁,但是跑在它上面的车辆与行人,是完全不同的。 这就要引申出另一个非常经典的 OS 设计理念,机制与策略。 UNIX 编程一书中有提到「分离原则:策略同机制分离,接口同引擎分离」,机制提供能力,策略提供能力方法。 其中,Linux 提供的内存管理、进程管理、VFS 层、网络编程接口均是机制。内存分配与释放机制、进程调度器、频率与核分配器以及不同的文件系统,他们均是策略。更进一步,由系统服务识别出哪些进程是跟用户有交互、哪些是后台任务,将此类信息同步到调度器后找出下一个调度周期窗口里的最佳进程,本质上也是基于 Linux 进程管理机制之上的策略。 同样道理,根据未来所需的计算任务需求将其信息通知到 CPU 频率分配器进行动态调整(DVFS),前者是策略后者是机制。 再比如,所有的现代 OS 都支持内存压缩能力,但是不同的 OS 要根据自身的特点来使用此机制,目的是尽可能满足业务特点。iOS 中可以看到针对进程级别的压缩,而 Android 中反倒是依赖 Linux 的 ZRAM。 OS 的机制或许类似,但是策略千差万别,他们之间的差异有多大,取决于 OS 设计者对自身业务的理解。 自身业务,指的是运行在 OS 之上的应用程序,到底要为用户提供什么样的体验与服务。早期的 Android 其实就是桌面机架构,随着国内厂家对它的魔改,反倒是越来越像 iOS 了,越来越符合一个移动设备操作系统所需要的系统能力了。 智能手机、智能汽车的 OS,估计也是同样局面,不可生搬硬套。 策略还有个很有意思的特点,当你实施一个策略的时候会引申出另外一个问题,为此你要引入另一种策略。当一个策略弥补另外一个策略,逐渐会形成一个链条,你会发现你形成了一个闭环的机制。 即,所有的策略同时生效,才能使你的系统发挥出最大的效益。 当我们学习竞品 OS 的策略的时候,一定要记得这一点,否则只学会了皮毛,功能上线后会带来更大的「负优化」。 那具体到 Android 与 iOS,他们的策略设计是从何而来? 3 屁股决定脑袋 苹果推出的操作系统有 macOS,iOS,iPadOS,watchOS 与 tvOS。它们之间并不是单纯的品牌名称差别,而是有具体的策略差异。底层的机制大部分有共享,但是策略却截然不同。iOS 上的后台运行机制与 macOS 就截然不同,iOS 更像是「限制版多任务」,而 macOS 是真正的多任务。所以,iOS 并不是不能实现多任务,而是它的有意为之。 我们所说的安卓,其实是谷歌开源的项目,即 AOSP(Android Open Source Project)。设备厂商拿到 AOSP 与与硬件的相关的代码之后会在此基础上加入自己的各种服务,这是基于它们自身的商业模式、目标用户的理解,所创造出来的。苹果只有一个苹果,但是设备厂家就有很多,它们各自的盈利模式跟对目标用户理解不同,在 AOSP 基础上魔改了一遭,至于哪个好,就让市场先生来做判断吧。 回到技术本身,AOSP 中有大量的机制但是缺乏策略。谷歌把这些策略实现在了 GMS 服务中。 如果你在非大陆地区使用安卓手机,如 Pixel、三星手机,会体验到谷歌的全家桶。大家都会说国内的生态比较烂,垃圾应用比较多,到了海外这可能会缓解。 其实也就那样,谷歌的生态控制跟影响能力跟苹果是没法比的。 连谷歌自身的策略表现不尽人意,那就更为国内厂商发挥拳脚腾出了空间。中国大陆手机出货量累计合是全球最大的,而且竞争尤为激烈。因此它们会非常重视消费者的反馈,各种各样的需求与痛点挖掘,自然不在话下。 简单总结就是,国内厂商更懂消费者的需求,这也是国内厂家对 AOSP 做各种魔改与优化成立的底层逻辑了。 所以当你再次见到有个老板说 “做手机很简单嘛,拿开源安卓跟厂商的方案整合一下就行了”,离他赶紧远一点,跟着他混简直就是枉费青春。 iOS 与安卓之间两者最大的差异来自于策略,他们之间拥有的机制都差不多顶多效率上可能有差异,但更大的差异来自于策略上。 这跟它两刚开始时不同的服务对象与发展目标,导致了技术选型上的巨大差异。 「乔布斯传」与「成为乔布斯」两本书中,关于 iPhone 研发章节中都提到过关于 AppStore 的讨论。起初乔布斯坚持认为移动设备上由于功耗 / 性能与安全的考虑,不允许让三方应用开发程序。由于系统复杂,所以直接采用了 macOS 的内核以此实现多媒体、浏览器,以及播放音频与视频等功能。macOS 内核本身的运行成本较高,在此情况下再让三方应用运行,硬件根本吃不消。他们原本可以基于 iPod 上的系统实现 iPhone,但乔布斯又要实现世人从未见过的智能手机,上马 macOS 也是他不得已的选择。 随着第一代 iPhone 在用户侧的成功(商业上成功还没有开始),大家都在呼吁在 iTunes 上可以下载应用程序。其实第一代黑客们就是这么做的,因为很多 API 是跟 macOS 共享,因此通过一些逆向手段观察到了 iPhone 上编写应用程序的方法。但乔布斯坚决反对,因为还是担心这会破坏安全性跟设备使用体验。从此处就能看出,乔布斯的目的是打造一个拥有完美用户体验的设备,用户的呼吁或者需求,其实是并不是首要的。 但 VP 们不这么想,由于之前的 iTunes + iPod 组合的成功,VP 们私底下开始安排相关的工程研究了。直到后来来乔布斯也没有再坚持反对,只是说自己不再管了。 对 UX 界面的优雅性,特别是图标与界面的一流体验是刻在苹果骨子里的基因。即使是开放出 API,他们也对整个应用运行机制做了修改,从此开始与 macOS 上的程序执行策略有差异了。 由于第一批应用都是苹果自己写的,因此他们积攒了大量在嵌入式设备上良好设计的应用应该是怎样的,也设计出了非常有效的 API 体系。 在这种局面下,苹果有自己的 OS、自己的 IDE、自己的商店系统(当时还是跟 iTunes 共用),自然而然会设计出「最佳应用」应该长什么样。 这里头缺一不可,还记得前面提到过的观点吗? 当你要实施一个策略的时候,可需要另一个策略来解决前一个策略带来的问题,当策略变多的时候就有可能形成了一个环路。 从这儿可看出,为了打造出一个完美的体验,从硬件到软件全部打通,是必然的结果。 这不是为了封闭而封闭,而是为了打造最佳体验而做出的唯一一条路。 反观 07 - 08 年的 Android 阵营,他们还在忙着如何让系统跑起来。 在由 Cheet 著作的 「The Team That Built the Android Operating Systems」书中讲述了安卓从零开始被 Andy Rubin 创建的过程。它是由 Andy Rubin 离职后创业的公司,起初目标是给相机提供 OS。但是随着市场的演变,他们的目标变成了提供给移动设备,特别是手机的移动操作系统。Andy 当初的目标是创建一种可以平衡开发者与设备制造商以及运营商利益的真正意义上的完全开放的操作系统。这就要求它尽可能采用市面上已有的组件,将他们简单改在后适应嵌入式环境后快速部署上线。毕竟是有创业压力嘛,也不可能精雕细琢,只能用快速发展来解决各种体验问题了(主要是这时候 iPhone 还没出来)。再加上从零开始,他们没有配套的 IDE,也只能先提供简单的基于命令行的工具来构建应用程序, 因此从开始他们就缺乏整个应用运行环境的管控能力。不过这也是相对于苹果而言,毕竟两者的目的完全不同。 Android 的目标之一就是有大量应用程序可用,因此照顾到市面上人群最多的开发者是很想当然的思考方式。当时市场上开发者最多的编程语言是 Java,而 Java 在嵌入式领域里也是很受欢迎的。你可能很难相信,07 年的时候嵌入式设备性能很差,但为什么会是受欢迎的编程语言呢? 个人理解原因是,除特殊设备之外大部分设备其实不关心性能,基本维持在能用就行的程度。而且 Java 的可移植性,也大大降低了开发者的负担。为了使 Java 速度更快,Andy 团队还聘请了一位大神重新写一套适合嵌入式设备的虚拟机,这在当时看来都是正确的选择,只是没有 iPhone 出来之前。 不过运气好在,智能手机刚好碰上了黄金的 CPU 单核性能与制程爆发的十年,因此它跟 iOS 相比并没有逊色太多。 在安卓开发的早期,使用的是基于 Eclipse 的构建与开发工具,虽然谈不上非常优秀但是够用。但是痛苦的根源来源于对比,苹果很早之前开始构建自己的 IDE 工具,即 Xcode。这套工具里集成了大量的应用开发与调试以及分发功能,这极大的提高了应用开发工程师的效率。 虽然安卓将开发环境切入到 AndroidStudio 之后相比之前进步了很多,但这主要还是得益于 IntelliJ 本身的优秀,单纯应用开发角度来看跟 Xcode 相比还是弱了一些。Xcode 提供了非常方便的编写与调试程序性能的工具,这会使开发者通过简单的学习就能快速找出程序上低性能的代码段,大大提高了程序的质量而且还使整个过程很愉快,这在安卓上可不是这么一回事了。 由此可见,苹果能够在应用生态管控上的强势,其中一个重要的原因是得益于它的强大的 IDE 工具,提供方便的来帮助开发者解决问题,而不是一味地给他们压力。更进一步,LLVM、Swift 以及 SwiftUI,这些都是苹果了编写出更好地应用而所做的基础工具与语言。 目的当然是为了自身应用生态的发展,为消费者提供最佳的体验。 它的思路是尽可能服务好开发者,让他们编写更能契合系统机制的应用程序,即给你限制又给你解决方案。 设计 OS 的目的是盈利,因此要想办法帮助应用开发者开发好程序。很多 OS 提供了能力之后,应用如何编写就不归他们管了,他们往往会把这个责任放到应用开发者身上,从苹果身上可以看到,这种做法可能不利于整个设备的最优体验,吃亏的还是消费者自己以及厂商,因为把消费者给磨没了。 所以当我看到苹果的强大时候,除了硬件的强大,在软件生态的建设上面的思路是非常值得借鉴的,简单总结就是: 构建 OS 是手段,提供完整且优秀的体验是目的,双方通力合作才能达到此目的。特别是 OS 与设备制造商,可能要做出更多的努力。 服务好应用开发者,努力帮助他们写好应用,甚至发现与诊断能出应用的问题,协助开发者改进应用程序。 提供更快更好用的 API,尽可能高效的满足应用开发者的需求。 通过优秀的 IDE 工具服务好开发者,让他们在此基础上开发更多更优秀的应用,OS 才能有更好的机会存活下去。 可以看到虽然苹果有绝对的话语权,但同时也提供了远超于行业平局水平的软件服务。 再次强调,提供 OS 只是手段,要认识到与开发者建立怎样的关系、提供怎样的开发者服务(如 IDE),是在认知层面更为有意的事情。 4 策略之「过载保护」 移动设备的最大特性就是可移动,它是由电池供电实现了可移动性。除此之外,由于是手持设备因此散热基本靠被动散热,没有主动散热一说(奇葩的游戏手机与外挂式风扇另说)。现在有两个趋势,其一是晶体管的制作工艺越来越趋近于物理极限,其二是越来越多的功能直接封装在同一颗芯片上。处于活跃状态的晶体管数量变大(或者面积)之后发热量也是蹭蹭往上涨,这在智能手机刚开始普及那一会儿倒不是主要的矛盾。对于智能手机来说,现在更大的矛盾是,功耗与性能的平衡。 活跃的线程多了,就会使 CPU 一直处于工作状态,当然分给用户相关程序的 CPU 时间片也会少一些,性能也就受到影响了。所以移动设备 OS 的设计上,就自然的引申出了要对应用程序的资源使用上的限制,如像服务器、台式机一样完全放开,性能与功耗是无法保证了(当然还有发热)。越是性能跟功耗约束大的设备,对应用程序的管控就越严苛,比如智能手表。 Android 与 iOS 各自均有资源保护机制,Android 中最为常见的当属 OOM 机制了。当 Java 应用的堆内存使用超过一定阈值之后,就会被系统终止。它也有 CPU 使用过度的检测,但从实现上比较简陋,而且只会监听普通应用程序的 CPU 使用量,不监控系统以及由 Native 编写的线程(不用 Java 写)。 iOS 中可谓百花齐放,从 CPU 到内存,甚至 IO 过多写入也有限制,具体为: 设备过热时被终止 VoIP 类应用有过多 CPU 唤醒时被终止 执行 BackgroundTask 时使用 CPU 超过阈值时会被终止(备注: BackgroundTask 是 iOS 上后台执行时的状态) 执行 BackgroundTask 时没有在规定时间内完成时会被终止 程序的线程使用 CPU 超过阈值时被终止 程序写数据到磁盘的量超过阈值时被终止 单位时间内程序的线程之间的交互超过阈值时被终止(如两个线程互相唤醒) 程序的内存超标时被终止 系统内存压力过大时被终止 程序打开了过多的文件时被终止 系统遭受 PageCache Thrashing 时被终止 显然不止于此(可见未来,iOS 会增加更多限制),但以上是跟普通应用开发者最为密切的。iOS 将这些行为写在了开发者文档,让开发者知道自己的应用被异常退出时的原因。 谷歌对 Android 设计上的” 放松 “ ,可就给国内厂家留出了很多发挥空间。各家都有各自的资源过载保护策略(与 iOS 的类似,仅有或多或少的差异),它们尽可能保护手机不会被垃圾应用给搞坏了。但问题也恰恰出在这部分,由于系统的查杀行为没有明文化,开发者不知道自己的应用为什么会被终止。 即使知道了原因,也无法获取被终止时的调试信息,也就没办法做改进,因为没有哪家会把这类信息开放给应用开发者。 这导致的结果是,应用开发者不得不想出各种各样的所谓黑科技来使自己保活、绕过系统的各种检测机制。本应该由开发者一起共建的生态,现在变成了两家的攻防战了。到最后,是两败俱伤,而其中最受伤的就是消费者。 理想中的世界: 明文化资源过载保护机制,写在应用开发文档上。 当系统进行过载保护时将上下文调试信息保存下来,开发者可以查阅此类信息(具体权限、范围、有效期可以另定。总之要有方法,可以由开发者拿到此类调试信息)。 厂商提供方便好用的调试工具,可以由开发者在本地进行开发时修复问题使用。 当超出厂商制定的质量标准水位线,责令开发者进行修复,若不修复则下架应用。 厂商跟开发者应当是合作关系,可能厂商要做更多的事情用于帮助开发者,因为很多能力只有厂家才有。只是一味地责怪质量差是开发者的问题,这种厂商我觉得不太会有竞争力。 在认知维度上,就已经错了。 5 策略之「生命周期管理」 不同的设备形态所追求的体验要求不同,因此对 OS 的设计要求也不尽相同。在桌面机 OS 中一个应用程序的生命周期完全是由自己掌控的,目的是尽可能发挥程序的能力。能这么设计的原因是桌面机里没有功耗跟散热的限制,很少也会有性能上的瓶颈。对它来说首要考虑的问题是如何把机器的能力榨干。 而在智能手机上却是另一种情况,原因是它有功耗跟发热的限制。与此类似,智能手表上也有功耗与散热的限制,但是它比手机设备更为严苛。谁都不希望手腕里戴着一个会发热的表,而且续航一天都撑不了。除此之外,由于它的性能跟内存受限,不能驻留太多的程序在后台,因此需要有一个集中式管理的模块来统一实现绝大部分常见应用的服务,即所谓的托管式架构。智能汽车虽然没有性能、功耗以及发热的限制,但是它对稳定性的要求非常高。除非是汽车彻底断电,核心系统服务需要保持一直运行,因此对系统防老化的设计尤为重要。 智能手机 OS 设计中一个核心策略是关于生命周期管理的,它决定了应用程序的由生到死的整个过程。Android 的设计上更偏向于桌面机系统,因此策略的设计上比较「宽松」,留给开发者的发挥空间比较多。而 iOS 上限制比较多,一个应用退到后台之后只有 5 秒左右的时间用于执行后台任务,随后便进入到 Suspend 状态。在这种状态下应用程序是得不到 CPU 调度执行,因此在后台的时候应用会比较「安静」。 国内厂家拿到 AOSP 代码之后实现了类似 iOS 的这种 Suspend 机制,不过碍于 AOSP 原生的不支持,因此做了很多让步,这导致了整体效果上不如 iOS 来的彻底。Android 把这种运行策略解释为把应用写好应用开发者的责任,而我觉得这个想法是幼稚且不切实际的。如果这个逻辑成立的话,人类发展历史上都不需要有法律了,这是违背人性的事情。 不过好在谷歌可能意识到了问题,每年的更新中也逐步完善了俗称「冻结」的策略。不过能力奇差,远不及国内厂商所做的改进。AOSP 的进步也是比较缓慢,目测未来两三年内,这部分的进步速度也会一直缓慢,没有实质性的改变。 如果应用在后台被 Suspend 住了,那在 iOS 上如何实现需要后台计算的任务呢? 它引入了 BackgroundTask 的机制,让应用程序申请后台执行任务权限,由系统智能的调度执行应用程序的后台任务。所以,iOS 是给了一套策略让应用程序执行后台任务,但是决定权是交给系统执行的。这有利于系统根据当时手机的不同的状态调度后台任务,比如系统负载比较高的时候就不会执行后台任务了,目的是降低系统整体的负载。系统给每个应用还设置了每日配额的概念,比如一天之内允许你最多执行多少次等等,当然执行时间也是其中一个很重要的考量因素。一般允许执行 30 秒左右,超过之后就会被系统终止了。 但是后台任务不止于计算一种,播放音乐、位置定位,这种需求如何处理? 应用想要使用此类服务,需要在 IDE 中显示声明之后才可以使用,App Store 会检查应用与所申请权限是否匹配,不匹配时不给予通过。App Store 是 iOS 能够实现这套生命周期管理机制的很核心的一环,通过它实现了在上架期与运行期间的质量管控。当发现一个应用的质量较差的时候,会通知开发者让其修复,否则就会下架应用。 继 Suspend 之后系统也会随之终止应用程序,目的是过载保护。最常见的理由是回收内存,毕竟内存芯片非常贵,不上大内存的前提 下只能通过终止应用来腾出更多的内存了。 那我连应用程序都不执行了,又怎么执行后台任务?接收消息呢? 得益于 BackgroundTask 的设计,即使应用被终止了,当条件满足的时候系统还会自动的拉起你的程序执行后台任务。至于消息接收,则是通过消息通知机制来实现。分为两种,一种是在通知栏里能看到具体的内容,只有用户当点击此通知的时候才会唤醒应用程序。另一种则是 VoIP 类应用,可以主动拉起被终止的应用程序。 其实 Android 也有类似的机制,但是需要配合它的 GMS 服务使用,由于不确定的原因这个服务在国内是无法使用。因此国内的 App 不得不又要上各种各样的黑科技、商业合作等手段,使自己的程序在后台保活用于接收消息。这就造成了一个非常畸形的局面,头部应用由于是用户常用的,因此厂商也对它一路开绿灯。而厂家也发现这个现象之后,不断加码各类的服务,把手机当做自己的来用,尽可能榨干系统内存。 那有没有可能厂商自己提供类似的通知服务呢? 有,但是由于建设与运营成本跟自己的销售利润完全不成比例,大家也就只能加大内存容量了,可以把价格压力传递到消费者身上。整机的成本是有上限的,在内存这部分加大那就要在其他地方减弱。国内厂家要突破高端,首先要意识到整个环路的问题所在,与应用共建整个生态才是唯一的破局之道。 纵观 iOS 的设计,相比于 macOS, 限制了应用的自由度,但也不是一刀切方案。而是尽可能的提供了各种各样的「窗口期」或者「统一的解决方案」,以满足开发者的不同需求。目的是尽可能让开发者在合理的范围内做事情,而不是榨干用户的电量与性能。 总结下技术之上的设计原则 移动设备受限因素多,因此要对应用「自由度」给予限制但不能一刀切,需要给予对应的解决方案。 应用之间的共性任务要由系统统一提供,可节省系统整体负担,这对限制因素较多设备尤为重要。 程序最终执行权交由系统而定,由他综合各类信息之后统一调度,这有利于保护最终的用户体验。 写到此处,似乎是两个体制的碰撞,一个崇尚自由,个体优先、一个崇尚统一安排与调度。无论是哪种体制,要看最终目的是什么,如果是要提供最佳的设备用户体验,显然后者体制被市场证明是正确的。 6 设计之上 纵观电子消费品的发展史,以专业设备平民化、 N in 1 式的功能整合,两个主旋律方向发展。纯靠 CPU 的计算也会被专用硬件代替,比如 DSA (Domain Specific Architecture)。在 DSA 之上,会构建领域专用的编程语言与编译器,与之最接近的就是图形处理领域,如 GPU 与 Shader Language。 软件吃 CPU 性能提升的红利已经接近了尾声,下一个十年的「大飞跃」,目前看来也就这 DSA 机会点了。 M1 本身代表了正常的微架构与制程工艺带来的红利,只是友商阵营太拉跨,把这种差距拉大了。当一个产品的核心部件由某几个特定供应商提供的时候,基本上也判定了其发展上限。这也就是常说的核心技术要掌握自己手里,是同样的道理。M1 中除 CPU 能力外,它在多媒体处理,特别是视频流的处理场景下相比 Intel 芯片性能指标超出一大截,能实现这些性能提升的得益于处理器的特定场景下的性能提升。 但这不代表基于 CPU 的性能提升已经走到尽头,正是因为 CPU 性能提升的停滞,通过对需求的准确理解,优化矩阵与架构设计来实现在已有 CPU 上的性能提升,变得更为紧迫。对硬件、编译器、算法以及操作系统(框架与内核)的理解,变得越来越重要。因为当你优化到一定层度的业务代码之后,注意力必然会往底层走。 只有相当多的失败的经验,才能未雨绸缪,以高屋建瓴的方式设计出最佳的架构与优化矩阵。 优化矩阵是指为了提供一个极佳的体验,并不是由一个 OS 就能搞定,他要有相配套的其他技术一起支撑才能做好。比如 IDE、比如云端配合、以及正确的认知。 想要提供一个极致的体验,是非常难的事情,但也正因为如此,你会发现了解越多可做的事情就越多。同样道理,也有别样的说法 → 使自己始终处于「知道自己不知道」的状态。 不过以上成立的前提,是设计者的认知要跟得上。 查理芒格说过,投资不是看看财报,看看走势图就能做好的。除经济学与金融学外,心理学、社会学、政治学、甚至生物学都有关系。只有当你踏平学科间的隔阂,不设边界地把多个学科内容融合在一起之后,才能看到别人看不到的世界。 我当然也没达到这种境界,芒格给世人的经验是我们值得学习的宝贵经验。努力跨学科、跨领域的刻意练习,如果能在此基础上做到思考输出,那对学习更有帮助。 我的剑留给能够挥舞它的人 - 查理 芒格 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
前一段时间有个 App 很火,是 Android App 利用了 Android 系统漏洞,获得了系统权限,做了很多事情。想看看这些个 App 在利用系统漏洞获取系统权限之后,都干了什么事,于是就有了这篇文章。由于准备仓促,有些 Code 没有仔细看,感兴趣的同学可以自己去研究研究,多多讨论,对应的文章和 Code 链接都在下面: 深蓝洞察:2022 年度最 “不可赦” 漏洞 XXX apk 内嵌提权代码,及动态下发 dex 分析 Android 反序列化漏洞攻防史话 关于这个 App 是如何获取这个系统权限的,Android 反序列化漏洞攻防史话,这篇文章讲的很清楚,就不再赘述了,我也不是安全方面的专家,但是建议大家多读几遍这篇文章 序列化和反序列化是指将内存数据结构转换为字节流,通过网络传输或者保存到磁盘,然后再将字节流恢复为内存对象的过程。在 Web 安全领域,出现过很多反序列化漏洞,比如 PHP 反序列化、Java 反序列化等。由于在反序列化的过程中触发了非预期的程序逻辑,从而被攻击者用精心构造的字节流触发并利用漏洞从而最终实现任意代码执行等目的。 这篇文章主要来看看 XXX apk 内嵌提权代码,及动态下发 dex 分析 这个库里面提供的 Dex ,看看 App 到底想知道用户的什么信息?总的来说,App 获取系统权限之后,主要做了下面几件事(正常 App 无法或者很难做到的事情),各种不把用户当人了。 自启动、关联启动相关的修改,偷偷打开或者默认打开:与手机厂商斗智斗勇。 开启通知权限。 监听通知内容。 获取用户的使用手机的信息,包括安装的 App、使用时长、用户 ID、用户名等。 修改系统设置。 整一些系统权限的工具方便自己使用。 另外也可以看到,这个 App 对于各个手机厂商的研究还是比较深入的,针对华为、Oppo、Vivo、Xiaomi 等终端厂商都有专门的处理,这个也是值得手机厂商去反向研究和防御的。 最好我还加上了这篇文章在微信公众号发出去之后的用户评论,以及知乎回答的评论区(问题已经被删了,但是我可以看到:如何评价拼多多疑似利用漏洞攻击用户手机,窃取竞争对手软件数据,防止自己被卸载? - Gracker的回答 - 知乎 https://www.zhihu.com/question/587624599/answer/2927765317,目前为止是 2471 个赞)可以说是脑洞大开(关于 App 如何作恶)。 0. Dex 文件信息 本文所研究的 dex 文件是从 XXX apk 内嵌提权代码,及动态下发 dex 分析 这个仓库获取的,Dex 文件总共有 37 个,不多,也不大,慢慢看。这些文件会通过后台服务器动态下发,然后在 App 启动的时候进行动态加载,可以说是隐蔽的很,然而 Android 毕竟是开源软件,要抓你个 App 的行为还是很简单的,这些 Dex 就是被抓包抓出来的,可以说是人脏货俱全了。 由于是 dex 文件,所以直接使用 https://github.com/tp7309/TTDeDroid 这个库的反编译工具打开看即可,比如我配置好之后,直接使用 showjar 这个命令就可以 showjar 95cd95ab4d694ad8bdf49f07e3599fb3.dex 默认是用 jadx 打开,就可以看到反编译之后的内容,我们重点看 Executor 里面的代码逻辑即可 打开后可以看到具体的功能逻辑,可以看到一个 dex 一般只干一件事,那我们重点看这件事的核心实现部分即可 1. 通知监听和通知权限相关 1.1 获取 Xiaomi 手机通知内容 文件 : 95cd95ab4d694ad8bdf49f07e3599fb3.dex 功能 :获取用户的 Active 通知 类名 :com.google.android.sd.biz_dynamic_dex.xm_ntf_info.XMGetNtfInfoExecutor 1. 反射拿到 ServiceManager 一般我们会通过 ServiceManager 的 getService 方法获取系统的 Service,然后进行远程调用 2. 通过 NotificationManagerService 获取通知的详细内容 通过 getService 传入 NotificationManagerService 获取 NotificationManager 之后,就可以调用 getActiveNotifications 这个方法了,然后具体拿到 Notification 的下面几个字段 通知的 Title 发生通知的 App 的包名 通知发送时间 key channelID :the id of the channel this notification posts to. 可能有人不知道这玩意是啥,下面这个图里面就是一个典型的通知 其代码如下 可以看到 getActiveNotifications 这个方法,是 System-only 的,普通的 App 是不能随便读取 Notification 的,但是这个 App 由于有权限,就可以获取 当然微信的防撤回插件使用的一般是另外一种方法,比如辅助服务,这玩意是合规的,但是还是推荐大家能不用就不用,它能帮你防撤回,他就能获取通知的内容,包括你知道的和不知道的 1.2. 打开 Xiaomi 手机上的通知权限(Push) 文件 :0fc0e98ac2e54bc29401efaddfc8ad7f.dex 功能 :可能有的时候小米用户会把 App 的通知给关掉,App 想知道这个用户是不是把通知关了,如果关了就偷偷打开 类名 :com.google.android.sd.biz_dynamic_dex.xm_permission.XMPermissionExecutor 这么看来这个应该还是蛮实用的,你个调皮的用户,我发通知都是为了你好,你怎么忍心把我关掉呢?让我帮你偷偷打开吧 App 调用 NotificationManagerService 的 setNotificationsEnabledForPackage 来设置通知,可以强制打开通知 frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 然后查看 NotificationManagerService 的 setNotificationsEnabledForPackage 这个方法,就是查看用户是不是打开成功了 frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java 还有针对 leb 的单独处理~ 细 ! 1.3. 打开 Vivo 机器上的通知权限(Push) 文件 :2eb20dc580aaa5186ee4a4ceb2374669.dex 功能 :Vivo 用户会把 App 的通知给关掉,这样在 Vivo 手机上 App 就收不到通知了,那不行,得偷偷打开 类名 :com.google.android.sd.biz_dynamic_dex.vivo_open_push.VivoOpenPushExecutor 核心和上面那个是一样的,只不过这个是专门针对 vivo 手机的 1.4 打开 Oppo 手机的通知权限 文件 :67c9e686004f45158e94002e8e781192.dex 类名 :com.google.android.sd.biz_dynamic_dex.oppo_notification_ut.OppoNotificationUTExecutor 没有反编译出来,看大概的逻辑应该是打开 App 在 oppo 手机上的通知权限 1.5 Notification 监听 文件 :ab8ed4c3482c42a1b8baef558ee79deb.dex 类名 :com.google.android.sd.biz_dynamic_dex.ud_notification_listener.UdNotificationListenerExecutor 这个就有点厉害了,在监听 App 的 Notification 的发送,然后进行统计 监听的核心代码 这个咱也不是很懂,是时候跟做了多年 SystemUI 和 Launcher 的老婆求助了....@史工 1.6 App Notification 监听 文件 :4f260398-e9d1-4390-bbb9-eeb49c07bf3c.dex 类名 :com.google.android.sd.biz_dynamic_dex.notification_listener.NotificationListenerExecutor 上面那个是 UdNotificationListenerExecutor , 这个是 NotificationListenerExecutor,UD 是啥? 这个反射调用的 setNotificationListenerAccessGranted 是个 SystemAPI,获得通知的使用权,果然有权限就可以为所欲为 1.7 打开华为手机的通知监听权限 文件 :a3937709-b9cc-48fd-8918-163c9cb7c2df.dex 类名 :com.google.android.sd.biz_dynamic_dex.hw_notification_listener.HWNotificationListenerExecutor 华为也无法幸免,哈哈哈 1.8 打开华为手机通知权限 文件 :257682c986ab449ab9e7c8ae7682fa61.dex 类名 :com.google.android.sd.biz_dynamic_dex.hw_permission.HwPermissionExecutor 2. Backup 状态 2.1. 鸿蒙 OS 上 App Backup 状态相关,保活用? 文件 :6932a923-9f13-4624-bfea-1249ddfd5505.dex 功能 :Backup 相关 这个看了半天,应该是专门针对华为手机的,收到 IBackupSessionCallback 回调后,执行 PackageManagerEx.startBackupSession 方法 查了下这个方法的作用,启动备份或恢复会话 2.2. Vivo 手机 Backup 状态相关 文件 :8c34f5dc-f04c-40ba-98d4-7aa7c364b65c.dex 功能 :Backup 相关 3. 文件相关 3.1 获取华为手机 SLog 和 SharedPreferences 内容 文件 : da03be2689cc463f901806b5b417c9f5.dex 类名 :com.google.android.sd.biz_dynamic_dex.hw_get_input.HwGetInputExecutor 拿这个干嘛呢?拿去做数据分析? 获取 SharedPreferences 获取 slog 4. 用户数据 4.1 获取用户使用手机的数据 文件 : 35604479f8854b5d90bc800e912034fc.dex 功能 :看名字就知道是获取用户的使用手机的数据 类名 :com.google.android.sd.biz_dynamic_dex.usage_event_all.UsageEventAllExecutor 看核心逻辑是同 usagestates 服务,来获取用户使用手机的数据,难怪我手机安装了什么 App、用了多久这些,其他 App 了如指掌 那么他可以拿到哪些数据呢?应有尽有~,包括但不限于 App 启动、退出、挂起、Service 变化、Configuration 变化、亮灭屏、开关机等,感兴趣的可以看一下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 frameworks/base/core/java/android/app/usage/UsageEvents.java private static String eventToString(int eventType) { switch (eventType) { case Event.NONE: return "NONE"; case Event.ACTIVITY_PAUSED: return "ACTIVITY_PAUSED"; case Event.ACTIVITY_RESUMED: return "ACTIVITY_RESUMED"; case Event.FOREGROUND_SERVICE_START: return "FOREGROUND_SERVICE_START"; case Event.FOREGROUND_SERVICE_STOP: return "FOREGROUND_SERVICE_STOP"; case Event.ACTIVITY_STOPPED: return "ACTIVITY_STOPPED"; case Event.END_OF_DAY: return "END_OF_DAY"; case Event.ROLLOVER_FOREGROUND_SERVICE: return "ROLLOVER_FOREGROUND_SERVICE"; case Event.CONTINUE_PREVIOUS_DAY: return "CONTINUE_PREVIOUS_DAY"; case Event.CONTINUING_FOREGROUND_SERVICE: return "CONTINUING_FOREGROUND_SERVICE"; case Event.CONFIGURATION_CHANGE: return "CONFIGURATION_CHANGE"; case Event.SYSTEM_INTERACTION: return "SYSTEM_INTERACTION"; case Event.USER_INTERACTION: return "USER_INTERACTION"; case Event.SHORTCUT_INVOCATION: return "SHORTCUT_INVOCATION"; case Event.CHOOSER_ACTION: return "CHOOSER_ACTION"; case Event.NOTIFICATION_SEEN: return "NOTIFICATION_SEEN"; case Event.STANDBY_BUCKET_CHANGED: return "STANDBY_BUCKET_CHANGED"; case Event.NOTIFICATION_INTERRUPTION: return "NOTIFICATION_INTERRUPTION"; case Event.SLICE_PINNED: return "SLICE_PINNED"; case Event.SLICE_PINNED_PRIV: return "SLICE_PINNED_PRIV"; case Event.SCREEN_INTERACTIVE: return "SCREEN_INTERACTIVE"; case Event.SCREEN_NON_INTERACTIVE: return "SCREEN_NON_INTERACTIVE"; case Event.KEYGUARD_SHOWN: return "KEYGUARD_SHOWN"; case Event.KEYGUARD_HIDDEN: return "KEYGUARD_HIDDEN"; case Event.DEVICE_SHUTDOWN: return "DEVICE_SHUTDOWN"; case Event.DEVICE_STARTUP: return "DEVICE_STARTUP"; case Event.USER_UNLOCKED: return "USER_UNLOCKED"; case Event.USER_STOPPED: return "USER_STOPPED"; case Event.LOCUS_ID_SET: return "LOCUS_ID_SET"; case Event.APP_COMPONENT_USED: return "APP_COMPONENT_USED"; default: return "UNKNOWN_TYPE_" + eventType; } } 4.2 获取用户使用数据 文件:b50477f70bd14479a50e6fa34e18b2a0.dex 类名:com.google.android.sd.biz_dynamic_dex.usage_event.UsageEventExecutor 上面那个是 UsageEventAllExecutor,这个是 UsageEventExecutor,主要拿用户使用 App 相关的数据,比如什么时候打开某个 App、什么时候关闭某个 App,6 得很,真毒瘤 4.3 获取用户使用数据 文件:1a68d982e02fc22b464693a06f528fac.dex 类名:com.google.android.sd.biz_dynamic_dex.app_usage_observer.AppUsageObserver 看样子是注册了 App Usage 的权限,具体 Code 没有出来,不好分析 5. Widget 和 icon 相关 经吃瓜群众提醒,App 可以通过 Widget 伪造一个 icon,用户在长按图标卸载这个 App 的时候,你以为卸载了,其实是把他伪造的这个 Widget 给删除了,真正的 App 还在 (不过我没有遇到过,这么搞真的是脑洞大开,且不把 Android 用户当人) 5.1. Vivo 手机添加 Widget 文件:f9b6b139-4516-4ac2-896d-8bc3eb1f2d03.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_widget.VivoAddWidgetExecutor 这个比较好理解,在 Vivo 手机上加个 Widget 5.2 获取 icon 相关的信息 文件:da60112a4b2848adba2ac11f412cccc7.dex 类名:com.google.android.sd.biz_dynamic_dex.get_icon_info.GetIconInfoExecutor 这个好理解,获取 icon 相关的信息,比如在 Launcher 的哪一行,哪一列,是否在文件夹里面。问题是获取这玩意干嘛???迷 5.3 Oppo 手机添加 Widget 文件:75dcc8ea-d0f9-4222-b8dd-2a83444f9cd6.dex 类名:com.google.android.sd.biz_dynamic_dex.oppoaddwidget.OppoAddWidgetExecutor 5.4 Xiaomi 手机更新图标? 文件:5d372522-b6a4-4c1b-a0b4-8114d342e6c0.dex 类名:com.google.android.sd.biz_dynamic_dex.xm_akasha.XmAkashaExecutor 小米手机上的桌面 icon 、shorcut 相关的操作,小米的同学来认领 6. 自启动、关联启动、保活相关 6.1 打开 Oppo 手机自启动 文件:e723d560-c2ee-461e-b2a1-96f85b614f2b.dex 类名:com.google.android.sd.biz_dynamic_dex.oppo_boot_perm.OppoBootPermExecutor 看下面这一堆就知道是和自启动相关的,看来自启动权限是每个 App 都蛋疼的东西啊 6.2 打开 Vivo 关联启动权限 文件:8b56d820-cac2-4ca0-8a3a-1083c5cca7ae.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_association_start.VivoAssociationStartExecutor 看名字就是和关联启动相关的权限,vivo 的同学来领了 直接写了个节点进去 6.3 关闭华为耗电精灵 文件:7c6e6702-e461-4315-8631-eee246aeba95.dex 类名:com.google.android.sd.biz_dynamic_dex.hw_hide_power_window.HidePowerWindowExecutor 看名字和实现,应该是和华为的耗电精灵有关系,华为的同学可以来看看 6.4 Vivo 机型保活相关 文件:7877ec6850344e7aad5fdd57f6abf238.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_get_loc.VivoGetLocExecutor 猜测和保活相关,Vivo 的同学可以来认领一下 7. 安装卸载相关 7.1 Vivo 手机回滚卸载 文件:d643e0f9a68342bc8403a69e7ee877a7.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_rollback_uninstall.VivoRollbackUninstallExecutor 这个看上去像是用户卸载 App 之后,回滚到预置的版本,好吧,这个是常规操作 7.2 Vivo 手机 App 卸载 文件:be7a2b643d7e8543f49994ffeb0ee0b6.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_official_uninstall.OfficialUntiUninstallV3 看名字和实现,也是和卸载回滚相关的 7.3 Vivo 手机 App 卸载相关 文件:183bb87aa7d744a195741ce524577dd0.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_official_uninstall.VivoOfficialUninstallExecutor 同上 其他 SyncExecutor 文件:f4247da0-6274-44eb-859a-b4c35ec0dd71.dex 类名:com.google.android.sd.biz_dynamic_dex.sync.SyncExecutor 没看懂是干嘛的,核心应该是 Utils.updateSid ,但是没看到实现的地方 UdParseNotifyMessageExecutor 文件:f35735a5cbf445c785237797138d246a.dex 类名:com.google.android.sd.biz_dynamic_dex.ud_parse_nmessage.UdParseNotifyMessageExecutor 看名字应该是解析从远端传来的 Notify Message,具体功能未知 6.3 TDLogcatExecutor 文件 8aeb045fad9343acbbd1a26998b6485a.dex 2aa151e2cfa04acb8fb96e523807ca6b.dex 类名 com.google.android.sd.biz_dynamic_dex.td.logcat.TDLogcatExecutor com.google.android.sd.biz_dynamic_dex.td.logcat.TDLogcatExecutor 没太看懂这个是干嘛的,像是保活又不像,后面有时间了再慢慢分析 6.4 QueryLBSInfoExecutor 文件:74168acd-14b4-4ff8-842e-f92b794d7abf.dex 类名:com.google.android.sd.biz_dynamic_dex.query_lbs_info.QueryLBSInfoExecutor 获取 LBS Info 6.5 WriteSettingsExecutor 文件:6afc90e406bf46e4a29956aabcdfe004.dex 类名:com.google.android.sd.biz_dynamic_dex.write_settings.WriteSettingsExecutor 看名字应该是个工具类,写 Settings 字段的,至于些什么应该是动态下发的 6.6 OppoSettingExecutor 文件:61517b68-7c09-4021-9aaa-cdebeb9549f2.dex 类名:com.google.android.sd.biz_dynamic_dex.opposettingproxy.OppoSettingExecutor Setting 代理??没看懂干嘛的,Oppo 的同学来认领,难道是另外一种形式的保活? 6.7 CheckAsterExecutor 文件:561341f5f7976e13efce7491887f1306.dex 类名:com.google.android.sd.biz_dynamic_dex.check_aster.CheckAsterExecutor Check aster ?不是很懂 6.8 OppoCommunityIdExecutor 文件:538278f3-9f68-4fce-be10-12635b9640b2.dex 类名:com.google.android.sd.biz_dynamic_dex.oppo_community_id.OppoCommunityIdExecutor 获取 Oppo 用户的 ID?要这玩意干么? 6.9 GetSettingsUsernameExecutor 文件:4569a29c-b5a8-4dcf-a3a6-0a2f0bfdd493.dex 类名:com.google.android.sd.biz_dynamic_dex.oppo_get_settings_username.GetSettingsUsernameExecutor 获取 Oppo 手机用户的 username,话说你要这个啥用咧? 6.10 LogcatExecutor 文件:218a37ea-710d-49cb-b872-2a47a1115c69.dex 类名:com.google.android.sd.biz_dynamic_dex.logcat.LogcatExecutor 配置 Log 的参数 6.11 VivoBrowserSettingsExecutor 文件:136d4651-df47-41b4-bb80-2ec0ab1bc775.dex 类名:com.google.android.sd.biz_dynamic_dex.vivo_browser_settings.VivoBrowserSettingsExecutor Vivo 浏览器相关的设置,不太懂要干嘛 评论区比文章更精彩 微信公众号评论区 知乎评论区 知乎回答已经被删了,我通过主页可以看到,但是点进去是已经被删了:如何评价拼多多疑似利用漏洞攻击用户手机,窃取竞争对手软件数据,防止自己被卸载? - Gracker的回答 - 知乎 https://www.zhihu.com/question/587624599/answer/2927765317 iOS 和 Android 哪个更安全? 这里就贴一下安全大佬 sunwear 的评论 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
2022.3.25 ,周五晚上九点,The Performance 知识星球(付费版本)举办了第一次线上茶话会,3 位星球主理人 + 5 位星球嘉宾,50 多位球友参加,非常感谢各位! 第一次茶话会没有预设主题,预计 1 个小时就可以结束,结果聊了 2 个半小时,光是主理人和嘉宾的个人介绍就花了一个小时。平时大家在群里和星球里交流很多,但是通过语音在线交流还是第一次,再者大家的公司基本上涵盖了 Android 上下游:既有 App 大厂的大佬,也有一线手机厂商的系统大咖,也有芯片公司和造车新势力的资深专家。所以在每个人自我介绍的时候,会聊一些公司相关的东西,再发散一下,其他人再问一些问题,时间就过去了。 后续会不定期举办星球茶话会,主题会更加明确,也会邀请更多嘉宾来一起聊聊,不局限于技术,欢迎大家多多参与和提问。考虑到隐私问题, 本次茶话会没有录屏,下面的内容也不会涉及到各位的隐私,文字稿是根据聊天内容部分还原的。 关于职业发展 领域的应届生真的被庞大的知识体系压的有点喘不过气 改变心态:学校跟专业领域是不一样的。学校是有课本,学完一门课就算结业了。从事专业事项的时候,两个不一样,而且很不一样。 从手边的问题开始学习:把工作中遇到的不懂的问题弄懂,做到 100 分甚至 110 分,平时储备自己不具备的知识。对于新人来说,把工作做好有很多好处。一是增加经验,二是获得更多学习与接触新东西的机会。切忌主业没做好,做另外一个方向,除非你确定目前的主业不适合自己干。 面向解决问题编程:工作中很重要的一个技能就是解决问题,老板发钱就是让你解决问题的。掌握好度,以经济学来说就是控制好边际收益,投入与产出应最大化是比较理想的情况。 掌握学习的方法和技巧:爬山的时候直接看山顶,更让人容易放弃。更好的方式是,头朝下一步一步走,偶尔看看地图是否走错,或者走着走着发现更适合自己的路。 刚从 App 开发转到安卓系统性能优化的小白 跟踪公司的项目走:如果有人带那就好,跟着公司项目走,这是最快的。 自驱力:如果愿意平时学习,会的多了争取到的机会也多一些。还是那个观点,对于新人来说,把手头工作做好的前提下努力学习跟主业相关的新知识。当你做手头工作的时候你的主观能动性会变强、周边同事与领导也会支持你,而且给你的奖励也是不错的。 不要给自己设限:知识都是融会贯通的,不要把自己局限在自己的一亩三分地,尤其是做性能的,App、Framework、Kernel、Hardware 这些,都可以去看去学习。努力使自己处于「知道自己不知道」的状态,最糟糕的是「不知道自己不知道」还觉得优化没啥可做,从经验与常识来看,这都是错误的。 知识体系化:性能优化涉及到的知识非常广,平时分析和解决问题的时候,要多看代码、多记录、总结和归纳,把学到的知识体系化,不要想着一蹴而就,要厚积薄发。 利用好现有的资源:跟着星球内容、博客内容、大厂的优秀文章这些学习,多提问,不管有多初级。 关于提问 星球是一个很好的平台,很多让你困惑的问题别人也经历过。但是你要利用好他的前提是能问出准确的问题。如果问问题,是个技术活。 可以参考这篇文章:到底如何提问?:https://mp.weixin.qq.com/s/l_Iz5pZ5yXhBAoPzNi4m-Q 决定探讨、思考、回答的质量和效率。「提出好的问题已经解决了问题的一半」,说得没什么科学依据,但也不妨理解好问题的重要性。 决定做事方向。曾经有人问爱因斯坦:「如果您有一个小时来拯救世界,您将如何使用?」他回答说:「我将花 55 分钟确定问题,然后花 5 分钟解决问题。」 提问是认识世界和人的桥梁。好问题能激发优秀者的教学欲望,将宝贵信息倾囊相授。 其实可以从一个人提问的能力来判断其段位。就像杨澜说的,「一个人的提问力,彰显在与外界互动质量的高低。」 尽量使用 Google 来搜索答案,百度和 Google 的答案差异很大。答案不区分中文资料和英文资料,能解决疑惑就是好资料,值得保存和分享(到星球或者星球微信群)。 需要提供日志分析问题时 如果涉及到敏感内容,比如 Systrace 中涉及到敏感信息,可以使用文本软件,比如 VSCode 打开 Systrace 文件,一键将敏感信息(比如 package name)替换。 Log 中如果涉密,尽量还是不要发出来。 如果是单独发给三个主理人,那么不脱敏也无所谓,我们承诺不会公开这些内容,只会做分析使用。 一个好的闭环:大家伙提问 -> 一起分析原因 -> 尝试各种方法解决 -> 通过数据验证有效果 -> 把结果分享给大家。 关于面试 有时候需要出去跟友商、新赛道的人沟通下,可以看看外部环境都在发生怎样的变化。首要目的不是为了跳槽(如果有好机会,跳也无妨),而是为了让自己在人力资源市场上有比较高的竞争力。 这里首先要区分职业与工作的区别。我是软件工程师,这是我的职业。我在 A 工作任职负责某个模块,这是我的工作。这两个是不一样的,如果你能把两者区分开(前提是能区分得开),那在择业、面对公司变化的时候都能从容应对。 最理想情况下,应该追求自己喜欢的职业,因为它伴随你绝大部分时间,如果这个职业本身不让你感到兴奋,你不太可能做出比较好的成绩,进而无法获得比较高的回报。 遇到好的工作,那就看天意了。这很复杂,与很多很多因素有关系。当你遇到面试不顺利,只能说这个工作不适合你,没有缘分,无法进一步说明更多的事情。 关于公司环境 不同的公司在不同的行业赛道、竞争格局上所采用的策略是不一样的。微观上,你的部门领导的风格以及同事们的结构,都影响到了你具体做事的环境。不同环境所追求的「回报最优值」是不一样的。 有的是需要你做到行业 5%,得到组织认可。有的是能把问题搞定就行而不关心如何搞定的,得到组织认可。 要么直接硬刚这种环境,按照你认为对的方式行事,要么就适应它。无论选择哪种,要知道局面是什么局面,而不是遇到与自己想象不一样的时候要么怀疑自己,要么以为这个世界就是这样。 关于学习新技术 对于优化业务来说,它是有底层逻辑可寻的。而这些底层逻辑遇到不同的技术栈、不同的局面的时候会藏的比较深。平时学习中,要努力找出这种底层不变的东西,将可变的东西套进去,一是增强对新技术的判断力,二是可以举一反三提出更进一步的优化方案。 不要在新技术的表面上游荡,一是自己觉得累,二是这没啥意义。 优化做到一定深度,肯定是往底层走。比如编译器、硬件特性,甚至硬件整合。因为这些东西直接决定了程序的速度,所以是绕不开的一道坎。 关于 GPU 讲 GPU 架构相关:下面是一些 PDF,各时代、各厂商的 GPU 架构都不一样,当然也有相似之处,建议都看看。初学 GPU 架构的人会遇到很多专业名词,遇到了不懂的词要去搜索,然后弄懂,会有文章介绍,看完后会更加拓展你的视野,发现新的一片天地。 GPU Architectures Introdution to GPU architecture Introduction to Modern GPU Architecture Graphics and Computing GPUs GPU Architecture and Function 讲 GPU 架构相关的书,几乎没啥书: Mobile 3D Graphics SoC From Algorithm to Chip 这本书网上只有付费的,免费的 PDF 可以在我们星球上搜。 讲 GPU 相关的博客,很多我找不到了,就列举了一些我能找到的: 深入 GPU 硬件架构及运行机制 - 0 向往 0 - 博客园,这个论坛讲的内容比较成体系,作者面向的读者应该是游戏开发的人群,不过学习图形的人都可以看看。 Tile Based 架构下的性能调校,关于 Tile base 如果性能调优。 厂商一般都没有 OpenGL 实现库源码,如果又对源码感兴趣,可以下载下面两个库代码看: 谷歌的 swiftshader,在谷歌 android 源码根目录 external/swiftshader 下,一个完全用 CPU 实现的 OpenGL 3.0 的版本,代码可读性非常高,可以帮助大家理解 OpenGL 的各种原如:状态机、资源管理、API 实现等。 Mesa 3D,一个开源的图形驱动库,网上有一些文档介绍可以搜搜。 如果没有图形开发经验,想学习 Vulkan 开发个人推荐先学习 OpenGL:因为 GPU 底层原理和机制都一样,学习了 OpenGL 入门 Vulkan 会更加简单,因为 Vulkan API 更加底层如果直接学习 Vulkan 估计会比较困难。如果有了 OpenGL 开发基础,想学 Vulkan,我分享一下我的学习之路(可能不是最优的,大家参考一下即可): Vulkan Tutoria l 学习最简单的 Vulkan 例子,如:如何绘制一个三角形。 《Vulkan Programming Guide》By Graham Sellers and John Kessenich,网上有 PDF,也有中文版,这本是官方出版的书,一本没有感情介绍 Vulkan API 的书,比较赤裸裸的介绍 API,几乎不讲原理,我学习它的目的是要熟悉 Vulkan 的 API。 《Vulkan 学习指南》作者帕敏德·辛格,也是一本入门的书,我是微信读书看的中文版,不过翻译得很一般,能看英文的尽量看原版,这本书比较注重渲染的流程和原理的讲解,不过 API 的介绍不全,需要配合《Vulkan Programming Guide》一起看。 看了入门的书,那就要动手写代码了,所以我推荐大家看一本用 Vulkan 来介绍游戏开发的书,我个人看的吴亚峰的《Vulkan 开发实践指南》,这本书个人觉得写的很一般,我看它的主要原因是,我大学的时候看过这本书 OpenGL 版本的,这本书基本就是 OpenGL 版本直译过来,讲的比较冰冷。 为什么都在推 Vulkan,Vulkan 比 OpenGL 有什么优势?可以看: 苹果开发官网的Bringing OpenGL Apps to Metal视频,Metal 和 Vulkan API 基本一样,目的也是一样,都是来自 AMD 的 Mantle,把视频中 Metal 当做 Vulkan 就可以了 上面书中《Vulkan 学习指南》作者帕敏德·辛格,开编就有讲 OpenGL 和 Vulkan 差异 最后就是:学习图形开发的人一定要沉得住气耐得住寂寞,多看书多练习,不然很难学得精通,这是一个长期的过程,不是看几本书就能精通的。学习 GPU 架构的人,国内基本没有一家像样的 GPU 供应商,GPU 方面资料会很少,所以一定要多到外网去收集学习资料,可以多用 GPU 调试工具调试去分析应用程序,去了解 GPU 内部的 Counters,如 DS5 Streamline、Snapdragon Profiler 等,另外在没有很全面的学习资料的前提下,去学习一门更加底层的图形开发技术对学习和了解 GPU 架构也很重要,如 Vulkan,这会有助于自顶而下的去了解 GPU 内部的工作原理。 关于提到的文章、书 文章:十年创业者万字长文分享招人 文章地址:https://presence.feishu.cn/docs/doccn71hTTKbaRGF8RvD2XzLJEK,里面列举了作者总结的 S、A、B、C 类人才,大家可以对照一下自己做事的方式,看看更高级别的人才是怎么做事的 书籍:程序员的自我修养 by 俞甲子 / 石凡 / 潘爱民 豆瓣地址:https://book.douban.com/subject/3652388/ 这本书主要介绍系统软件的运行机制和原理,涉及在 Windows 和 Linux 两个系统平台上,一个应用程序在编译、链接和运行时刻所发生的各种事项,包括:代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现,C/C++运行库的工作原理,以及操作系统提供的系统服务是如何被调用的。每个技术专题都配备了大量图、表和代码实例,力求将复杂的机制以简洁的形式表达出来。本书最后还提供了一个小巧且跨平台的 C/C++运行库 MiniCRT,综合展示了与运行库相关的各种技术。 对装载、链接和库进行了深入浅出的剖析,并且辅以大量的例子和图表,可以作为计算机软件专业和其他相关专业大学本科高年级学生深入学习系统软件的参考书。同时,还可作为各行业从事软件开发的工程师、研究人员以及其他对系统软件实现机制和技术感兴趣者的自学教材。 书籍:软件调试(第 2 版) by 张银奎 本书堪称是软件调试的“百科全书”。作者围绕软件调试的“生态”系统(ecosystem)、异常(exception)和调试器 3 条主线,介绍软件调试的相关原理和机制,探讨可调试性(debuggability)的内涵、意义以及实现软件可调试性的原则和方法,总结软件调试的方法和技巧。 卷 1:硬件基础 豆瓣地址:https://book.douban.com/subject/30379453/ 第 1 卷主要围绕硬件技术展开介绍。全书分为 4 篇,共 16 章。第一篇“绪论”(第 1 章),介绍了软件调试的概念、基本过程、分类和简要历史,并综述了本书后面将详细介绍的主要调试技术。第二篇“CPU 及其调试设施”(第 2 ~ 7 章),以英特尔和 ARM 架构的 CPU 为例系统描述了 CPU 的调试支持。第三篇“GPU 及其调试设施”(第 8 ~ 14 章),深入探讨了 Nvidia、AMD、英特尔、ARM 和 Imagination 这五大厂商的 GPU。第四篇“可调试性”(第 15 ~ 16 章),介绍了提高软件可调试性的意义、基本原则、实例和需要注意的问题,并讨论了如何在软件开发实践中实现可调试性。 本书理论与实践紧密结合,既涵盖了相关的技术背景知识,又针对大量具有代表性和普遍意义的技术细节进行了讨论,是学习软件调试技术的宝贵资料。本书适合所有从事软件开发工作的读者阅读,特别适合从事软件开发、测试、支持的技术人员,从事反病毒、网络安全、版权保护等工作的技术人员,以及高等院校相关专业的教师和学生学习参考。 卷 2:Windows 平台调试 书籍信息:https://book.douban.com/subject/35233332/ 第 2 卷分为 5 篇,共 30 章,主要围绕 Windows 系统展开介绍。第一篇(第 1- 4 章)介绍 Windows 系统简史、进程和线程、架构和系统部件,以及 Windows 系统的启动过程,既从空间角度讲述 Windows 的软件世界,也从时间角度描述 Windows 世界的搭建过程。第二篇(第 5-8 章)描述特殊的过程调用、垫片、托管世界和 Linux 子系统。第三篇(第 9-19 章)深入探讨用户态调试模型、用户态调试过程、中断和异常管理、未处理异常和 JIT 调试、硬错误和蓝屏、错误报告、日志、事件追踪、WHEA、内核调试引擎和验证机制。第四篇(第 20-25 章)从编译和编译期检查、运行时库和运行期检查、栈和函数调用、堆和堆检查、异常处理代码的编译、调试符号等方面概括编译器的调试支持。第五篇(第 26-30 章)首先纵览调试器的发展历史、工作模型和经典架构,然后分别讨论集成在 Visual Studio 和 Visual Studio(VS)Code 中的调试器,最后深度解析 WinDBG 调试器的历史、结构和用法。 本书理论与实践结合,不仅涵盖了相关的技术背景知识,还深入研讨了大量具有代表性的技术细节,是学习软件调试技术的珍贵资料。 这本书应当还有卷 3,此卷里会讲基于 Linux 平台的调试方法。 关于 The Performance 知识星球 The Performance 是一个分享 Android 开发领域性能优化相关的圈子,主理人是三个国内一线手机厂商性能优化方面的一线开发者,有多年性能相关领域的知识积累和案例分析经验,可以提供性能、功耗分析知识的一站式服务,涵盖了基础、方法论、工具使用和最宝贵的案例分析 星球 免费版,定位是知识分享和交流,也可以微信扫码加入 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文是 Systrace 线程 CPU 运行状态分析技巧系列的第三篇,本文主要讲了使用 Systrace 分析 CPU 状态时遇到的 Sleep 与 Uninterruptible Sleep 状态的原因排查方法与优化方法,这两个状态导致性能变差概率非常高,而且排查起来也比较费劲,网上也没有系统化的文档。 本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 Systrace 基础知识 - Systrace 预备知识 或者 博客文章目录 这里看到完整的目录 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Linux 中的 Sleep 状态是什么 TASK_INTERUPTIBLE vs TASK_UNINTERRUPTIBLE 一个线程的状态不属于 Running 或者 Runnable 的时候,那就是 Sleep 状态了(严谨来说,还有其他状态,不过对性能分析来说不常见,比如 STOP、Trace 等)。 在 Linux 中的Sleep 状态可以细分为 3 个状态: TASK_INTERUPTIBLE → 可中断 TASK_UNINTERRUPTIBLE → 不可中断 TASK_KILLABLE → 等同于 TASK_WAKEKILL | TASK_UNINTERRUPTIBLE 在 Systrace/Perfetto 中,Sleep 状态指的是 Linux 中的TASK_INTERUPTIBLE,trace 中的颜色为白色。Uninterruptible Sleep 指的是 Linux 中的 TASK_UNINTERRUPTIBLE,trace 中的颜色为橙色。 本质上他们都是处于睡眠状态,拿不到 CPU的时间片,只有满足某些条件时才会拿到时间片,即变为 Runnable,随后是 Running。 TASK_INTERRUPTIBLE 与 TASK_UNINTERRUPTIBLE 本质上都是 Sleep,区别在于前者是可以处理 Signal 而后者不能,即使是 Kill 类型的Signal。因此,除非是拿到自己等待的资源之外,没有其他方法可以唤醒它们。 TASK_WAKEKILL 是指可以接受 Kill 类型的Signal 的TASK_UNINTERRUPTIBLE。 Android 中的 Looper、Java/Native 锁等待都属于 TAKS_INTERRUPTIBLE,因为他们可以被其他进程唤醒,应该说绝大部分的程序都处于 TAKS_INTERRUPTIBLE 状态,即 Sleep 状态。 看看 Systrace 中的一大片进程的白色状态就知道了(trace 中表现为白色块),它们绝大部分时间都是在 Runnning 跟 Sleep 状态之间转换,零星会看到几个 Runnable 或者 UninterruptibleSleep,即蓝色跟橙色。 TASK_UNINTERRUPTIBLE 作用 似乎看来 TASK_INTERUPTIBLE 就可以了,那为什么还要有 TASK_UNINTERRUPTIBLE 状态呢? 中断来源有两个,一个是硬件,另一个就是软件。硬件中断是外围控制芯片直接向 CPU 发送了中断信号,被 CPU 捕获并调用了对应的硬件处理函数。软件中断,前面说的 Signal、驱动程序里的 softirq 机制,主要用来在软件层面触发执行中断处理程序,也可以用作进程间通讯机制。 一个进程可以随时处理软中断或者硬件中断,他们的执行是在当前进程的上下文上,意味着共享进程的堆栈。但是在某种情况下,程序不希望有任何打扰,它就想等待自己所等待的事情执行完成。比如与硬件驱动打交道的流程,如 IO 等待、网络操作。 这是为了保护这段逻辑不会被其他事情所干扰,避免它进入不可控的状态。 Linux 处理硬件调度的时候也会临时关闭中断控制器、调度的时候会临时关闭抢占功能,本质上为了 防止程序流程进入不可控的状态。这类状态本身执行时间非常短,但系统出异常、运行压力较大的时候可能会影响到性能。 https://elixir.bootlin.com/linux/latest/ident/TASK_UNINTERRUPTIBLE 可以看到内核中使用此状态的情况,典型的有 Swap 读数据、信号量机制、mutex 锁、内存慢路径回收等场景。 分析时候的注意点 首先要认识到 TASK_INTERUPTIBLE、TASK_UNINTERRUPTIBLE 状态的出现是正常的,但是如果这些这些状态的累计占比达到了一定程度,就要引起注意了。特别是在关键操作路径上这类状态的占比较多的时候,需要排查原因之后做相应的优化。 分析问题以及做优化的时候需要牢牢把握两个关键点,它类似于内功心法一样: 原因的排查方法 优化方法论 你需要知道是什么原因导致了这次睡眠,是主动的还是被动的?如果是主动的,通过走读代码调查是否是正常的逻辑。如果是被动的,故事的源头是什么? 这需要你对系统有足够多的认识,以及分析问题的经验,你需要经常看案例以增强自己的知识。 以下把 TASK_INTERUPTIBLE 称之为 Sleep,TASK_UNINTERRUPTIBLE称之为 UninterruptibleSleep,目的是与 Systrac 中的用词保持一致。 初期分析 Sleep 与 UninterruptibleSleep 状态的经验不足时你会感到困惑,这种困惑主要是来自于对系统的不了解。你需要读大量的框架层、内核层的代码才能从 Trace 中找出蛛丝马迹。目前并没有一种 Trace 工具能把整个逻辑链路描述的很清楚,而且他们有时候还有不准的时候,比如 Systrace 中的 wakeup_from 信息。只有广泛的系统运行原理做为支持储备,再结合 Trace 工具分析问题,才能做到准确定位问题根因。否则就是我经常说的「性能优化流氓」,你说什么是什么,别人也没法证伪。反复折磨测试同学复测,没测出来之后,这个问题也就不了了之了。 本文没办法列举完所有状态的原因,因此只能列举最为常见的类型,以及典型的实际案例。更重要的是,你需要掌握诊断方法,并结合源代码来定位问题。 Trace 中的可视化效果 Sleep 状态分析 诊断方法 通过 wakeup from tid: ***查看唤醒线程 Sleep 最常见的有图 1(UIThread 与 RenderThread 同步)的情况与图 2(Binder 调用)的情况。 Sleep 状态一般是由程序主动等待某个事件的发生而造成的,比如锁等待,因此它有个比较明确的唤醒源。比如图 1,UIThread 等待的是 RenderThread,你可以通过阅读代码来了解这种多线程之间的交互关系。虽然最直接,但是对开发者的要求非常高,因为这需要你熟读图形栈的代码。这可不是一般的难度,是追求的目标,但不具备普适性。 更简单的方法是通过所谓的 wakeup from tid: *** 来调查线程之间的交互关系。从前面的 Runnable 文章 中讲过,任何线程进入 Running 之前会先进入到 Runnable 状态,由此再转换成 Running。从 Sleep 状态切换到 Running,必然也要经过 Runnable。 进入到 Runnable 有两种方式,一种是 Running 中的程序被抢占了,暂时进入到 Runnable。还有一种是由另外一个线程将此线程(处于 Sleep 的线程)变成了 Runnable。 我们在调查Sleep 唤醒线程关系的时候,应用到的原理是第二种情况。在 Systrace 中这种是被 wakeup from tid: *** 信息所呈现。线程被抢占之后变成 Runnable,在 Systrace 中是被 Running Instead 呈现。 需要特别注意的是 wakeupfrom 这个有时候不准,原因是跟具体的 tracepoint 类型有关。分析的时候要注意甄别,不要一味地相信这个数据是对的。 其他方法 Simpleperf 还原代码执行流 在 Systrace 寻找时间点对齐的事件 方法 1 适合用来看程序到底在执行什么操作进入到这种状态,是 IO 还是锁等待?球里连载 Simpleperf 工具的使用方法,其中「Simpleperf 分析篇 (1): 使用 Firefox Profiler 可视分析 Simpleperf 数据」介绍了可以按时间顺序看函数调用的可视化方法。其他使用也会陆续更新,直接搜关键字即可。 方法 2 是个比较笨的方法,但有时候也可以通过它找到蛛丝马迹,不过缺点是错误率比较高。 耗时过长的常见原因 Binder 操作 → 通过打开 Binder 对应的 trace,可方便地观察到调用到远端的 Binder 执行线程。如果 Binder 耗时长,要分析远端的 Binder 执行情况,是否是锁竞争?得不到CPU 时间片?要具体问题具体分析 Java\futex锁竞争等待 → 最常见也是最容易引起性能问题,当负载较高时候特别容易出现,特别是在 SystemServer 进程中。这是 Binder 多线程并行化或抢占公共资源导致的弊端。 主动等待 → 线程主动进入 Sleep 状态,等待其它线程的唤醒,比如等待信号量的释放。优化建议:需要看代码逻辑分析等待是否合理,不合理就要优化掉。 等待 GPU 执行完毕 → 等 GPU 任务执行完毕,Trace 中可以看到等 GPU fence 时间。常见的原因有渲染任务过重、 GPU 能力弱、GPU 频率低等。优化建议:提升 GPU 频率、降低渲染任务复杂度,比如精简 Shader、降低渲染分辨率、降低Texture 画质等。 UninterruptibleSleep 状态分析 诊断方法 本质上UninterruptibleSleep 也是一种 Sleep,因此分析 Sleep 状态时用到的方法也是通用的。不过此状态有两个特殊点与 Sleep 不同,因此在此特别说明。 UninterruptibleSleep 分为 IOWait 与 Non-IOWait UninterruptibleSleep 有 Block reason UninterruptibleSleep 分为 IOWait 与 Non-IOWait IO 等待好理解,就是程序执行了 IO 操作。最简单的,程序如果没法从 PageCache 缓存里快速拿到数据,那就要与设备进行 IO 操作。CPU 内部缓存的访问速度是最快的,其次是内存,最后是磁盘。它们之间的延迟差异是数量级差异,因此系统越是从磁盘中读取数据,对整体性能的影响就越大。 非 IO 等待主要是指内核级别的锁等待,或者驱动程序中人为设置的等待。Linux 内核中某些路径是热点区域,因此不得不拿锁来进行保护。比如Binder 驱动,当负载大到一定程度,Binder 的内部的锁竞争导致的性能瓶颈就会呈现出来。 Block Reason 谷歌的 Riley Andrews(riandrews@google.com) 15年左右往内核里提交了一个 tracepoint 补丁,用于记录当发生 UninterruptibleSleep 的时候是否是 IO 等待、调用函数等信息。Systrace 中的展示的 IOWait 与 BlockReason,就是通过解析这条 tracepoint 而来的。这条代码提交的介绍如下(由于这笔提交未合入到 Linux 上游主线,因此要注意你用的内核是否单独带了此补丁): 1 2 3 4 5 6 7 sched: add sched blocked tracepoint which dumps out context of sleep. Decare war on uninterruptible sleep. Add a tracepoint which walks the kernel stack and dumps the first non-scheduler function called before the scheduler is invoked. Change-Id: [I19e965d5206329360a92cbfe2afcc8c30f65c229](https://android-review.googlesource.com/#/q/I19e965d5206329360a92cbfe2afcc8c30f65c229) Signed-off-by: Riley Andrews [riandrews@google.com](mailto:riandrews@google.com) 在 ftrace(Systrace 使用的数据抓取机制) 中的被记录为 1 sched_blocked_reason: pid=30235 iowait=0 caller=get_user_pages_fast+0x34/0x70 这句话被 Systrace 可视化的效果为: 主线程中有一段 Uninterruptible Sleep 状态,它的 BlockReason 是 get_user_pages_fast。它是一个 Linux 内核中函数的名字,代表着是线程是被它切换到了 UninterruptibleSleep 状态。为了查看具体的原因,需要查看这个函数的具体实现。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /** * get_user_pages_fast() - pin user pages in memory * @start: starting user address * @nr_pages: number of pages from start to pin * @gup_flags: flags modifying pin behaviour * @pages: array that receives pointers to the pages pinned. * Should be at least nr_pages long. * * Attempt to pin user pages in memory without taking mm->mmap_lock. * If not successful, it will fall back to taking the lock and * calling get_user_pages(). * * Returns number of pages pinned. This may be fewer than the number requested. * If nr_pages is 0 or negative, returns 0. If no pages were pinned, returns * -errno. */ int get_user_pages_fast(unsigned long start, int nr_pages, unsigned int gup_flags, struct page **pages) { if (!is_valid_gup_flags(gup_flags)) return -EINVAL; /* * The caller may or may not have explicitly set FOLL_GET; either way is * OK. However, internally (within mm/gup.c), gup fast variants must set * FOLL_GET, because gup fast is always a "pin with a +1 page refcount" * request. */ gup_flags |= FOLL_GET; return internal_get_user_pages_fast(start, nr_pages, gup_flags, pages); } EXPORT_SYMBOL_GPL(get_user_pages_fast); 从函数解释上可以看到,函数首先是通过无锁的方式pin 应用侧的 pages,如果失败的时候不得不尝试持锁后走慢速执行路径。此时,无法持锁的时候那就要等待了,直到先前持锁的人释放锁。那之前被谁持有了呢?这时候可以利用之前介绍的Sleep 诊断方法,如下图。 UninterruptibleSleep 状态相比 Sleep 有点复杂,因为它涉及到 Linux 内部的实现。可能是内核本身的机制有问题,也有可能是应用层使用不对,因此要联合上层的行为综合诊断才行。毕竟内核也不是万能的,它也有自己的能力边界,当应用层的使用超过其边界的时候,就会出现影响性能的现象。 IOWait 常见原因与优化方法 1. 主动IO 操作 程序进行频繁、大量的读或者写 IO 操作,这是最常见的情况。 多个应用同时下发 IO 操作,导致器件的压力较大。同时执行的程序多的时候 IO 负载高的可能性也大。 器件本身的 IO 性能较差,可通过 IO Benchmark 来进行排查。 常见的原因有磁盘碎片化、器件老化、剩余空间较少(越是低端机越明显)、读放大、写放大等等。 文件系统特性,比如有些文件系统的内部操作会表现为 IO 等待。 开启 Swap 机制的内核下,数据从 Swap 中读取。 优化方法 调优 Readahead 机制 指定文件到 PageCache,即 PinFile 机制 调整 PageCache 回收策略 调优清理垃圾文件策略 2. 低内存导致的 IO 变多 内存是个非常有意思的东西,由于它的速度比磁盘快,因此 OS 设计者们把内存当做磁盘的缓存,通过它来避免了部分IO操作的请求,非常有效的提升了整体 IO 性能。有两个极端情况,当系统内存特别大的时候,绝大部分操作都可以在内存中执行,此时整体 IO 性能会非常好。当系统内存特别低,以至于没办法缓存 IO 数据的时候,几乎所有的 IO 操作都直接与器件打交道,这时候整体性能相比内存多的时候而言是非常差的。 所以系统中的内存较少的时候 IO 等待的概率也会变高。所以,这个问题就变成了如何让系统中有足够多的内存?如何调节磁盘缓存的淘汰算法? 优化方法 关键路径上减少 IO 操作 通过Readahead 机制读数据 将热点数据尽量聚集在一起,使被 Readahead 机制命中的概率高 最后一个老生常谈的,减少大量的内存分配、内存浪费等操作 系统中的内存是被各个进程所共用。当app 只考虑自己,肆无忌惮的使用计算资源,必然会影响到其他程序。这时候系统还是会回来压制你,到头来亏损的还是自己。 不过能想到这一步的开发者比较少,也不现实。明文化的执行系统约定,可能是个终极解决方案。 Non-IOWait 常见原因 低内存导致等待 → 低内存的时候要回收其他程序或者缓存上的内存。 Binder 等待 → 有大量 Binder 操作的时候出现概率较高。 各种各样的内核锁,不胜枚举。结合「诊断方法」来分析。 系统调度与 UninterruptibleSleep 耦合的问题 当线程处于 UninterruptibleSleep 非 IO等待状态(即内核锁),而持有该锁的其他线程因 CPU 调度原因,较长时间处于 Runnable 状态。这时候就出现了有意思的现象,即使被等待的线程处于高优先级,它的依赖方没有被调度器及时的识别到,即使是非常短的锁持有,也会出现较长时间的等待。 规避或者彻底解决这类问题都是件比较难的事情,不同厂家实现了不同的解决方案,也是比较考虑厂家技术能力的一个问题。 附录 Linux 线程状态释义 线程状态描述 SSLEEPING R、R+RUNNABLE DUNINTR_SLEEP TSTOPPED tDEBUG ZZOMBIE XEXIT_DEAD xTASK_DEAD KWAKE_KILL WWAKING DK DW 案例: 从 Swap 读取数据时的等待 案例: 同进程的多个线程进行 mmap 共享同一个 mm_struct 的线程同时执行 mmap() 系统调用进行 vma 分配时发生锁竞争。 mmap_write_lock_killable() 与 mmap_write_unlock() 包起来的区域就是由锁受保护的区域。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long pgoff) { unsigned long ret; struct mm_struct *mm = current->mm; unsigned long populate; LIST_HEAD(uf); ret = security_mmap_file(file, prot, flag); if (!ret) { if (mmap_write_lock_killable(mm)) return -EINTR; ret = do_mmap(file, addr, len, prot, flag, pgoff, &populate, &uf); mmap_write_unlock(mm); userfaultfd_unmap_complete(mm, &uf); if (populate) mm_populate(ret, populate); } return ret; } 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文是 Systrace 线程 CPU 运行状态分析技巧系列的第二篇,主要分析了 Systrace 中 cpu 的 Running 状态出现的原因和 Running 过长时的一些优化思路。 本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 Systrace 基础知识 - Systrace 预备知识 或者 博客文章目录 这里看到完整的目录 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Running 时间长 显示方式 Trace 中显示绿色,表示线程处于运行态 原因 1: 代码本身复杂度高,执行耗时久 这是最常见的一种方式,当然不排除平台有bug,有时候厂商在libc、syscal等高频核心函数,加了一些逻辑导致了代码运行时间长。 优化建议: 优化逻辑、算法,降低复杂度。为了进一步判断具体是哪个函数耗时,可使用 AS CPU Profiler、simpleperf,或者自己通过 Trace.begin/end() API 添加更多 tracepoint 点 当然不排除有的时候平台有bug,在关键的libc或内核函数加了一些逻辑 原因 2: 代码以解释方式执行 Trace 中看到 「Compiling」字眼时可能意味着它是解释执行方式进行。刚安装的应用(未做 odex)的程序经常会出现这种情况 优化建议: 使用 dex2oat 之后的版本试试,解释执行方式下的低性能暂无改善方法,除非执行 dex2oat 或者提高代码效率本身 除此之外,使用了编程语言的某种特性,如频繁的调用 JNI,反复性反射调用。除了通过积攒经验方式之外,通过工具解决的方法就是通过 CPU Profiler、simpleperf 等工具进行诊断 原因 3: 线程跑小核,导致执行时间长 对 CPU Bound 的操作来说跑在小核可能没法满足性能需求,因为小核的定位是处理非UX 强相关的线程。不过 Android 没办法保证这一点,有时候任务就是会安排在小核上执行。 优化建议:线程绑核、SchedBoost 等操作,让线程跑尽量跑更高算力的核上,比如大核。有时候即使迁核了也不见效,这时候要看看频率是否拉得足够高,见“原因 4” 原因 4: 线程所跑的大核运行频率太低 优化建议: 优化代码逻辑,主动降低运行负载,CPU 频率低也能流畅运行 修改调度器升频相关的参数,让 CPU 根据负载提频更激进 用平台提供的接口锁定 CPU 频率(俗称的「锁频」) 原因 5: 温升导致 CPU 关核、限频 优化建议: 手机因结构原因导致散热能力差或温升参数过于激进时,为了保护体验跟不烫伤人,几乎所有手机厂家的系统会限制 CPU 频率或者直接关核。排查思路是首先需要找到触发温升的原因。 温升的排查的第一步,首先要看是外因导致还是内因导致。外因是指是否由外部高温导致,如太阳底下,火炉边;往往夏天的时候导致手机发热的情况越严重 内因主要由 CPU、Modem、相机模组或者其他发热比较厉害的器件导致的。以 CPU 为例,如果后台某个线程吃满 CPU,那就首先要解决它。如果是前台应用负载高导致大电流消耗,同样道理,那就降低前台本身的负载。其他器件也是同样道理,首先要看是否是无意义的运行,其次是优化业务逻辑本身 除此之外,温升参数过于激进的话导致触发限频关核的概率也会提高,因此通过与竞品对比等方式调优温升参数本身来达到优化目的 原因 6: CPU 算力弱 优化建议: ARM 处理器在相同频率下不同微架构的配置导致的性能差异是非常明显的,不同运行频率、L1/L2 Cache 的容量均能影响 CPU 的 MIPS(Million Instructions Per Second) 执行结果。 优化思路有两条: 编译器参数 优化代码逻辑 第一条比较难,大部分应用开发者来说也不太现实,系统厂商如华为,方舟编译器优化 JNI 的思路本质是不改应用代码情况下提高代码执行效率来达到性能上的提升 第二条可以通过 simpleperf 等工具,找到热点代码或者观察 CPU 行为后做进一步的改善,如: Cache miss 率过高导致执行耗时,就要优化内存访问相关逻辑 代码复杂指令过多导致耗时,就要优化代码逻辑,降低代码复杂度 设计好业务缓存,尽量提高缓存命中率,避免抖动(反复地申请与释放) 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文是 Systrace 线程 CPU 运行状态分析技巧系列的第一篇,主要分析了 Systrace 中 cpu 的 runnable 状态出现的原因和 Runnable 过长时的一些优化思路。 本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 Systrace 基础知识 - Systrace 预备知识 或者 博客文章目录 这里看到完整的目录 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Runnable 状态说明 Runnable 状态在 Trace 中的显示方式 Perfetto/Systrace: 不同 CPU 运行状态异常原因 101 - Running 长 中讲解了导致 CPU 的 Running 状态耗时久的原因与优化方法,这一节介绍 Runnable 状态切换原理与对应的排查与优化思路。在 Systrace 中显示为蓝色,表示线程处于 Runnable,等待被 CPU 调度执行。 从图 2 可知,一个 CPU 核在某个时刻只能执行一个线程,因此所有待执行的任务都在一个「可执行队列」里排队,一个 CPU 核就有一个队列。能插入到这个队列里排队的,代表着这个线程除了 CPU 资源,其他资源均已获取,如 IO、锁、信号量等。处于「可执行队列」的时候,线程的状态就会被置为 RUNNABLE,也就是 Systrace 里看到的 Runnable 状态。 Linux 内核是通过赋予不同线程执行时间片并通过轮转的方式来达到同时执行多个线程的效果,因此当一个 Running 中的线程的时间片用完时(通常是 ms 级别)将此线程置为 Runnable,等待下一次被调度。也有比较特殊的情况,那就是抢占。有些高优先级的线程可以抢占当前执行的线程,而不必等到此线程的时间片到期。 当一个 CPU 有多个核的时候显然可以多个核同时工作,这时候不必都在一个 CPU 核上排队,根据负载情况(也就是排队情况),将线程迁移到其他核执行是必要的操作。掌管这些调度策略的,是通过 Linux 的调度器来实现的,它具体通过多个调度类(Schedule Class)来管理不同线程的优先级,常见的有: SCHED_RR、SCHED_FIFO: 实时调度类,整体优先级上高于 NORMAL。 SCHED_NORMAL: 普通调度类,目前常用的是 CFS(Complete Fair Scheduler)调度器。 实时类的优先级高于普通调度类,高优先级的能抢占低优先级,并且要等待高优先级的执行完才能执行低优先级的。一般情况下 Runnable 的时间都很短,但出异常的的时候它会影响关键线程的关键任务在指定时间内完成。 这个可能不止是一个线程,甚至是多个。特别是涉及到 UI 相关的任务,这种情况就更为复杂了。AOSP 体系下典型的一帧绘制是经过 UI Thread → Render Thread → SurfaceFlinger → HWC(参考 图 3),其中任何一个线程被 Runnable 阻塞导致没有在规定时间内完成渲染任务,都将会导致界面的卡顿(也就是掉帧)。 Runnable 过长的原因和优化思路 我们从实践中总结出以下 5 大门类,系统层面出异常的原因较多,但也见过应用自身逻辑导致 Runnable 过长情况。 原因 1: 优先级设置错误 应用设置了过高的优先级:至于抢占了其他线程的任务,对后者来说显得自己优先级太低了。 应用设置了过低的优先级:当此线程处于「关键链路」时,以 Runnable 执行的概率就越高,导致卡顿概率也高。 系统出 Bug 时把线程优先级设为过高或者过低。 优化思路: 应用视情况调整线程优先级,可从 Trace 中可以看到是被哪个线程抢占了。 系统将关键线程调度策略设置成 FIFO。 我们在实践中见到过不少应用因为设置错了优先级反而导致更卡。原因比较复杂,可能开发者所使用的机器用当时的优先级策略没问题,但是在别的厂商的调度器(头部大厂基本都有自己改动调度器)下就会出现水土不兼容的情况。一般情况下,三方应用开发者不建议直接调用这类 API,弄巧成拙,屡见不鲜。 长远看来更靠谱的方式是合理安排自己的任务模型,不要把对实时性要求很高的任务放到 worker 线程上。 原因 2: 绑核不合理 有时候为了让线程运行得更快,会把线程绑定到大核,在前面解决 Running 时间长时也有建议绑大核,但是绑核一定要谨慎,因为一旦把线程绑定在某个核,表示线程只能运行在这个核上即使其它核很空闲。如果多个线程都绑定在某个核,当这个核很繁忙调度不过来时,这些线程就会出现 Runnable 时间很长的情况。所以绑核一定要谨慎!下面是绑核需要注意的一些事项: 线程绑核不要绑定在单个核上,这样容错率会特别低,因为一旦这个核被其它线程抢占绑定这个核的线程就要等着,所以尽量以 CPU 簇为单位进行绑核,比如线程要绑定大核,可以指定 4-7 大核而不是指定某个一大核。 2 个大核平台尽可能减少绑定大的核线程数目,不然会使得大核很容易繁忙,把绑核会变成「负优化」。 要正确区分大小核,比如 8 个核的平台,4-7 不一定就是大核,有的平台可能 0-3 才是大核。 只能在 CPUSET 允许范围内绑核,如果 CPUSET 只允许进程跑 0-3,如果进程试图绑定在 4-7 会绑核失败,甚至会有一些意料之外的致命错误。 原因 3: 软件架构设计不合理 重申下,Runnable 是指在 CPU 核上的排队耗时,按常识可可知道排队长、频繁排队时出问题概率也就越高。一个绘制任务所依赖的线程数量越多,出问题的概率也越高,因为排队次数变多了嘛。 软件架构不止要满足业务需求,也要在性能、扩展性方面上做思考,从上面推导可知,如果你程序编程模型需要大量线程协同运行来完成关键操作,如绘制,那出问题的概率就越高。 最常见的有,两个线程之间有频繁的有通讯与等待(线程 A 把任务转移到线程 B 执行,A 等待 B 任务执行完后被唤醒), CPU 繁忙时很容易打出 Runnable 等待状态,CPU 越忙概率越高。 优化思路: 应用调整线程优先级,见「原因 1」。 优化代码架构/逻辑,免频繁等待其他线程的唤醒,在 Trace 中可以看到线程的依赖关系。可借助 CPU Profiler 探查代码执行逻辑,提高分析唤醒关系的效率。 平台通过修改调度器来识别有关系链的线程组,优先调度这个组里的线程。 原因 4: 应用自己或系统整体负载高导致排队的任务非常多 从上述的调度原理可知,如果大量任务挤在一个核的「可执行队列」上,显然越是后面,优先级越低的任务排队时间就越长。 排查的时候你可以在 Perfetto/Systrace 的 CPU 核维度任务上,即使在放大后的界面看到排满了密密麻麻的任务,这基本上就意味着系统整体负载较高了。通过计算,可算出 CPU 每时刻的使用量,基本上都会在 90%以上。 你可以通过选择一个区间,以时间来排序,看看都在执行什么任务,以此来逐个排查同时执行大量程序的原因是什么。 简单总结就是,同时执行的任务太多了,主要原因来自两方面: 1.应用自身高占用 应用自身就把 CPU 资源都给占满了,狂开十来个线程来做事情,即使是头部大厂也会做这种事。 优化建议: 找出应用所有占用高的线程,看看各线程此刻跑起来的行为是否异常,如果异常则要优化它。 优化线程负载本身,可使用 simpleperf 等工具进行函数级别的定位。 调整优先级,使用比 CFS 更高优先级的调度器,如设置为 RT。不过它带来的隐患也较多,需要慎重。 优化软件架构,区分关键与非关键线程,通过合理设置「绑核 & 优先级」来为关键线程让出资源。 如,不重要线程绑到小核运行或设置低优先级、渲染相关线程设置高优先级等,让渲染线程相关的线程能占用到更多的 CPU 资源。设计架构的时候一定要考虑运行环境恶劣的情况,因为安卓从设计上就不敢保证所有资源都优先供给你,肯定有别人跟你抢资源。 2.系统服务高占用 有的厂商 ROM 自己本身就有很多任务,设计不合理的话自己家程序就吃满了大量资源,导致留给应用运行的资源较少。还有些是管控措施设计的一般,以至于留给了大量流氓应用可乘之机,各路神仙利用自己的「黑科技」在后台保活后进行各种拉活同步操作。 3.平台厂家的黑科技 厂家除了要优化自身服务,以做到「点到为止」外,可以实现如下功能来尽可能把资源分配合理化,让出更多资源给前台应用。 通过 CGROUP 的 CPUSET 子系统,让不同优先级的线程运行在不同的 CPU 核心。AOSP 自带了 CPUSET 分组功能,不过有些缺陷如: 分组不够精细,很多后台都可以跑满所有核 没有考虑进程的工作状态,如 音乐、导航、录音、视频、通话、下载 对 Java 进程 fork 的子进程放任不管 通过 CGROUP 的 CPUCTL 子系统,进行资源配额,如限制异常进程、普通后台进程的不同量级的 CPU 最高使用量。 通过线程&进程级别的冻结技术,在应用退出后台之后冻结进程让其拿不到 CPU 资源,类似 iOS 的做法。难点在于: 切断和恢复各跨进程通信 进程关系的梳理 兼容性问题,需要有大量的测试验证 按需启动系统进程与管控好后台进程自启动。 每一个优化说简单也简单,说难也难,依赖厂家的技术积累。 原因 5: CPU 算力限制、锁频、锁核、状态异常 排队做核酸检测一样,检测窗口多的队列排队时间少。CPU 算力差、关核、限频,导致 Runnable 的概率也更高。通常的原因有: 场景控制 不同场景模式下的不同频率、核心策略 高温下的锁频锁核 CPU 省电模式:如高通的 Low Power Mode。 CPU 状态切换:如 C2/C1 切换到 C0 耗时久。 CPU 损坏,概率小但也有可能会出现。 低端机 :安卓上的低端机。 其中: 原因 1 场景控制, 考验厂家的能力与各自的标准,应用程序能做的还是那句名言 → 降低自己负载,少惹平台。 厂家为了设计好「场景控制」,需要有精细化的场景识别与合理的控制能力,将功耗与性能的平衡做到全局最优化,不同场景下应突出不同的业务能力,而不是一杆子拍死。 高温下的优化建议请参考「Perfetto/Systrace: 不同 CPU 运行状态异常原因 101 - Running 长」中的「原因 5: 温升导致 CPU 关核、限频」。 原因 3 CPU 状态切换 是芯片固有的特性,出现的概率小,但也不是不可能,每个芯片架构升级换代的时候就时不时遇到「妥协」版的 CPU 产品。厂家对芯片的评估是个比较隐性的能力,很少会被大众提及,但是非常重要的一个能力。电子消费品历史中,也总是重演关键器件选错了,导致厂家走入万劫不复境地的真实案例。 原因 5,安卓上的低端机,真的就指配备里低算力的 CPU,这与苹果的做法不一样,它的 CPU 至少跟当期旗舰是一样的。同样参考 「Perfetto/Systrace: 不同 CPU 运行状态异常原因 101 - Running 长」中的「原因 6: 算力弱」。 原因 6: 调度器异常 几乎所有的厂家都做了调度器优化方面的工作,虽然概率小,但也有可能会出异常。场景锁频锁核机制有问题、内核各种 governor 的出问题的时候,会出现明明 CPU 的其他核都很闲,但任务都挤在某几个核上。 系统开发者能做的就是把基础「可观测性技术」建好,出问题时可以快速诊断,因为这类问题一是不好复现,二是现象出现时机较短,可能立马就恢复了。 原因 7: 处理器区分执行 32 位与 64 位进程 有些过渡期的芯片,如最近推出的骁龙 8Gen1 与 天玑 9000,会有非常奇葩的运行限制。32 位的程序只能运行某个特定微架构上,64 位的则畅通无阻。且先不说这种「脑残设计」是处于什么所谓「平衡」,他带来的问题是,当你用的应用大量还是 32 位的时候,很多任务(以进程为单位)都挤在某个核心上运行,结合前面的理论,都挤在一起,出现 Runnable 的概率就更高。 对应用开发者,建议尽快升级至 64 位程序。如果你用的是第三方方案,尽早通知改进或者改用其他方案。 对系统开发者,一是根据问题联系应用厂商做更新,二是特殊加强后台管理功能,进一步降低 32 位程序的运行负载。 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
In his book Hackers & Painters, Paul Graham asserted, “The disparity in the efficiency of languages is becoming more pronounced, hence the rising importance of profilers. Currently, performance analysis isn’t given the attention it deserves. Many still seem to hold onto the belief that the key to accelerating program execution lies in developing compilers that generate faster code. As the gap between code efficiency and machine performance widens, it will become increasingly apparent that enhancing the execution speed of application software hinges on having a good profiler to guide program development.” by Paul Graham, Hackers & Painters A Google search for “Android optimization tools” yields an abundance of related content. The issue with these results is that they either contain highly repetitive content or directly explain usage methods. Rarely do they introduce a holistic architecture, inadvertently instilling a misguided belief of “one tool fixes all”. Drawing from the extensive experience of my team, I can assert that in the realm of performance analysis, no such magic bullet tool exists. Tools evolve, old problems re-emerge in new forms, and without mastering core logic, one remains on the technological surface. This article first systematically untangles the observability technology in performance analysis, encompassing data types, capture methods, and analysis techniques. Subsequently, we introduce the “big three” analysis tools provided by Google. The aim is to impart immutable theoretical knowledge and corresponding tools available in the Android environment to the reader. This wealth of information can facilitate a more direct application of predecessors’ experiences, circumventing unnecessary detours. It’s crucial to note that there are certainly more than these three tools available for performance optimization. However, these three are our “go-to first-hand tools”. Prior to delving into further analysis, you’ll find yourself dependent on these three tools for bottleneck identification. Subsequent analyses, tailored to distinct domain characteristics, should then leverage corresponding tools. 1 Observability Techniques in Performance Analysis Has this operation been executed? How long did it take? Why is there a significant difference between two versions? What operations is the system executing when CPU usage is high? Why has the startup speed slowed down? Why does this page always stutter when scrolling? You’ve likely been asked similar questions by colleagues or bosses more than once. The most primitive idea might be to obtain the relevant logs and analyze them one by one. Based on past experience, one would search for clues by looking for keywords. If the desired information is not available, the next step is to add logs and attempt to reproduce the issue locally. This approach is not only time-consuming and laborious, but also wastes developmental resources. Have you ever wondered if there is a more efficient method in the industry? A method that can improve efficiency by an order of magnitude, allowing us to spend our time solving problems instead of on mundane, repetitive physical tasks? Of course, there is (otherwise this article wouldn’t exist)—we refer to it as observability techniques. As the computer industry has evolved, pioneers in computing have devised a category known as “observability techniques.” It involves utilizing tools to observe the intricate details of complex systems’ operations—the more detailed, the better. Mobile operating systems evolved from embedded systems. Nowadays, the computing power of mid-to-high-end Android phones can catch up with a mainframe from two decades ago, and the resulting software complexity is also immense. Employing a well-designed and smoothly operating observability technique can significantly accelerate software development efficiency. This is because, despite using a variety of preemptive static code detection and manual code reviews, it is impossible to block software issues 100%. Problems only become apparent after the software is run in a real environment, which might be an automated test case of yours. Even then, you still need to sift through your logs and re-read code to identify the problem. For these reasons, every engineering team needs a fully functional observability tool as one of their fundamental infrastructures. Observability is a systematic engineering effort that allows you to delve deeper into occurrences within the software. It can be used to understand the internal operational processes of software systems (especially complex business logic or interactive systems), troubleshoot, and even optimize the program by identifying bottlenecks. For complex systems, understanding the entire operational process through code reading can be challenging. A more efficient approach is to utilize observability tools to obtain the software’s operational status most intuitively. We will explore data types, data acquisition methods, and analysis methods to help you understand observability techniques in the sections below. 1.1 Data Types Logs can be in the form of key-value pairs, JSON, CSV, relational databases, or any other formats. We recreate the entire state of the system at the time it was running through logs to solve a specific issue, observe the operation of a module, or even depict the behavioral patterns of system users. In observability technology, log types are classified into Log, Metric, and Trace types. Log Type Logs are the most rudimentary form of data recording, typically noting what happened at what time in which module, whether the log level is a warning or an error. Nearly all systems, whether embedded devices or computers in cars, utilize this form of log. It is the simplest, most direct, and easiest to implement. Almost all Log types are stored as strings, presenting data in lines of text. Logs are the most basic type, and through conversion, can be turned into Metric or Trace types, though the conversion process can become a bottleneck when dealing with massive amounts of data. Different log types are usually distinguished by error, warning, and debug levels. Naturally, error logs are your primary concern. However, in practice, this classification is not always strict, as many engineers do not differentiate between them, possibly due to a lack of classification analysis for different log levels in their engineering development environment. In summary, you can grade Log types according to your objectives. It acts like an index, enhancing the efficiency of problem analysis and target information location. Metric Type Metric types are more focused compared to Log types, recording numerical changes in a particular dimension. Key points are the “dimension” and “numerical change.” Dimensions could be CPU usage, CPU Cluster operation frequency, or context switch counts. Numerical changes can be instant values at the time of sampling (snapshot type), the difference from the previous sampling, or aggregated statistical values over a period. Statistics are often used in practice, such as when wanting to observe the average CPU usage five minutes before an issue occurred. In this case, an arithmetic mean or weighted average calculation of all values within these five minutes is required. Aggregation is a useful tool because it’s not possible for a person to analyze all Metric values individually. Determining the existence of a problem through aggregation before conducting detailed analysis is a more economical and efficient method. Another benefit of the Metric type is its fixed content format, allowing data storage through pre-encoding, utilizing space more compactly and occupying less disk space. The most straightforward application is data format storage; Metric types, using integers or floating numbers of fixed byte data, are more space-efficient than Log types, which generally use ASCII encoding. In addition to specific values, enumeration values can also be stored (to some extent, their essence is numerical). Different enumeration values represent different meanings, possibly indicating on and off statuses, or different event types. Trace Type Trace types indicate the time, name, and duration of an event. Relationships among multiple events identify parent-child or sibling connections. Trace types are the most convenient data analysis method when dissecting complex call relationships across multiple threads. Trace types are particularly suitable for Android application and system-level analysis scenarios because they can diagnose: Function call chains Binder call chains during invocation Cross-process event stream tracing In the design of Android’s application running environment, an application can’t perform all functionalities independently; it requires extensive interaction with the SystemServer. Communication with the SystemServer is facilitated through Binder, a communication method detailed later in this article. For now, understand that it involves cross-process calling. Accurate restoration of call relationships requires data from both ends, making Trace the optimal information recording method. You can manually add starting and ending points for Trace types and insert multiple intervals within a function. With pre-compilation technology or language features, Trace intervals can automatically be instrumented at the beginning and end of functions. In an ideal scenario, the latter is the best approach as it allows for understanding what functions are running in the system, their execution conditions, and call relationships. This information can identify the most frequently called (hottest) functions and the most time-consuming ones. Understandably, this method incurs a significant performance loss due to the frequency and magnitude of function calls, especially in complex systems. An alternative approach involves approximating the above effect by sampling call stacks. Shorter sampling intervals more closely approximate real call relationships and durations, but they can’t be too short, as obtaining stack operations itself becomes a load due to increased frequency. This method, known as a Profiler in the industry, is the basis for most programming language Profiler tools. 1.2 Data Acquisition Methods Static Code and Dynamic Tracing Static code collection is the most primitive method. It’s straightforward to implement but requires recompiling and reinstalling the program each time new content is added. If the information you need to diagnose a problem isn’t available, you have no choice but to repeat the entire process. A more advanced approach is to pre-install data collection points at all potential areas of interest, and use dynamic switches to control their output. This technique balances performance impacts and allows dynamic enabling of logs as needed, albeit at a high cost. Dynamic tracing technology has always been available but is often considered the “holy grail” in the debugging and tracing field due to its steep learning curve. It demands a deep understanding of low-level technologies, especially in areas like compilation, ELF format, the kernel, and programming languages associated with pre-set probes and dynamic tracing. Indeed, dynamic tracing even has its own set of programming languages to cater to the dynamic implementation needs of developers. This approach balances performance and flexibility and enables dynamic retrieval of desired information even in live versions. In Android application development and system-level development, dynamic tracing is rarely used and is occasionally employed in kernel development. Typically, only specialized performance analysts might utilize these tools. Two critical elements of dynamic tracing are probes and dynamic languages. The program’s execution permission must be handed over to the dynamic tracing framework at specific probe points during runtime. The logic executed by the framework is written by developers using dynamic languages. Therefore, your program must first have probes. Linux kernel and other frameworks have embedded corresponding probe points, but Android application layers lack these. Currently, dynamic frameworks like eBPF on Android are mainly used by kernel developers. Unconditional and Conditional Capture Unconditional capture is straightforward: data is continuously captured after triggering, regardless of any conditions. The drawback is that when the observed object generates a large volume of data, it could significantly impact the system. In such cases, reducing the volume of data captured can mitigate the impact, striking a balance between meeting requirements and minimizing performance loss. Conditional capture is often employed in scenarios where anomalies can be identified. For instance, capturing logs is triggered when a specific observed value exceeds a pre-set threshold and continues for a certain duration or until another threshold is reached. This method is a slight improvement over unconditional capture as it only impacts the system when an issue arises, leaving it unaffected at other times. However, it requires the capability to identify anomalies, and those anomalies should not necessitate historical data preceding the occurrence. Lowering the threshold can increase the probability of triggering data capture, leading to the same issues faced with unconditional capture, and requiring a balance of performance loss. Disk Write Strategy Continuous disk writing involves storing all data captured during the entire data capture process, which can strain storage resources. If the trigger point, such as an anomaly, can be identified, selective disk writing becomes an option. To ensure the validity of historical data, logs are temporarily stored in a RingBuffer and only written to disk upon receiving a disk write command. This method balances performance and storage pressure but at the cost of runtime memory consumption and the accuracy of the trigger. 1.3 Analysis Methods Data Visualization Analysis As the complexity of problem analysis increases, especially with the need to address performance issues arising from the interactions among multiple modules, data visualization analysis methods have emerged. These methods visualize events on respective lanes with time as the horizontal axis, facilitating a clear understanding of when specific events occur and their interactions with other systems. In Android, tools like Systrace/Perfetto and, earlier, KernelShark, are fundamentally of this type. The “Trace Type” mentioned in “Data Types” often employs this kind of visualization. Systrace’s visualization framework is built on a Chrome subproject called Catapult. The Trace Event Format outlines the data formats supported by Catapult. If you have Trace type data, you can use this framework for data visualization. AOSP build systems and the Android app compilation process also output corresponding Trace files, with visualization effects based on Catapult. Database Analysis For extensive data analysis, formatting data and converting it into two-dimensional data tables enables efficient query operations using SQL. In the server domain, technology stacks like ELK offer flexible formatted search and statistical functions. With databases and Python, you can even create an automated data diagnostic toolchain. From the discussion above, it’s evident that text analysis and database analysis serve different analytical purposes. Text analysis is sufficient for evaluating the time consumption of a single module, visualization tools are needed for interactions among multiple systems, and SQL tools are required for complex database analysis. Regardless of the analysis method, the core is data analysis. In practice, we often convert data using other tools to support different analysis methods, such as transitioning from text analysis to database analysis. Choosing the right analysis method according to your objectives can make your work highly efficient. For Android developers, Google provides several essential performance analysis tools to assist both system and app developers in optimizing their programs. 2 Google’s Android Performance Analysis Tools Based on practical experience, the most commonly used tools are Systrace, Perfetto, and the Profiler tool in Android Studio. Only after identifying the main bottlenecks using these tools would you need to resort to other domain-specific tools. Therefore, we will focus on the application scenarios, advantages, and basic usage of these three tools. For a horizontal comparison between the tools, please refer to the content in the next chapter, “Comprehensive Comparison.” 2.1 First Generation System Performance Analysis Tool - Systrace Systrace is a visualization analysis tool for the Trace type and represents the first generation of system-level performance analysis tools. It supports all the features facilitated by the Trace type. Before the emergence of Perfetto, Systrace was essentially the only performance analysis tool available. It presents the operating information of both the Android system and apps graphically. Compared to logs, Systrace’s graphical representation is more intuitive; and compared to TraceView, the performance overhead of capturing Systrace can be virtually ignored, minimizing the impact of the observer effect to the greatest extent. Systrace Design Philosophy Systrace embeds information similar to logs, known as TracePoints (essentially Ftrace information), at key system operations (such as Touch operations, Power button actions, sliding operations, etc.), system mechanisms (including input distribution, View drawing, inter-process communication, process management mechanisms, etc.), and software and hardware information (covering CPU frequency information, CPU scheduling information, disk information, memory information, etc.). These TracePoints depict the execution time of core operation processes and the values of certain variables. The Android system collects these TracePoints scattered across various processes and writes them into a file. After exporting this file, Systrace analyzes the information from these TracePoints to obtain the system’s operational information over a specific period. In the Android system, some essential modules have default inserted TracePoints, classified by TraceTag, with information sources as follows: TracePoints in the Framework Java layer are implemented through the android.os.Trace class. TracePoints in the Framework Native layer are executed using the ATrace macro. App developers can customize Trace through the android.os.Trace class. Consequently, Systrace can collect and display all information from both upper and lower layers of Android. For Android developers, Systrace’s most significant benefit is turning the entire Android system’s operational status from a black box into a white box. Its global nature and visualization make Systrace the first choice for Android developers when analyzing complex performance issues. Practical Applications The parsed Systrace, rich in system information, is naturally suited for analyzing the performance issues of both Android Apps and the Android system. Android app developers, system developers, and kernel developers can all use Systrace to diagnose performance problems. From a Technical Perspective: Systrace can cover major categories involved in performance, such as response speed, frame drops or janks, and ANR (Application Not Responding) issues. From a User Perspective: Systrace can analyze various performance issues encountered by users, including but not limited to: Application Launch Speed Issues: Including cold start, warm start, and hot start. Slow Interface Transitions: Including slow transitions and janky animations. Slow Non-Transition Click Operations: Such as toggles, pop-ups, long presses, selections, etc. Slow Screen Brightness Adjustment Speed: Including slow on/off speed, slow unlocking, slow face recognition, etc. List Scrolling Jankiness: Window Animation Lag: Interface Loading Jankiness: Overall System Lag: App Unresponsiveness: Including freeze and crash issues. When encountering the above problems, various methods can be employed to capture Systrace. The parsed file can then be opened in Chrome for analysis. The ability to trace and visualize these issues makes Systrace an invaluable tool for developers aiming to optimize the performance of Android applications and the system itself. By analyzing the data collected, developers can identify bottlenecks and problematic areas, formulate solutions, and effectively improve the performance and responsiveness of apps and the Android operating system. 2.2 The Next-Generation Performance Analysis Full Stack Tool - Perfetto Google initiated the first submission in 2017, and over the next four years (up until Dec 2021), over 100 developers made close to 37,000 commits. There are PRs and merges almost daily, marking it as an exceptionally active project. Besides its powerful features, its ambition is significant. The official website claims it to be the next-generation cross-platform tool for Trace/Metric data capture and analysis. Its application is also quite extensive; apart from the Perfetto website, Windows Performance Tool, Android Studio, and Huawei’s GraphicProfiler also support the visualization and analysis of Perfetto data. We believe Google will continue investing resources in the Perfetto project. It is poised to be the next-generation performance analysis tool, wholly replacing Systrace. Highlighted Features The most significant improvement of Perfetto over Systrace is its ability to support long-duration data capture. This is made possible by a service that runs in the background, enabling the encoding of collected data using Protobuf and saving it to disk. From the perspective of data sourcing, the core principle is consistent with Systrace, both based on the Linux kernel’s Ftrace mechanism for recording key events in both user and kernel spaces (ATRACE, CPU scheduling). Perfetto supports all functionalities provided by Systrace, hence the anticipation of Systrace being replaced by Perfetto entirely. Perfetto’s support for data types, acquisition methods, and analysis approaches is unprecedentedly comprehensive. It supports virtually all types and methods. ATRACE enables the support for Trace type, a customizable node reading mechanism supports Metric type, and in UserDebug versions, Log type support is realized by obtaining Logd data. You can manually trigger capture and termination via the Perfetto.dev webpage or command-line tools, initiate long-duration capture via the developer options in the settings, or dynamically start data capture via the Perfetto Trigger API integrated within the framework. This covers all scenarios one might encounter in a project. In terms of data analysis, Perfetto offers a data visualization analysis webpage similar to Systrace, but with an entirely different underlying implementation. The biggest advantage is its ability to render ultra-large files, a feat Systrace cannot achieve (it might crash or become extremely laggy with files over 300M). On this visualization webpage, one can view various processed data, execute SQL query commands, and even view logcat content. Perfetto Trace files can be converted into SQLite-based database files, enabling on-the-spot SQL execution or running pre-written SQL scripts. You can even import it into data science tool stacks like Jupyter to share your analysis strategies with colleagues. For example, if you want to calculate the total CPU consumption of the SurfaceFlinger thread, or identify which threads are running on large cores, etc., you can collaborate with domain experts to translate their experiences into SQL commands. If that still does not meet your requirements, Perfetto also offers a Python API, converting data into DataFrame format, enabling virtually any desired data analysis effect. With all these offerings, developers have abundant aspects to explore. From our team’s practical experience, it can almost cover every aspect from feature development, function testing, CI/CD, to online monitoring and expert systems. In the subsequent series of articles on our planet, we will focus on Perfetto’s powerful features and the expert systems developed based on it, aiding you in pinpointing performance bottlenecks with a single click. Practical Application Perfetto has become the primary tool used in performance analysis, with Systrace’s usage dwindling. Hence, the tool you should master first is Perfetto, learning its usage and the metrics it provides. However, Perfetto has its boundaries. Although it offers high flexibility, it essentially remains a static data collector and not a dynamic tracing tool, fundamentally different from eBPF. The runtime cost is relatively high because it involves converting Ftrace data to Perfetto data on the mobile device. Lastly, it doesn’t offer text analysis methods; additional analyses can only be performed via webpage visualization or operating SQLite. In summary, Perfetto is powerful, covering almost every aspect of observability technology, but also has a relatively high learning curve. The knowledge points worth exploring and learning are plentiful, and we will focus on this part in our upcoming articles. 2.3 Android Studio Profiler Tool The integrated development environment for Android application development (officially recommended) is Android Studio (previously it was Eclipse, but that has been phased out). It naturally needs to integrate development and performance optimization. Fortunately, with the iterations and evolution of Android Studio, it now has its own performance analysis tool, Android Profiler. This is a collective tool integrating several performance analysis utilities, allowing developers to optimize performance without downloading additional tools while developing applications in Android Studio. Currently, Android Studio Profiler has integrated four types of performance analysis tools: CPU, Memory, Network, and Battery. The CPU-related performance analysis tool is the CPU Profiler, the star of this chapter. It integrates all CPU-related performance analysis tools, allowing developers to choose based on their needs. Many people might know that Google has developed some independent CPU performance analysis tools, like Perfetto, Simpleperf, and Java Method Trace. CPU Profiler does not reinvent the wheel; it gathers data from these known tools and parses it into a desired style, presenting it through a unified interface. Highlighted Features CPU Profiler integrates performance analysis tools: Perfetto, Simpleperf, and Java Method Trace. It naturally possesses all or part of the functionalities of these tools, such as: System Trace Recording: Information captured with Perfetto, useful for analyzing process function duration, scheduling, rendering, etc. However, it’s a simplified version, only displaying process-strongly related information and filtering out short-duration events. It’s recommended to export the Trace file for analysis on https://ui.perfetto.dev/. Java Method Trace Recording: It gathers function call stack information from the virtual machine, used for analyzing Java function calls and duration. C/C++ Function Trace: Information captured with Simpleperf. Simpleperf gathers data from the CPU’s performance monitoring unit (PMU) hardware component. C/C++ Method Trace has only partial functionalities of Simpleperf, used for analyzing C/C++ function calls and durations. Practical Application Application performance issues are mainly divided into two categories: slow response and lack of smoothness. Slow response issues include slow app startup, slow page transitions, slow list loading, slow button responses, etc. Lack of smoothness issues include unsmooth list scrolling, page sliding not following hand movements, animation judders, etc. How to use CPU Profiler in these scenarios? The basic approach is to capture a System Trace first, analyze and locate the issue with System Trace. If the issue can’t be pinpointed, further analysis and location should be done with Java Method Trace or C/C++ Function Trace. Taking an extremely poor-performing application as an example, suppose Systrace TracePoint is inserted at the system’s critical positions and the code is unfamiliar. How do you identify the performance bottleneck? First, run the application and record a System Trace with CPU Profiler (the tool usage will be introduced in later articles), as shown below: From the above Trace, it’s evident that the onDrawFrame operation in the egl_core thread is time-consuming. If the issue isn’t apparent, it’s advised to export it to https://ui.perfetto.dev/ for further analysis. By looking into the source code, we find that onDrawFrame is the duration of the Java function onDrawFrame. To analyze the duration of the Java function, we need to record a Java Method Trace, as follows: From the above Trace, it’s easy to see that a native function called Utils.onDraw is time-consuming. Because it involves C/C++ code, another C/C++ Function Trace needs to be recorded for further analysis, as shown below: It becomes clear that the code executed a sleep function within the native Java_com_gl_shader_Utils_onDraw, pinpointing the culprit for the poor performance! The greatest advantage of CPU Profiler in AS is the integration of various sub-tools, enabling all operations in one place. It’s incredibly convenient for application developers. However, system developers might not be so lucky. 2.4 Comparative Analysis Tool NameApplication ScenarioData TypeData Acquisition MethodAnalysis Method SystraceAndroid System & App Performance AnalysisTrace TypeUnconditional Capture, Continuous LoggingVisual Analysis PerfettoAndroid System & App Performance AnalysisMetric Type, Trace TypeUnconditional Capture, Continuous LoggingVisual Analysis, Database Analysis AS ProfilerAndroid System & App Performance AnalysisTrace TypeUnconditional Capture, Continuous LoggingVisual Analysis SimplePerfJava/C++ Function Execution Time Analysis, PMU CountersTrace TypeUnconditional Capture, Continuous LoggingVisual Analysis, Text Analysis Snapdragon Profiler Tools & ResourcesPrimarily for Qualcomm GPU Performance AnalyzerTrace Type, Metric TypeUnconditional Capture, Continuous LoggingVisual Analysis Mali Graphics DebuggerARM GPU Analyzer (for MTK, Kirin chips)Trace Type, Metric TypeUnconditional Capture, Continuous LoggingVisual Analysis Android Log/dumpsysComprehensive AnalysisLog TypeConditional Capture, Continuous Capture but not LoggingText Analysis AGI (Android GPU Inspector)Android GPU AnalyzerTrace Type, Metric TypeUnconditional Capture, Continuous LoggingVisual Analysis eBPFDynamic Tracing of Linux Kernel BehaviorMetric TypeDynamic Tracing, Conditional Capture, Continuous Capture but not LoggingText Analysis FTraceLinux Kernel TracingLog TypeStatic Code, Conditional Capture, Continuous Capture but not LoggingText Analysis 3 On “Instruments, Techniques, Philosophy” Technical revolutions and improvements are often reflected at the “instruments” level. The development direction of tools by the Linux community and Google is towards enhancing the integration of tools so that necessary information can be easily found in one place, or towards the collection of more information. In summary, the development trajectory at the instruments level is traceable and developmental rules can be summarized. We need to accurately understand their capabilities and application scenarios during rapid iterations of tools, aiming to improve problem-solving efficiency rather than spending time learning new tools. The “techniques” level depends on specific business knowledge, understanding how a frame is rendered, how the CPU selects processes for scheduling, how IO is dispatched, etc. Only with an understanding of business knowledge can one choose the right tools and correctly interpret the information provided by these tools. With rich experience, sometimes you can spot clues even without looking at the detailed information provided by tools. This is a capability that arises when your business knowledge is enriched to a certain extent, and your brain forms complex associative information, elevating you above the tools. At the “philosophy” level, considerations are about the nature of the problem that needs to be solved. What is the essence of the problem? What extent should be achieved, and what cost should be incurred to achieve what effect? For solving a problem, which path has the highest “input-output ratio”? What is the overall strategy? To accomplish something, what should be done first and what should be done next, and what is the logical dependency relationship? In subsequent articles, explanations will be provided in the “instruments, techniques, philosophy” manner for a technology or a feature. We aim not only to let you learn a knowledge point but also to stimulate your ability to extrapolate. When faced with similar tools or problems, or even completely different systems, you can handle them with ease. Firmly grasping the essence, you can choose the appropriate tools or information through evaluating the “input-output ratio” and solve problems efficiently. About Me && Blog About Me: I am eager to interact and progress together with everyone. Follow me on Twitter Blog Content Navigation Record of Excellent Blog Articles - Essential Skills and Tools for Android Performance Optimization An individual can move faster, a group can go further.
Paul Graham 在其著作 中断言:“不同语言的执行效率差距正变得越来越大,所以性能分析器(profiler)将变得越来越重要。目前,性能分析并没有受到重视。许多人好像仍然相信,程序运行速度提升的关键在于开发出能够生成更快速代码的编译器。代码效率与机器性能的差距正在不断加大,我们将会越来越清楚地看到,应用软件运行速度提升的关键在于有一个好的性能分析器帮助指导程序开发。” by Paul Graham 黑客与画家 谷歌搜索 「Android 优化工具」,你会找到很多与此相关的内容。他们的问题在于要么是内容高度重复、要么是直接讲使用方法,很少会给你介绍整体性的架构,一不小心就会让人会种「一个工具搞定一切」的错误认知。以笔者团队的多年经验来看,在性能分析领域这种银弹级别的工具是不存在的。工具在发展,老问题会以新的方式变样出现,不掌握核心逻辑的话始终会让你浮于技术的表面。 本文首先系统性的梳理性能分析中的可观测性技术,它涵盖数据类型、抓取方法以及分析方法等三部分内容,之后是介绍谷歌提供的「三大件」分析工具。目的是想让你了解不变的理论性的知识,以及与之对应的在安卓环境中可用的工具,这些可以让你少走一些弯路,直接复用前辈们的经验。 需要特别说明的是,对于性能优化肯定不止有这三个工具可用,但这个三个工具是我们平时用到的「第一手工具」。进行进一步分析之前,你都需要依赖这三个工具进行瓶颈定位,之后才应不同领域特性选择对应的工具进行下钻分析。 1 性能分析中的可观测性技术 这个操作到底有没有被执行?执行时间有多长? 为什么两个版本的前后差异这么大? 当 CPU 使用量变高的时候系统都在执行什么操作? 为什么启动速度变慢了? 为什么这个页面滑动总是会卡一下? 相信你不止一次被同事、被老板问到过类似的问题。最原始的想法应该是,首先是拿到相关的日志进行逐个分析。根据以往经验,通过查找关键字寻找蛛丝马迹。如果没有想看的信息,那就加上日志尝试本地复现。费时费力不说,也还费研发资源。但你有没有想过行业里有没有更高效的方法?可以提高一个数量级的那种,把我们的时间花在问题解决上而不是无聊的重复性体力活儿上? 答案当然是有的(否则就不会有这篇文章了),我们称他为可观测性技术。 计算机行业发展至今,计算机前辈们捣鼓出了所谓的「可观测性技术」的类别。它研究的是通过工具,来观测复杂系统的运行细节,内容越细越好。 移动操作系统之前是由嵌入式发展而来的,现在的中高端安卓手机算力都能赶得上二十几年前的一个主机的算力,在此算力基础上所带来的软件复杂度也是非常巨大的。 如果你的程序部署了一个精心设计且运行良好的可观测性技术,可以大大加快研发软件的效率,因为即使我们使用了各种各样的前置性静态代码检测、人工代码审查,也无法 100% 拦截软件的问题。只有在真实环境里运行之后才知道是否真正发生了问题,即使这个环境可能是一个你的自动化测试用例。即使这样,你还需要翻阅你的日志,重读代码来找出问题。出于这些原因,每个工程团队都需要有一个功能完备的可观测性工具作为他们的基础设施之一。 可观测性技术是一个系统性工程,它能够让你更深入的了解软件里发生的事情。可用于了解软件系统内部运行过程(特别是对于业务逻辑或者交互关系复杂的系统)、排查问题甚至通过寻找瓶颈点优化程序本身。对于复杂的系统来说,你通过阅读代码来了解整个运行过程其实是很困难的事情,更高效的方法就是借助此类工具,以最直观的的方式获取软件运行的状态。 下面将从 数据类型、数据获取方法、分析方法 这三个主题来帮助你了解可观测性技术。 1.1 数据类型 日志的形式可能是键值对(key=Value),JSON、CSV,关系型数据库或者其他任何格式。其次我们通过日志还原出系统当时运行的整个状态,目的是为了解决某个问题,观察某个模块的运行方式,甚至刻画系统使用者的行为模式。在可观测性技术上把日志类型分类为 Log 类型、Metric 类型,以及 Trace 类型。 Log 类型 Log 是最朴素的数据记录方式,一般记录了什么模块在几点发生了什么事情,日志等级是警告还是错误。 绝大部分系统,不管是嵌入式设备还是汽车上的计算机,他们所使用的日志形式几乎都是这种形式。这是最简单,最直接也最好实现的一种方式。几乎所有的 Log 类型是通过 string 类型的方式存储,数据呈现形式是一条一条的文本数据。Log 是最基本的类型,因此通过转换,可以将 Log 类型转换成 Metric 或者 Trace 类型,当然成本就是转换的过程,当数据量非常巨大的时候这可能会成为瓶颈。 为了标识出不同的日志类型等级,一般使用错误、警告、调试等级别来划分日志等级。显然,错误类型的是你首要关注的日志等级。不过实践中也不会严格按照这种方式划分,因为很多工程师不会严格区分他们之间的差异,这可能是他们的工程开发环境中不太会对不同等级的日志进行分类分析有关。总之,你可以根据你的目的,将 Log 类型进行等级划分,它就像一个索引一样,可以进一步可以提高分析问题、定位目标信息的效率。 Metric 类型 Metric 类型相比 Log 类型使用目的上更为聚焦,它记录的是某个维度上数值的变化。知识点是「维度」与「数值」的变化。维度可能是 CPU 使用率、CPU Cluster 运行频率,或者上下文切换次数。数值变化既可以是采样时候的瞬时值(成为快照型)、与前一次采样时的差值(增或减)、或者某个时段区间的统计聚合值。实践中经常会使用统计值,比如我想看问题发生时刻前 5 分钟的 CPU 平均使用量。这时候需要将这五分钟内的所有数值做算数平均计算,或者是加权平均(如: 离案发点越近的样本它的权重就越高)。Log 类型当然可以实现 Metric 类型的效果,但是操作起来非常麻烦而且其性能损耗可能也不小。 聚合是非常有用的工具,因为人不可能逐个分析所有的 Metric 值,因此借助聚合的方式判断是否出了问题之后再进行详细的分析是更为经济高效的方法。 Metric 类型的另外一个好处是它的内容格式是比较固定的,因此可以通过预编码的方式进行数据存储,空间的利用率会更紧凑进而占用的磁盘空间就更少。最简单的应用就是数据格式的存储上,如果使用 Log 类型,一般采用的是 ASCII 编码,而 Metric 使用的是整数或者浮点等固定 byte 数的数据,当存储较大数值时显然 ASCII 编码需要的字节数会多于数字型数据,并且在进行数据处理的时候你可以直接使用 Metric 数据,而不需要把 Log 的 ASCII 转换成数字型后再做转换。 除了是具体的数值之外,也可以存储枚举值(某种程度上它的本质就是数值)。不同的枚举值代表不同的意义,可能是开和关、可能是不同的事件类型。 Trace 类型 Trace 类型标识了事件发生的时间、名称、耗时。多个事件通过关系,标识出了是父与子还是兄弟。当分析多个线程间复杂的调用关系时 Trace 类型是最方便的数据分析方式。 Trace 类型特别适用于 Android 应用与系统级的分析场景,因为用它可以诊断: 函数调用链 Binder 调用时的调用链 跨进程事件流跟踪 Android 的应用程序运行环境的设计中,一个应用程序是无法独自完成所有的功能的,它需要跟 SystemServer 有大量的交互才能完成它的很多功能。与 SystemServer 间的通讯是通过 Binder 完成,它的通讯方式后面的文章再详细介绍,到目前为止你只需要知道它的调用关系是跨进程调用即可。这需要本端与远端的数据才能准确还原出调用关系,Trace 类型是完成这种信息记录的最佳方式。 Trace 类型可以由你手动添加开始与结束点,在一个函数里可以添加多个这种区间。通过预编译技术或者编程语言的特性,在函数的开头与结尾里自动插桩 Trace 区间。理想情况下后者是最好的方案,因为我们能知道系统中运行的所有的函数是哪些、执行情况与调用关系是什么。可以拿这些信息统计出调用次数最多(最热点)的函数是什么,最耗时的函数又是什么。可想而知这种方法带来的性能损耗非常大,因为函数调用的频次跟量级是非常大的,越是复杂的系统量级就越大。 因此有一种迂回的方法,那就通过采样获取调用栈的方式近似拟合上面的效果。采样间隔越短,就越能拟合真实的调用关系与耗时,但间隔也不能太小因为取堆栈的操作本身的负载就会变高因为次数变多了。这种方法,业界管他叫 Profiler,你所见过的绝大部分编程语言的 Profiler 工具都是基于这个原理实现的。 1.2 数据获取方法 静态代码与动态跟踪 静态代码的采集方式是最原始的方式,优点是实现简单缺点是每次新增内容的时候需要重新编译、安装程序。当遇到问题之后你想看的信息恰好没有的话,就没有任何办法进一步定位问题,只能重新再来一遍整个过程。更进一步的做法是预先把所有可能需要的地方上加入数据获取点,通过动态判断开关的方式选择是否输出,这既可以控制影响性能又能够在需要日志的时候可以动态打开,只不过这种方法的成本非常高。 动态跟踪技术其实一直都存在,只是它的学习成本比较高,被誉为调试跟踪领域里的屠龙刀。它需要你懂比较底层的技术,特别是编译、ELF 格式、内核、以及熟悉代码中的预设的探针、动态跟踪所对应的编程语言。对,你没看错,这种技术甚至还有自己的一套编程语言用于「动态」的实现开发者需求。这种方式兼具性能、灵活性,甚至线上版本里遇到异常后可以动态查看你想看的信息。 Android 应用开发、系统级开发中用的比较少,内核开发中偶尔会用一些。只有专业、专职的性能分析人员才可能会用上这类工具。它有两个关键点,探针与动态语言,程序运行过程中需要有对应的探针点将程序执行权限交接到动态跟踪框架,框架执行的逻辑是开发者使用动态语言来编写的逻辑。 所以,你的程序里首先是要有探针,好在 Linux 内核等框架埋好了对应的探针点,但是 android 应用层是没有现成的。所以目前 Android 上能用动态框架,如 eBPF 基本都是内核开发者在使用。 无条件式抓取与有条件式抓取 无条件式抓取比较好理解,触发抓取之后不管发生任何事情,都会持续抓取数据。缺点是被观测对象产生的数据量非常大的时候可能会对系统造成比较大的影响,这种时候只能通过降低数据量的方式来缓解。需要做到既能满足需求,性能损失又不能太大。 有条件式抓取经常用在可以识别出的异常的场景里。比如当系统的某个观测值超过了预先设定的阈值时,此时触发抓取日志并且持续一段时间或者达到另外一种阈值之后结束抓取。这相比于前面一个方法稍微进步了一些,仅在出问题的时候对系统有影响,其他时候没有任何影响点。但它需要你能够识别出异常,并且这种异常是不需要异常发生之前的历史数据。当然你可以通过降低阈值来更容易达到触发点,这可能会提高触发数据抓取的概率,这时候会遇到前面介绍的无条件式抓取遇到的同样的问题,需要平衡性能损失。 落盘策略 持续落盘是存储整个数据抓取过程中的所有数据,代价是存储的压力。如果能知道触发点,比如能够检测到异常点,这时候可以选择性的落盘。为了保证历史数据的有效性,因此把日志先暂存储到 RingBuffer 中,只有接受到落盘指令后再进行落盘存储。这种方式兼顾了性能与存储压力,但成本是运行时内存损耗与触发器的准确性。 1.3 分析方式 数据可视化分析 随着问题分析的复杂化,出现了要解决多个模块间交互的性能问题需求,业界就出现了以时间为横轴把对应事件放到各自泳道上的数据可视化分析方法,可以方便的看到所关心事件什么时候发生、与其他系统的交互信息等等。在 Android 里我们常用的 Systrace/Perfetto 以及更早之前的 KernelShark 等工具本质上都是这一类工具。在「数据类型」提到的 「Trace 类型」,经常采用这种可视化分析方法。 Systrace 的可视化框架是基于 Chrome 的一个叫 Catapult 的子项目构建。Trace Event Format 讲述了 Catapult 所支持的数据格式,如果你有 Trace 类型的数据,完全可以使用此框架来展示可视化数据。AOSP 编译系统,安卓应用的编译过程,也都有相应的 Trace 文件输出,它们也都基于 Catapult 实现了可视化效果。 数据库分析 面对大量数据分析的分析,通过对数据进行格式化,把他们转换成二维数据表,借助 SQL 语言可实现高效的查询操作。在服务器领域中 ELK 等技术栈可以实现更为灵活的格式化搜索与统计功能。借助数据库与 Python,你甚至可以实现一套自动化数据诊断工具链。 从上面的讨论可知,从文本分析到数据库分析他们要面对的分析目的是不一样的。单纯的看一个模块的耗时用文本分析就够用了,多个系统间的交互那就要用可视化工具,复杂的数据库分析就要用到 SQL 的工具。无论哪种分析方式,本质上都是针对数据的分析,在实战中我们经常会通过其他工具对数据进行转换以支持不同的分析方式,比如从文本分析方式改成数据库分析方式。 根据自己的目的,选择合适的分析方式才会让你的工作事倍功半。 对于 Android 开发者来说,Google 提供了几个非常重要的性能分析工具,帮助系统开发者、应用开发者来优化他们的程序。 2 谷歌提供的 Andorid 性能分析工具 从实践经验来看最常用的工具有 Systrace,Perfetto 与 Android Studio 中的 Profiler 工具。通过他们定位出主要瓶颈之后,你才需要用到其他领域相关工具。因此,会重点介绍这三个工具的应用场景,它的优点以及基本的使用方法。 工具之间的横向对比,请参考下一个「综合对比」这一章节的内容。 2.1 初代系统性能分析工具 - Systrace Systrace 是 Trace 类型的可视化分析工具,是第一代系统级性能分析工具。Trace 类型所支持的功能它都有支持。在 Perfetto 出现之前,基本上是唯一的性能分析工具,它将 Android 系统和 App 的运行信息以图形化的方式展示出来,与 Log 相比,Systrace 的图像化方式更为直观;与 TraceView 相比,抓取 Systrace 时候的性能开销基本可以忽略,最大程度地减少观察者效应带来的影响。 Systrace 的设计思路 在系统的一些关键操作(比如 Touch 操作、Power 按钮、滑动操作等)、系统机制(input 分发、View 绘制、进程间通信、进程管理机制等)、软硬件信息(CPU 频率信息、CPU 调度信息、磁盘信息、内存信息等)的关键流程上,插入类似 Log 的信息,我们称之为 TracePoint(本质是 Ftrace 信息),通过这些 TracePoint 来展示一个核心操作过程的执行时间、某些变量的值等信息。然后 Android 系统把这些散布在各个进程中的 TracePoint 收集起来,写入到一个文件中。导出这个文件后,Systrace 通过解析这些 TracePoint 的信息,得到一段时间内整个系统的运行信息。 Android 系统中,一些重要的模块都已经默认插入了一些 TracePoint,通过 TraceTag 来分类,其中信息来源如下 Framework Java 层的 TracePoint 通过 android.os.Trace 类完成 Framework Native 层的 TracePoint 通过 ATrace 宏完成 App 开发者可以通过 android.os.Trace 类自定义 Trace 这样 Systrace 就可以把 Android 上下层的所有信息都收集起来并集中展示,对于 Android 开发者来说,Systrace 最大的作用就是把整个 Android 系统的运行状态,从黑盒变成了白盒。全局性和可视化使得 Systrace 成为 Android 开发者在分析复杂的性能问题的时候的首选。 实践中的应用情况 解析后的 Systrace 由于有大量的系统信息,天然适合分析 Android App 和 Android 系统的性能问题, Android 的 App 开发者、系统开发者、Kernel 开发者都可以使用 Systrace 来分析性能问题。 从技术角度来说,Systrace 可覆盖性能涉及到的 响应速度 、卡顿丢帧、 ANR 这几个大类。 从用户角度来说,Systrace 可以分析用户遇到的性能问题,包括但不限于: 应用启动速度问题,包括冷启动、热启动、温启动 界面跳转速度慢、跳转动画卡顿 其他非跳转的点击操作慢(开关、弹窗、长按、选择等) 亮灭屏速度慢、开关机慢、解锁慢、人脸识别慢等 列表滑动卡顿 窗口动画卡顿 界面加载卡顿 整机卡顿 App 点击无响应、卡死闪退 在遇到上述问题后,可以使用多种方式抓取 Systrace ,将解析后的文件在 Chrome 打开,然后就可以进行分析 2.2 新一代性能分析全栈工具 - Perfetto 谷歌在 2017 年开始了第一笔提交,随后的 4 年(截止到 2021.12)内总共有 100 多位开发者提交了近 3.7W 笔提交,几乎每天都有 PR 与 Merge 操作,是一个相当活跃的项目。 除了功能强大之外其野心也非常大,官网上号称它是下一代面向可跨平台的 Trace/Metric 数据抓取与分析工具。应用也比较广泛,除了 Perfetto 网站,Windows Performance Tool 与 Android Studio,以及华为的 GraphicProfiler 也支持 Perfetto 数据的可视化与分析。 我们相信谷歌还会持续投入资源到 Perfetto 项目,可以说它应该就是下一代性能分析工具了,会完全取代 Systrace。 提供的亮点功能 Perfetto 相比 Systrace 最大的改进是可以支持长时间数据抓取,这是得益于它有一个可在后台运行的服务,通过它实现了对收集上来的数据进行 Protobuf 的编码并存盘。从数据来源来看,核心原理与 Systrace 是一致的,也都是基于 Linux 内核的 Ftrace 机制实现了用户空间与内核空间关键事件的记录(ATRACE、CPU 调度)。Systrace 提供的功能 Perfetto 都支持,由此才说 Systrace 最终会被 Perfetto 替代。 Perfetto 所支持的数据类型、获取方法,以及分析方式上看也是前所未有的全面,它几乎支持所有的类型与方法。数据类型上通过 ATRACE 实现了 Trace 类型支持,通过可定制的节点读取机制实现了 Metric 类型的支持,在 UserDebug 版本上通过获取 Logd 数据实现了 Log 类型的支持。 你可以通过 Perfetto.dev 网页、命令行工具手动触发抓取与结束,通过设置中的开发者选项触发长时间抓取,甚至你可以通过框架中提供的 Perfetto Trigger API 来动态开启数据抓取,基本上涵盖了我们在项目上能遇到的所有的情境。 在数据分析层面,Perfetto 提供了类似 Systrace 操作的数据可视化分析网页,但底层实现机制完全不同,最大的好处是可以支持超大文件的渲染,这是 Systrace 做不到的(超过 300M 以上时可能会崩溃、可能会超卡)。在这个可视化网页上,可以看到各种二次处理的数据、可以执行 SQL 查询命令、甚至还可以看到 logcat 的内容。Perfetto Trace 文件可以转换成基于 SQLite 的数据库文件,既可以现场敲 SQL 也可以把已经写好的 SQL 形成执行文件。甚至你可以把他导入到 Jupyter 等数据科学工具栈,将你的分析思路分享给其他伙伴。 比如你想要计算 SurfaceFlinger 线程消耗 CPU 的总量,或者运行在大核中的线程都有哪一些等等,可以与领域专家合作,把他们的经验转成 SQL 指令。如果这个还不满足你的需求, Perfetto 也提供了 Python API,将数据导出成 DataFrame 格式近乎可以实现任意你想要的数据分析效果。 这一套下来供开发者可挖掘的点就非常多了,从笔者团队的实践来看,他几乎可以覆盖从功能开发、功能测试、CI/CD 以及线上监控、专家系统等方方面面。本星球的后续系列文章中,也会重点介绍 Perfetto 的强大功能与基于它开发的专家系统,可以帮助你「一键解答」性能瓶颈。 实践中的应用情况 性能分析首要用到的工具就是 Perfetto,使用 Systrace 的场景是越来越少了。所以,你首要掌握的工具应该是 Perfetto,学习它的用法以及它提供的指标。 不过 Perfetto 也有一些边界,首先它虽然提供了较高的灵活性但本质上还是静态数据收集器,不是动态跟踪工具,跟 eBPF 还是有本质上的差异。其次运行时成本比较高,因为涉及到在手机中实现 Ftrace 数据到 Perfetto 数据的转换。最后他不提供文本分析方式,只能通过网页可视化或者操作 SQLite 来进行额外的分析了。综合来看 Perfetto 是功能强大,几乎涵盖了可观测性技术的方方面面,但是使用门槛也比较高。值得挖掘与学习的知识点比较多,我们后续的文章中也会重点安排此部分的内容。 2.3 Android Studio Profiler 工具 Android 的应用开发集成环境(官方推荐)是 Android Studio (之前是Eclipse,不过已经淘汰了) ,它自然而然也需要把开发和性能调优集成一起。非常幸运的是,随着 Android Studio 的迭代、演进,到目前,Android Studio 有了自己的性能分析工具 Android Profiler,它是一个集合体,集成了多种性能分析工具于一体,让开发者可以在 Android Studio 做开发应用,也不用再下载其它工具就能让能做性能调优工作。 目前 Android Studio Profiler 已经集成了 4 类性能分析工具: CPU、Memory、Network、Battery,其中 CPU 相关性能分析工具为 CPU Profiler,也是本章的主角,它把 CPU 相关的性能分析工具都集成在了一起,开发者可以根据自己需求来选择使用哪一个。可能很多人都知道,谷歌已经开发了一些独立的 CPU 性能分析工具,如 Perfetto、Simpleperf、Java Method Trace 等,现在又出来一个 CPU Profiler,显然不可能去重复造轮子,CPU Profiler 目前做法就是:从这些已知的工具中获取数据,然后把数据解析成自己想要的样式,通过统一的界面展示出来。 提供的亮点功能 CPU Profiler 集成了性能分析工具:Perfetto、Simpleperf、Java Method Trace,它自然而然具备了这些工具的全部或部分功能,如下: System Trace Recording,它是用 Perfetto 抓取的信息,可用于分析进程函数耗时、调度、渲染等情况,但是它一个精简版,只能显示进程强相关的信息且会过滤掉耗时短的事件,建议将 Trace 导出文件后在 https://ui.perfetto.dev/ 上进行分析。 Java Method Trace Recording,它是从虚拟机获取函数调用栈信息,用于分析 Java 函数调用和耗时情况。 C/C++ Function Trace,它是用 Simpleperf 抓取的信息,Simpleperf 是从 CPU 的性能监控单元 PMU 硬件组件获取数据。 C/C++ Method Trace 只具备 Simpleperf 部分功能,用于分析 C/C++ 函数调用和耗时情况。 实践中的应用情况 应用的性能问题主要分为两类:响应慢、不流畅。 响应慢问题常有:应用启动慢、页面跳转慢、列表加载慢、按钮响应慢等 不流畅问题常有:列表滑动不流畅、页面滑动不跟手、动画卡顿等 CPU Profiler 在这些场景中要如何使用呢?基本的思路是:首先就要抓 System Trace,先用System Trace 分析、定位问题,如果不能定位到问题,再借助 Java Method Trace 或 C/C++ Function Trace 进一步分析定位。 以一个性能极差的应用为例,在系统的关键位置插了 Systrace TracePoint,假设对代码不熟悉,那要怎么找到性能瓶颈呢?我们先把应用跑起来,通过 CPU Profiler 录制一个 System Trace (后面文章会介绍工具的使用方法)如下: 通过上面 Trace 可以知道是在 egl_core 线程中的 onDrawFrame 操作耗时,如果发现不了问题,建议导出到 https://ui.perfetto.dev/ 进一步分析,可以查找源代码看看 onDrawFrame 是什么东西, 我们通过查找发现 onDrawFrame 是 Java 函数 onDrawFrame 的耗时,要分析 Java 函数耗时情况,我们要录制一个 Java Method Trace,如下: 通过上面 Trace 很容易发现是一个叫做 Utils.onDraw 的 native 函数耗时,因为涉及到C/C++ 代码,所以要再录制一个 C/C++ Function Trace 进一步分析,如下: 可以发现在 native 的 Java_com_gl_shader_Utils_onDraw 中代码执行了 sleep,它就是导致了性能低下的罪魁祸首! AS 中的 CPU Profiler 最大优势是集成了各种子工具,在一个地方就能操作一切,对应用开发者来说是非常方便的,不过对系统开发者来说可能没那么幸运。 2.4 综合对比 工具名称应用场景数据类型获取方法分析方式 SystraceAndroid 系统与应用性能分析Trace 类型无条件抓取 持续落盘可视化分析 PerfettoAndroid 系统与应用性能分析Metric 类型 Trace 类型无条件抓取 持续落盘可视化分析 数据库分析 AS ProfilerAndroid 系统与应用性能分析Trace 类型无条件抓取 持续落盘可视化分析 SimplePerfJava/C++ 函数执行耗时 分析 PMU 计数器Trace 类性无条件抓取 持续落盘可视化分析 文本分析 Snapdragon Profiler Tools & Resources主要是高通 GPU 性能分析器Trace 类型 Metric 类型无条件抓取 持续落盘可视化分析 Mali Graphics DebuggerARM GPU 分析器(MTK、麒麟芯片)Trace 类型 Metric 类型无条件抓取 持续落盘可视化分析 Android Log/dumpsys综合分析Log 类型有条件抓取 持续抓取但不落盘文本分析 AGI(Android GPU Inspector)Android GPU 分析器Trace 类型 Metric 类型无条件抓取 持续落盘可视化分析 eBPFLinux 内核行为动态跟踪Metric 类型动态跟踪 有条件抓取 持续抓取但不落盘文本分析 FTraceLinux 内核埋点Log 类型静态代码 有条件抓取 持续抓取但不落盘文本分析 3 关于「器、术、道」 技术上的变革、改进更多是体现在「器」层面,Linux 社区以及谷歌所开发的工具发展方向朝着提高工具的集成化使得在一个地方可以方便查到所需的信息、或者是朝着获取更多信息的方向发展。总之,器层面他们的发展轨迹是可寻的,可总结出发展规律。 我们需要在工具快速迭代的时候准确的认识到他们能力以及应用场景,其目的是提高解决问题的效率,而不是把时间花在学习新工具上。 「术」层面依赖具体的业务知识,知道一帧是如何被渲染的、CPU 是如何选择进程调度的、IO 是如何被下发的等等。只有了解了业务知识才能正确的选择工具并正确的解读工具所提供的信息。随着经验的丰富,有时候你都不需要看到工具提供的详细信息,也可以查到蛛丝马迹,这就是当你业务知识丰富到一定程度,大脑里形成了复杂的关联性信息之后凌驾于工具之上的一种能力。 「道」层面思考的是要解决什么问题,问题的本质是什么?做到什么程度以及需要投入什么样的成本达成什么样的效果。为了解决一个问题,什么样的路径的「投入产出比」是最高的?整体打法是什么样?为了完成一件事,你首先要做什么其次是做什么,前后依赖关系的逻辑又是什么? 后续的文章中,会依照「器、术、道」方式讲解一个技术、一个功能,我们不止想让你学习到一个知识点,更想激发你举一反三的能力。遇到类似的工具或者类似的问题、更进一步是完全不同的系统,都能够从容应对。牢牢抓住本质,通过评估「投入产出比」选择合适的工具或信息,高效解决问题。 4 关于「The Performance 知识星球」 为了更好地交流与输出高质量文章,我们创建了名为 「The Performance」的知识星球,主理人是三个国内一线手机厂商性能优化方面的一线开发者,有多年性能相关领域的工作经验,提供Android 性能相关的一站式知识服务,涵盖了基础、方法论、工具使用和最宝贵的案例分析。 目前星球的内容规划如下(两个 ## 之间的是标签,相关的话题都会打上对应的标签,方便大家点击感兴趣的标签查看对应的知识) #The Performance# — 可以提早阅读「Android 性能优化 - 系统性课程」的电子书,每周会放出已经写好的章节。「Android 性能优化 - 系统性课程」是我们规划的一本讲 Android 性能优化的电子书,目前开发者社区有相当多高质量的性能优化理论知识和实践文章和开源库,但是目前市面上缺乏一个完整的、系统性的、包含了性能优化原理、工具、实践等内容、面向初级开发中和中级开发者、面向 App 开发者和系统开发者,且持续更新的 Android 性能优化工具书。书的大纲 (暂定) 我们已经基本上列好了,预计会花费一年左右的时间来完成,在星球中会放出写好的章节,让大家提前看到。 Part 1: → 性能工程 Part 2: → 以性能角度分析 Android 交互与核心系统 Part 3: → 以性能角度分析 Linux 内核核心子系统设计与实现 Part 4: → 问题场景分析思路 Part 5: → 分析与调试工具 Part 6: → 质量守护 - 性能监控方法与工具 #性能工具# — 分享 Android 开发中使用到的性能分析工具以及其使用方法,同时也提供 1V1 的 Systrace、Perfetto 等性能工具的视频指导。性能工具的使用,最好还是以视频的方式展示会直观很多,文章是静态的,很多地方比较难讲清楚,1V1 的视频会议指导也算是一个学习的方法 #案例分析# — 典型案例分析思路总结、球友提供的案例分析与讨论。案例分析是学习的一个很重要的途径,阅读大量的实际性能案例对以后自己分析和解决性能问题是非常有帮助的,同时也欢迎大家提供案例和解决方法,怕泄露信息的话,我们会对关键信息进行打码 #经典解读# — 经典方案、课程重读,例如优秀的三方库解析、Android 开发高手课重读等。比如可以对方案进行深度的剖析,横向对比等;对 Android 开发高手课进行重读和查漏补缺 #知识分享# — 优秀文章、博客、工具分享。业界有很大大牛的博客、经过实际业务考验的开源方案、各种性能工具等,我们会寻找这些优秀的内容,分享给大家 #知识沉淀# — 微信群聊精华、微信问答、博客留言解答等 #性能面试# — Android 性能相关的面试题搜集和解答,也算是刚需了吧 #编程语言# — 编程语言相关的使用技巧分享 #效能提升# — 效能提升分享,包括开发者开发效能、工作效能提升方法、工程效率、工具推荐等,磨刀不误砍柴工嘛 #行业动态# — 性能相关新技术第一时间解读报告,包括但不限于下面的内容 行业峰会、学术峰会新思路解读报告 论文、行业、书籍介绍、视频 Android 大版本性能相关介绍 Android 新硬件性能相关内容介绍 Android 性能相关开源项目解读 #大咖分享# — 每月定期邀请行业大咖进行经验分享、案例分析 #工作内推# — 各大厂商内推工作机会介绍 注意: iOS 手机用户不要直接在星球里面付款,在微信界面长按图片扫描二维码加入即可,否则苹果会收取高昂的手续 5 附录 Perfetto 项目地址 官方使用文档: https://perfetto.dev/docs/ Github 代码库 https://github.com/google/perfetto ,可以提 bug 或者需求,当然也可以提 PR 贡献你的力量。 Android GPU Inspector 官方使用文档: https://developer.android.com/agi 其他大厂的性能分析工具 Windows Performance Toolkit 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
2021 已经过去,趁着元旦假期,回顾一下 2021,随意一些,想到哪里写哪里吧。主要是对 2021 年的一个回顾,以及 2022 年的展望,2021 年当了爸爸,换了工作(中间还居家无聊了好久),收获了更多的朋友,也算是过的还可以 不过在个人成长方面,甚至感觉有点退步,这让我觉得有点慌,学如逆水行舟,不进则退,2022 年是需要好好深耕的一年,希望能和看到这篇文章的同学一起进步,共勉 另外也盘点了一下知识分享相关的数据,分享了一下这方面的收入,个人新增和推荐的硬件、个人推荐的软件等,感兴趣的可以自取 2021 最大收获 - 小橘子 今年的最大收获那必然是家里多了一个小橘子,体验了一下当爸爸的感觉,多了个小棉袄,双人行变成了三人行,到现在快十个月了,妥妥小天使。我们对小橘子要求不高,健健康康快快乐乐长大就可以了,老父亲老母亲是你坚强的后盾 知识分享数据统计 个人博客数据 博客 2021 新增了 8 篇文章(Systrace 系列总算是完结了,总共 18 篇内容,你们先看,我去准备准备 Perfetto 版本的….) Systrace 流畅性实战 1 :了解卡顿原理 - https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/ Systrace 流畅性实战 2 :案例分析 - MIUI 桌面滑动卡顿分析 - https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/ Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 - https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/ Systrace 响应速度实战 1 :了解响应速度原理 - https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/ Systrace 响应速度实战 2 :响应速度实战分析 - 以启动速度为例 - https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/ Systrace 响应速度实战 3 :响应速度延伸知识 - https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/ Android 系统开发系列(1):Android 12 源代码下载、编译和刷机 - https://www.androidperformance.com/2021/10/26/build-android-12/ 一本讲 Android 流畅性的书,应该有什么内容? - https://www.androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/ 一年就写了这几篇文章,跟年初定的目标 一周一篇 差的实在是有点远,就离谱(2022 年不能再立这种 Flag 了,量力而行,一个月两篇我觉得还可以一战。 另外去年还维护了一个 Android Weekly 的知乎专栏,感兴趣的也可以订阅一下 https://www.zhihu.com/column/c_1278963991947780096) 博客 AndroidPerformance 使用了 Google Analytics 来进行统计,我看了一下 2021 和 2020 年的对比数据,还是不错的,下面是 Google Analytics 的统计数据 **访问用户数 **对比 2020 年增长了 38.9%,总的来说还是不错的(那几个明显的低谷是新年假期、五一和十一,好好过节,咱不卷) 用户最常访问的页面,还是主要以 Android Systrace 系列为主 微信公众号数据 微信公众号 AndroidPerformance 的关注数目前是 :7364,由于原创文章不多,且有很多转载,所以公众号增长比较慢,活跃的都是老用户了。微信公众号文章由于是封闭的,所以数据没有什么意义,就不贴了,希望 2022 能突破 1W 吧 微信公众号主要是微信里面阅读方便,但是对于写作的人来说就没那么友好了,主要是没法贴微信之外的超链接,这个设定也太 XX 了,把互联网搞成了局域网,无力吐槽 知乎 知乎 的关注者目前有 2W+ ,不过知乎貌似越来越不重视技术这一块了,所以也就没怎么活跃了,刷到的文章和回答大部分都是搬运工或者水军,遇到好的文章都要赶紧点赞收藏分享一键三连,后续还是刷刷即刻和 Twitter 吧,上面的真实活跃开发者还是多 掘金 掘金 技术氛围还是很浓厚的,我也经常刷,不过总感觉掘金差了点啥,又说不上来是啥… 掘金的粉丝可以忽略不计了,不过后续有技术文章还是会同步上去的(你不同步,就会有人替你同步,当然作者就换人了) 即刻 即友可以加个好友 其他平台 CSDN ……可以忽略 微博 ……可以忽略 Twitter :关注了很多国内外的技术大佬,技术氛围还是蛮不错的 赚钱?交个朋友而已 今天听了 Happy Xiao 的播客,讲了他一年下来,通过知识分享的各个平台,每个月大概有 2500 RMB 左右的收入,所以也想了想自己去年一年在这方面的收入,想想也还蛮惨淡的 博客收入:0 ,甚至应该还是负的,因为还有域名+服务器的费用…. 微信公众号:因为我没有开文中广告,所以广告收入几乎可以忽略不计,主要是靠各位的打赏了,我看了下打赏数据,总共是 1039 小专栏:总共 560 所以算下来,总收入是 1039 + 560 = 1596 。平均每个月 133,可见是真的不赚钱……用老罗的话来说,交个朋友..不过真心感谢微信打赏的小伙伴,每天早餐加个蛋就靠你们了(各位想赞赏的话,可以扫描文末的二维码,随便找一篇原创的文章打赏即可) 说到交个朋友,确实今年新加了很多技术的小伙伴,拉了四个微信群,时不时在群里水一水,在群里总的感觉是:群除我佬,时常怀疑自己是不是出现在了错的地方。说到底还是太菜,很多东西都不知道,或者一知半解。立个 Flag:今年要把基础打牢,知识体系化,争取跟上群里的大佬们的讨论 微信赞赏码,各位觉得有用,可以加个鸡腿 2022 有什么计划? 新年立 Flag 一般就那几样,我也没法免俗,不管怎么说先立了再说,等后续细化成每周每天的行动项,说不定就成了呢? 健身:应该还是主要以 走路上下班 + 篮球 + 跑步 + 划船机 + 家里的健身器材为主(抱橘橘也算是一种健身了) 强化英语阅读和听说:最近在有意识地听英文播客、看比较好懂的英文视频、看英文版本的文章和电子书,iPad 上的分屏看英文电子书配合有道词典,左边复制右边自动翻译+加入生词本,还是很不错的 知识体系化:读和写更多的代码、读更多的书、写一本 性能相关电子书 **更频繁地更新 博客**:这个 flag 上面已经立了 **跟小团队运营好 The Performance 知识星球(2022-06-20更新:暂时停止付费星球加入)**:一个人的力量还是太小了,跟几个小伙伴搞了一个收费版本的知识星球,希望能提供服务的同时,也给自己一点压力(你不压榨压榨自己,怎么知道自己不能用来榨油呢?) 尝试一些新鲜的东西:试试播客、视频、VLog 、拍照等 夯实基础,高效工作:多思考,多总结,多分享,多读书,多写代码,用工具提升工作效率 2021 有什么软件推荐? 2021 用的最舒心的软件 Notion,笔记软件,谁用谁知道:https://www.notion.so/ Typora,笔记软件,Blog 就是用这个写的:https://typora.io/ Github Copilot,你的 AI 编程伴侣:https://copilot.github.com/ flomo,笔记软件,记录转瞬即逝的小想法:https://flomoapp.com/ DeepL,号称全球最准确的翻译:https://www.deepl.com/zh/translator 2021 有什么硬件推荐? 2021 还是折腾了不少硬件的,觉得还不错的推荐给大家 NAS,家庭私有云:https://item.jd.com/100014187272.html 静电容键盘,机械键盘退烧神器,程序员和文字工作者必备:https://item.jd.com/100013910167.html 小米的 27 寸 4K 显示器,你买不了吃亏,买不了上当:https://item.jd.com/100016659987.html Apple TV 4K,也就是最新的那款,请注意这玩意有前置条件,如果你都满足,那么我推荐你搞一个,部分满足也行,就是体验会打一些折扣(1. 你有一种科学上网的方法 2. 你有一个软路由或者旁路由 3. 你有一台 NAS 4. 你有一台 4K 电视 5. 你有港区或者美区的 AppleID 6.你有一台 iphone):https://npcitem.jd.hk/100011599035.html 趣动乐 JOY25 电动升降桌,1.8m 的宽度是真的爽,乐歌就没这么宽的,感觉可以用个 10 年,站累了坐一会,坐累了站一会,吃嘛嘛香,身体倍儿棒!https://item.jd.com/10043115461593.html 2022 最想要什么? 池大已经替我说了,我感觉我手上这台闲鱼淘的 M1 版本的 Mac Mini,在 M1 Max 芯片的 MacBook Pro 面前瞬间就不香了,操作起来也变卡了……一定是库克在远程施法了 不过想要归想要,需要归需要,要理性消费(Mini 坏了的话就可以换了) 话说这个椅子也太好看了吧!(https://item.taobao.com/item.htm?id=614505490996 各位自取) 当然还有最新款的 iPhone 13 Pro Max 谁会拒绝呢? 结尾 2021 总的来说工作上不怎么理想,生活上有了小棉袄还是添加了不少乐趣,个人成长上几乎停滞,有各方面的原因,归根结底还是自己对自己的要求太低了,逆水行舟,不进则退 希望 2022 年能和看到这篇文章的各位一起努力,共勉! 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
最近读了一本新书:《打造流畅的 Android App》,京东链接:https://item.jd.com/10035215362170.html 。因为书名所以买了这本书,读完之后觉得有必要写一篇文章,让还没有买此书的同学了解一下 我个人的建议是:如果你是个老鸟,不建议买,这本书里面没有介绍太多原理性的东西,对于 Android 流畅性也没有一个比较全面的介绍;如果你是新手,这本书用来当做开阔视野 + 查漏补缺还可以,想更深入的了解 Android 流畅度还是差了点东西 之所以我会这么建议,是因为这本书确实没有讲太多性能或者流畅度相关的东西,也没有比较深入的原理部分,篇幅更多在讲静态代码审查、AS Profiler 的使用、App 架构、保活、网络性能优化、APK 大小优化、App 耗电等,内容也不深,浅尝辄止 内容介绍 简单介绍一下这本书的内容,其章节如下 概述 :简单介绍为何要做性能优化,以及 Android Studio 的配置 静态代码审查 :大篇幅降了各种静态代码审查工具,比如 Android Lint 、CheckStyle、SpotBugs、PMD 等,除了 Lint 其他的我接触不多,也算是查漏补缺了 使用 Android Profiler 优化性能 :主要降了 AS Profiler 工具里面的 CPU Profiler、Memory Profiler、Network Profiler、Network Profiler ,这里主要重点是工具的使用,大概性地介绍了一下 高质量的 App 从架构开始:主要是架构原则、MVC、MVP、MVVM 这些 优雅地保活 App :简单介绍了下保活相关的技术 网络性能优化专题 :网络交互与多线程 + 海量数据传输优化 优化 APK 体积 :老生常谈的 APK 大小优化,多渠道打包 + 优化资源文件 + 代码混淆 App 耗电及 Crash 体验优化:简单介绍了一下 从上面章节标题大家也可以看到,跟流畅性相关的内容比较少,内容相对会比较杂一些,感兴趣的可以买一本看看 我认为一本讲流畅性的书,应该有什么? 如果让我写这么一本书,我肯定是写不来的,非常钦佩能出书的技术小伙伴,给作者点个赞。 不过这并不妨碍我嘴炮打个山响(I am good at it):所以我觉得如果让我来写这本书,我会加入下面这些内容,确保大家通过这本书,就可以深入理解 Android 的流畅性原理,且可以熟练使用各种工具来分析所遇到的流畅性问题 鉴于在讨论 Android 性能问题的时候,卡顿(流畅性)、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲,因为引起卡顿、响应慢、ANR 的原因类似,只不过根据重要程度,被人为分成了卡顿(流畅性)、响应慢、ANR 三种,所以我们可以定义广义上的流畅性,包含了卡顿(流畅性)、响应慢和 ANR 三种,所以如果用户反馈说手机卡顿或者 App 卡顿(流畅性),大部分情况下都是广义上的卡顿(流畅性),需要搞清楚,到底出现了哪一种问题 所以我设想的章节应该包含下面的内容 第一章:Android 流畅性概述:这一章主要会讲性能相关的一些概念,包括从用户角度、开发角度、测试角度、AOSP 的角度、硬件角度等,讲述流畅性的一些概念。这一点很重要,因为在实践中发现,用户和开发、测试往往是同不同的角度来看待流畅度的,思考问题的时候别把自己的思维定在某一个角色,往往会有不一样的结果 第二章:Android 运行机制概述:这一章主要会讲一些 Android 运行机制相关的内容,了解这些知识点,对于分析 Android 流畅性问题是必须的,当下面这些知识点你非常熟悉之后,碰到流畅性的问题,你的脑海中就有一个图形化的工具在运转:用户怎么操作的、系统怎么反馈的、App 运行到了哪里、最有可能是哪里出现了问题、用什么工具去 Debug 最方便 App 主线程运行原理(主线程和渲染线程) Message、Handler、MessageQueue、Looper 机制 屏幕刷新机制和 Vsync Choreogrepher 机制 Buffer 工作流和 SurfaceFlinger 工作流 Input 流程 ANR 的设计思想 第三章:性能分析工具介绍:正所谓 工欲善其事必先利其器,趁手的工具对于分析性能问题至关重要,这一章主要会讲性能分析经常遇到的工具,并非是简单的介绍,会结合 Android 系统机制来讲解,工具主要包括但不限于 Systrace(Perfetto) 、AS Profiler、SimplePerf、MAT、Log 工具(Log 内容分析和 Log 原理)、命令行工具(dumpsys meminfo、dumpsys gfxinfo、dumpsys cpuinfo、dumpsys SurfaceFlinger、dumpsys activity、dumpsys input、dumpsys window 等)、三方性能库(Koom、Matrix、Facebook profilo、BlockCanary、LeakCanary、Tailor/Raphael 等) 第四章:深入分析 Android 卡顿问题:运行机制和工具都介绍完了,那么接下来就是如何进行实战了,这一章主要会讲卡顿出现的原因、分析卡顿问题的套路、案例分享、编码最佳实践等 第五章:深入分析 Android 响应速度问题:同上,响应速度问题实战环节,这一章主要会讲响应速度问题出现的原因、分析响应速度问题的套路、案例分享、编码最佳实践等 第六章:深入分析 Android ANR 问题:ANR 也是用户体验的一部分,这里主要会讲 ANR 的设计思想、ANR 的几种类型、ANR 出现的原因、ANR 问题的分析套路、案例分享、编码最佳实践等(目测会有很大的篇幅) 第七章:深入分析 Android 内存问题:内存问题同样是影响用户体验一部分,而且是一个比较重要的性能指标,你懂得。本章会介绍 App 的内存占用、App 内存分析工具、内存泄漏分析、内存持续增长分析等,这里面的内容估计会牵扯到比较多的知识点,任重而道远啊…. 第八章:性能测试:从测试的角度来看流畅性问题,这里会讲一些 性能指标获取(侵入式和非侵入式)、性能标准制定、竞品分析、提 Bug 的标准和流程、整机测试方法、权威第三方的性能测试方法和标准介绍(绿色联盟、鲁大师、友盟、Bugly 等)、性能监控工具开发(比如 Matrix、Koom、Fastbot、UI Automator、内存增长测试等),以及一些软技能:如何区分 Android 系统问题和 App 问题、如何与开发和 PM 扯皮(开个玩笑) 等 第九章:线上性能监控:上一章讲的是本地性能测试,而这一章会讲线上是如何监控流畅度的,跟线下监控有区别的是,线上监控既要能体现真实的用户体验,又要尽量减少对用户的影响,还需要在发现问题的时候,能及时进行数据上报 第十章:系统性能优化介绍:App 开发者使用各种方法和黑科技来进行性能监控和性能分析,那么 Android 系统开发者又是如何做的呢?这一章会介绍一些各种厂商的性能优化、AOSP 的性能优化、高通和 MTK 的优化等 第十一章:高效工作指南:内容暂定,包括但不限于 AOSP 代码编译的必要性和流程 阅读 AOSP 代码的技巧,比如 cs.android.com、导入 AS、导入 vscode 等、画流程图等 Windows、Linux 、Mac 开发环境推荐、配置命令行等 工作方式推荐:多写、多记、多总结、多分享 嘴炮输出完毕,万事俱备,只欠大佬来完善内容了… 市面上还有哪些讲性能的书? 讲道理目前市面上的书都有点年代了,倒是掘金社区的 Android 性能优化文章非常多,各种大厂也乐意将他们的内部工具开源,给这些热爱分享小伙伴点个赞,让我们站在巨人的肩膀上前行 我本人看过的几本书 腾讯 TMQ 专项测试团队出的:《移动 App 性能评测与优化》,2016 年出版,专业性和实战拉满,值得一看,https://item.jd.com/11976603.html 微信读书:有电子书 邓老师的 《深入理解 Android:Java 虚拟机 ART》,ART 虚拟机的大部头书,对于了解 ART 虚拟机的运行有很大的帮助,App 的不少黑科技都会涉及到虚拟机 https://item.jd.com/12510921.html 微信读书:有电子书 道格・西勒斯(Doug Sillars)的 《高性能 Android 应用开发》,2016 年出版,英文原版更早一些,算是一个比较早的全方位讲解 Android App 性能的书了,感兴趣的可以收藏一本 https://item.jd.com/11995735.html 微信读书:没有电子书 腾讯大佬出的:《Android 应用性能优化最佳实践》,2017 年出版,内容也是性能相关 https://item.jd.com/12043655.html 微信读书:有电子书 Brendan Gregg 大师新作:《BPF 之巅:洞悉 Linux 系统和应用性能》,中文版 2020 年出版,大部头工具书,屯之 https://item.jd.com/12769029.html 微信读书:没有电子书 同样是 Brendan Gregg 的 《性能之巅:洞悉系统、企业与云计算》,中文版 2020 年出版,大部头工具书,搞性能的应该人手一本… https://item.jd.com/12749867.html 微信读书:有电子书 张绍文的《Android 开发高手课》https://time.geekbang.org/column/intro/142 最近重新听的感悟:高手就是高手 倪朋飞的 Linux 性能优化实践 写在最后 欢迎大家留言分享自己看过的觉得非常不错的 Android 性能相关的书籍、博客、视频课、官方教程等 欢迎大家留言分享你们认为一本讲 Android 流畅性的书,应该包含哪些内容 本文不涉及任何推广,大家放心食用 博客交流不方便,有疑问的可以在知乎或者微信公众号下面留言,或者直接加我微信(553000664),备注 Blog 即可 本文知乎地址:https://zhuanlan.zhihu.com/p/423605434 本文微信公众号地址:https://mp.weixin.qq.com/s/WUGWJx5FRJqXboQ2KKGRwA 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
Android 12 正式版 已经发布:https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw 。Android 12 正式版的代码也已经发布,官方文档 也进行了更新:https://source.android.google.cn/ 本文就带大家下载和编译最新的 Android 12 代码,本地编译的代码有下面几个好处 可以刷真机,方便开发者进行本地 Debug,同时代码可以导入 Android Studio 进行 Debug 可以编译 Userdebug 版本,可以 root 和 remount,方便对系统和 App 进行 Debug,Debug 模式下可以看到许多 User 版本上看不到的问题;同时由于可以看到更多的信息,也方便进行 App 竞品分析、App 行为分析 可以更方便地进行 Android 源代码的学习,本地版本可以打开很多系统级别的 Debug Log,也可以自己加 Log,或者自己修改流程 如果大家没有下载编译 Debug 的需求,只是单纯的看代码的话,推荐使用 cs.android.com 即可。想深入了解 Android 系统的小伙伴和 Android 系统开发初学者可以看看,建议编译配置如下 闲鱼搞一个二手不带锁的 Pixel 3 以上(Android 12 只支持 Pixel 3 及以上) 一个有足够大硬盘(最好是 ssd)、足够大内存(最好是 32g,不行就设 swap)、没那么弱的 cpu(否则会影响编译时间)、安装了 Linux 的台式机 1. 代码下载 由于在国内使用 Google 的官方下载站点,会有下不动的情况,有时候 .repo 都下载不下来,所以本教程是以国内的镜像站点为例子,如果你有方法可以爬墙,那么可以简单参考 官方的教程 https://source.android.google.cn/source/downloading 科大 AOSP 镜像站点地址:https://mirrors.ustc.edu.cn/help/aosp.html 下载只需要跟着下面几个步骤走即可(以下方法可以在 Ubuntu、WSL、WSL2、Mac 上运行,但是后面进行代码编译的时候,只能使用 Linux ,所以建议大家还是使用 Ubuntu 这样的 Linux 系统来进行代码的下载、编译、开发工作) 1.1 步骤1:Repo 工具下载 1 2 3 4 mkdir ~/bin PATH=~/bin:$PATH curl -sSL 'https://gerrit-googlesource.proxy.ustclug.org/git-repo/+/master/repo?format=TEXT' |base64 -d > ~/bin/repo chmod a+x ~/bin/repo 1.2 步骤2:配置个人信息 如果没有安装 git,先自己安装一下 git,然后执行下面的命令,填上自己的 Name 和 Email 1 2 git config --global user.name "Your Name" git config --global user.email "you@example.com" 比如我填的 1 2 git config --global user.name "Gracker" git config --global user.email "dreamtale.jg@gmail.com" 1.2 步骤3:创建工程目录 在本地建立一个工作目录(名字任意,这里以 Android_12_AOSP 为例子) 1 2 mkdir Android_12_AOSP cd Android_12_AOSP 1.4 步骤4:初始化仓库 仓库初始化有两种方式,一种是直接下载,另外一种是加 Tag,下载特定的 Tag 版本,下面会对这两种方法分别进行介绍,大家可以自己选择哪一种方式 (注意:这里的两种下载方式会影响后续的驱动下载,所以要记清楚自己使用的是哪种方式,在 驱动下载 章节选择合适的驱动) 1.4.1 直接下载(推荐) 这种方法会下载所有的代码,默认分支是 master ,不愁空间的话,直接用这种方法下载即可 1 2 3 repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest ## 如果提示无法连接到 gerrit.googlesource.com,可以编辑 ~/bin/repo,把 REPO_URL 一行替换成下面的: ## REPO_URL = 'https://gerrit-googlesource.proxy.ustclug.org/git-repo' 这里需要注意,默认的 repo 使用的地址是 REPO_URL = ‘https://gerrit.googlesource.com/git-repo‘ ,这里我们需要修改 REPO_URL,否则会出现无法下载的情况 修改方法1:在你的 rc 文件里面,加入一条配置即可:REPO_URL=”https://gerrit-googlesource.proxy.ustclug.org/git-repo“ 修改方法2:直接打开 ~/bin/repo, 把 REPO_URL 一行替换成下面的: REPO_URL = ‘https://gerrit-googlesource.proxy.ustclug.org/git-repo‘ 下载好 .repo 之后会有下面的信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ➜ Android12 repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest Downloading Repo source from https://gerrit-googlesource.proxy.ustclug.org/git-repo ... A new version of repo (2.17) is available. ... You should upgrade soon: cp /home/gracker/Code/Android12/.repo/repo/repo /home/gracker/bin/repo Downloading manifest from git://mirrors.ustc.edu.cn/aosp/platform/manifest remote: Enumerating objects: 91965, done. remote: Total 91965 (delta 0), reused 0 (delta 0) Your identity is: Gracker If you want to change this, please re-run 'repo init' with --config-name repo has been initialized in /home/gracker/Code/Android12 如果选择了直接下载,那么就不需要看 3.2 了 1.4.2 下载特定的 Tag 这种方法指的是只下载单个 Tag 所对应的代码,这里的 Tag 可以 查看这里 https://source.android.google.cn/setup/start/build-numbers,比如我的开发机是 Google Pixel 3 XL,我在 Tag 列表查看对应的机型都有哪些 TAG,目前 Android 12 只发布了两个,如下 对应的 Tag 分别是 android-12.0.0_r3 和 android-12.0.0_r1 ,所以下载的时候我可以制定对应的 TAG,这样的好处是下载的代码比较少,下载速度会快一些;不方便的点是更新不方便,Google 会定期发邮件告诉你哪些新的 Tag 发布了,你可以根据这个来更新代码 1 repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-12.0.0_r3 1.5 步骤5 :同步代码 上面步骤三只是下载了 .repo 文件,具体的代码还需要执行 repo sync 来进行下载。由于镜像站的限制和下载过程中可能会遇到的问题,建议大家用 -j4 来下载 1 repo sync -j4 然后就开始了漫长的下载,由于下载过程中可能会出现失败的情况,你可以搞一个 sh 脚步来循环下载,一觉醒来就下载好了 1 2 3 4 5 6 7 8 #!/bin/bash repo sync -j4 while [ $? -ne 0 ] do echo "======sync failed ,re-sync again======" sleep 3 repo sync -j4 done 具体方法 1 2 3 4 touch repo.sh # 1. 创建 repo.sh 文件 vim repo.sh # 2. 复制上面的脚本内容到 repo.sh 里面,这里你可以使用你自己喜欢的方法打开并修改文件,比如 vscode chmod a+x repo.sh #3. 修改权限 ./repo.sh # 4. 运行脚本,万事大吉 2. 驱动下载 代码下载完成之后,我们先不着急编译,如果要想在真机上跑,需要下载一些厂商闭源的驱动文件,这样后续编译的代码才可以跑到真机上,此处对应的 官方文档 https://source.android.google.cn/setup/build/downloading#obtaining-proprietary-binaries 上面下载代码的时候,我们提到了两种方式,直接下载和下载特定 Tag,不同的下载方式对应的驱动也不一样 2.1 直接下载方式所对应的驱动 直接下载的代码使用的是 master 分支,驱动程序需要在这里下载 https://developers.google.cn/android/blobs-preview 以我的 pixel 3 XL 为例,我需要下载的驱动是 点击 Link 下载两个文件,然后进行解压到代码根目录,然后执行 sh 脚本释放驱动到合适的位置,二进制文件及其对应的 makefile 将会安装在源代码树的 vendor/ 层次结构中 2.2 下载特定 Tag 的代码所对应的驱动 如果下载的时候加了 -b ,那么就需要查看对应的 tag 所对应的驱动,地址如下:https://developers.google.cn/android/drivers 以我的 pixel 3 XL 为例,下载的 TAG 为 android-12.0.0_r3 (repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest -b android-12.0.0_r3) 那么我们需要找到下面的部分,这里的 SP1A.210812.016.A1 跟上面 4.2 节是对应的,即 Tag android-12.0.0_r3 对应的 Build ID 是 SP1A.210812.016.A1。大家可以根据自己下载的 TAG 找到对应的 Build ID,然后根据 Build ID 寻找对应的驱动即可 https://developers.google.cn/android/drivers 跟 4.2 节下载的 Tag 是对应的: 2.3 驱动提取 下载的内容解压后,是两个 sh 文件,以我的 Pixel 3 XL 为例,在代码根目录执行,使用 D 来向下翻页,直到最后手动输入 I ACCEPT 1 2 # 解压缩 extract-google_devices-crosshatch.sh ./extract-google_devices-crosshatch.sh 1 2 # 解压缩 ./extract-qcom-crosshatch.sh ./extract-qcom-crosshatch.sh 3. 代码编译 代码和驱动都下载好之后,就可以开始代码的编译工作了,由于新版本不再支持 Mac 编译,所以建议大家还是使用 Linux 来进行编译,推荐使用 Ubuntu 3.1 设置编译环境 参考:https://source.android.google.cn/setup/build/initializing Ubuntu 18.04 以上直接运行: 1 sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig 3.2 设置代码编译环境 每次关闭 Shell 之后都需要重新执行下面这个脚本,相当于配置了一下编译环境 1 source build/envsetup.sh 或者 1 . build/envsetup.sh 3.3 选择编译目标 1 lunch 运行 lunch 之后,会有一堆设备出来让你选择,还是以我的 Pixel 3 XL 为例,其代号是 ,在这里可以查看所有机型对应的代号:https://source.android.google.cn/setup/build/running#selecting-device-build Pixel 3 XL 对应的代号是:crosshatch 所以我选择编译的是 aosp_crosshatch-userdebug ,这里可以输入编号也可以直接输入 aosp_crosshatch-userdebug 然后脚本会进行一系列的配置,输出下面的内容 3.4 全部编译 使用 m 构建所有内容。m 可以使用 -jN 参数处理并行任务。如果您没有提供 -j 参数,构建系统会自动选择您认为最适合您系统的并行任务计数。 1 m 如上所述,您可以通过在 m 命令行中列出相应名称来构建特定模块,而不是构建完整的设备映像。此外,m 还针对各种特殊目的提供了一些伪目标。以下是一些示例: droid - m droid 是正常 build。此目标在此处,因为默认目标需要名称。 all - m all 会构建 m droid 构建的所有内容,加上不包含 droid 标记的所有内容。构建服务器会运行此命令,以确保包含在树中且包含 Android.mk 文件的所有元素都会构建。 m - 从树的顶部运行构建系统。这很有用,因为您可以在子目录中运行 make。如果您设置了 TOP 环境变量,它便会使用此变量。如果您未设置此变量,它便会从当前目录中查找相应的树,以尝试找到树的顶层。您可以通过运行不包含参数的 m 来构建整个源代码树,也可以通过指定相应名称来构建特定目标。 mma - 构建当前目录中的所有模块及其依赖项。 mmma - 构建提供的目录中的所有模块及其依赖项。 croot - cd 到树顶部。 clean - m clean 会删除此配置的所有输出和中间文件。此内容与 rm -rf out/ 相同。 运行 m help 即可查看 m 提供的其他命令 输入 m 之后开始第一次全部编译,漫长的等待,编译时间取决于你的电脑配置…主要是 cpu 和内存,建议内存 32G 走起,cpu 也别太烂 编译成功之后,会有下面的输出 4. 刷机 自己编译的 UserDebug 固件用来 Debug 是非常方便的,不管是用来 Debug Framework 还是 App 编译好之后下面开始刷机,以我的测试机器 Pixel 3 XL 为例,依次执行下面的命令 1 2 3 4 5 6 7 adb reboot fastboot # 等待手机进入 fastboot 界面之后 fastboot flashall -w # 刷机完成之后,执行 fastboot reboot 长期系统即可 fastboot reboot 刷机截图如下 之后手机会自动重启,然后进入主界面,至此,我们的代码下载-编译-刷机的这部分就结束了 自己编译的 AOSP 的 Launcher 比较丑,因为没有 Google 闭源的那些套件的加持,看上去还是很简陋的,自带的 App 非常少,而且基本上没怎么维护,给到手机厂商的就是这么一个东西 还是官方的 Pixel 带的 Launcher 好看(Google 开发和维护) 如果在刷机的过程中遇到问题,可刷官方的刷机包拯救 :https://developers.google.cn/android/images 5. End 本文主要是讲如何下载、编译、刷机,后续的代码导入、修改和编译模块、代码 Debug 等,会另起一篇文章来介绍 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
在讨论 Android 性能问题的时候,卡顿、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲,因为引起卡顿、响应慢、ANR 的原因类似,只不过根据重要程度,被人为分成了卡顿、响应慢、ANR 三种,所以我们可以定义广义上的卡顿,包含了卡顿、响应慢和 ANR 三种,所以如果用户反馈说手机卡顿或者 App 卡顿,大部分情况下都是广义上的卡顿,需要搞清楚,到底出现了哪一种问题 如果是动画播放卡顿、列表滑动卡顿这种,我们一般定义为 狭义的卡顿,对应的英文描述我觉得应该是 Jank;如果是应用启动慢、亮灭屏慢、场景切换慢,我们一般定义为 响应慢,对应的英文描述我觉得应该是 Slow ;如果是发生了 ANR,那就是 应用无响应问题 。三种情况所对应的分析方法和解决方法不太一样,所以需要分开来讲 另外在 App 或者厂商内部,卡顿、响应速度、ANR 这几个性能指标都是有单独的标准的,比如 掉帧率、启动速度、ANR 率等,所以针对这些性能问题的分析和优化能力,对开发者来说就非常重要了 本文是响应速度系列的第三篇,主要是讲在使用 Systrace 分析应用响应速度问题的时候,其中的一些延伸知识,包括启动速度测试、Log 输出解读、Systrace 状态解读、三方启动库等内容 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 1. Systrace 中进程三种状态解读 Systrace 中,进程的任务最常见的有三种状态:Sleep、Running、Runnable。在优化的过程中,这几个状态也需要我们关注。进程任务状态在最上面,以颜色来做区分: 绿色:Running 蓝色:Runnable 白色:Sleep 1.1 如何分析 Sleep 状态的 Task 一般白色的 Sleep 有两种,即应用主动 Sleep 和被动 Sleep nativePoll 这种,一般属于主动 Sleep,因为没有消息处理了,所以进入 Sleep 状态等待 Message,这种一般是正常的,我们不需要去关注。比如两帧之间的那段,就是主动 sleep 的 被动 Sleep 一般是由用户主动调用 sleep,或者用 Binder 与其他进程进行通信,这个是我们最常见的,也是分析性能问题的时候经常会遇到的,需要重点关注 如下图,这种在启动过程中,有较长时间的 sleep 情况,一般下面就可以看到是否在进行 Binder 通信,如果在启动过程中有频繁的 Binder 通信,那么应用等待的时间就会变长,导致响应时间变慢 这种一般可以点击这个 Task 最下面的 binder transaction 来查看 Binder 调用信息,比如 有时候没有 Binder 信息,是被其他的等待的线程唤醒,那么可以查看唤醒信息,也可以找到应用是在等待什么 放大上图中我们点击的 Runnable 的地方 1.2 如何分析 Running 状态的 Task Running 状态的任务就是目前在 CPU 某一个核心上运行的任务,如果某一段任务是 Running 状态,且耗时变长,那么需要分析: 是否应用的本身逻辑耗时,比如新增了某些代码逻辑 是否跑在了对应的核心上 在某些 Android 机器上,大家一般会对 App 的主线程和渲染线程进行调度方面的优化:一般前台应用的 UI Thread 和 RenderThread 都是跑在大核上的 1.3 如何分析 Runnable 状态的 Task 一个 Task 要从 Sleep 状态转到 Running 状态,必须先变成 Runnable 状态,其状态转换图如下 在 Systrace 上的表现如下 正常情况下,应用进入 Runnable 状态之后,会马上被调度器调度,进入 Running 状态,开始干活;但是在系统繁忙的时候,应用就会有大量的时间在 Runnable 状态,因为 cpu 已经跑满,各种任务都需要排队等待调度 如果应用启动的时候出现大量的 Runnable 任务,那么需要查看系统的状态 2. TraceView 工具在响应速度方面的使用 TraceView 指的是我们在 AS Profiler 里面抓取 CPU 信息的时候出现的那个,大家看下面的截图就知道了 2.1 如何抓取应用启动时候的 TraceView 使用下面的命令可以抓取应用的冷启动,这些命令也可以分开执行,需要把里面的包名和 Activity 名切换成自己应用的包名 1 adb shell am start -n com.aboback.wanandroidjetpack/.splash.SplashActivity --start-profiler /data/local/tmp/traceview.trace --sampling 1 && sleep 10 && adb shell am profile stop com.aboback.wanandroidjetpack && adb pull /data/local/tmp/traceview.trace . 或者分开执行上面的命令 1 2 3 4 5 6 7 8 9 10 // 1. 冷启动 App,sampleing = 1 意思是 1ms 采样一次 adb shell am start -n com.aboback.wanandroidjetpack/.splash.SplashActivity --start-profiler /data/local/tmp/traceview.trace --sampling 1 // 2. 等待应用完全启动之后,结束 profile adb shell am profile stop com.aboback.wanandroidjetpack // 3. 将 Trace 文件从手机里面 pull 出来 adb pull /data/local/tmp/traceview.trace . // 4. 使用 Android Studio 打开 traceview.trace 文件 2.2 TraceView 工具怎么看 抓出来的 TraceView 可以直接在 Android Studio 中打开 其中图里面用绿色标记的函数,就是应用自己的函数,黄色标注的是系统的函数 Application.onCreate Activity.onCreate doFrame WebView 初始化 2.3 TraceView 工具的弊端 由于采样比较细,所以会性能损耗比较大,所以抓出来的 TraceView,其中每个方法的执行时间是不准的,所以不可用作为真实的时间参考,但是可以用来定位具体的函数调用栈。 需要跟 Systrace 来进行互补 3. SimplePerf 工具在启动速度分析的使用 使用 SimplePerf 工具也可以抓取启动时候的堆栈信息,既包括 Java 也包括 Native 比如我们要抓取 com.aboback.wanandroidjetpack 这个应用的冷启动,可以执行下面的命令(SimplePerf 的环境初始化参考 https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md 这篇文章 ,其中 app_profiler.py 就是 SimplePerf 的工具) 1 python app_profiler.py -p com.aboback.wanandroidjetpack 执行上面的命令之后,需要手动在手机上启动 App,然后主动结束 1 2 3 4 5 $ python app_profiler.py -p com.aboback.wanandroidjetpack INFO:root:prepare profiling INFO:root:start profiling1 INFO:root:run adb cmd: ['adb', 'shell', '/data/local/tmp/simpleperf', 'record', '-o', '/data/local/tmp/perf.data', '-e task-clock:u -f 1000 -g --duration 10', '--log', 'info', '--app', 'com.aboback.wanandroidjetpack'] simpleperf I environment.cpp:601] Waiting for process of app com.aboback.wanandroidjetpack simpleperf I environment.cpp:593] Got process 32112 for package com.aboback.wanandroidjetpack 抓取结束之后,调用解析脚本来生成 html 报告 1 python report_html.py 就会得到下面这个 不仅可以看到 Java 层的堆栈,也可以看到 Native 的堆栈,这里只是简单的使用,更详细的方法可以参考下面几个文档 SimplePerf 初步试探 https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md Android application profiling https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md Android platform profiling https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_platform_profiling.md Executable commands reference https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/executable_commands_reference.md Scripts reference https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/scripts_reference.md 4. 其他组件启动时在 Systrace 中的位置 4.1 Service 的启动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { updateProcessState(processState, false); CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; s.compatInfo = compatInfo; sendMessage(H.CREATE_SERVICE, s); } public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, int processState) { updateProcessState(processState, false); BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; s.rebind = rebind; sendMessage(H.BIND_SERVICE, s); } 可以看到,代码执行都是往 H 这个 Handler 中发送 Message,所以如果我们在代码里面启动 Service,并不是马上就执行的,而是由 MessageQueue 里面的 Message 顺序决定的 放大真正执行的部分可以看到,其执行的时机是在 MessageQueue 按照 Message 的顺序执行(这里是在应用第一帧执行结束后),后面的 Message 就是应用自己的 Message、启动 Service、执行广播接收器 4.2 执行自己的 Message 执行自定义的 Message 在 Systrace 中的显示 4.3 启动 Service Service 启动在 Systrace 中的显示 4.4 启动 BroadcastReceiver 执行 Receiver 在 Systrace 中的显示 Broadcast 的注册:一般是在 Activity 生命周期函数中注册,在哪里注册就在哪里执行 4.5 ContentProvider 的启动时机 5. AppStartup 是否能优化启动速度? 三方库的初始化 很多三方库都需要在 Application 中进行初始化,并顺便获取到 Application 的上下文 但是也有的库不需要我们自己去初始化,它偷偷摸摸就给初始化了,用到的方法就是使用 ContentProvider 进行初始化,定义一个 ContentProvider,然后在 onCreate 拿到上下文,就可以进行三方库自己的初始化工作了。而在 APP 的启动流程中,有一步就是要执行到程序中所有注册过的 ContentProvider 的 onCreate 方法,所以这个库的初始化就默默完成了。 这种做法确实给集成库的开发者们带来了很大的便利,现在很多库都用到了这种方法,比如 Facebook,Firebase,WorkManager ContentProvider 的初始化时机如下: 但是当大部分三方库使用这种方法初始化的时候,就会有下面几个问题 启动过程中的 ContentProvider 过多 应用开发者无法控制使用这种方式初始化的库的初始化时机 无法处理这些三方库的依赖 AppStartup 库 针对上面的情况,Google 推出了 AppStartup 库,AppStartup 库的优点 可以共享单个 Contentprovider 可以明确地设置初始化顺序 通过这个库可以移除三方库的 ContentProvider 启动时候自动初始化的步骤,手动通过 LazyLoad 的方式启动,这样可以起到优化启动速度的作用 根据测算结果来看,使用 AppStartup 库并不能显著加快应用启动速度,除非你有非常多 (50+)的 ContentProvider 在应用启动的时候初始,那么 AppStartup 才会有比较明显的效果 如果三方的 SDK 使用 ContentProvider 初始化耗时,那么可以考虑针对这个 ContentProvider 进行延迟初始化,比如 1 2 3 4 5 6 7 8 ExampleLoggerInitializer 的 meta-data 当中加入了一个 tools:node=”remove”的标记 总结 App Startup 的设计是为了解决一个问题:即不同的库使用不同的 ContentProvider 进行初始化,导致 ContentProvider 太多,管理杂乱,影响耗时的问题 App Startup 具体能减少多少耗时时间:根据测试,如果二三十个三方库都集成了 App Startup,减少的耗时大概在 20ms 以内 App Startup 的使用场景应该 APK 有很多的 ContentProvider 在启动时候初始化 APK 中有的三方库 ContentProvider 初始化很耗时,但是又不是必须要在启动的时候初始,可以按需初始化 应用开发者想自己控制各个库的初始化时机或者初始化顺序 需要 App 开发同学验证 检查打包出来的 apk 的配置文件里面看一下,有多少个三方库是利用 ContentProvider 初始化的(或者在 AS 的 src\main\AndroidManifest.xml 文件最下面打开 Merged Manifest 标签查看) 确认这些 ContentProvider 在启动时候的耗时 确认哪些 ContentProvider 可以延迟加载或者用时加载 如果需要的话,接入 AppStartup 库 6. IdleHandler 在 App 启动场景下的使用 在启动优化的过程中,idleHandler 可以在 MessageQueue 空闲的时候执行任务,如下图,可以很清晰地查看 idleHandler 的执行时机 其使用场景如下: 在启动的过程中,可以借助 idleHandler 来做一些延迟加载的事情。比如在启动过程中 Activity 的 onCreate 里面 addIdleHandler,这样在 Message 空闲的时候,可以执行这个任务 进行启动时间统计:比如在页面完全加载之后,调用 activity.reportFullyDrawn 来告知系统这个 Activity 已经完全加载,用户可以使用了,比如下面的例子,在主页的 List 加载完成后,调用 activity.reportFullyDrawn 其对应的 Systrace 如下 这时候得到的应用的冷启动时间才是正常的 另外系统有些功能,也会依赖于 FullyDrawn,所以建议主动上报(即主动在 App 完全启动后调用 activity.reportFullyDrawn) 系列文章 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 基础知识系列-放个链接在这里方便大家直接点过去 参考文章 Android 应用启动全流程分析 探究 | App Startup 真的能减少启动耗时吗 Jetpack 新成员,App Startup 一篇就懂 App Startup Android App 启动优化全记录 Android application profiling 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
在讨论 Android 性能问题的时候,卡顿、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲,因为引起卡顿、响应慢、ANR 的原因类似,只不过根据重要程度,被人为分成了卡顿、响应慢、ANR 三种,所以我们可以定义广义上的卡顿,包含了卡顿、响应慢和 ANR 三种,所以如果用户反馈说手机卡顿或者 App 卡顿,大部分情况下都是广义上的卡顿,需要搞清楚,到底出现了哪一种问题 如果是动画播放卡顿、列表滑动卡顿这种,我们一般定义为 狭义的卡顿,对应的英文描述我觉得应该是 Jank;如果是应用启动慢、亮灭屏慢、场景切换慢,我们一般定义为 响应慢 ,对应的英文描述我觉得应该是 Slow ;如果是发生了 ANR,那就是 应用无响应问题 。三种情况所对应的分析方法和解决方法不太一样,所以需要分开来讲 另外在 App 或者厂商内部,卡顿、响应速度、ANR 这几个性能指标都是有单独的标准的,比如 掉帧率、启动速度、ANR 率等,所以针对这些性能问题的分析和优化能力,对开发者来说就非常重要了 本文是响应速度系列的第二篇,主要是以 Android App 冷启动为例,讲解如何使用 Systrace 来分析 App 冷启动 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 1. 准备工作 这个案例和对应的 Systrace 偏工程化一些,省略了很多细节,因为应用的启动流程涉及的知识非常广,如果每个都细化的话,会有很大的篇幅。推荐大家看这篇文章,非常详细:Android 应用启动全流程分析 所以这里以 Systrace 为主线,讲解应用启动的时候各个关键模块的大概工作流程。了解大概流程之后,就可以分段去深入自己感兴趣或者自己负责的部分,这里首先放一张 Systrace 和手机截图所对应的图,大家可以先看看这个图,然后再往下看(博客里面 Perfetto 和 Systrace 混合使用) 为了更方便分析应用冷启动,我们需要做下面的准备工作 打开 Binder 调试,方便在 Trace 中显示 Binder 信息(即可以在 Systrace 中看到 Binder 调用的函数)- 需要 Root 开启 ipc debug: adb shell am trace-ipc start 抓取结束后,可以执行下面的命令关闭adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt Trace 命令加入 irq tag,默认的命令不包含 irq,需要自己加 irq 的 TAG,这样打开 Trace 之后,就可以看到 irq 相关的内容,最后的抓 trace 命令如下: python /mnt/d/Android/platform-tools/systrace/systrace.py gfx input view webview wm am sm rs bionic power pm ss database network adb idle pdx sched irq freq idle disk workq binder_driver binder_lock -a com.xxx.xxx ,注意这里的 com.xxx.xxx 换成自己的包名,如果不是调试特定的包名,可以去掉 -a com.xxx.xxx 推荐 :如果要 Debug 的 App 可以进行编译(即可以使用 Gradle 编译,一般自己开发的项目都可以),可以在分析响应速度问题的时候,引入 TraceFix 库(接入方法参考 https://github.com/Gracker/TraceFix)。接入之后,编译的时候就会进行代码插桩,在 App 代码的每一个函数中都插入 Trace 点,这样在分析的时候可以看到更详细的 App 的信息 使用插件前,只能看到 Framework 里面的 Trace 点 使用插件后,可以看到 Trace 中显示的信息多了很多(App 自身的代码逻辑,Framework 的代码没法插桩) 2. Android App 冷启动流程分析 本文以 在桌面上冷启动一个 Android App 为例,应用冷启动的整个流程包含了从用户触摸屏幕到应用完全显示的整个流程,其中涉及到 触摸屏中断处理阶段 InputReader 和 InputDispatcher 处理 input 事件阶段 Launcher 处理 input 事件阶段 SystemServer 处理启动事件 启动动画 应用启动和自身逻辑阶段 上一篇文章有讲到响应速度问题,需要搞清楚 起点 和 终点,对于应用冷启动来说,起点就是 input 事件,终点就是应用完全展示给用户(用户可操作) 下面将从上面几个关键流程,通过 Systrace 的来介绍整个流程 2.1 触摸屏中断处理阶段 由于我们的案例是在桌面冷启动一个 App,那么在手指触摸手机屏幕的时候,触摸屏会触发中断,这个中断我们最早能在 Systrace 中看到的地方如下: 对应的 cpu ss 区域和 中断区域(加了 irq 的 tag 才可以看到) 一般来说,点击屏幕会触发若干个中断,这些信号经过处理之后,触摸屏驱动会把这些点更新到 EventHub 中,让 InputReader 和 InputDIspatcher 进行进一步的处理。这一步一般不会出现什么问题,厂商这边对触摸屏的调教可能会关注这里 2.2 InputReader 和 InputDispatcher 处理 Input 事件阶段 InputReader 和 InputDispatcher 这两个线程跑在 SystemServer 里面,专门负责处理 Input 事件,具体的流程可以参考Android Systrace 基础知识 - Input 解读 这篇文章 这里由于我们是点击桌面上的一个 App 的图标,可以看到底层上报上来的事件包括一个 Input_Down 事件 + 若干个 Input Move 事件 + 一个 Input Up 事件,组成了一个完整的点击事件 由于 Launcher 在进程创建的时候就注册了 Input 监听,且此时 Launcher 在前台且可见,所以 Launcher 进程可以收到这些 Input 事件,并根据 Input 事件的类型进行处理,input 事件在 SystemServer 和 App 的流转在 Systrace 中的具体表现可以参考 Android Systrace 基础知识 - Input 解读 ,这里把核心的两张图放上来 2.2.1 Input 事件在 SystemServer 中流转 看下图即可,如果要看更详细的,可以查看 Android Systrace 基础知识 - Input 解读 2.2.2 Input 事件在 Launcher 进程流转 看下图即可,如果要看更详细的,可以查看 Android Systrace 基础知识 - Input 解读 2.3 Launcher 进程处理 Input 事件阶段 Launcher 处理 Input 事件也是响应时间的一个重要阶段,主要包括两个响应速度指标 点击桌面到桌面第一帧响应(一般 Launcher 会在接收到 Down 事件的时候,将 App 图标置灰,以表示接收到了事件;有的定制桌面 App 图标会有一个缩小的动画,表示被按压) 桌面第一帧响应到启动 App(这段时间指的是桌面在收到 Down 对 App 图标做处理后,到收到 Up 事件判断需要启动 App 的时间) 另外提一下,滑动桌面到桌面第一帧响应时间(这个指的是滑动桌面的场景,左右滑动桌面的时候,用高速相机拍摄,从手指动开始,到桌面动的第一帧的时间)也是一个很重要的响应速度指标,部分厂商也会在这方面做优化,感兴趣的可以自己试试主流厂商的桌面滑动场景(跟原生的机器对比 Systrace 即可) 在冷启动的场景里面,Launcher 在收到 up 事件后,会进行逻辑判断,然后启动对应的 App(这里主要是交给 AMS 来处理,又回到了 SystemServer 进程) 这个阶段通常也是做系统优化的会比较关注,做 App 的同学还不需要关注到这里(Launcher App 的除外);另外在最新的版本,应用启动的动画是由 Launcher 和 SystemServer 共同完成的,目的就是可以做一些复杂的动画而没有割裂感,大家可以用慢镜头拍一下启动时候和退出应用的动画,可以看到有的应用图标是分层的,甚至会动,这是之前纯粹由 SystemServer 这边来做动画所办不到的 2.4 SystemServer 处理 StartActivity 阶段 SystemServer 处理主要是有2部分 处理启动命令 通知 Launcher 进入 Pause 状态 fork 新的进程 处理启动命令 这个 SystemServer 进程中的 Binder 调用就是 Launcher 通过 ActivityTaskManager.getService().startActivity 调用过来的 fork 新的进程,则是在判断启动的 Activity 的 App 进程没有启动后,需要首先启动进程,然后再启动 Activity,这里是冷启动和其他启动不一样的地方。fork 主要是 fork Zygote64 这个进程(部分 App 是 fork 的 Zygote32 ) Zygote 64 位进程执行 Fork 操作 对应的 App 进程出现 对应的代码如下,这里就正式进入了 App 自己的进程逻辑了 应用启动后,SystemServer 会记录从 startActivity 被调用到应用第一帧显示的时长,在 Systrace 中的显示如下(注意结尾是应用第一帧,如果应用启动的时候是 SplashActivity -> MainActivity,那么这里的结尾只是 SplashActivity,MainActivity 的完全启动需要自己查看) 2.5 应用进程启动阶段 通常的大型应用,App 冷启动通常包括下面三个部分,每一个部分耗时都会导致应用的整体启动速度变慢,所以在优化启动速度的时候,需要明确知道应用启动结束的点(需要跟测试沟通清楚,一般是界面保持稳定的那个点) 应用进程启动到 SplashActivity 第一帧显示(部分 App 没有 SplashActivity,所以可以省略这一步,直接到进程启动到 主 Activit 第一帧显示 ) SplashActivity 第一帧显示到主 Activity 第一帧显示 主 Activity 第一帧显示到界面完全显示 下面针对这三个阶段来具体分析(当然你的 App 如果简单的话,可能没有 SplashActivity ,直接进的就是主 Activity,那么忽略第二步就可以了) 应用进程启动到 SplashActivity 第一帧显示 由于是冷启动,所以 App 进程在 Fork 之后,需要首先执行 bindApplication ,这个也是区分冷热启动的一个重要的点。Application 的环境创建好之后,就开始组件的启动(这里是 Activity 组件,通过 Service、Broadcast、ContentProvider 组件启动的进程则会在 bindApplication 之后先启动这些组件) Activity 的生命周期函数会在 Activity 组件创建的时候执行,包括 onStart、onCreate、onResume 等,然后还要经过一次 Choreographer#doFrame 的执行(包括 measure、layout、draw)以及 RenderThread 的初始化和第一帧任务的绘制,再加上 SurfaceFlinger 一个 Vsync 周期的合成,应用第一帧才会真正显示(也就是下图中 finishDrawing 的位置),这部分详细的流程可以查看 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 SplashActivity 第一帧显示到主 Activity 第一帧显示 大部分的 App 都有 SplashActivity 来播放广告,播放完成之后才是真正的主 Activity 的启动,同样包括 Activity 组件的创建,包括 onStart、onCreate、onResume 、自有启动逻辑的执行、WebView 的初始化等等等等,直到主 Activity 的第一帧显示 主 Activity 第一帧显示到界面完全加载并显示 一般来说,主 Activity 需要多帧才能显示完全,因为有很多资源(最常见的是图片)是异步加载的,第一帧可能只加载了一个显示框架、而其中的内容在准备好之后才会显示出来。这里也可以看到,通过 Systrace 不是很方便来判断应用冷启动的终点(除非你跟测试约定好,在某个 View 显示之后就算启动完成,然后你在这个 View 里面打个 Systrace 的 Tag,通过跟踪这个 Tag 就可以粗略判断具体 Systrace 里面哪一帧是启动完成的点) 我制作了一个 Systrace + 截图的方式,来进行演示,方便你了解 App 启动各个阶段都对应在 Systrace 的哪里(使用的是一个开源的 WanAndroid 客户端) End 本文重点放在了如何在 Systrace 中展示 App 的完整冷启动流程,方便大家在做 App 的启动优化的时候,可以通过 Systrace 来快速定位到启动瓶颈,也方便进行竞品的对比和分析, 至于如何分析,可以查看 Systrace 响应速度实战 1 :了解响应速度原理 的分析套路部分 至于如何优化,可以查看 Android App 启动优化全记录 这篇文章,这里就不再重复了。不过随着技术的发展,有些优化手段会消失,而会有新的优化手段冒出来,我也会对这篇文章进行维护,如果大家发现新的优化技术,麻烦博客留言或者加微信(553000664)通知我,我来进行调研和更新 系列文章 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 基础知识系列-放个链接在这里方便大家直接点过去 参考文章 Android 应用启动全流程分析 探究 | App Startup 真的能减少启动耗时吗 Jetpack 新成员,App Startup 一篇就懂 App Startup Android App 启动优化全记录 Android application profiling 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
在讨论 Android 性能问题的时候,卡顿、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲,因为引起卡顿、响应慢、ANR 的原因类似,只不过根据重要程度,被人为分成了卡顿、响应慢、ANR 三种,所以我们可以定义广义上的卡顿,包含了卡顿、响应慢和 ANR 三种,所以如果用户反馈说手机卡顿或者 App 卡顿,大部分情况下都是广义上的卡顿,需要搞清楚,到底出现了哪一种问题 如果是动画播放卡顿、列表滑动卡顿这种,我们一般定义为 狭义的卡顿,对应的英文描述我觉得应该是 Jank;如果是应用启动慢、亮灭屏慢、场景切换慢,我们一般定义为 响应慢 ,对应的英文描述我觉得应该是 Slow ;如果是发生了 ANR,那就是 应用无响应问题 。三种情况所对应的分析方法和解决方法不太一样,所以需要分开来讲 另外在 App 或者厂商内部,卡顿、响应速度、ANR 这几个性能指标都是有单独的标准的,比如 掉帧率、启动速度、ANR 率等,所以针对这些性能问题的分析和优化能力,对开发者来说就非常重要了 本文是响应速度系列的第一篇,主要是讲响应速度相关的理论知识,包括性能工程概述、响应速度涉及到的知识点、响应速度的分析方法和套路等 关于卡顿的文章可以参考这一篇 Systrace 流畅性实战 1 :了解卡顿原理 ,ANR 的文章后续会介绍,本文主要是讲响应速度相关的基本原理 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 性能工程 在介绍响应速度的原理之前,这里先放一段 这本书中对于性能的描述,具体来说就是方法论,非常贴合本文的主题,也强烈推荐各位搞性能优化的同学,把这本书作为手头常读的方法论书籍: 性能是充满挑战的 系统性能工程是一个充满挑战的领域,具体原因有很多,其中包括以下事实,系统性能是主观的、复杂的,而且常常是多问题并存的 性能是主观的 技术学科往往是客观的,太多的业界人士审视问题非黑即白。在进行软件故障查找的时候,判断 bug 是否存在或 bug 是否修复就是这样。bug 的出现总是伴随着错误信息,错误信息通常容易解读,进而你就明白错误为什么会出现了 与此不同,性能常常是主观性的。开始着手性能问题的时候,对问题是否存在的判断都有可能是模糊的,在问题被修复的时候也同样,被一个用户认为是“不好”的性能,另一个用户可能认为是“好”的 系统是复杂的 除了主观性之外,性能工程作为一门充满了挑战的学科,除了因为系统的复杂性,还因为对于性能,我们常常缺少一个明确的分析起点。有时我们只是从猜测开始,比如,责怪网络,而性能分析必须对这是不是一个正确的方向做出判断 性能问题可能出在子系统之间复杂的互联上,即便这些子系统隔离时表现得都很好。也可能由于连锁故障(cascading failure)出现性能问题,这指的是一个出现故障的组件会导致其他组件产生性能问题。要理解这些产生的问题,你必须理清组件之间的关系,还要了解它们是怎样协作的 瓶颈往往是复杂的,还会以意想不到的方式互相联系。修复了一个问题可能只是把瓶颈推向了系统里的其他地方,导致系统的整体性能并没有得到期望的提升。 除了系统的复杂性之外,生产环境负载的复杂特性也可能会导致性能问题。在实验室环境很难重现这类情况,或者只能间歇式地重现 解决复杂的性能问题常常需要全局性的方法。整个系统——包括自身内部和外部的交互——都可能需要被调查研究。这项工作要求有非常广泛的技能,一般不太可能集中在一人身上,这促使性能工程成为一门多变的并且充满智力挑战的工作 可能有多个问题并存 找到一个性能问题点往往并不是问题本身,在复杂的软件中通常会有多个问题 性能分析的又一个难点:真正的任务不是寻找问题,而是辨别问题或者说是辨别哪些问题是最重要的 要做到这一点,性能分析必须量化(quantify)问题的重要程度。某些性能问题可能并不适用于你的工作负载或者只在非常小的程度上适用。理想情况下,你不仅要量化问题,还要估计每个问题修复后能带来的增速。当管理层审查工程或运维资源的开销缘由时,这类信息尤其有用。 有一个指标非常适合用来量化性能,那就是 延时(latency) – 以上几段内容摘录自 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 响应速度概述 响应速度是应用 App 性能的重要指标之一。响应慢通常表现为点击效果延迟、操作等待或白屏时间长等,主要场景包括: 应用启动场景,包括冷启动、热启动、温启动等 界面跳转场景,包括应用内页面跳转、App 之间跳转 其他非跳转的点击场景(开关、弹窗、长按、控件选择、单击、双击等) 亮灭屏、开关机、解锁、人脸识别、拍照、视频加载等场景 从原理上来说,响应速度场景往往是由一个 input 事件(以 Message 的形式给到需要处理的应用主线程)触发(比如点击、长按、电源键、指纹等),由一个或者多个 Message 的执行结束为结尾,而这些 Message 中一般都有关键的界面绘制相关的 Message 。衡量一个场景的响应速度,我们通常从事件触发开始计时,到应用处理完成计时结束,这一段时间就称为响应时间。 如下图所示,响应速度的问题,通常就是这些 Message 的某个执行超过预期(主观),导致最终完成的时间长于用户期待的时间 由于响应速度是一个比较主观的性能指标(而流畅度就是一个很精确的指标,掉一帧就是掉一帧),而且根据角色的不同,对这个性能指标的判定也不同,比如 Android 系统开发者和应用开发者以及测试同学,对 应用冷启动 的起点和终点就有不同的判定: 系统开发者 往往从 input 中断开始看,部分以应用第一帧为结束点(因为比较好计算),部分以应用加载完成为结束点(比较主观,除非结束点比较容易通过工具去判断),主要是以优化应用的整体性能为主,涉及到的方面就比较广,包括 input 事件传递、SystemServer、SurfaceFlinger、Kernel 、Launcher 等 App 开发者 一般从 Application 的 onCreate 或者 attachContext 开始看,大部分以界面完全加载或者用户可操作为结束点,因为是自己的应用,结束点在代码里面可以主动加,主要还是以优化应用自身的启动速度为主,市面上讲启动速度优化的,大部分是讲这部分 测试同学 则更多从用户的真实体验角度来看,以桌面点击应用图标且应用图标变色为第一帧,内容完全加载为结束点。测试过程一般使用 高速相机 + 自动化,通过机械手和图形识别技术,可以自动进行响应速度测试并抓取相关的测试数据 响应速度问题分析思路 分清起点和终点 分析响应速度,最重要的是要找到起点和终点,上一节讲到,不同角色的开发者,对这个性能指标的判定起点和终点都不一样;而且这个指标有很主观的成分,所以在开始的时候,就要跟各方来确定好起点和终点,具体的数值标准,下面一些手段可以帮助大家来确定 竞品分析。一般来说,响应速度这个指标都会有一个对标的竞品,竞品手机或者竞品 App,相同的条件下,竞品手机或者竞品 App 从点击到响应花费了多少时间,可以作为一个标准 对比前一个版本。有时候系统进行大版本升级或者 App 进行版本迭代,那么上一个版本的数据就可以拿来作为标准进行对比 一般来说,起点都比较好确定,无非是一个点击事件或者一个自定义的触发事件;而终点的确定就比较麻烦,比如如何确定一个复杂的 App (比如淘宝)启动完成的时间点,用 Systrace 的第一帧或者 Log 输出的 Displayed 时间或者 onWindowFocusChange 回调的时间显然是不准确的。目前市面上使用高速相机 + 图像识别来做是一个比较主流的做法 响应速度常见问题 Android 系统自身原因导致响应慢 下面这些列举的是 Android 系统自身的原因,与 Android 机器的性能有比较大的关系,性能越差,越容易出现响应速度问题。下面就列出了 Android 系统原因导致的 App 响应速度出现问题的原因,以及这个时候 App 端在 Systrace 中的表现 CPU 频率不足 App 端的表现:主线程处于 Running 状态,但是执行耗时变长 CPU 大小核调度:关键任务跑到了小核 App 端的表现:Systrace 看主线程处于 Running 状态,但是执行耗时变长 SystemServer 繁忙,主要影响 响应 App 主线程 Binder 调用处理耗时 App 端的表现:Systrace 看主线程处于 Sleep 状态,在等待 Binder 调用返回 应用启动过程逻辑处理耗时 App 端的表现:Systrace 看主线程处于 Sleep 状态,在等待 Binder 调用返回 SurfaceFlinger 繁忙,主要影响应用的渲染线程的 dequeueBuffer、queueBuffer App 端的表现:Systrace 看应用渲染线程的 dequeueBuffer、queueBuffer 处于 Binder 等待状态 系统低内存,低内存的时候,很大概率出现下面几种情况,都会对 SystemServer 和应用有影响 低内存的时候,有些应用会频繁被杀和启动,而应用启动时一个重操作,会占用 CPU 资源,导致前台 App 启动变慢 App 端的表现:Systrace 看应用主线程 Runnable 状态变多,Running 状态变少,整体函数执行耗时增加 低内存的时候,, 很容易触发各个进程的 GC , , 用于内存回收的 HeapTaskDeamon、kswapd0 出现非常频繁 App 端的表现:Systrace 看应用主线程 Runnable 状态变多,Running 状态变少,整体函数执行耗时增加 低内存会导致磁盘 IO 变多, 如果频繁进行磁盘 IO , 由于磁盘 IO 很慢, 那么主线程会有很多进程处于等 IO 的状态, 也就是我们经常看到的 Uninterruptible Sleep App 端的表现:Systrace 看应用主线程 Uninterruptible Sleep 和 Uninterruptible Sleep - IO 状态变多,Running 状态变少,整体函数执行耗时增加 系统触发温控频率被限制:由于温度过高,CPU 最高频率被限制 App 端的表现:主线程处于 Running 状态,但是执行耗时变长 整机 CPU 繁忙:可能有多个高负载进程同时在运行,或者有单个进程负载过高跑满了 CPU App 端的表现:从 Systrace 来看,CPU 区域的任务非常满,所有的核心上都有任务在执行,App 的主线程和渲染线程多处于 Runnable 状态,或者频繁在 Runnable 和 Running 之间切换 应用自身原因 应用自身原因主要是应用启动时候的组件初始化、View 初始化、数据初始化耗时等,具体包括: Application.onCreate:应用自身的逻辑 + 三方 SDK 初始化耗时 Activity 的生命周期函数:onStart、onCreate、onResume 耗时 Services 的生命周期函数耗时 Broadcast 的 onReceive 耗时 ContentProvider 初始化耗时(注意已经被滥用) 界面布局初始化:measure、layout、draw 等耗时 渲染线程初始化:setSurface、queueBuffer、dequeueBuffer、Textureupload 等耗时 Activity 跳转:从 SplashActivity 到 MainActivity 耗时 应用向主线程 post 的耗时 Message 耗时 主线程或者渲染线程等待子线程数据更新耗时 主线程或者渲染线程等待子进程程数据更新耗时 主线程或者渲染线程等待网络数据更新耗时 主线程或者渲染线程 binder 调用耗时 WebView 初始化耗时 初次运行 JIT 耗时 响应速度问题分析套路(以 Systrace 为主) 确认前提条件(老化,数据量、下载等)、操作步骤、问题现象,本地复现 需要明确测试标准 启动时间的起点是哪里 启动时机的终点是哪里 抓取所需的日志信息(Systrace、常规 log 等) 首先分析 Systrace,大概找出差异的点 首先查看应用耗时点,分析对比机差异,这里可以把应用启动阶段分成好几段来看,来对比分析是哪部分时间增加 Application 创建 Activity 创建 第一个 doFrame 后续内容加载 应用自己的 Message 分析应用耗时的点 是否某一段方法自身执行耗时比较久(Running 状态) –> 应用自身问题 主线程是否有大段 Running 状态,但是底下没有任何堆栈 –> 应用自身问题,加 TraceTag 或者使用 TraceView 来找到对应的代码逻辑 是否在等 Binder 耗时比较久(Sleep 状态) –> 检测 Binder 服务端,一般是 SystemServer 是否在等待子线程返回数据(Sleep 状态) –> 应用自身问题,通过查看 wakeup 信息,来找到依赖的子线程 是否在等待子进程返回数据(Sleep 状态) –> 应用自身问题,通过查看 wakeup 信息,来找到依赖的子进程或者其他进程(一般是 ContentProvider 所在的进程) 是否有大量的 Runnable –> 系统问题,查看 CPU 部分,看看是否已经跑满 是否有大量的 IO 等待(Uninterruptible Sleep | WakeKill - Block I/O) –> 检查系统是否已经低内存 RenderThread 是否执行 dequeueBuffer 和 queueBuffer 耗时 –> 查看 SurfaceFlinger 如果分析是系统的问题,则根据上面耗时的点,查看系统对应的部分,一般情况要优先查看系统是否异常,参考上面列出的的系统原因,主要看下面四个区域(Systrace) Kernel 区域 查看关键任务是否跑在了小核 –> 一般小核是 0-3(也有特例),如果启动时候的关键任务跑到了小核,执行速度也会变慢 查看频率是否没有跑满 –> 表现是核心频率没有达到最大值,比如最大值是 2.8Ghz,但是只跑到了 1.8Ghz,那么可能是有问题的 查看 CPU 使用率,是否已经跑满了 –> 表现是 CPU 区域八个核心上,任务和任务之间没有空隙 查看是否低内存 应用进程状态有大量的 Uninterruptible Sleep | WakeKill - Block I/O HeapTaskDeamon 任务执行频繁 kswapd0 任务执行频繁 SystemServer 进程区域 input 事件读取和分发是否有异常 –> 表现是 input 事件传递耗时,比较少见 binder 执行是否耗时 –> 表现是 SystemServer 对应的 Binder 执行代码逻辑耗时 binder 等 am、wm 锁是否耗时–> 表现是 SystemServer 对应的 Binder 都在等待锁,可以通过 wakeup 信息跟踪等锁情况,分析等锁是不是由于应用导致的 是否有应用频繁启动或者被杀 –> 在 Systrace 中查看 startProcess,或者查看 Event Log SurfaceFlinger 进程区域 dequeueBuffer 和 queueBuffer 是否执行耗时 –> 表现是 SurfaceFlinger 的对应的 Binder 执行 dequeueBuffer 和 queueBuffer 耗时 主线程是否执行耗时 –> 表现是 SurfaceFlinger 主线程耗时,可能是在执行其他的任务 Launcher 进程区域(冷热启动场景) Launcher 进程处理点击事件是否耗时 –> 表现在处理 input 事件耗时 Launcher 自身 pause 是否耗时 –> 表现在执行 onPause 耗时 Launcher 应用启动动画是否耗时或者卡顿 –> 表现在动画耗时或者卡顿 初步分析有怀疑的点之后 如果是系统的原因,首先需要看应用自身是否能规避,如果不能规避,则转给系统来处理 如果是应用自身的原因,可以使用 TraceView(AS 自带的 CPU Profiler)、Simple Perf 等继续查看更加详细的函数调用信息,也可以使用 TraceFix 插件,插入更多的 TraceTag 之后,重新抓取 Systrace 来对比分析 问题可能有很多个原因 首先要把影响最大的因素找出来优化,影响比较小的因素可以先忽略 有些问题需要系统的配合才能解决,这时候需要跟系统一起进行调优(比如各大 App 厂商就会有专门跟手机厂商打交道的,手机厂商会以 SDK 的形式,暴露部分系统接口给 App 来使用,比如 Oppo 、华为、Vivo 等) 有些问题影响很小或者无解,这时候需要跟测试同学沟通清楚 有些问题是重复问题或不同平台的相同,可以在 Bug 库中搜索是否有案例 本篇文章主要是一个响应速度基础知识方面的一个普及,其中涉及到大量的系统知识,不熟悉的同学可以跟着 Systrace 基础知识系列 过一下 系列文章 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 基础知识系列-放个链接在这里方便大家直接点过去 参考文章 Android 应用启动全流程分析 探究 | App Startup 真的能减少启动耗时吗 Jetpack 新成员,App Startup 一篇就懂 App Startup Android App 启动优化全记录 Android application profiling 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
不同的人对流畅性(卡顿掉帧)有不同的理解,对卡顿阈值也有不同的感知,所以有必要在开始这个系列文章之前,先把涉及到的内容说清楚,防止出现不同的理解,也方便大家带着问题去看这几篇问题,下面是一些基本的说明 对手机用户来说,卡顿包含了很多场景,比如在 滑动列表的时候掉帧、应用启动白屏过长、点击电源键亮屏慢、界面操作没有反应然后闪退、点击图标没有响应、窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿 等场景,这些场景跟我们开发人员所理解的卡顿还有点不一样,开发人员会更加细分去分析这些问题,这是开发人员和用户之间的一个认知差异,这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意 对开发人员来说,上面的场景包括了 流畅度(滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿)、响应速度(应用启动白屏过长、点击电源键亮屏慢、滑动不跟手)、稳定性(界面操作没有反应然后闪退、点击图标没有响应)这三个大的分类。之所以这么分类,是因为每一种分类都有不太一样的分析方法和步骤,快速分辨问题是属于哪一类很重要 在技术上来说,流畅度、响应速度、稳定性(ANR)这三类之所以用户感知都是卡顿,是因为这三类问题产生的原理是一致的,都是由于主线程的 Message 在执行任务的时候超时,根据不同的超时阈值来进行划分而已,所以要理解这些问题,需要对系统的一些基本的运行机制有一定的了解,本文会介绍一些基本的运行机制 流畅性这个系列主要是分析流畅度相关的问题,响应速度和稳定性会有专门的文章介绍,在理解了流畅性相关的内容之后,再去分析响应速度和稳定性问题会事半功倍 流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析,之所以 Systrace 为切入点,是因为影响流畅度的因素很多,有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto) 工具可以从一个整机运行的角度来展示问题发生的过程,方便我们去初步定位问题 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下上面列出的 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 Systrace 的 Frame 颜色是什么意思? 这里的 Frame 标记指的是应用主线程上面那个圈,共有三个颜色,每一帧的耗时不同,则标识的颜色不同 点击这个小圆圈就可以看到这一帧所对应的主线程+渲染线程(会以高亮显示,其他的则变灰显示) 绿帧 绿帧是最常见的帧,表示这一帧在一个 Vsync 周期里面完成 黄帧 黄帧表示这一帧耗时超过1个 Vsync 周期,但是小于 2 个 Vsync 周期。黄帧的出现表示这一帧可能存在性能问题,可能会导致卡顿情况出现 红帧 红帧表示这一帧耗时超过 2 个 Vsync 周期,红帧的出现表示这一帧可能存在性能问题,大概率会导致卡顿情况出现 没有红帧就没有掉帧? 不一定,判断是否掉帧要看 SurfaceFlinger,而不是看 App ,这部分需要有 https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/ 这篇文章的基础 出现黄帧但是不掉帧的情况 如上所述,红帧和黄帧都表示这一帧存在性能问题,黄帧表示这一帧耗时超过一个 Vsync 周期,但是由于 Android Triple Buffer(现在的高帧率手机会配置更多的 Buffer)的存在,就算 App 主线程这一帧超过一个 Vsync 周期,也会由于多 Buffer 的缓冲,使得这一帧并不会出现掉帧 出现黄帧且掉帧的情况 这次分析的 Systrace(见附件),就是没有红帧只有黄帧,连续出现两个黄帧,第一个黄帧导致了卡顿,而第二个黄帧则没有 主线程为何要等待渲染线程? 还是这个 Systrace(附件) 中的情况,第一个疑点处两个黄帧,可以看到第二个黄帧的主线程耗时很久,这时候不能单纯以为是主线程的问题(因为是 Sleep 状态) 如下图所示,是因为前一帧的渲染线程超时,导致这一帧的渲染线程任务在排队等待,如(https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/)这篇文章里面的流程,主线程是需要等待渲染线程执行完 syncFrameState 之后 unblockMainThread,然后才能继续。 为什么一直滑动不松手,就不会卡? 还是这个场景(桌面左右滑动),卡顿是发生在松手之后的,如果一直不松手,那么就不会出现卡顿,这是为什么? 如下图,可以看到,如果不松手,cpu 这里会有一个持续的 Boost,且此时 RenderThread 的任务都跑在 4-6 这三个大核上面,没有跑到小核,自然也不会出现卡顿情况 这一段 Boost 的 Timeout 是 120 ms,具体的配置每个机型都不一样,熟悉 PerfLock 的应该知道,这里就不多说了 如果不卡,怎么衡量性能好坏? 如果这个场景不卡,那么我们怎么衡量两台不同的机器在这个场景下的性能呢? 可以使用 adb shell dumpsys gfxinfo ,使用方法如下 首先确定要测试的包名,到 App 界面准备好操作 执行2-3次 adb shell dumpsys gfxinfo com.miui.home framestats reset ,这一步的目的是清除历数据 开始操作(比如使用命令行左右滑动,或者自己用手指滑动) 操作结束后,执行 adb shell dumpsys gfxinfo com.miui.home framestats 这时候会有一堆数据输出,我们只需要关注其中的一部分数据即可 重点关注 Janky frames :超过 Vsync 周期的 Frame,不一定出现卡顿 95th percentile :95% 的值 HISTOGRAM : 原始数值 PROFILEDATA :每一帧的详细原始数据 我们拿这个场景,跟 Oppo Reno 5 来做对比,只取我们关注的一部分数据 小米 - 90 fps Oppo - 90 fps 下面是一些对比,可以看到小米在桌面滑动这个场景,性能是要弱于 Oppo 的 Janky frames 小米:27 (35.53%) Oppo:1 (1.11%) 95th percentile 小米:18ms Oppo:5ms 另外 GPU 的数据也比较有趣,小米的高通 865 配的 GPU 要比 Reno 5 Pro 配的 GPU 要强很多,所以 GPU 的数据小米要比 Reno 5 Pro 要好,也可以推断出这个场景的瓶颈在 CPU 而不是在 GPU 为什么录屏看不出来卡顿? 可能有下面几种情况 如果使用的手机是大于 60 fps 的,比如小米这个是 90 fps,而录屏的时候选择 60 fps 的录屏,则录屏文件会看不出来卡顿 (使用其他手机录像也会有这个问题) 如果录屏是以高帧率(90fps)录制的,但是播放的时候是使用低帧率(60fps)的设备观看的(小米就是这个情况),也不会看出来卡顿,比如用 90 fps 的规格录制视频,但是在手机上播放的时候,系统会自动切换到 60 fps, 导致看不出来卡顿 系列文章 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 附件 附件已经上传到了 Github 上,可以自行下载:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件,这次案例主要是分析这个 Systrace 文件 xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件 oppo_launcher_scroll.zip :对比文件 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
不同的人对流畅性(卡顿掉帧)有不同的理解,对卡顿阈值也有不同的感知,所以有必要在开始这个系列文章之前,先把涉及到的内容说清楚,防止出现不同的理解,也方便大家带着问题去看这几篇问题,下面是一些基本的说明 对手机用户来说,卡顿包含了很多场景,比如在 滑动列表的时候掉帧、应用启动白屏过长、点击电源键亮屏慢、界面操作没有反应然后闪退、点击图标没有响应、窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿 等场景,这些场景跟我们开发人员所理解的卡顿还有点不一样,开发人员会更加细分去分析这些问题,这是开发人员和用户之间的一个认知差异,这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意 对开发人员来说,上面的场景包括了 流畅度(滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿)、响应速度(应用启动白屏过长、点击电源键亮屏慢、滑动不跟手)、稳定性(界面操作没有反应然后闪退、点击图标没有响应)这三个大的分类。之所以这么分类,是因为每一种分类都有不太一样的分析方法和步骤,快速分辨问题是属于哪一类很重要 在技术上来说,流畅度、响应速度、稳定性(ANR)这三类之所以用户感知都是卡顿,是因为这三类问题产生的原理是一致的,都是由于主线程的 Message 在执行任务的时候超时,根据不同的超时阈值来进行划分而已,所以要理解这些问题,需要对系统的一些基本的运行机制有一定的了解,本文会介绍一些基本的运行机制 流畅性这个系列主要是分析流畅度相关的问题,响应速度和稳定性会有专门的文章介绍,在理解了流畅性相关的内容之后,再去分析响应速度和稳定性问题会事半功倍 流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析,之所以 Systrace 为切入点,是因为影响流畅度的因素很多,有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto) 工具可以从一个整机运行的角度来展示问题发生的过程,方便我们去初步定位问题 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下上面列出的 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 Systrace 分析卡顿问题的套路 使用 Systrace 分析卡顿问题,我们一般的流程如下 复现卡顿的场景,抓取 Systrace,可以用 shell 或者手机自带的工具来抓取 双击抓出来的 trace.html 直接在 Chrome 中打开 Systrace 文件 如果不能直接打开,可以在 Chrome 中输入 chrome://tracing/,然后把 Systrace 文件拖到里面就可以打开 或者使用 Perfetto View 中的 Open With Legacy UI 打开 分析卡顿问前,我们需要了解问题发生的背景,以提高分析 Systrace 的效率 用户(或者测试)的操作流程 卡顿复现概率 竞品机器是否也有同样的卡顿问题 分析问题之前或者分析的过程中,也可以通过检查 Systrace 来了解一些基本的信息 CPU 频率、架构、Boost 信息等 是否触发温控:表现为cpu 频率被压低 是否是高负载场景:表现为 cpu 区域任务非常满 是否是低内存场景:表现为 lmkd 进程繁忙,App 进程的 HeapTaskDeamon 耗时,有很多的 Block io 定位 App 进程在 Systrace 中的位置 打开 Systrace 后,首先要首先要看的就是 App 进程,主要是 App 的主线程和渲染线程,找到 Systrace 中每一帧耗时的部分,比如下面这种,可以看到 App 的 UI Thread 的红框部分,耗时 110ms,明显是不正常的(这个案例是 Bilibili 列表滑动卡顿) 事实上,所有超过一个 Vsync 周期的 doFrame 耗时(即大家看到的黄帧和红帧),我们都需要去看一下是否真的发生的掉帧,就算没有掉帧,也要看一下原因,比如下面这个 Vsync 周期与刷新率的对照 60fps 对应的 Vsync 周期是 16.6ms 90fps 对应的 Vsync 周期是 11.1ms 120fps 对应的 Vsync 周期是 8.3ms 分析 SurfaceFlinger 进程的主线程和 Binder 线程 由于多个 Buffer 缓冲机制存在,App 主线程和渲染线程,有时候即使超过一个 Vsync 周期,也不一定会出现卡顿,所以这里我们需要看SurfaceFlinger 进程的主线程,来确认是否真的发生了卡顿 ,比如上面 3.1 部分的图,App 主线程耗时 110 ms,对应的 SurfaceFlinger 主线程如下 Systrace 中的 SurfaceFlinger 进程区域,对应的 App 的 Buffer 个数也是空的 从整机角度分析和 Binder 调用分析(不涉及可以不用看) 上面的案例,可以很容易就看到是 App 自身执行耗时,那么只需要把耗时的部分涉及到的 View 找到,进行代码或者设计方面的优化就可以了 有时候 App 进程的主线程会出现大量的 Runnable 或者 Binder 调用耗时,也会导致 App 出现卡顿,这时候就需要分析整机问题,要看具体是什么原因导致大量的 Runnable 或者 Binder 调用耗时 按照这个流程分析之后,需要再反过来看各个进程,把各个线索联系起来,推断最有可能的原因 App 掉帧的原因非常多,有 APP 本身的问题,有系统原因导致卡顿的,也有硬件层的、整机卡的,这个可以参考下面四篇文章,不同的卡顿原因在 Systrace 中会有不同的表现 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 低内存篇 使用 Systrace 分析卡顿问题的案例 Systrace 作为分析卡顿问题的第一手工具,给开发者提供了一个从手机全局角度去看问题的方式,通过 Systrace 工具进行分析,我们可以大致确定卡顿问题的原因:是系统导致的还是应用自身的问题 当然 Systrace 作为一个工具,再进行深入的分析的时候就会有点力不从心,需要配合 TraceView + 源码来进一步定位和解决问题,最后再使用 Systrace 进行验证 所以本文更多地是讲如何发现和分析卡顿问题,至于如何解决,就需要后续自己寻找合适的解决方案了,比如对比竞品的 Systrace 表现、优化代码逻辑、优化系统调度、优化布局等 案例说明 个人在使用小米 10 Pro 的时候,在桌面滑动这个最常用的场景里面,总会有一种卡顿的感觉,10 Pro 是 90Hz 的屏幕,FPS 也是 90,所以一旦出现卡顿,就会有很明显的感觉(个人对这个也比较敏感)。之前没怎么关注,在升级 12.5 之后,这个问题还是没有解决,所以我想看看到底是怎么回事 抓了 Systrace 之后分析发现,这个卡顿场景是一个非常好的案例,所以把这个例子拿出来作为流畅度的一个实战分享 建议大家下载附件中的 Systrace,对照文章食用最佳 鉴于卡顿问题的影响因素比较多,所以在开始之前,我把本次分析所涉及的硬件、软件版本沟通清楚,如果后续此场景有优化,此文章也不会进行修改,以文章附件中的 Systrace 为准 硬件:小米 10 Pro 软件:MIUI 12.5.3 稳定版 小米桌面版本:RELEASE-4.21.11.2922-03151646 从 Input 事件开始 这次抓的 Systrace 我只滑动了一次,所以比较好定位,滑动的 input 事件由一个 Input Down 事件 + 若干个 Input Move 事件 + 一个 Input Up 事件组成 在 Systrace 中,SystemServer 中的 InputDispatcher 和 InputReader 线程都有体现,我们这里主要看在 App 主线程中的体现 如上图,App 主线程上的 deliverInputEvent 标识了应用处理 input 事件的过程,input up 之后,就进入了 Fling 阶段,这部分的基础知识可以查看下面这两篇文章 https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/ https://www.androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/ 分析主线程 由于这次卡顿主要是松手之后才出现的,所以我们主要看 Input Up 之后的这段 主线程上面的 Frame 有颜色进行标注,一般有绿、黄、红三种颜色,上面的 Systrace 里面,没有红色的帧,只有绿色和黄色。那么黄色就一定是卡顿么?红色就一定是卡顿么?其实不一定,单单通过主线程,我们并不能确定是否卡顿,这个在下面会讲 从主线程我们没法确定是否发生了卡顿,我们找出了三个可疑的点,接下来我们看一下 RenderThread 分析渲染线程 放大第一个可疑点,可以看到,这一帧总耗时在 19ms, RenderThread 耗时 16ms,且 RenderThread 的 cpu 状态都是 running(绿色),那么这一帧这么耗时的原因大概率是下面两个原因导致的: RenderThread 本身耗时,任务比较繁忙 RenderThread 的任务受 CPU 影响(可能是频率低了、或者是跑到小核了) 由于只是可疑点,所以我们先不去看 cpu 相关的,先查看 SurfaceFlinger 进程,确定这里有卡顿发生 分析 SurfaceFlinger 对于 Systrace 中 SurfaceFlinger 部分解读不熟悉的可以先预习一下这篇文章 https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/ 这里我们主要看两个点 App 对应的 BufferQueue 的 Buffer 情况。通过这个我们可以知道在 SurfaceFlinger 端,App 是否有可用的 Buffer 提供给 SurfaceFlinger 进行合成 SurfaceFlinger 主线程的合成情况。通过查看 SurfaceFlinger 在 sf-vsync 到来的时候是否进行了合成工作,就可以判断这一帧是否出现了卡顿。 判断是否卡顿的标准如下 如果 SurfaceFlinger 主线程没有合成任务,而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作,但是对应的 App 的 BufferQueue 里面没有可用的 Buffer,那么说明这一帧卡了 — 卡顿出现 这种情况如下图所示(也是上图中第一个疑点所在的位置) 如果 SurfaceFlinger 进行了合成,而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作,但是对应的 App 的 BufferQueue 里面没有可用的 Buffer,那么这一帧也是卡了,之所以 SurfaceFlinger 会正常合成,是因为有其他的 App 提供了可用来合成的 Buffer — 卡顿出现 这种情况如下图所示(也在附件的 Systrace 里面) 如果 SurfaceFlinger 进行了合成,而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作,而且对应的 App 的 BufferQueue 里面有可用的 Buffer,那么这一帧就会正常合成,此时没有卡顿出现 — 正常情况 正常情况如下,作为对比还是贴上来方便大家对比 回到本例的第一个疑点的地方,我们通过 SurfaceFlinger 端的分析,发现这一帧确实是掉了,原因是 App 没有准备好可用的 Buffer 供 SurfaceFlinger 来合成,那么接下来就需要看为什么这一帧 App 没有可用的 Buffer 给到 SurfaceFlinger 回到渲染线程 上面我们分析这一帧所对应的 MainThread + RenderThread 耗时在 19ms,且 RenderThread 耗时就在 16ms,那么我们来看 RenderThread 的情况 出现这种情况主要是有下面两个原因 RenderThread 本身耗时,任务比较繁忙 RenderThread 的任务受 CPU 影响(可能是频率低了、或者是跑到小核了) 但是桌面滑动这个场景,负载并不高,且松手之后并没有多余的操作,View 更新之类的,本身耗时比前一帧多了将近 3 倍,可以推断不是自身负载加重导致的耗时 那么就需要看此时的 RenderThread 的 cpu 情况: 既然在 Running 情况,我们就去 CPU Info 区域查看这一段时间这个任务的调度情况 分析 CPU 区域的信息 查看 CPU (Kernel 区域,这部分的基础知识可以查看 Android Systrace 基础知识 - CPU Info 解读 和 Android Systrace 基础知识 – 分析 Systrace 预备知识)这两篇文章 回到这个案例,我们可以看到 App 对应的 RenderThread 大部分跑在 cpu 2 和 cpu 0 上,也就是小核上(这个机型是高通骁龙 865,有四个小核+3 个大核+1 个超大核) 其此时对应的频率也已经达到了小核的最高频率(1.8Ghz) 且此时没有 cpu boost 介入 那么这里我们猜想,之所以这一帧 RenderThread 如此耗时,是因为小核就算跑满了,也没法在这么短的时间内完成任务 那么接下来要验证我们的猜想,需要进行下面两个步骤 对比其他正常的帧,是否有跑在小核的。如果有且没有出现掉帧,那么说明我们的猜想是错误的 对比其他几个异常的帧,看看掉帧的原因是否也是因为 RenderThread 任务跑到了小核导致的。如果不是,那么就需要做其他的假设猜想 在用同样的流程分析了后面几个掉帧之后,我们发现 对比其他正常的帧,没有在小核跑的,包括掉帧后的下一帧,调度器马上把 RenderThread 摆到了大核,没有出现连续掉帧的情况 对比其他几个异常的帧,都是由于 RenderThread 跑到了小核,但是小核的性能不足导致 RenderThread 执行耗时,最终引起卡顿 至此,这一次的卡顿分析我们就找到了原因:RenderThread 掉到了小核 至于 RenderThread 的任务为啥跑着跑着就掉到了小核,这个跟调度器是有关系的,大小核直接的调度跟任务的负载有关系,任务从大核掉到小核、或者从小核迁移到大核,调度器这边都是有参数和算法来控制的,所以后续的优化可能需要从这方面去入手 调整大小核迁移的阈值参数或者修改调度器算法 参考竞品表现,看看竞品在这个场景的性能指标,调度情况等,分析竞品可能使用的策略 Triple Buffer 在这个场景发挥了什么作用? 在 Triple-Buffer-的作用 这篇文章,讲到了 Triple Buffer 几个作用 缓解掉帧 减少主线程和渲染线程等待时间 降低 GPU 和 SurfaceFlinger 瓶颈 那么在桌面滑动卡顿这个案例里面,Triple Buffer 发挥了什么作用呢?结论是:有的场景没有发挥作用,反而有副作用,导致卡顿现象更明显,下面是分析流程 可以看文章中 Triple Buffer 缓解掉帧 的原理: 在分析小米桌面滑动卡顿这个案例的时候,我发现在有一个问题,小米桌面对应的 App 的 BufferQueue,有时候会出现可用 Buffer 从 2 →0 ,这相当于直接把一个 Buffer 给抛弃掉了,如下图所示 这样的话,如果在后续的桌面 Fling 过程中,又出现了一次 RenderThread 耗时,那么就会以卡顿的形式直接体现出来,这样也就失去了 Triple Buffer 的缓解掉帧的作用了 下图可以看到,由于丢弃了一个 Buffer,导致再一次出现 RenderThread 耗时的时候,表现依然是无 Buffer 可用,出现掉帧 仔细看前面这段丢弃 Buffer 的逻辑,也很容易想到,这里本身就已经丢了一帧了,还把这个耗时帧所对应的 Buffer 给丢弃了(也可能丢弃的是第二帧),不管是哪种情况,滑动时候的每一帧的内容都是计算好的(参考 List Fling 的计算过程),如果把其中一帧丢了,再加上本身 SurfaceFlinger 卡的那一下,卡顿感会非常明显 举个例子,以滑动为例,offset 指的是离屏幕一个左边的距离 正常情况下,滑动的时候,offset 是:2→4→6→8→10→12 掉了一帧的情况下,滑动的 Offset 是:2→4→6→6→8→10→12 (假设 计算 8 的这一帧超时了,就会看到两个 6 ,这是掉了一帧的情况) 像上图里面,如果直接扔掉了那个耗时的帧,就会出现下面这种 Offset:2→4→6→6→10→12 ,直接从 6 跳到了 10,相当于卡了 1 次,步子扯大了一次,感官上会觉得卡+跳跃 系列文章 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 附件 附件已经上传到了 Github 上,可以自行下载:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件,这次案例主要是分析这个 Systrace 文件 xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件 oppo_launcher_scroll.zip :对比文件 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
不同的人对流畅性(卡顿掉帧)有不同的理解,对卡顿阈值也有不同的感知,所以有必要在开始这个系列文章之前,先把涉及到的内容说清楚,防止出现不同的理解,也方便大家带着问题去看这几篇问题,下面是一些基本的说明 对手机用户来说,卡顿包含了很多场景,比如在 滑动列表的时候掉帧、应用启动白屏过长、点击电源键亮屏慢、界面操作没有反应然后闪退、点击图标没有响应、窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿 等场景,这些场景跟我们开发人员所理解的卡顿还有点不一样,开发人员会更加细分去分析这些问题,这是开发人员和用户之间的一个认知差异,这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意 对开发人员来说,上面的场景包括了 流畅度(滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿)、响应速度(应用启动白屏过长、点击电源键亮屏慢、滑动不跟手)、稳定性(界面操作没有反应然后闪退、点击图标没有响应)这三个大的分类。之所以这么分类,是因为每一种分类都有不太一样的分析方法和步骤,快速分辨问题是属于哪一类很重要 在技术上来说,流畅度、响应速度、稳定性(ANR)这三类之所以用户感知都是卡顿,是因为这三类问题产生的原理是一致的,都是由于主线程的 Message 在执行任务的时候超时,根据不同的超时阈值来进行划分而已,所以要理解这些问题,需要对系统的一些基本的运行机制有一定的了解,本文会介绍一些基本的运行机制 流畅性这个系列主要是分析流畅度相关的问题,响应速度和稳定性会有专门的文章介绍,在理解了流畅性相关的内容之后,再去分析响应速度和稳定性问题会事半功倍 流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析,之所以 Systrace 为切入点,是因为影响流畅度的因素很多,有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto) 工具可以从一个整机运行的角度来展示问题发生的过程,方便我们去初步定位问题 Systrace 系列文章如下 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace (Perfetto) 工具的基本使用如果还不是很熟悉,那么需要优先去补一下上面列出的 Systrace 基础知识系列,本文假设你已经熟悉 Systrace(Perfetto)的使用了 了解卡顿原理 卡顿现象及影响 如文章开头所述,本文主要是分析流畅度相关的问题。流畅度是一个定义,我们评价一个场景的流畅度的时候,往往会使用 fps 来表示。比如 60 fps,意思是每秒画面更新 60 次;120 fps,意思是每秒画面更新 120 次。如果 120 fps 的情况下,每秒画面只更新了 110 次(连续动画的过程),这种情况我们就称之为掉帧,其表现就是卡顿,fps 对应的也从 120 降低到了 110 ,这些都可以被精确地监控到 同时掉帧帧的原因非常多,有 APP 本身的问题,有系统原因导致卡顿的,也有硬件层的、整机卡的,这个可以参考下面四篇文章 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 低内存篇 用户在使用手机的过程中,卡顿是最容易被感受到的 偶尔出现的小卡顿会降低用户的使用体验,比如刷微博的时候卡了一下,比如返回桌面动画卡顿这种 整机出现卡顿的则会让手机无法使用 现在的高帧率时代,如果用户习惯了 120 fps ,在用户比较容易感知的场景下突然切换到 60 fps,用户也会有明显的感知,并觉得出现了卡顿 所以不管是应用还是系统,都应该尽量避免出现卡顿,发现的卡顿问题最好优先进行解决 卡顿定义 应用一帧渲染的整体流程 为了知道卡顿是如何发生的,我们需要知道应用主线程的一帧是如何工作的 从执行顺序的角度来看 从 Choreographer 收到 Vsync 开始,到 SurfaceFlinger/HWC 合成一帧结束(后面还包含屏幕显示部分,不过是硬件相关,这里就不列出来了) 从 Systrace 的角度来看 上面的流程图从 Systrace (Perfetto)的角度来看会更加直观 具体的流程参考上面两个图以及代码就会很清楚了,上述整体流程中,任何一个步骤超时都有可能导致卡顿,所以分析卡顿问题,需要从多个层面来进行分析,比如应用主线程、渲染线程、SystemServer 进程、SurfaceFlinger 进程、Linux 区域等 卡顿定义 我对卡顿的定义是:稳定帧率输出的画面出现一帧或者多帧没有绘制 。对应的应用单词是 Smooth VS Jank 比如下图中,App 主线程有在正常绘制的时候(通常是做动画或者列表滑动),有一帧没有绘制,那么我们认为这一帧有可能会导致卡顿(这里说的是有可能,由于 Triple Buffer 的存在,这里也有可能不掉帧) 下面从三个方面定义卡顿 从现象上来说,在 App 连续的动画播放或者手指滑动列表时(关键是连续),如果连续 2 帧或者 2 帧以上,应用的画面都没有变化,那么我们认为这里发生了卡顿 从 SurfaceFlinger 的角度来说,在 App 连续的动画播放或者手指滑动列表时(关键是连续),如果有一个 Vsync 到来的时候 ,App 没有可以用来合成的 Buffer,那么这个 Vsync 周期 SurfaceFlinger 就不会走合成的逻辑(或者是去合成其他的 Layer),那么这一帧就会显示 App 的上一帧的画面,我们认为这里发生了卡顿 从 App 的角度来看,如果渲染线程在一个 Vsync 周期内没有 queueBuffer 到 SurfaceFlinger 中 App 对应的 BufferQueue 中,那么我们认为这里发生了卡顿 这里没有提到应用主线程,是因为主线程耗时长一般会间接导致渲染线程出现延迟,加大渲染线程执行超时的风险,从而引起卡顿;而且应用导致的卡顿原因里面,大部分都是主线程耗时过长导致的 卡顿还要区分是不是逻辑卡顿,逻辑卡顿指的是一帧的渲染流程都是没有问题的,也有对应的 Buffer 给到 SurfaceFlinger 去合成,但是这个 App Buffer 的内容和上一帧 App Buffer 相同(或者基本相同,肉眼无法分辨),那么用户看来就是连续两帧显示了相同的内容。这里一般来说我们也认为是发生了卡顿(不过还要区分具体的情况);逻辑卡顿主要是应用自身的代码逻辑造成的 系统运行机制简介 由于卡顿的原因比较多,如果要分析卡顿问题,首先得对 Android 系统运行的机制有一定的了解,下面简单介绍一下分析卡顿问题需要了解的系统运行机制: App 主线程运行原理 Message、Handler、MessageQueue、Looper 机制 屏幕刷新机制和 Vsync Choreogrepher 机制 Buffer 流程和 TripleBuffer Input 流程 系统机制 - App 主线程运行原理 App 进程在创建的时候,Fork 完成后会调用 ActivityThread 的 main 方法,进行主线程的初始化工作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 frameworks/base/core/java/android/app/ActivityThread.java public static void main(String[] args) { ...... // 创建 Looper、Handler、MessageQueue Looper.prepareMainLooper(); ...... ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ...... // 开始准备接收消息 Looper.loop(); } // 准备主线程的 Looper frameworks/base/core/java/android/os/Looper.java public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } // prepare 方法中会创建一个 Looper 对象 frameworks/base/core/java/android/os/Looper.java private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } // Looper 对象创建的时候,同时创建一个 MessageQueue frameworks/base/core/java/android/os/Looper.java private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread() } 主线程初始化完成后,主线程就有了完整的 Looper、MessageQueue、Handler,此时 ActivityThread 的 Handler 就可以开始处理 Message,包括 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数,都会以 Message 的形式,在主线程按照顺序处理,这就是 App 主线程的初始化和运行原理,部分处理的 Message 如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 frameworks/base/core/java/android/app/ActivityThread.java class H extends Handler { public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int SERVICE_ARGS = 115; public static final int STOP_SERVICE = 116; public void handleMessage(Message msg) { switch (msg.what) { case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; } } } 这部分可以看 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 这篇文章 系统机制 - Message 机制 上一节应用的主线程初始化完成后,主线程就进入阻塞状态,等待 Message,一旦有 Message 发过来,主线程就会被唤醒,处理 Message,处理完成之后,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待 从下图可以看到 ,Android Message 机制的核心就是四个:Handler、Looper、MessageQueue、Message 网上有很多关于 Message 机制代码细节的分析,所以这里只是简单介绍 Message 机制的四个核心组件的作用 Handler : Handler 主要是用来处理 Message,应用可以在任何线程创建 Handler,只要在创建的时候指定对应的 Looper 即可,如果不指定,默认是在当前 Thread 对应的 Looper Looper : Looper 可以看成是一个循环器,其 loop 方法开启后,不断地从 MessageQueue 中获取 Message,对 Message 进行 Delivery 和 Dispatch,最终发给对应的 Handler 去处理。由于 Looper 中应用可以在 Message 处理前后插入自己的 printer,所以很多 APM 工具都会使用这个作为性能监控的一个切入点,具体可以参考 Tencent-Matrix 和 BlockCanary MessageQueue:MessageQueue 入上图所示,就是一个 Message 管理器,队列中是 Message,在没有 Message 的时候,MessageQueue 借助 Linux 的 nativePoll 机制,阻塞等待,直到有 Message 进入队列 Message:Message 是传递消息的对象,其内部包含了要传递的内容,最常用的包括 what、arg、callback 等 从第一节 App 主线程运行原理可知,ActivityThread 的就是利用 Message 机制,处理 App 各个生命周期和组件各个生命周期的函数 系统机制 - 屏幕刷新机制 和 Vsync 首先我们需要知道什么是屏幕刷新率,简单来说,屏幕刷新率是一个硬件的概念,是说屏幕这个硬件刷新画面的频率:举例来说,60Hz 刷新率意思是:这个屏幕在 1 秒内,会刷新显示内容 60 次;那么对应的,90Hz 是说在 1 秒内刷新显示内容 90 次 与屏幕刷新率对应的,FPS 是一个软件的概念,与屏幕刷新率这个硬件概念要区分开,FPS 是由软件系统决定的 :FPS 是 Frame Per Second 的缩写,意思是每秒产生画面的个数。举例来说,60FPS 指的是每秒产生 60 个画面;90FPS 指的是每秒产生 90 个画面 VSync 是垂直同期( Vertical Synchronization )的简称。基本的思路是将你的 FPS 和显示器的刷新率同期起来。其目的是避免一种称之为”撕裂”的现象. 60 fps 的系统 , 1s 内需要生成 60 个可供显示的 Frame , 也就是说绘制一帧需要 16.67ms ( 1/60 ) , 才会不掉帧 ( FrameMiss ). 90 fps 的系统 , 1s 内生成 90 个可供显示的 Frame , 也就是说绘制一帧需要 11.11ms ( 1/90 ) , 才不会掉帧 ( FrameMiss ). 一般来说,屏幕刷新率是由屏幕控制的,FPS 则是由 Vsync 来控制的,在实际的使用场景里面,屏幕刷新率和 FPS 一般都是一一对应的,具体可以参考下面两篇文章: Android 新的流畅体验,90Hz 漫谈 Android Systrace 基础知识 - Vsync 解读 系统机制 - Choreographer 上一节讲到 Vsync 控制 FPS,其实 Vsync 是通过 Choreographer 来控制应用刷新的频率的 Choreographer 的引入,主要是配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机. 至于为什么 Vsync 周期选择是 16.6ms (60 fps) ,是因为目前大部分手机的屏幕都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每隔 16.6 ms,Vsync 信号到来唤醒 Choreographer 来做 App 的绘制操作 ,如果每个 Vsync 周期应用都能渲染完成,那么应用的 fps 就是 60,给用户的感觉就是非常流畅,这就是引入 Choreographer 的主要作用 Choreographer 扮演 Android 渲染链路中承上启下的角色 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) . 下图就是 Vsync 信号到来的时候,Choreographer 借助 Message 机制开始一帧的绘制工作流程图 这部分详细的流程可以看 Android 基于 Choreographer 的渲染机制详解 这篇文章 系统机制 - Buffer 流程和 TripleBuffer BufferQueue 是一个生产者(Producer)-消费者(Consumer)模型中的数据结构,一般来说,消费者(Consumer) 创建 BufferQueue,而生产者(Producer) 一般不和 BufferQueue 在同一个进程里面 在 Android App 的渲染流程里面,App 就是个生产者(Producer) ,而 SurfaceFlinger 是一个消费者(Consumer),所以上面的流程就可以翻译为 当 App 需要 Buffer 时,它通过调用 dequeueBuffer()并指定 Buffer 的宽度,高度,像素格式和使用标志,从 BufferQueue 请求释放 Buffer App 可以用 cpu 进行渲染也可以调用用 gpu 来进行渲染,渲染完成后,通过调用 queueBuffer()将缓冲区返回到 App 对应的 BufferQueue(如果是 gpu 渲染的话,这里还有个 gpu 处理的过程,所以这个 Buffer 不会马上可用,需要等 GPU 渲染完成) SurfaceFlinger 在收到 Vsync 信号之后,开始准备合成,使用 acquireBuffer()获取 App 对应的 BufferQueue 中的 Buffer 并进行合成操作 合成结束后,SurfaceFlinger 将通过调用 releaseBuffer()将 Buffer 返回到 App 对应的 BufferQueue 知道了 Buffer 流转的过程,下面需要说明的是,在目前的大部分系统上,每个应用都有三个 Buffer 轮转使用,来减少由于 Buffer 在某个流程耗时过长导致应用无 Buffer 可用而出现卡顿情况 下图是双 Buffer 和 三 Buffer 的一个对比图 三 Buffer 的好处如下 缓解掉帧 :从上图 Double Buffer 和 Triple Buffer 的对比图可以看到,在这种情况下(出现连续主线程超时),三个 Buffer 的轮转有助于缓解掉帧出现的次数(从掉帧两次 -> 只掉帧一次)。,App 主线程超时不一定会导致掉帧,由于 Triple Buffer 的存在,部分 App 端的掉帧(主要是由于 GPU 导致),到 SurfaceFlinger 这里未必是掉帧,这是看 Systrace 的时候需要注意的一个点 减少主线程和渲染线程等待时间 :双 Buffer 的轮转,App 主线程有时候必须要等待 SurfaceFlinger(消费者)释放 Buffer 后,才能获取 Buffer 进行生产,这时候就有个问题,现在大部分手机 SurfaceFlinger 和 App 同时收到 Vsync 信号,如果出现 App 主线程等待 SurfaceFlinger(消费者)释放 Buffer,那么势必会让 App 主线程的执行时间延后 降低 GPU 和 SurfaceFlinger 瓶颈 :这个比较好理解,双 Buffer 的时候,App 生产的 Buffer 必须要及时拿去让 GPU 进行渲染,然后 SurfaceFlinger 才能进行合成,一旦 GPU 超时,就很容易出现 SurfaceFlinger 无法及时合成而导致掉帧;在三个 Buffer 轮转的时候,App 生产的 Buffer 可以及早进入 BufferQueue,让 GPU 去进行渲染(因为不需要等待,就算这里积累了 2 个 Buffer,下下一帧才去合成,这里也会提早进行,而不是在真正使用之前去匆忙让 GPU 去渲染),另外 SurfaceFlinger 本身的负载如果比较大,三个 Buffer 轮转也会有效降低 dequeueBuffer 的等待时间 坏处就是 Buffer 多了会占用内存 这部分详细的流程可以看 Android Systrace 基础知识 - Triple Buffer 解读 这篇文章 系统机制 - Input 流程 Android 系统是由事件驱动的,而 input 是最常见的事件之一,用户的点击、滑动、长按等操作,都属于 input 事件驱动,其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Systrace 的 Input 事件流,首先是找到这里。 InputReader 负责从 EventHub 里面把 Input 事件读取出来,然后交给 InputDispatcher 进行事件分发 InputDispatcher 在拿到 InputReader 获取的事件之后,对事件进行包装和分发 (也就是发给对应的) OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件 WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件 PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件,这里可以看到已经到了应用进程 deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒 InputResponse 标识 Input 事件区域,这里可以看到一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件的处理阶段都被算到了这里 App 响应 Input 事件 : 这里是滑动然后松手,也就是我们熟悉的桌面滑动的操作,桌面随着手指的滑动更新画面,松手后触发 Fling 继续滑动,从 Systrace 就可以看到整个事件的流程 上面流程对应的 Systrace 如下 这部分详细的流程可以看 Android Systrace 基础知识 - Input 解读 这篇文章 系列文章 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 附件 附件已经上传到了 Github 上,可以自行下载:https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件,这次案例主要是分析这个 Systrace 文件 xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件 oppo_launcher_scroll.zip :对比文件 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
技术群里的小伙伴发了一条微博, https://weibo.com/1808884742/IApbpEVQr, 博主 @王波粒 发现, Mate 30 Pro 有个很特别的现象(建议先去看一下视频) 但是这个视频描述和底下的猜测都不对,我这边总结一下这个现象: 微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片,而同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片 下面就针对这个现象,从技术的角度来深入分析产生这种现象的原因,以及我们能从里面学到什么 这个现象有什么特别呢 ? 从技术上讲,滑动列表停止后再加载图片是目前列表滑动优化中一个比较常见的优化项,很多主流 App 也都是这么做的 ,做这种处理主要是因为 如果在列表滑动的时候,碰到图片视频就加载,那么会加载很多无用的图片&&视频,浪费资源不说,还可能会影响真正用户看到的图片的加载速度 (加载一般都有并行上限和队列,队列里面无效的图片太多,后来的图片就得排队等待)。 这里比较 特别的就是同一个版本的微博 APK,在华为的机型上与在其他机型上表现不一致,作为一个系统优化工程师,这个还是值得去搞清楚的(大胆猜测是微博针对华为的机型做了优化),那么这个优化的内容是什么? 从用户体验的角度来讲,列表滑动的同时加载图片,用户可以更早地看到图片,减少图片占位白图的显示时间,可以提升滑动的体验 第三个现象就得认真体验才会感觉到: 华为手机上的微博在松手后的滑动曲线和其他手机上的微博在松手后的滑动曲线是不一样的,华为的微博列表松手后的滑动曲线速度更慢,更柔和,结束的时候也不会太突兀,与系统默认的列表滑动曲线明显不一样 上面三个是从现象上来说的,下面就从技术上来验证,从最后的结果来看,华为和微博的合作毫无疑问是很成功的,可以作为一个案例推广到其他头部 App,同时作为 Android 开发者,对华为这种非常细致的体验优化真的是非常敬佩 背景备注 由于 “列表滑动的同时加载图片” 这个功能由微博官方服务器控制,可以随时开启或者关闭,所以文章中所说的 “同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片” 这个现象在 “列表滑动的同时加载图片” 这个功能开启后,现象就会变成 “主页列表上下滑动的时候就会加载图片” 在 2020-6 月左右分析这个问题的时候,“列表滑动的同时加载图片” 这个功能还是关闭的,只有华为手机做了优化才有效果,其他手机是 “滑动停止之后才会加载图片” 在 2020-8 月再看这个问题的时候,“列表滑动的同时加载图片” 这个功能在其他手机上已经开启 华为的 PerfSDK 还有效果么?答案是有,具体分析可以看下文,因为有了这个 SDK,不仅对微博有好处(减少图片加载个数),对华为也有好处(提升微博主页列表在华为手机上的滑动体验,即 Fling 曲线优化) ;而粗暴开启 “列表滑动的同时加载图片” 的其他手机,如果性能不足,开启后反而会增加卡顿出现的概率(微博官方应该有性能监控数据可以看到) 反编译的微博版本:10.8.1 结论先行 “微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片” 这个现象是因为华为和微博做了联合优化,主要是为了优化微博列表滑动时候的用户体验,其优化点如下 华为提供了一个简单的接口打包成 SDK 提供给微博,这个接口可以让微博的列表监听到列表的当前速度(Velocity),在速度高于阈值或者低于阈值的时候,都会及时通知 App 微博拿到这个速度回调之后,就可以根据列表的滑动速度来决定是否要在滑动过程中加载图片,一旦列表的滑动速度低于设定的阈值,就开启图片加载;一旦列表的滑动速度高于设定的阈值,就关闭图片加载 华为检测到这个应用使用了 SDK,就可以将优化过后的滑动曲线应用在这个 App 的列表 Fling 阶段,提升用户体验 对细节感兴趣的同学可以继续阅读,有能力的同学看完后可以修改 Framework 相关代码,编译一个 SDK,然后自己写个 Demo 接入 SDK,就可以打通我下面所说的所有内容了,我自己在 AOSP 的代码上实现了一遍,Demo 也可以正常运行,有兴趣可以跟我私下交流 微博+华为是怎么优化? 现象分析 我们在滑动微博列表的时候,一个滑动操作主要由下面三部分组成 手指接触屏幕,上下滑动微博主页列表,但是手指 没有离开屏幕 ,这个阶段我们称之为阶段一,技术术语为 SCROLL_STATE_TOUCH_SCROLL 手指上下滑动的时候 离开屏幕 (必须有一个上滑或者下滑的速度),微博列表有了一个惯性,根据惯性的方向继续滑动,这个阶段我们称之为阶段二,技术术语为 SCROLL_STATE_FLING 列表惯性滑动后停止,这 个 阶段我们称之为阶段三 , 技术术语为 SCROLL_STATE_I DLE 而华为和微博的优化主要在阶段一和阶段二 阶段一优化 优化前:只要手指不离开屏幕,图片加载功能关闭 优化后:只要手指不离开屏幕,列表就不会滑动太快,这时候图片加载功能开启 阶段二优化 滑动图片加载优化 优化前:只要列表滑动不停止,图片加载功能关闭 优化后:图片加载功能是否开启取决于当前列表滑动的速度 列表滑动速度太快,这时候图片加载功能关闭 列表滑动速度掉落到一个阈值,图片加载功能开启 列表 Fling 曲线优化 优化前:列表滑动的曲线是默认值,滑动时间比较短,停止的时候比较突兀,不柔和 优化后:列表滑动的曲线是华为经过优化的,滑动时间比较长,停止的时候比较柔,不突兀,比较接近 iPhone 的列表滑动曲线 技术分析 技术分析的代码主要来源于微博 apk 的反编译,微博版本 10.8.1,通过反编译的代码可以看到, 微博主页在初始化的时候,会接入华为提供的 PerfSDK,从而获得监听列表滑动速度的能力 阶段一优化的技术分析 列表的 ScrollStateChange 是标识列表状态的一个回调,微博在 ScrollStateChange 这个回调中会根据当前的状态来决定是否加载图片, 从下面的代码逻辑来看 当 滑动图片加载优化生效 的时候,如果 State != 2,那么就允许 ImageLoader 加载图片,State 为 2 也就是 SCROLL_STATE_FLING,熟悉列表滑动的同学应该知道,SCROLL_STATE_FLING 就是 滑动列表的时候手指松手后列表继续滑动的那一段 ,叫 fling,毕竟只有 fling 的时候才有 Velocity,松手后会根据这个值的大小计算滑动曲线和滑动时长 当 滑动图片加载优化不生效 的时候,就到了常规的列表滑动优化:即列表停止之后才开始加载图片 :State !=0,0 即 SCROLL_STATE_IDLE 阶段二优化的技术分析 微博的主页在初始化的时候,会给首页的 ListView 注册一个 HwPerfVelocityCallback,从名字可以看出来,这个回调是监听 Velocity 的,也就是滑动的速度,两个回调: HwPerfonVelocityDownToThreshold : 当速度降低到阈值之后,打开 ImageLoader 的图片加载功能 HwPerfonVelocityUpToThreshold: 当速度升高到阈值之后,关闭 ImageLoader 的图片加载功能 下图为反编译后的源码 至于滑动曲线,则需要查看华为的 Framework 的代码,由于代码量比较大,这里只贴一下 OverScroller.java 中的 update 方法,具体感兴趣的可以自己去翻一番华为的 Framework 代码 计算 Distance 的代码 计算 Velocity 的代码 关于滑动曲线的解释,大家可以看这一篇知乎回答,其中对比了 iOS 和 Android 的滑动曲线的不同 :为什么 iOS 的过渡动画看起来很舒服? 其他厂商处理 上面图中代码最后一段还有一个判断开关, 如果 boolean a = HwPerfUtil.m14290a 这个返回的是 false,这就是说有可能华为这个优化关闭了,有可能是非华为机器,那么会 判断 Android 版本号和全局 Feature 开关 对应的 FeedAbManager 就是一个 Feature 管理器,可以在线开关某些 Feature 而 m52580k 的实现如下 可以看到这里还受到一个全局的 Feature 配置:feed_scroll_loadimage_enable,这个 Feature 是服务端可以配置的 这里就是处理其他厂商的逻辑 最后一个问题:滑动点击 滑动点击是个什么问题呢?列表在滑动的过程中,如果用户点击列表的某一个 Item,那么根据 Android 的事件分发机制,这时候列表的 Item 并不会被点击到,而是整个列表先收到点击事件,然后 触发列表滑动停止;列表停止的时候点击 Item 才会触发 Item 的点击 上面阶段二的优化中,在优化了滑动曲线之后,列表处于 Fling 状态的时间变长,如果用户想点击去某一个 Item 查看详情,那么需要先点击一下让列表停止,然后再点击一下才能进去,这就是这一节想说的 :滑动点击问题 滑动(Fling 状态)和点击其实是需要一个平衡的,这个平衡需要开发者自己去把控: 滑动(Fling 状态)的时间越短,列表越容易停下,用户点击列表越容易触发 Item 的点击,但是容易停止带来的问题就是不够柔和。想象你在粗糙的水泥地上滑出去一块石头,这石头没有滑动多久就会停止,不管是扔石头的你还是旁边看你扔石头的我,都不会觉得这有什么美感,但是没得选。这个的 代表其实就是 Android 原生的 Fling 曲线 滑动(Fling 状态)的时间越长,滑动(Fling 状态)的时间越长,列表越不容易停下,用户点击列表越不容易触发 Item 的点击,如果曲线优化的好,给人的感觉就是很柔和,符合物理规律,想象你在光滑的冰面上滑出去一块冰,冰面越滑,冰块滑动的时间就越长,越不容易停下。这其中的极端代表就是 iOS 的 Fling 曲线。说 iOS 极端是因为,iOS 的滑动曲线调的太磨叽了,时间长不说,停的异常慢,很多时候你都需要点击一下列表让他先停止,然后再进行下一步的点击动作。而小米的 MIUI12 对这个也进行了调整,效果要比 iOS 好一些,如果再和三方进行类似华为和微博的合作,体验会更上一层楼 滑动点击问题其实也可以通过厂商和 App 合作来解决,比如,当滑动到整个滑动距离的 98%(或者 95%) 之后,用户点击列表不再是让列表停止,而是列表内的 item 响应这个点击。这个思想来源于 Launcher 的代码,Launcher 的每一页在左右滑动的时候,如果滑动还没有停止但是用户比较手速快点击了某个 icon 想启动,那么这时候不会触发 Page 停止,而是直接响应 icon 的点击启动应用操作 延伸阅读 列表滑动图片加载的性能考虑 前文有提到这个问题,滑动的时候进行图片加载主要有两个问题: 如果用户滑动非常快,比如是想找昨天发的某个微博,那么今天发的所有的带图片的微博在用户滑动的时候是没必要加载的,因为用户的目标不是这些图片,而 App 去加载这些图片,而程序员是不会为用户提前加载你未看到的数据,因为加载过多的数据不仅容易发生数据复用、缓存过多、内存溢出等错误,还会对服务器造成不必要的资源请求。 如果用户滑动非常快,那么图片加载队列势必有许多无效的资源(对这一刻的用户来说),而用户真正想看的图片反而排在了加载队列后面,造成加载速度变慢,也会影响用户的体验 滑动中加载图片最大的风险其实就是造成卡顿,因为图片加载本身就是一个比较重的操作,而高帧率的手机上,一帧的时间被压缩到很短,任何小的不确定性都有可能造成卡顿 所以厂商+应用的这个优化: 快速滑动不加载图片,慢速的时候再加载,然后优化滑动曲线 ,其实对厂商和应用都是非常有益处的 列表滑动监听背景知识 下面的 AbsListView 的 OnScrollListener 里面标注了列表滑动的三个状态 滑动停止:SCROLL_STATE_IDLE 手指在屏幕上滑动:SCROLL_STATE_TOUCH_SCROLL 手指离开屏幕,列表靠惯性继续滑动:SCROLL_STATE_FLING 两个回调 列表状态变化时的回调 :onScrollStateChanged 列表滑动时候的回调:onScroll 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface OnScrollListener { // The view is not scrolling. Note navigating the list using the trackball counts as being in the idle state since these transitions are not animated. public static int SCROLL_STATE_IDLE = 0; //The user is scrolling using touch, and their finger is still on the screen public static int SCROLL_STATE_TOUCH_SCROLL = 1; //The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop public static int SCROLL_STATE_FLING = 2; // Callback method to be invoked while the list view or grid view is being scrolled. If the view is being scrolled, this method will be called before the next frame of the scroll is rendered. In particular, it will be called before any calls to public void onScrollStateChanged(AbsListView view, int scrollState); // Callback method to be invoked when the list or grid has been scrolled. This will be called after the scroll has completed public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount); } 列表滑动状态的变化 TOUCH_SCROLL、FLING、IDLE 三个状态对应的列表滑动操作如下 TOUCH_SCROLL: 手指滑动 List 阶段,但是手指没有离开屏幕,这时候上下滑动都是 TOUCH_SCROLL FLING: 手指滑动 List 后抬手到 List 停止的阶段(必须有一个上滑或者下滑的速度,否则不会进入 Fling) IDLE:List 停止阶段 这三个状态的变化情况如下 手指滑动列表,停止后松手:IDLE -> TOUCH_SCROLL -> IDLE 手指滑动列表,松手后列表继续滑动,然后停止:IDLE -> TOUCH_SCROLL -> FLING -> IDLE 列表的 Fling 曲线计算 Fling 触发之后,每一帧都会调用 update 函数来更新 distance 和 mCurrVelocity,所以我们只需要监听 mCurrVelocity 的值,超过一定的阈值,就可以回调给 App frameworks/base/core/java/android/widget/OverScroller.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 boolean update() { final long time = AnimationUtils.currentAnimationTimeMillis(); final long currentTime = time - mStartTime; double distance = 0.0; switch (mState) { case SPLINE: { // Fling 状态 final float t = (float) currentTime / mSplineDuration; final int index = (int) (NB_SAMPLES * t); float distanceCoef = 1.f; float velocityCoef = 0.f; if (index < NB_SAMPLES) { final float t_inf = (float) index / NB_SAMPLES; final float t_sup = (float) (index + 1) / NB_SAMPLES; final float d_inf = SPLINE_POSITION[index]; final float d_sup = SPLINE_POSITION[index + 1]; velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); distanceCoef = d_inf + (t - t_inf) * velocityCoef; } distance = distanceCoef * mSplineDistance; mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f; break; } case BALLISTIC: { // 列表滑到 底 之后的 拉伸阶段 final float t = currentTime / 1000.0f; mCurrVelocity = mVelocity + mDeceleration * t; distance = mVelocity * t + mDeceleration * t * t / 2.0f; break; } case CUBIC: { // 列表滑到底拉伸 之后的 回弹阶段 final float t = (float) (currentTime) / mDuration; final float t2 = t * t; final float sign = Math.signum(mVelocity); distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2); mCurrVelocity = sign * mOver * 6.0f * (- t + t2); break; } } mCurrentPosition = mStart + (int) Math.round(distance); return true; } 厂商应用联合优化 微博这个优化就是厂商和应用之间联合优化的一个案例,应用对用户体验的极致追求,让这种合作在未来会变得更加频繁,像微信、快手、抖音这些… 下面这个招聘是拼多多的一个 JD,看职位描述是专门对接厂商的优化,也可以看出应用对厂商的合作越来越重视。之前厂商和应用是魔高一尺道高一丈的关系,互相攻防导致最终体验受损的还是用户;而现在这种厂商和应用合作的关系,不仅提升了双方的体验,也会带动 Android 生态圈向好的方面去发展 本文其他地址 微信公众号 - https://mp.weixin.qq.com/s/wJKOvU7CqP3vM0TG7rO66g 知乎专栏(求个赞) - https://zhuanlan.zhihu.com/p/191460094 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
2020 年 5 月 23 号凌晨 1 点 30 左右, 大量三星手机用户的手机出现死机, 无限重启、进 Recovery 等问题, 并且操作不当会导致数据丢失, 并且上了知乎的热点, 售后点更是人满为患 知乎的部分回答中, 大家更是对三星的家属送上了亲切的问候, 甚至有的人已经将这次事故与 Note7 事件、充电门、绿屏门事件相提并论, 甚至预言三星因此会退出国内市场 ; 有的人因为这个丢了 Offer , 有的人准备了很久的资源丢失, 有的人甚至直接把手机砸了… 作为一个 Android 开发者, 我并不想对三星落井下石 , 我只想搞清楚到底是什么原因导致了这场事故 , 以及我们能从里面学到什么 . 我认为既然是 Android 系统出了问题, 我们有必要从技术的角度来分析为什么会出现这样的问题 甚至商场里的机器都变砖了 结论 结论先行, 对于不喜欢看长文的吃瓜群众来说, 直接看结论即可: 这次事故表现是一部分三星用户的手机系统中关键系统服务重复 Crash 并强制进入 Recovery 界面. 关键系统服务指的是三星的 SystemUI 进程 , SystemUI 进程在初始化 AOD 插件的时候 AOD 插件初始化出错导致 SystemUI Crash, 由于是系统服务, SystemUI Crash 到一定的次数之后, 就会强制进入 Recovery 界面, 所以 大部分用户看到的都是 Recovery 界面(下面有图) AOD 全称 Always On Display, 中文翻译是息屏显示, 就是你按电源键锁屏后, 在屏幕上显示时间、天气、图案等的服务, 这个只有部分高端机型才有这个功能. AOD Crash 的原因是 2020 年 5 月 23 是闰四月, AOD 显示阴历的时候, 需要显示闰四月, 所以在代码中会走到显示闰四月这个一般很难走进的分支条件, 走进这个条件之后, 需要获取 common_data_leap_month 这个字段, 但是由于代码编译出现了 Bug, 导致无法找到这个字段, 所以该进程直接报了 FATAL EXCEPETION, 进程重启, 重启之后还是要获取这个字段, 再重启, 如此反复 , 最终触发系统的自救措施, 进入 Recovery 界面 这也是为什么只有中国用户才会出现这个问题, 就是因为 AOD 在 5 月 23 号需要显示”闰”四月 , 但是没找到 “闰” 这个字, 所以就挂了 . 所以并不是千年虫 , 也不是服务器被黑, 更不是三星故意恶心人, 这种编译导致的 Bug , 再碰上几年一遇的闰月 , 遇到了就认了吧 , 老老实实道歉, 不丢人. 分析 吃瓜群众可以折返了, 感兴趣的 Android 开发者可以继续往下看, 内容虽然简单, 但是个人觉得还是可以看一下的 对于三星的开发人员来说, 分析这个 Crash 非常简单, 直接在监控里面捞 Log 就可以了, 从后面的分析来看, 这个问题也很快被发现, 并进行了修复(持续了半年左右);但是对这个问题感兴趣的其他开发者来说, 需要借助其他的工具 不过分析的过程也非常简单, 这里会把自己分析的思路和用到的工具记录下来, 方便大家使用 从现象入手 上面结论有说, Persist 进程频繁 Crash 会导致系统触发自救, 进入 Recovery 界面, 所以用户很多反馈截图大家看到都是 Recovery 界面 , 如下 不过也有用户的界面直接显示了报错信息(我猜测是三星这边自己加的功能吧, 知道的麻烦告知一下), 这个界面对我们分析代码来说很重要 开发者对于这个堆栈是最熟悉不过了, 这是在一帧的渲染流程中, AOD 的 LocalDataView 在初始化的时候, 调用 getLunarCalendarInChina 方法出错了, LunarCalendar 是阴历的意思, 报错主要是因为找不到 common_data_leap_month 这个 string 值. 那么问题就很清楚了, 我们只需要查下面两个点 common_data_leap_month 这个 string 字段出现的代码逻辑 common_data_leap_month 这个 string 字段没有找到导致运行报错的原因 分析代码 首先看 common_data_leap_month 字段出现的代码逻辑, 既然上面已经列出了函数堆栈, 那么我们需要直接查看代码来分析这个问题产生的逻辑, 如何拿到代码?自然是需要反编译, 推荐的反编译工具: TTDeDroid 反编译需要三星 AOD 的代码, 可以在 ApkMirror 里面搜 Always-On-Display, 就可以找到对应的文件, 可以看到三星的 AOD 更新的频率还是很频繁的, 通过用户反馈可以知道, 并非所有的用户都有这个问题, 且更新到新版本就没有问题了, 那么我们推测问题是出在老版本上的( 从堆栈来猜测应该是 V4.0 的版本 ) 正常版本_V5.2.05 最新版本是正常的, 没有 Crash 的情况 首先我们先看一下最新版本这一段代码的逻辑 1 String month = shouldShowLeapMonth(locale) ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth] 这个就是说如果需要显示闰月, 那就取 common_date_leap_month 的值, 全局搜索 common_date_leap_month 发现最新版本里面是有定义这个值的 . 这里就可以看到 common_data_leap_month 字段出现的代码逻辑 : 只有需要显示闰月的时候, 才会去获取 common_date_leap_month 这个字段的值, 其他 99.9% 的时候都不会触发这个值的获取 . R.java 文件里面存在的 common_date_leap_month, 说明是存在的, 查看对应的 string.xml 中也有这个字段的定义 出问题版本_V4.1.70 既然新版本没有问题, 且我们也知道了 common_date_leap_month 这个字段的代码逻辑 , 那么我们从老版本来看 common_date_leap_month 这个字段没有找到的原因. 这里找的这个老版本是有问题的, 使用这个版本(这几个版本) 的用户到了 23 号会出现频繁 Crash 的现象. 之所以我认为他是有问题的 , 是因为全局搜索 common_date_leap_month 字段, 发现 R 文件里面没有对应的字段, 对应的 string.xml 里面也没有这个字段和他对应的值, 也就是说 , 这里代码只使用, 没有定义和赋值( 那怎么编译过的呢 ???) 上面对应的代码逻辑如下, 可以看到函数名和行数和报错是一致的, InChina…. 具体对应的代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private String getLunarCalendarInChina(Context context) { if (sSolarLunarConverter == null) { sSolarLunarConverter = SECCalendarFeatures.getInstance().getSolarLunarConverter(); if (sSolarLunarConverter == null) { return ""; } } Time time = new Time(); time.set(Calendar.getInstance().getTimeInMillis()); sSolarLunarConverter.convertSolarToLunar(time.getYear(), time.getMonth(), time.getMonthDay()); String[] months = context.getResources().getStringArray(R.array.common_LunarMonth); String[] days = context.getResources().getStringArray(R.array.common_LunarDay); int convertMonth = sSolarLunarConverter.getMonth(); int convertDay = sSolarLunarConverter.getDay() - 1; ACLog.d(TAG, "Lunar month and day : " + convertMonth + ", " + convertDay); if (convertMonth = months.length || convertDay = days.length) { ACLog.e(TAG, "getLunarCalendarInChina, array out of bound month = " + months.length + ", days = " + days.length); return ""; } String chinaLunar = (sSolarLunarConverter.isLeapMonth() ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth]) + days[convertDay]; String str = chinaLunar; return chinaLunar; } 问题出现的时间 根据我这边的调查, 发现这个问题其实在 AOD 这个应用从 v3.3.18 升级到 v4.0.57 的时候就出现了, 但是中间一直都没有出问题, 没有闰四月, 用户也就不会有问题, 测试也没有测出来, 直到 2019 年 6 月 24 号发布的 v4.2.44 版本才修复了这个问题 v3.3.18 版本我们可以看到, common_date_leap_month 这个字段还是存在的 升级到 v4.0.57(第一个出问题的版本) 之后 , 这个字段就没有了( 那怎么编译过的呢 ???) 理一下: AOD 从 v3.3.18 升级到 v4.0.57 的引入了这个问题(2018 年 10 月 24 号引入) AOD 从 v4.2.24 升级到 v4.2.44 解决了这个问题(2019 年 6 月 24 号 修复) 这期间所有 AOD 版本在 v4.0.57 - v4.2.44 却从来没有升级的机型, 都会在 2020-5-23 号这一天进入 Recovery 模式. 编译问题 上面一个很重要的点就是编译问题, Android 开发者都知道, 如果我在代码中写 getString(R.string.common_date_leap_month) , 那我得在 strings.xml 里面定义这个 common_date_leap_month, 然后给他赋值, 比如 “闰” , 这样才能在 R 文件中看到这个字段, 我们才能使用 getString(R.string.common_date_leap_month) 这样的语法去调用 ; 否则在编译阶段就会出现问题 , 编译提示 R.string.common_date_leap_month 不存在 但是通过上面的分析我们发现, 频繁 Crash 的版本就是因为找不到 common_date_leap_month 这个字段才 Crash 的, 既然找不到那也应该编译不过才对, 但是既然我们拿到了 apk, 那说明编译也是没问题的. 这种情况出现的话, 一般有下面两种情况 项目中有同一个 jar 包的不同版本, 因此编译和运行时使用了不同的 jar 包 编译使用的是 Maven, 项目中的依赖由于使用了不同版本的包, 最后打包的时候使用的不是你需要的版本 猜测三星这次出问题的是因为第二种情况, 主项目和子 modules 使用了不同版本的包, 导致可以编译通过, 但是最终打包进项目的并不是编译时候的包, 就出现了运行时的 FATAL EXCEPTION : NoSuchFieldError ( 如果有知道具体原因的可以留言讨论一波 ) 开个玩笑, 这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉 , 不丢人 想必三星对这一天也会铭心刻骨 总结 上面的分析过程虽然比较简单, 但是有一些比较繁琐的工作, 比如找版本, 反编译 , 看代码逻辑等. 最终也算是找到了问题的根本原因 : 编译导致的问题碰上十几年才遇到一次的闰四月. 那么从这件事我们学到了什么呢? 功能测试 : 闰月是日历中的一个功能 , 不算是常用功能 , 但是相对来说比较专业 , 像这种涉及到专业的地方, 一定要谨慎 , 列出所有可能出现的情况去做测试, 必要的情况下, 交给专业的人来评估测试用例 涉及到多方依赖编译的项目, 在编译的时候要确保引用的版本和本地的版本一致 , 对于多方依赖的模块, 每次发版本之前最好跟对应的依赖的模块确认 SystemUI (锁屏\状态栏\手势\多任务\ AOD 等) 模块和桌面模块是用户直接能感受到的模块, 这些模块对稳定性的要求要非常高, 因为一旦这些模块发生 FATAL , 带来的影响是非常巨大的, 就像三星这次, 所以这几个模块的开发人员也是最辛苦的, 既要承接一些亮点功能的实现, 又要保证稳定性, 同时也位于系统开发和应用开发中间, 两边都有很大的耦合, 着实不容易 (媳妇做这一块 6 年多了, 晚上加个鸡腿…) 厂商提供的系统更新和厂商自己的应用更新(尤其是系统应用) , 一定要及时更新, 每次系统和系统应用更新一般都会修复很多 Bug , 增强稳定性和性能. 系统和系统应用没有盈利的压力, 所以更新都是以提升质量为主, 可以放心更新. 开发者对这种事情要保持好奇和敬畏 : 好奇可以帮助我学到很多东西, 透过现象看本质 ; 敬畏可以让我知道自己知识的欠缺, 在庞大的 Android 体系中, 自己知道的不过沧海一粟.. 这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉 , 不丢人」 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
MIUI 12 的发布, 将之前一直是应用开发者和 Rom 开发者斗争最激烈的部分展示给了普通消费者, 让普通消费者也知道了这场斗争的细节, 正所谓 “魔高一尺道高一丈” , Rom 开发者由于有更高的代码修改权限, 始终占据着上风 ; App 开发者当然也不甘示弱, 各种保活拉起黑科技层出不穷,甚至 Google 都参与到了这部分斗争中, 居中调和, 制定各种规则来规范双方. 当然斗争对双方来说都算是好事, 毕竟任何一方完全的胜利都会导致 “狡兔死走狗烹,飞鸟尽良弓藏” 不过双方斗争的受害者无疑还是使用手机的消费者 , App 如果斗争成功, 那么手机上各种后台进程乱跑, 杀不掉, 占用 CPU 和内存 , 这不是消费者想看到的 ; 如果 Rom 开发者斗争成功 , App 的体验必定会大打折扣 , 各位 App 开发者应该深有体会. 从文章最后一段可以看到, 其实各个手机厂商对付这一套都有自己的策略, 基本上都可以搞定自启动和关联启动. 至于隐私 , 李彦宏曾经说过 “中国人对隐私问题的态度更加开放,也相对来说没那么敏感。如果他们可以用隐私换取便利、安全或者效率。在很多情况下,他们就愿意这么做“ . 大家想想在微信里面复制一段话打开到淘宝就可以自动跳转到这个物品, 方不方便? 好不好用? 还想不想用? 剪贴板再借我看一看? 希望大家在隐私问题上不要打哈哈, 技术是把双刃剑, 如果隐私落到别有用心的人手上, 后果是很严重的, 就算不是为了自己, 为了下一代. 欧盟为什么要搞《通用数据保护条例》(General Data Protection Regulation,简称 GDPR), 就是为了隐私. 举个例子 , 国内很多厂商的产品现在要区分是否在欧盟买, 如果是在欧盟卖的话, 就得把里面那些收集用户数据的功能都关掉 , 否则抓住了就能罚你罚到吐血 . 至于中国和印度, 随便收集. 本篇文章不涉及到隐私部分, 我是对隐私保护无条件支持的 . 这里只从技术的角度 , 来讲一下 MIUI 12 爆出来的应用自启动和关联唤醒的问题. PS: 大家在自己的手机上可能看不到我列的一些例子, 是因为我是用的 Android 10 的 AOSP 代码, 大部分的国产 Rom 都已经阻断了应用的这些行为. 技术名词解释 首先解释几个技术名词, 方便大家对号入座 进程启动 在 Android 中 , 一个 App 包含六部分, 进程(必选) + Activity (可选) + BroadcastReceiver (可选)+Service (可选)+ContentProvider (可选) + 子进程(可选) 一个必选项加五个可选项, 组成了一个 App , 其中 Activity(可选) + BroadcastReceiver(可选)+Service(可选)+ContentProvider(可选) 这四个又称为 Android 的四大组件, 之所以这四兄弟这么特殊, 是因为这四个组件都可以单独启动, 但是这四兄弟启动之前, 系统都会检查对应的进程是否存在, 如果进程不存在 , 那么就需要先启动进程, 再启动这个组件. 我们在桌面上点击一个应用图标, 其实启动的就是他的 Activity , 系统会先创建进程, 然后再启动 Activity , 我们才可以看到对应的界面 一般自启动和关联启动, 一般不会直接启动 Activity , 因为 Activity 是用户可感知的 , 你在后台莫名其妙起了一个界面到前台, 用户分分钟卸了你 . 所以一般自启动和关联启动都是在 BroadcastReceiver (可选) + Service (可选) + ContentProvider (可选) 三个上面做文章. 自启动 自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情. 关联启动 关联启动指的是借助其他应用来启动自己, 比如大家列出来的起点读书启动作家助手\电信营业厅\百词斩这种. 启动阻断 启动阻断也叫切断唤醒, Rom 开发人员在四大组件启动的地方加入逻辑判断, 符合条件的组件才能拉起自己的进程 , 不符合条件的组件直接返回 , 这样就达到了启动阻断的目的. 当然这里面还有很多工作要做, 比如工作状态判断, 拉起合理性判断 , 一旦错误的阻断必然会引起用户的使用逻辑的断裂, 比如用户在一个 App 里面要拉起支付宝进行支付 , 结果启动支付宝的支付组件的时候被你给阻断了, 可以想象用户的愤怒 有了上面几个简单的概念, 下面我们就简单说一下自启动和关联启动的技术分析 . 分析手段 Monkey 要分析应用启动,首先需要安装大量应用,然后执行 Monkey,让大部分进程都跑起来。我使用的 Monkey 命令如下,跑完就自己去睡觉了 1 adb shell monkey --kill-process-after-error --ignore-security-exceptions --ignore-crashes --pct-appswitch 90 --pct-touch 10 --throttle 10000 --ignore-timeouts --ignore-native-crashes 100000000 EventLog 首先可以用 EventLog 来查看进程的启动信息,EventLog 会如实记录每个进程的启动、死亡信息。我使用下面的命令来进行进程启动和死亡的过滤 1 adb logcat -b events | egrep "am_proc_died|am_proc_start" Dumpsys 这里主要是使用了 Dumpsys activity ,主要是用来分析进程的各个组件的信息 1 adb shell dumpsys activity 自启动的技术分析 自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情. 这些系统的事件就包括开机广播 / 网络变化 / 媒体库扫描等(这里只列了一部分) . 开机广播 用户重启手机后, 系统会向注册了开机广播的应用发广播, 收到广播的应用就可以把自己拉起来, 开始处理对应的逻辑(拉起更多的进程) , 对应的广播如下: 1 android.intent.action.BOOT_COMPLETED 应用可以监听这个广播, 在用户重启手机后, 将自己唤醒, 处理自己的逻辑 , 比如说继续图片备份/ 继续同步联系人 / 检查是否有固件更新 / 推送最新的新闻等操作 当然监听这个广播也是应用自启动的一个手段 案例: 腾讯新闻监听开机广播拉起后台进程 典型的广播接受处理记录 : com.tencent.news 的 com.tencent.news.system.BootBroadcastReceiver 组件接收了 android.intent.action.BOOT_COMPLETED 广播 ,处理了 7s ,至于怎么处理, 当然是先把 com.tencent.news 这个进程拉起来, 然后执行 BootBroadcastReceiver 的 onReceive 方法 . 这是一个典型的自启动的例子 网络变化 网络变化包括网络连接 / 断开 / wifi 移动网络切换等操作 , 一旦发生这些事件, 系统会向对应注册了这个事件的应用发送广播 . 对应的广播如下: 1 android.net.conn.CONNECTIVITY_CHANGE 应用就可以监听这个广播来执行对应的逻辑 , 比如你在看直播 ,突然 wifi 断了切换成了 4G 网络, 应用就可以提醒用户是否使用移动网络继续观看, 毕竟网络直播还是很耗流量的. 当然监听这个广播也是应用自启动的一个手段 案例 下图可以看到五个监听了网络变大的广播接收器 (只显示了五个 , 其实有 200 多个) , 监听到网络变化后拉起自身 包名: com.alibaba.android.rimet(钉钉) 接收器 : com.xiaomi.push.service.receivers.NetworkStatusReceiver 包名: cn.xuexi.android 接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver 包名: com.sina.weibo 接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver 包名: com.sdu.didi.psnger 接收器 : com.didi.sdk.push.PushNetReceiver 包名: com.meelive.ingkee 接收器 : com.network_optimization.NetWorkStateReceiver 图中 packageName 就是对于的应用的包名, name 是启动的组件 1 android.net.wifi.STATE_CHANGE 媒体库扫描 系统监听到文件变化或者存储盘变化也会发通知给各个应用 , 比如说增加了一个图片或者文档 , 其对于的广播如下 1 2 3 android.intent.action.MEDIA_SCANNER_STARTED android.intent.action.MEDIA_SCANNER_FINISHED android.intent.action.MEDIA_EJECT 当然监听这个广播也是应用自启动的一个手段 案例: jd 监听 MEDIA_SCANNER_STARTED 广播自启 下面是一个典型的监听媒体库扫描广播进行自启动的案例: com.jd.jrapp 的广播接收器 com.jd.jrapp.library.longconnection.receiver.BootReceiver 监听到 android.intent.action.MEDIA_SCANNER_STARTED 广播后, 启动自己进程开始处理 三方 SDK - 个推 个推是各个应用接入的一个三方 SDK , 用于消息推送 , 但其实个推也集成了上面说的哪几种自启动的方式 , 包括 BOOT_COMPLETED,CONNECTIVITY_CHANGE,USER_PRESENT 这些 关于个推,由于可定制型比较强, 比如 在项目源码中添加一个继承自 com.igexin.sdk.PushService 的自定义 Service 就可以 , 所以从 EventLog 和 Dumpsys 没法直接看出来哪个用了个推来保活或者相互唤醒, 不过其对于的子进程得设置为 :pushservice , 可以根据这个做判断(有可能不准) 所以我们直接看个推的配置文档 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 像最前面同提到的 BootComplete , com.ss.android.ugc.aweme:pushservice 可能就是接入了个推 关联启动的技术分析 关联启动指的是借助其他应用来启动自己 , 比如说很多 App 接入了同一个 SDK , 那么一旦你启动了接入这个 SDK 的应用 ,那么这个 SDK 就可以启动同样接入了这个 SDK 的其他应用, 达到关联唤醒的目的 这个 SDK 可以是 BAT 集团内部自研的通用 SDK , 也可以是三方提供的 SDK , 根据我自己的调试来看 , 大家提到的 xxx 启动了 xxx , 大部分都是通过三方 SDK 来实现的 , 大部分是用了极光推送. 下面就以一个案例来看极光推送是怎么利用一个已经启动的 App 来启动另外一个没有启动的 App 的. 首先我们看 EventLog 可以看到进程的启动信息 , 包括进程名, 进程 pid , 启动的组件, 启动的组件类型. 1 [0,19428,10195,com.qq.reader,service,{com.qq.reader/cn.jpush.android.service.DaemonService}] 上面这条 Log 解释一下就是 启动进程 : com.qq.reader(QQ 阅读) 启动 pid :19428 启动组件: cn.jpush.android.service.DaemonService 组件类型: service 案例:起点 App 通过极光推送拉起 QQ 阅读 执行 adb shell am force-stop com.qq.reader , 强制杀掉 QQ 阅读 , 观察 EventLog, 从下面的可以看到 , QQ 阅读的进程被拉起, 拉起的是 Service 这个组件, 其具体的内容是 com.qq.reader/cn.jpush.android.service.DaemonService 当然从 Event Log 里面我们看不出来是谁拉起了这个 Service ,这时候就需要 dumpsys activity 的帮助了, 由于是 Service 组件被拉起, 那么我们可以看 com.qq.reader 的 ServiceRecord , 其内容如下, 可以看到其 Connections 一栏, 是被 com.qidian.QDReader:pushcore 这个进程拉起的 那么对应的 , 在小米的 MIUI 12 关联启动界面就会显示 : 起点读书 在 8:48 分拉起了 QQ 阅读(由于没有小米手机, 所以没法截图, 大家自己看高票答案 https://www.zhihu.com/question/391494145 自己脑补一下就可以了) 极光推送的官方文档其实也说的很清楚, 提供了被拉起和拉起别人的功能, 看你自己怎么用. 极光官方文档唤醒配置 极光关联启动文档 手机厂商应对 最前面的有说到, 进程管理是应用开发者和 Rom 开发者斗争最激烈的部分 , MIUI 选择了将斗争的过程展示给了普通消费者, 让普通消费者也知道了这场斗争的细节 . 其他的厂商也做了相同的事情 , 否则整个系统基本上是没法用的 , 就像我手上现在这台测试用的 pixel , 不断有进程因为整机内存太小被 LMK 杀掉, 然后马上被各种手段重新启动 , 耗电极快, 卡的连娘都不认识了. 我们从极光和个推的官方文档就可以看到各个手机厂商的应对方法和开关的界面, 这里列出来是方便大家进去看一下, 因为各个手机厂商的白名单配置不一样, 或者有时候用户自己改过但是忘记了 , 都可以进去重新设置一下 , 对于那些你退出了就不想让他继续活动的应用 ,果断去掉白名单. 极光推送白名单配置 个推白名单配置 EMUI OS(华为) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程不会开启,只能手动开启应用 后台应用保护:需要手动把应用加到此列表,否则设备进入睡眠后会自动杀掉应用进程,只有手动开启应用才能恢复运行 通知管理:应用状态有三种:提示、允许、禁止。禁止应用则通知栏不会有任何提醒 Flyme OS(魅族) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启 通知栏推送:关闭应用通知则收到消息不会有任何展示 Funtouch OS(VIVO) 自启动管理:需要将应用加入“i 管家”中的【自启动管理】列表,否则重启手机后进程不会自启。但强制手动杀进程,即使加了这个列表中,后续进程也无法自启动。 Color OS(OPPO) 冻结应用管理:需要将应用加入纯净后台,否则锁屏状态下无法及时收到消息 自启动管理:将应用加入【自启动管理】列表的同时,还需要到设置-应用程序-正在运行里锁定应用进程,否则杀进程或者开机后进程不会开启,只能手动开启应用 MIUI OS (小米) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启 省电策略:需要禁用应用省电策略,否则后台几分钟后会被系统限制联网 MIUI 7 神隐模式: 允许用户设置后台联网应用,开启后应用即可在后台保持联网,否则应用进入后台时,应用无法正常接收消息。【设置】->【电量和性能】->【神隐模式】 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
本文是 Systrace 系列文章的第五篇,主要是对 SurfaceFlinger 的工作流程进行简单介绍,介绍了 SurfaceFlinger 中几个比较重要的线程,包括 Vsync 信号的解读、应用的 Buffer 展示、卡顿判定等,由于 Vsync 这一块在 Systrace 基础知识 - Vsync 解读 和 Android 基于 Choreographer 的渲染机制详解 这两篇文章里面已经介绍过,这里就不再做详细的讲解了。 本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。 系列文章目录 Systrace 简介 Systrace 基础知识 - Systrace 预备知识 Systrace 基础知识 - Why 60 fps ? Systrace 基础知识 - SystemServer 解读 Systrace 基础知识 - SurfaceFlinger 解读 Systrace 基础知识 - Input 解读 Systrace 基础知识 - Vsync 解读 Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解 Systrace 基础知识 - MainThread 和 RenderThread 解读 Systrace 基础知识 - Binder 和锁竞争解读 Systrace 基础知识 - Triple Buffer 解读 Systrace 基础知识 - CPU Info 解读 Systrace 流畅性实战 1 :了解卡顿原理 Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析 Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Systrace 响应速度实战 1 :了解响应速度原理 Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Systrace 响应速度实战 3 :响应速度延伸知识 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 正文 这里直接上官方对于 SurfaceFlinger 的定义 大多数应用在屏幕上一次显示三个层:屏幕顶部的状态栏、底部或侧面的导航栏以及应用界面。有些应用会拥有更多或更少的层(例如,默认主屏幕应用有一个单独的壁纸层,而全屏游戏可能会隐藏状态栏)。每个层都可以单独更新。状态栏和导航栏由系统进程渲染,而应用层由应用渲染,两者之间不进行协调。 设备显示会按一定速率刷新,在手机和平板电脑上通常为 60 fps。如果显示内容在刷新期间更新,则会出现撕裂现象;因此,请务必只在周期之间更新内容。在可以安全更新内容时,系统便会收到来自显示设备的信号。由于历史原因,我们将该信号称为 VSYNC 信号。 刷新率可能会随时间而变化,例如,一些移动设备的帧率范围在 58 fps 到 62 fps 之间,具体要视当前条件而定。对于连接了 HDMI 的电视,刷新率在理论上可以下降到 24 Hz 或 48 Hz,以便与视频相匹配。由于每个刷新周期只能更新屏幕一次,因此以 200 fps 的帧率为显示设备提交缓冲区就是一种资源浪费,因为大多数帧会被舍弃掉。SurfaceFlinger 不会在应用每次提交缓冲区时都执行操作,而是在显示设备准备好接收新的缓冲区时才会唤醒。 当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。 SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。」 —- 引用自SurfaceFlinger 和 Hardware Composer 下面是上述流程所对应的流程图, 简单地说, SurfaceFlinger 最主要的功能:SurfaceFlinger 接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。 那么 Systrace 中,我们关注的重点就是上面这幅图对应的部分 App 部分 BufferQueue 部分 SurfaceFlinger 部分 HWComposer 部分 这四部分,在 Systrace 中都有可以对应的地方,以时间发生的顺序排序就是 1、2、3、4,下面我们从 Systrace 的这四部分来看整个渲染的流程 App 部分 关于 App 部分,其实在Systrace 基础知识 - MainThread 和 RenderThread 解读这篇文章里面已经说得比较清楚了,不清楚的可以去这篇文章里面看,其主要的流程如下图: 从 SurfaceFlinger 的角度来看,App 部分主要负责生产 SurfaceFlinger 合成所需要的 Surface。 App 与 SurfaceFlinger 的交互主要集中在三点 Vsync 信号的接收和处理 RenderThread 的 dequeueBuffer RenderThread 的 queueBuffer Vsync 信号的接收和处理 关于这部分内容可以查看 Android 基于 Choreographer 的渲染机制详解 这篇文章,App 和 SurfaceFlinger 的第一个交互点就是 Vsync 信号的请求和接收,如上图中第一条标识,Vsync-App 信号到达,就是指的是 SurfaceFlinger 的 Vsync-App 信号。应用收到这个信号后,开始一帧的渲染准备 RenderThread 的 dequeueBuffer dequeue 有出队的意思,dequeueBuffer 顾名思义,就是从队列中拿出一个 Buffer,这个队列就是 SurfaceFlinger 中的 BufferQueue。如下图,应用开始渲染前,首先需要通过 Binder 调用从 SurfaceFlinger 的 BufferQueue 中获取一个 Buffer,其流程如下: App 端的 Systrace 如下所示 SurfaceFlinger 端的 Systrace 如下所示 RenderThread 的 queueBuffer queue 有入队的意思,queueBuffer 顾名思义就是讲 Buffer 放回到 BufferQueue,App 处理完 Buffer 后(写入具体的 drawcall),会把这个 Buffer 通过 eglSwapBuffersWithDamageKHR -> queueBuffer 这个流程,将 Buffer 放回 BufferQueue,其流程如下 App 端的 Systrace 如下所示 SurfaceFlinger 端的 Systrace 如下所示 通过上面三部分,大家应该对下图中的流程会有一个比较直观的了解了 BufferQueue 部分 BufferQueue 部分其实在Systrace 基础知识 - Triple Buffer 解读 这里有讲,如下图,结合上面那张图,每个有显示界面的进程对应一个 BufferQueue,使用方创建并拥有 BufferQueue 数据结构,并且可存在于与其生产方不同的进程中,BufferQueue 工作流程如下: 上图主要是 dequeue、queue、acquire、release ,在这个例子里面,App 是生产者,负责填充显示缓冲区(Buffer);SurfaceFlinger 是消费者,将各个进程的显示缓冲区做合成操作 dequeue(生产者发起) : 当生产者需要缓冲区时,它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区,并指定缓冲区的宽度、高度、像素格式和使用标记。 queue(生产者发起):生产者填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。 acquire(消费者发起) :消费者通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容 release(消费者发起) :当消费者操作完成后,它会通过调用 releaseBuffer() 将该缓冲区返回到队列 SurfaceFlinger 部分 工作流程 从最前面我们知道 SurfaceFlinger 的主要工作就是合成: 当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。 其 Systrace 主线程可用看到其主要是在收到 Vsync 信号后开始工作 其对应的代码如下,主要是处理两个 Message MessageQueue::INVALIDATE — 主要是执行 handleMessageTransaction 和 handleMessageInvalidate 这两个方法 MessageQueue::REFRESH — 主要是执行 handleMessageRefresh 方法 frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS { ATRACE_CALL(); switch (what) { case MessageQueue::INVALIDATE: { ...... bool refreshNeeded = handleMessageTransaction(); refreshNeeded |= handleMessageInvalidate(); ...... break; } case MessageQueue::REFRESH: { handleMessageRefresh(); break; } } } //handleMessageInvalidate 实现如下 bool SurfaceFlinger::handleMessageInvalidate() { ATRACE_CALL(); bool refreshNeeded = handlePageFlip(); if (mVisibleRegionsDirty) { computeLayerBounds(); if (mTracingEnabled) { mTracing.notify("visibleRegionsDirty"); } } for (auto& layer : mLayersPendingRefresh) { Region visibleReg; visibleReg.set(layer->getScreenBounds()); invalidateLayerStack(layer, visibleReg); } mLayersPendingRefresh.clear(); return refreshNeeded; } //handleMessageRefresh 实现如下, SurfaceFlinger 的大部分工作都是在handleMessageRefresh 中发起的 void SurfaceFlinger::handleMessageRefresh() { ATRACE_CALL(); mRefreshPending = false; const bool repaintEverything = mRepaintEverything.exchange(false); preComposition(); rebuildLayerStacks(); calculateWorkingSet(); for (const auto& [token, display] : mDisplays) { beginFrame(display); prepareFrame(display); doDebugFlashRegions(display, repaintEverything); doComposition(display, repaintEverything); } logLayerStats(); postFrame(); postComposition(); mHadClientComposition = false; mHadDeviceComposition = false; for (const auto& [token, displayDevice] : mDisplays) { auto display = displayDevice->getCompositionDisplay(); const auto displayId = display->getId(); mHadClientComposition = mHadClientComposition || getHwComposer().hasClientComposition(displayId); mHadDeviceComposition = mHadDeviceComposition || getHwComposer().hasDeviceComposition(displayId); } mVsyncModulator.onRefreshed(mHadClientComposition); mLayersWithQueuedFrames.clear(); } handleMessageRefresh 中按照重要性主要有下面几个功能 准备工作 preComposition(); rebuildLayerStacks(); calculateWorkingSet(); 合成工作 begiFrame(display); prepareFrame(display); doDebugFlashRegions(display, repaintEverything); doComposition(display, repaintEverything); 收尾工作 logLayerStats(); postFrame(); postComposition(); 由于显示系统有非常庞大的细节,这里就不一一进行讲解了,如果你的工作在这一部分,那么所有的流程都需要熟悉并掌握,如果只是想熟悉流程,那么不需要太深入,知道 SurfaceFlinger 的主要工作逻辑即可 掉帧 通常我们通过 Systrace 判断应用是否掉帧的时候,一般是直接看 SurfaceFlinger 部分,主要是下面几个步骤 SurfaceFlinger 的主线程在每个 Vsync-SF 的时候是否没有合成? 如果没有合成操作,那么需要看没有合成的原因: 因为 SurfaceFlinger 检查发现没有可用的 Buffer 而没有合成操作? 因为 SurfaceFlinger 被其他的工作占用(比如截图、HWC 等)? 因为 SurfaceFlinger 在等 presentFence ? 因为 SurfaceFlinger 在等 GPU fence? 如果有合成操作,那么需要看 你关心的 App 的 可用 Buffer 个数是否正常:如果 App 此时可用 Buffer 为 0,那么看 App 端为何没有及时 queueBuffer(这就一般是应用自身的问题了),因为 SurfaceFlinger 合成操作触发可能是其他的进程有可用的 Buffer 关于这一部分的 Systrace 怎么看,在 Systrace 基础知识 - Triple Buffer 解读-掉帧检测 部分已经有比较详细的解读,大家可以过去看这一段 HWComposer 部分 关于 HWComposer 的功能部分我们就直接看 官方的介绍 即可 Hardware Composer HAL (HWC) 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示设备硬件原始设备制造商 (OEM) 完成。 当您考虑使用叠加平面时,很容易发现这种方法的好处,它会在显示硬件(而不是 GPU)中合成多个缓冲区。例如,假设有一部普通 Android 手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成(后一种方法可以显著提高效率): 将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。 将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。 显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。为了适应这些选项,HWC 会执行以下计算(由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能): SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?” HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。 SurfaceFlinger 会处理所有 GLES 合成,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。 当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以选择为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 返回来要求合成同一组缓冲区,HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。 运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对能耗和性能产生重大影响。 ——– 引用自SurfaceFlinger 和 Hardware Composer 我们继续接着看 SurfaceFlinger 主线程的部分,对应上面步骤中的第三步,下图可以看到 SurfaceFlinger 与 HWC 的通信部分 这也对应了最上面那张图的后面部分 不过这其中的细节非常多,这里就不详细说了。至于为什么要提 HWC,因为 HWC 不仅是渲染链路上重要的一环,其性能也会影响整机的性能,Android 中的卡顿丢帧原因概述 - 系统篇 这篇文章里面就有列有 HWC 导致的卡顿问题(性能不足,中断信号慢等问题) 想了解更多 HWC 的知识,可以参考这篇文章Android P 图形显示系统(一)硬件合成HWC2,当然,作者的Android P 图形显示系这个系列大家可以仔细看一下 参考文章 Android P 图形显示系统(一)硬件合成HWC2 Android P 图形显示系统 SurfaceFlinger 的定义 surfacefliner 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
Medium 上 @MindOrks 发布了一篇 2020 年 Android 程序员的学习线路,鉴于一部分人无法阅读原文(你懂得原因),我把这篇文章的内容结合自己的 2020 年的学习计划,一起发出来,给大家一个参考 原文比较简单,并没有介绍为什么要推荐这些,只是单纯地列了一下知识点,我这边针对每个知识点做一些简单的介绍,有些知识点原文并没有提到,我会根据自己的理解加上,仅供参考 这篇文章主要针对 Android 开发者,如果你是新手,那么下面的内容可以帮助你找到学习的线路;如果你是老手,这篇文章列出的内容也可以帮助你查漏补缺。如果各位有什么其他的建议,欢迎留言交流 Programming Java Java 是 Android App 开发默认的语言, Android Framework 也是默认使用 Java 语言,熟练掌握 Java 语言是 Android 开发者的必备技能。 希望深入 Java 虚拟机的同学,也可以参考下面两本书: 周志明的《深入理解Java虚拟机(第3版)》 邓老师的 《深入理解Android Java 虚拟机 ART》 Kotlin Google 几年前就开始走 “Kotlin First” 的路线,目前很多官方的文档和 Demo 都是使用 Kotlin 语言作为默认,Kotlin 的重要性不言而喻。 Google 官方也出了个“Refactoring to Kotlin”的教程,其介绍如下: 此 Codelab 的适用对象为任何使用 Java 并考虑将其项目迁移到 Kotlin 的开发者。我们将从数个 Java 类入手,引导您使用 IDE 将它们转换为 Kotlin。接着,我们会审视转换后的代码,研究如何加以改善,使其更符合使用习惯,同时避免常见错误 Flutter Flutter 作为 Google 的亲儿子,其官方的扶持力度大家有目共睹。 Flutter 于几天前发布了v1.12.13_hotfix.7 版本,修复了几个比较严重的 Bug,如Flutter 1.12 最新 hotfix 与 2020 路线计划 这篇文章介绍所述,“v1.12.13+hotfix.7 版本主要在于解决了我比较关心的三个问题,包括: reportFullyDrawn 异常、华为手机上崩溃、光标和键盘输入异常 这几个问题。”.感兴趣也可以看一下其 1 月 30 号发布的 2020 Roadmap Flutter 的发展大家可以看一下 Gityuan 的这一篇Flutter 跨平台演进及架构开篇,目前字节跳动的多个 App 已经接入 Flutter 进行混合开发。个人对 2020 年 Flutter 不再持观望态度,读者可以根据自己的技术规划决定是否开始学习 Android Studio Android Studio IDE Overview Android Studio 作为 Android 默认的开发者工具,目前的版本更新已经解决了诸多之前的性能问题,虽然目前对硬件资源的要求仍然比较高,但是一旦你接受了这个设定,真香预警! AS 主要需要熟悉下面几点 AS 快捷键 AS 插件 AS Profile (内存、CPU、IO、NetWork) Project Structure — Java/Kotlin/Flutter, XML, .gradle files 熟悉各种项目的目录结构,资源文件、Gradle 文件 Android 基础知识 四大组件 这部分不必做过多的解释,下面列出的就是大家熟悉的 Android 四大组件,Android 开发的基础 Activity — Activity Lifecycle, Tasks & Back Stack Service Broadcast Receiver Content Provider Intents Types of Intent - Implicit, Explicit Intent Filter Static User Interface View — Button, ImageView, TextView, EditText, and etc :这是开发中会遇到的常用的组件,许多复杂的布局都是用简单基础的 View 组合而成 ViewGroup - LinearLayout, RelativeLayout, FrameLayout:三大传统布局,适用于不同的场合 ConstraintLayout : Google 新推的布局,目前已经取代 RelativeLayout 成为默认的 App 布局,具体使用可以参考官方文档 Dynamic User Interface RecyclerView - 列表类的布局首选控件,性能相对 ListView 要好一些,功能也比 ListView 要多一些 ViewPager Spinner CustomView Android 默认的布局很多时候都没法满足设计的需求,这时候就需要自定义 View,你需要掌握下面几个知识点的使用 Canvas Bitmap Paint UI Resources 相比 HardCode,使用资源文件会让代码的可修改性更高 Drawables String Styles Fragments 许多人提倡 App 使用 单 Activity + 多个 Fragment 的组合,可见 Fragment 在开发中的重要性,但是 Fragment 的管理又是一门技术,Fragment 的坑,只能在实际开发中慢慢填平了,不过下面的 Fragment 基础还是要牢固 Fragment Lifecycle Fragment Manager Support User Interface 这里列的同样是一些功能组件,需要知道这是什么东西,基本的用法 ProgressBar - 进度条 Dialogs - 弹框 Toast & Snackbar - 提示 Storage App 开发不免要和文件打交道,文件的读写、存储都是必不可少的,下面列出了几种 Android 中存储相关的知识点 Shared Preferences - 适合存储字段 File Systems - 文件存储 Database — RoomDB - 数据库存储,RoomDB 是 Google 新推出的数据库解决方案(在 AndroidX 中),具体使用可以参考官方文档 Build Android App 默认使用 Gradle 进行编译,关于 Gradle 的使用必须要熟悉,以及如何区分开发版本和 Release 版本,以及国内特有的多渠道打包技术、以及 ASM 等 Gradle Debug / Release Configuration 多渠道打包 ASM Threading 理解 Thread 非常重要,Android App 只有一个主线程,其余的我们称之为工作线程,我们的很多工作需要再工作线程和主线程直接切换,如何高效创建和释放线程、线程池、线程间通信、Message-Looper-Handler 模型这些知识点都要了熟于心,另外进阶的话 Binder 通信也是需要掌握的知识 Threads Handler / Looper / Message / MessageQueue AIDL / Binder Debugging 这里列举了一些 Debug 的基本手段,实际开发中遇到具体问题的时候一般都会用到,不过有的可能入手难度要高一些,需要花时间去掌握。Debug 工具除了下面这几个还有很多 Memory profiling - MAT,AS Memory Profile Logging - Log 包含非常丰富的信息,可以帮助我们还原现场 Systrace - Systrace 工具可以查看一段时间内手机系统各个进程的运行状态,具体使用可以参考我博客的 Systrace 系列教程 Exceptions - 各种异常,保证程序的健壮性 Error Handling - Error 是必须要解决的问题,一般会导致 App 直接闪退,需要非常重视 Memory Leak 内存泄漏是一个很大的专题,包括 Java 内容泄漏和 Native 内存泄漏,涉及的知识点非常多,可以单独拿出来做一个大的知识栈。一般来说, Java 内存泄漏会比较好检测和修复,但是 Native 内存泄漏就会比较难。 Detecting and Fixing Memory Leaks - 内存泄漏检测和修复,是一个比较大的工程,可以参考 LeakCanary、Matrix 等开源工具 Context - 使用不当会造成该释放的对象没有释放造成内存泄漏 Native Memory Leaks 3rd Party Library 经典的第三方类库,可以大幅节约我们的开发时间 Image Loading - Glide, Picasso Dependency Injection - Dagger Networking - Fast Android Networking Library, Retrofit MultiThreading - RxJava, Coroutines Data Format 常见的一些数据保存流格式 JSON — GSON Flat Buffer Protocol Buffer Android Jetpack Jetpack 是 Google 推出的一套库、工具和指南,可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务,以便您将精力集中放在所需的代码上。Jetpack 包含与平台 API 解除捆绑的 androidx.* 软件包库。这意味着,它可以提供向后兼容性,且比 Android 平台的更新频率更高,以此确保您始终可以获取最新且最好的 Jetpack 组件版本。 Foundation Components — AppCompat, Android KTX, Multidex Architecture Components — LiveData, ViewModel, DataBinding, Paging, Work Manager, Navigation Behaviour Components - Download Manager, Media Playback, Notification, Permissions, Preference, Sharing, Slice UI Component - Animation & Transition, Android Auto, Emoji, Palette, Android TV, Android Wear Architecture 传统的开发架构,没有绝对的哪个好哪个不好,只有哪个适合哪个不适合,下面三种你都应该知道并有一定的了解 MVVM - MVVM 是 Model-View-ViewModel的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开 MVI ? MVP - MVP 从更早的 MVC 框架演变过来,与 MVC 有一定的相似性:Controller/Presenter 负责逻辑的处理,Model 提供数据,View 负责显示 Unit Testing Local Unit Testing Instrumentation Testing Firebase Firebase 国内很多开发者用不到,这里简单看一下即可(说不定哪天国内就可以用了呢) FCM Crashlytics Analytics Remote Config App Indexing Dynamic Link Security 安全方面接触毕竟多的应该是加密、解密、混淆等,毕竟用户数据安全大于一切,不重视这个欧盟会教你做人 Encrypt / Decrypt Proguard R8 App Release 应用发布相关的知识,国内还得加上多渠道打包、插件化 .keystore file App Bundle Playstore 多渠道打包 插件化 Keep Learning and Improving 作为一个有进取心的 Android 开发者,拥有自己的技术栈和规划非常重要,技术栈确保你有足够的市场竞争力,从而形成护城河;技术规划则可以给你一个明确的学习目标。卸载抖音、微博、斗鱼、游戏吧,做好 2020 年的规划,Keep Learning and Improving ,共勉 如果你苦于没有好的时间管理方法,可以参考这个视频我是怎么做周计划 | 生产力提升 | 我的方法,这个是我熟悉的一个大佬的工作学习方法实践,推荐给大家 凡是预则立,不预则废,年度计划太长,日计划又太短。实践下来发现以周为单位做时间管理(时间管理)最靠谱,既考虑了短期又考虑了长期,可以使自己长期坚持做某事,也有一定的时间长度用来甄有价值的事情。 本文其他地址 由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎或者掘金页面 知乎 - Android 开发者学习路线(2020 版) 关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师! 博主个人介绍 :里面有个人的微信和微信群链接。 本博客内容导航 :个人博客内容的一个导航。 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可) Android性能优化知识星球 : 欢迎加入,多谢支持~ 一个人可以走的更快 , 一群人可以走的更远
您可以订阅此RSS以获取更多信息