<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance on zed的博客</title><link>https://www.dust-zed.site/android/performance/</link><description>Recent content in Performance on zed的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 15 Jun 2025 23:19:59 +0800</lastBuildDate><atom:link href="https://www.dust-zed.site/android/performance/index.xml" rel="self" type="application/rss+xml"/><item><title>Android 包体积优化：从 APK 分析到 R8、资源和 ABI</title><link>https://www.dust-zed.site/android/apk-size-optimization/</link><pubDate>Sun, 15 Jun 2025 23:19:59 +0800</pubDate><guid>https://www.dust-zed.site/android/apk-size-optimization/</guid><description>&lt;p>包体积优化不能只靠“开混淆”。正确路径是先知道体积花在哪里，再按代码、资源、原生库、Assets、动态模块逐项处理。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>先用工具看 APK/AAB 构成，再决定优化方向。&lt;/li>
&lt;li>R8 的主要价值是裁剪、优化和混淆，其中裁剪对体积影响最大。&lt;/li>
&lt;li>资源优化重点是删除无用资源、压缩图片和控制多套资源。&lt;/li>
&lt;li>原生库要重点检查 ABI、重复 &lt;code>.so&lt;/code> 和调试符号。&lt;/li>
&lt;li>大功能可以考虑 Play Feature Delivery 或按需下载，不要全部塞进首包。&lt;/li>
&lt;/ol>
&lt;h2 id="分析入口">分析入口
&lt;/h2>&lt;p>常用工具：&lt;/p>
&lt;ol>
&lt;li>Android Studio &lt;code>Build &amp;gt; Analyze APK&lt;/code>&lt;/li>
&lt;li>Android Studio App Size Analyzer&lt;/li>
&lt;li>Gradle 构建输出和依赖报告&lt;/li>
&lt;li>&lt;code>bundletool&lt;/code> 分析 AAB 生成的设备 APK&lt;/li>
&lt;/ol>
&lt;p>先看四类占比：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">classes.dex -&amp;gt; Java/Kotlin 字节码
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">res/resources -&amp;gt; Android 资源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">lib/*.so -&amp;gt; Native 库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">assets -&amp;gt; 原始资产文件
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="代码体积">代码体积
&lt;/h2>&lt;p>R8 可以完成三件事：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Shrinking&lt;/strong>：删除未使用类、方法和字段；&lt;/li>
&lt;li>&lt;strong>Optimization&lt;/strong>：内联、常量传播、简化控制流；&lt;/li>
&lt;li>&lt;strong>Obfuscation&lt;/strong>：缩短名称，提高逆向成本。&lt;/li>
&lt;/ol>
&lt;p>配置入口通常是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="n">android&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">buildTypes&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">release&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">isMinifyEnabled&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">isShrinkResources&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="k">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">proguardFiles&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">getDefaultProguardFile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;proguard-android-optimize.txt&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;proguard-rules.pro&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意点：&lt;/p>
&lt;ol>
&lt;li>不要盲目 &lt;code>-keep&lt;/code> 整个包；&lt;/li>
&lt;li>反射、序列化、JNI、路由框架需要明确 keep 边界；&lt;/li>
&lt;li>先看 R8 输出，再判断哪些规则过宽；&lt;/li>
&lt;li>Debug 包不要拿来评估最终体积。&lt;/li>
&lt;/ol>
&lt;h2 id="资源体积">资源体积
&lt;/h2>&lt;p>资源优化重点：&lt;/p>
&lt;ol>
&lt;li>开启 &lt;code>shrinkResources&lt;/code>；&lt;/li>
&lt;li>删除未使用资源；&lt;/li>
&lt;li>图片优先使用 WebP 或 VectorDrawable；&lt;/li>
&lt;li>控制多语言、多密度、多主题资源；&lt;/li>
&lt;li>避免把临时大图、设计稿、日志文件放进 &lt;code>assets&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>排查命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./gradlew lintRelease
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>图片策略：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>类型&lt;/th>
&lt;th>建议&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>简单图标&lt;/td>
&lt;td>VectorDrawable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>照片类图片&lt;/td>
&lt;td>WebP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>透明图片&lt;/td>
&lt;td>WebP lossless 或 PNG&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>大型离线素材&lt;/td>
&lt;td>按需下载或独立资源包&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="native-库体积">Native 库体积
&lt;/h2>&lt;p>原生库常见体积来源：&lt;/p>
&lt;ol>
&lt;li>多 ABI 同时打包；&lt;/li>
&lt;li>&lt;code>.so&lt;/code> 中包含调试符号；&lt;/li>
&lt;li>第三方 SDK 带入重复库；&lt;/li>
&lt;li>Rust/C++ 静态链接带来额外体积。&lt;/li>
&lt;/ol>
&lt;p>可以按目标市场控制 ABI：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="n">android&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">defaultConfig&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ndk&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">abiFilters&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">listOf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;arm64-v8a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;armeabi-v7a&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果使用 AAB，Google Play 会按设备拆分 ABI，首包压力会小很多。&lt;/p>
&lt;h2 id="assets-和动态能力">Assets 和动态能力
&lt;/h2>&lt;p>&lt;code>assets&lt;/code> 不会像普通资源那样参与细粒度优化，放进去的文件通常会原样进入包体。&lt;/p>
&lt;p>适合放在 &lt;code>assets&lt;/code> 的内容：&lt;/p>
&lt;ol>
&lt;li>小型配置文件；&lt;/li>
&lt;li>必须随包内置的模型或规则；&lt;/li>
&lt;li>首次启动必须可用的轻量数据。&lt;/li>
&lt;/ol>
&lt;p>不适合：&lt;/p>
&lt;ol>
&lt;li>大视频；&lt;/li>
&lt;li>大型模型；&lt;/li>
&lt;li>可在线更新的内容；&lt;/li>
&lt;li>调试文件和临时数据。&lt;/li>
&lt;/ol>
&lt;h2 id="回看清单">回看清单
&lt;/h2>&lt;ol>
&lt;li>体积优化先分析构成，不要凭感觉删。&lt;/li>
&lt;li>R8 的体积收益主要来自裁剪，不是混淆本身。&lt;/li>
&lt;li>&lt;code>shrinkResources&lt;/code> 要和 R8 一起开启才更有效。&lt;/li>
&lt;li>Native 体积重点看 ABI 和符号。&lt;/li>
&lt;li>大资源优先考虑按需下载或动态交付。&lt;/li>
&lt;/ol></description></item><item><title>Android 内存管理：泄漏、缓存与排查工具</title><link>https://www.dust-zed.site/android/memory-management-practices/</link><pubDate>Sun, 15 Jun 2025 13:05:49 +0800</pubDate><guid>https://www.dust-zed.site/android/memory-management-practices/</guid><description>&lt;p>Android 内存问题通常分两类：一类是对象本来应该被释放却仍被引用，也就是内存泄漏；另一类是对象没有泄漏，但占用过大或释放不及时，导致频繁 GC、卡顿甚至 OOM。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>泄漏本质是生命周期较长的对象持有了生命周期较短的对象。&lt;/li>
&lt;li>&lt;code>Activity&lt;/code>、&lt;code>Fragment&lt;/code>、&lt;code>View&lt;/code>、&lt;code>Context&lt;/code> 是最常见的泄漏对象。&lt;/li>
&lt;li>&lt;code>Bitmap&lt;/code>、大集合、缓存和 Native 资源要主动控制大小和释放时机。&lt;/li>
&lt;li>&lt;code>onTrimMemory()&lt;/code> 是系统给应用释放资源的信号，不应该忽略。&lt;/li>
&lt;li>排查时先用 LeakCanary 找引用链，再用 Profiler/MAT 分析大对象和堆占用。&lt;/li>
&lt;/ol>
&lt;h2 id="常见泄漏来源">常见泄漏来源
&lt;/h2>&lt;h3 id="context-泄漏">Context 泄漏
&lt;/h3>&lt;p>不要让单例、静态变量、长生命周期任务持有 &lt;code>Activity Context&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="k">object&lt;/span> &lt;span class="nc">ImageLoader&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">lateinit&lt;/span> &lt;span class="k">var&lt;/span> &lt;span class="py">appContext&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Context&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">fun&lt;/span> &lt;span class="nf">init&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">appContext&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">applicationContext&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="handler-和回调泄漏">Handler 和回调泄漏
&lt;/h3>&lt;p>如果延迟任务持有 View 或 Activity，页面销毁后任务仍在队列中，就可能导致泄漏。&lt;/p>
&lt;p>处理方式：&lt;/p>
&lt;ol>
&lt;li>在 &lt;code>onDestroy()&lt;/code> / &lt;code>onDestroyView()&lt;/code> 移除回调；&lt;/li>
&lt;li>避免匿名内部类长期持有页面；&lt;/li>
&lt;li>协程场景使用生命周期绑定的 scope。&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="k">override&lt;/span> &lt;span class="k">fun&lt;/span> &lt;span class="nf">onDestroyView&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">handler&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">removeCallbacksAndMessages&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">onDestroyView&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="注册未反注册">注册未反注册
&lt;/h3>&lt;p>常见对象：&lt;/p>
&lt;ol>
&lt;li>&lt;code>BroadcastReceiver&lt;/code>&lt;/li>
&lt;li>监听器&lt;/li>
&lt;li>EventBus / Flow collector&lt;/li>
&lt;li>Sensor callback&lt;/li>
&lt;li>Location callback&lt;/li>
&lt;/ol>
&lt;p>原则：在哪里注册，就要有清晰的反注册位置。&lt;/p>
&lt;h2 id="大对象和缓存">大对象和缓存
&lt;/h2>&lt;h3 id="bitmap">Bitmap
&lt;/h3>&lt;p>Bitmap 是 Android 内存问题高发点。&lt;/p>
&lt;p>建议：&lt;/p>
&lt;ol>
&lt;li>使用 Glide/Coil 这类成熟图片库；&lt;/li>
&lt;li>按目标尺寸加载，不加载原图；&lt;/li>
&lt;li>避免在列表中持有大量 Bitmap；&lt;/li>
&lt;li>大图预览要关注采样率和生命周期。&lt;/li>
&lt;/ol>
&lt;h3 id="缓存">缓存
&lt;/h3>&lt;p>缓存不是越大越好。缓存应该有上限、淘汰策略和内存压力响应。&lt;/p>
&lt;p>常用策略：&lt;/p>
&lt;ol>
&lt;li>内存缓存用 &lt;code>LruCache&lt;/code>；&lt;/li>
&lt;li>磁盘缓存用成熟库或明确目录；&lt;/li>
&lt;li>收到 &lt;code>onTrimMemory()&lt;/code> 时主动降级或清理；&lt;/li>
&lt;li>不把页面对象放进全局缓存。&lt;/li>
&lt;/ol>
&lt;h2 id="ontrimmemory">onTrimMemory
&lt;/h2>&lt;p>&lt;code>onTrimMemory(level)&lt;/code> 表示系统希望应用释放一部分内存。&lt;/p>
&lt;p>常见处理：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="k">override&lt;/span> &lt;span class="k">fun&lt;/span> &lt;span class="nf">onTrimMemory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">level&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">super&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">onTrimMemory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">level&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">level&lt;/span> &lt;span class="o">&amp;gt;=&lt;/span> &lt;span class="n">TRIM_MEMORY_RUNNING_LOW&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">imageCache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">trimToSize&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">imageCache&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">size&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要关注的信号：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>level&lt;/th>
&lt;th>含义&lt;/th>
&lt;th>建议&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>TRIM_MEMORY_UI_HIDDEN&lt;/code>&lt;/td>
&lt;td>UI 不可见&lt;/td>
&lt;td>释放 UI 相关缓存&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>TRIM_MEMORY_RUNNING_LOW&lt;/code>&lt;/td>
&lt;td>运行中内存偏低&lt;/td>
&lt;td>降低缓存&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>TRIM_MEMORY_RUNNING_CRITICAL&lt;/code>&lt;/td>
&lt;td>内存非常紧张&lt;/td>
&lt;td>尽可能释放非必要资源&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="排查工具">排查工具
&lt;/h2>&lt;h3 id="leakcanary">LeakCanary
&lt;/h3>&lt;p>适合自动发现 Activity、Fragment、ViewModel 等对象泄漏。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="n">debugImplementation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;com.squareup.leakcanary:leakcanary-android:2.x&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重点看：&lt;/p>
&lt;ol>
&lt;li>泄漏对象是什么；&lt;/li>
&lt;li>GC Root 到泄漏对象的引用链；&lt;/li>
&lt;li>哪个长生命周期对象持有了它。&lt;/li>
&lt;/ol>
&lt;h3 id="android-studio-profiler">Android Studio Profiler
&lt;/h3>&lt;p>适合观察：&lt;/p>
&lt;ol>
&lt;li>堆内存变化；&lt;/li>
&lt;li>GC 频率；&lt;/li>
&lt;li>对象分配热点；&lt;/li>
&lt;li>Heap Dump。&lt;/li>
&lt;/ol>
&lt;h3 id="mat">MAT
&lt;/h3>&lt;p>适合分析复杂堆快照：&lt;/p>
&lt;ol>
&lt;li>Dominator Tree 看谁占用最大；&lt;/li>
&lt;li>Path to GC Roots 看为什么无法释放；&lt;/li>
&lt;li>Histogram 看对象数量是否异常。&lt;/li>
&lt;/ol>
&lt;h2 id="回看清单">回看清单
&lt;/h2>&lt;ol>
&lt;li>生命周期长的对象不要持有生命周期短的对象。&lt;/li>
&lt;li>页面销毁时清理 Handler、监听器、Receiver 和异步任务。&lt;/li>
&lt;li>大图和缓存必须有大小上限。&lt;/li>
&lt;li>系统内存压力回调要释放非必要资源。&lt;/li>
&lt;li>泄漏看引用链，OOM 看对象占用和分配趋势。&lt;/li>
&lt;/ol></description></item><item><title>Android View 性能优化：从测量、绘制到过度绘制</title><link>https://www.dust-zed.site/android/view-performance-optimization/</link><pubDate>Fri, 13 Jun 2025 23:55:35 +0800</pubDate><guid>https://www.dust-zed.site/android/view-performance-optimization/</guid><description>&lt;p>View 性能问题通常不是单点问题，而是发生在一帧渲染链路中的某个阶段：测量太重、布局太深、绘制指令太多、GPU 重复填充，或者动画过程中反复触发布局。&lt;/p>
&lt;p>这篇笔记的目标是把优化动作放回渲染链路里理解，而不是只记一堆零散技巧。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>&lt;strong>布局阶段看层级和测量次数&lt;/strong>：减少嵌套，避免无意义的二次测量。&lt;/li>
&lt;li>&lt;strong>绘制阶段看 &lt;code>onDraw()&lt;/code> 成本&lt;/strong>：不要在绘制路径里分配对象、做 I/O 或触发布局。&lt;/li>
&lt;li>&lt;strong>GPU 阶段看过度绘制和混合&lt;/strong>：减少被完全覆盖的背景和大面积半透明。&lt;/li>
&lt;li>&lt;strong>动画阶段看是否触发重新布局&lt;/strong>：优先使用 &lt;code>translationX/Y&lt;/code>、&lt;code>alpha&lt;/code>、&lt;code>scale&lt;/code> 等属性动画。&lt;/li>
&lt;li>&lt;strong>硬件层只作为优化选项&lt;/strong>：具体原理和使用边界放到《Android 硬件加速》里单独整理。&lt;/li>
&lt;/ol>
&lt;h2 id="一帧里的性能检查点">一帧里的性能检查点
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">输入事件 / 状态变化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; measure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; layout
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; draw / record DisplayList
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; GPU rasterize
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; SurfaceFlinger 合成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; 显示
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>优化时先判断卡顿发生在哪一段：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>阶段&lt;/th>
&lt;th>常见问题&lt;/th>
&lt;th>典型优化&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>measure&lt;/td>
&lt;td>布局层级深、权重布局二次测量&lt;/td>
&lt;td>减少嵌套，使用 &lt;code>ConstraintLayout&lt;/code>，缓存测量结果&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>layout&lt;/td>
&lt;td>频繁 &lt;code>requestLayout()&lt;/code>&lt;/td>
&lt;td>避免在动画中改变布局参数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>draw&lt;/td>
&lt;td>&lt;code>onDraw()&lt;/code> 分配对象或复杂计算&lt;/td>
&lt;td>复用 &lt;code>Paint/Path/Rect&lt;/code>，提前计算&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPU&lt;/td>
&lt;td>过度绘制、半透明混合&lt;/td>
&lt;td>移除无效背景，控制透明层&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>合成&lt;/td>
&lt;td>大量离屏图层&lt;/td>
&lt;td>谨慎使用硬件层和裁剪&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="布局优化">布局优化
&lt;/h2>&lt;p>布局优化关注 &lt;code>measure&lt;/code> 和 &lt;code>layout&lt;/code> 两个阶段。&lt;/p>
&lt;p>常见策略：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>减少布局嵌套&lt;/strong>&lt;/p>
&lt;p>深层嵌套会增加遍历和测量成本。简单页面可以用 &lt;code>ConstraintLayout&lt;/code> 合并层级，但不要为了“扁平”制造过度复杂的约束。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>避免 &lt;code>LinearLayout&lt;/code> 权重滥用&lt;/strong>&lt;/p>
&lt;p>&lt;code>layout_weight&lt;/code> 可能导致额外测量。列表项、复杂卡片或高频刷新区域要谨慎使用。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>使用 &lt;code>&amp;lt;merge&amp;gt;&lt;/code> 和 &lt;code>ViewStub&lt;/code>&lt;/strong>&lt;/p>
&lt;p>&lt;code>&amp;lt;merge&amp;gt;&lt;/code> 适合消除无意义的根容器，&lt;code>ViewStub&lt;/code> 适合延迟加载低频出现的内容。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>自定义 ViewGroup 缓存测量结果&lt;/strong>&lt;/p>
&lt;p>如果子 View 尺寸和约束稳定，可以缓存中间计算，避免每次 &lt;code>onMeasure()&lt;/code> 重复推导。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="绘制优化">绘制优化
&lt;/h2>&lt;p>&lt;code>onDraw()&lt;/code> 会在一帧内被频繁调用，必须保持轻量。&lt;/p>
&lt;p>不要在 &lt;code>onDraw()&lt;/code> 中做这些事：&lt;/p>
&lt;ol>
&lt;li>创建 &lt;code>Paint&lt;/code>、&lt;code>Path&lt;/code>、&lt;code>Bitmap&lt;/code>、&lt;code>Rect&lt;/code> 等对象；&lt;/li>
&lt;li>读取文件、解析数据、访问网络；&lt;/li>
&lt;li>做复杂业务计算；&lt;/li>
&lt;li>调用 &lt;code>invalidate()&lt;/code> 或 &lt;code>requestLayout()&lt;/code> 形成循环；&lt;/li>
&lt;li>绘制屏幕外或被遮挡的内容。&lt;/li>
&lt;/ol>
&lt;p>推荐做法：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ChartView&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">View&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">val&lt;/span> &lt;span class="py">linePaint&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">Paint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nc">Paint&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ANTI_ALIAS_FLAG&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">val&lt;/span> &lt;span class="py">contentRect&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">RectF&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">override&lt;/span> &lt;span class="k">fun&lt;/span> &lt;span class="nf">onSizeChanged&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">w&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">oldw&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">oldh&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">contentRect&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">w&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">toFloat&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">toFloat&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">override&lt;/span> &lt;span class="k">fun&lt;/span> &lt;span class="nf">onDraw&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">canvas&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Canvas&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">canvas&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">quickReject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentRect&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nc">Canvas&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nc">EdgeType&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">AA&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">canvas&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">drawLine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">2f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">width&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">toFloat&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">height&lt;/span> &lt;span class="p">/&lt;/span> &lt;span class="m">2f&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">linePaint&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="过度绘制优化">过度绘制优化
&lt;/h2>&lt;p>过度绘制指同一个像素在一帧内被重复绘制多次。它会增加 GPU 填充压力，尤其在列表、复杂卡片和半透明蒙层中很常见。&lt;/p>
&lt;p>排查入口：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">开发者选项 -&amp;gt; 调试 GPU 过度绘制
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>优化动作：&lt;/p>
&lt;ol>
&lt;li>移除 Activity 根布局、主题、容器里的重复背景；&lt;/li>
&lt;li>删除被完全覆盖的中间层背景；&lt;/li>
&lt;li>避免大面积半透明 View；&lt;/li>
&lt;li>自定义绘制时使用 &lt;code>clipRect()&lt;/code> 限制绘制区域；&lt;/li>
&lt;li>对屏幕外区域使用 &lt;code>quickReject()&lt;/code> 提前跳过。&lt;/li>
&lt;/ol>
&lt;h2 id="硬件层提示">硬件层提示
&lt;/h2>&lt;p>硬件层属于绘制与合成阶段的优化手段，适合短时间属性动画，但不适合长期、大面积启用。这里不展开原理，具体的 CPU/GPU 分工、DisplayList 和硬件层边界，统一放在《Android 硬件加速：CPU、GPU 与硬件层》中整理。&lt;/p>
&lt;h2 id="常用工具">常用工具
&lt;/h2>&lt;ol>
&lt;li>&lt;strong>Profile GPU Rendering&lt;/strong>：观察每帧耗时，判断是否超过 16.6ms。&lt;/li>
&lt;li>&lt;strong>Layout Inspector&lt;/strong>：查看布局层级、约束和 View 树。&lt;/li>
&lt;li>&lt;strong>Debug GPU Overdraw&lt;/strong>：定位重复绘制。&lt;/li>
&lt;li>&lt;strong>Perfetto / System Trace&lt;/strong>：分析主线程、RenderThread 和系统调度。&lt;/li>
&lt;li>&lt;strong>Memory Profiler&lt;/strong>：排查绘制过程中的异常分配。&lt;/li>
&lt;/ol>
&lt;h2 id="回看清单">回看清单
&lt;/h2>&lt;ol>
&lt;li>卡顿先定位阶段，不要一上来就改代码。&lt;/li>
&lt;li>布局优化看层级、测量次数和 &lt;code>requestLayout()&lt;/code> 触发频率。&lt;/li>
&lt;li>绘制优化看 &lt;code>onDraw()&lt;/code> 是否分配对象、做计算或绘制不可见区域。&lt;/li>
&lt;li>GPU 优化看过度绘制、半透明和离屏图层。&lt;/li>
&lt;li>硬件层只作为候选优化项，具体使用边界回到硬件加速笔记判断。&lt;/li>
&lt;/ol></description></item><item><title>Android 硬件加速：CPU、GPU 与硬件层</title><link>https://www.dust-zed.site/android/hardware-acceleration/</link><pubDate>Fri, 13 Jun 2025 22:50:57 +0800</pubDate><guid>https://www.dust-zed.site/android/hardware-acceleration/</guid><description>&lt;p>硬件加速的核心不是“打开 GPU 就一定更快”，而是把绘制流程拆成两部分：CPU 负责记录绘制命令，GPU 负责把命令转换成像素。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>硬件加速后，&lt;code>onDraw()&lt;/code> 仍然会在主线程执行，但绘制命令会被记录成 DisplayList。&lt;/li>
&lt;li>GPU 负责光栅化，把图形命令转换成最终像素。&lt;/li>
&lt;li>硬件层可以缓存 View 的绘制结果，适合短期属性动画。&lt;/li>
&lt;li>硬件层不是越多越好，大面积或长期启用会增加显存压力。&lt;/li>
&lt;/ol>
&lt;h2 id="未启用硬件加速">未启用硬件加速
&lt;/h2>&lt;p>简化流程：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">View 树遍历
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; measure/layout
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; CPU 执行 onDraw()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; CPU 光栅化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; 写入图形缓冲区
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; 显示
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此时 CPU 既要处理业务逻辑、布局计算，也要承担像素计算，复杂页面容易成为瓶颈。&lt;/p>
&lt;h2 id="启用硬件加速">启用硬件加速
&lt;/h2>&lt;p>简化流程：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">View 树遍历
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; measure/layout
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; CPU 执行 onDraw() 并记录 DisplayList
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; RenderThread / GPU 消费绘制命令
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; GPU 光栅化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; 写入 Buffer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -&amp;gt; SurfaceFlinger 合成显示
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的关键变化是：&lt;code>Canvas&lt;/code> 的很多调用不再立即生成像素，而是记录成绘制指令。GPU 后续可以并行处理这些指令。&lt;/p>
&lt;h2 id="displaylist-的意义">DisplayList 的意义
&lt;/h2>&lt;p>DisplayList 可以理解为 View 绘制结果的命令列表：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">drawRect(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drawText(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">drawBitmap(...)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">clipPath(...)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果 View 内容没有变化，系统可以复用已有 DisplayList，减少主线程重复记录绘制命令的成本。&lt;/p>
&lt;p>但如果调用了 &lt;code>invalidate()&lt;/code>，对应 View 的 DisplayList 仍然需要重新记录。&lt;/p>
&lt;h2 id="硬件层">硬件层
&lt;/h2>&lt;p>硬件层会把 View 的绘制结果缓存成 GPU 纹理：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">View -&amp;gt; DisplayList -&amp;gt; GPU 光栅化 -&amp;gt; 离屏纹理
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>后续如果只是做平移、缩放、旋转、透明度变化，就可以直接复用纹理合成，不必重新绘制 View 内容。&lt;/p>
&lt;p>适合场景：&lt;/p>
&lt;ol>
&lt;li>短时间属性动画；&lt;/li>
&lt;li>小面积复杂 View；&lt;/li>
&lt;li>内容不变，只做变换。&lt;/li>
&lt;/ol>
&lt;p>不适合场景：&lt;/p>
&lt;ol>
&lt;li>大面积列表；&lt;/li>
&lt;li>每帧内容都变化的自定义 View；&lt;/li>
&lt;li>长期启用的普通控件；&lt;/li>
&lt;li>内存紧张的页面。&lt;/li>
&lt;/ol>
&lt;h2 id="使用方式">使用方式
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="n">view&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setLayerType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nc">View&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LAYER_TYPE_HARDWARE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">view&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">animate&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">translationY&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">100f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">alpha&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="m">0f&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="n">withEndAction&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">view&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setLayerType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nc">View&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LAYER_TYPE_NONE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="回看清单">回看清单
&lt;/h2>&lt;ol>
&lt;li>硬件加速不是跳过 &lt;code>onDraw()&lt;/code>，而是改变绘制命令的执行方式。&lt;/li>
&lt;li>CPU 负责记录命令，GPU 负责光栅化和合成。&lt;/li>
&lt;li>DisplayList 可以减少重复记录绘制命令。&lt;/li>
&lt;li>硬件层适合短期动画缓存，不适合长期全局开启。&lt;/li>
&lt;li>判断是否需要硬件层，要看 View 内容是否稳定、面积是否可控、动画是否只做属性变换。&lt;/li>
&lt;/ol></description></item><item><title>RecyclerView 缓存机制：Scrap、Cache 与 RecycledViewPool</title><link>https://www.dust-zed.site/android/recyclerview-caching-mechanism/</link><pubDate>Wed, 11 Jun 2025 15:26:57 +0800</pubDate><guid>https://www.dust-zed.site/android/recyclerview-caching-mechanism/</guid><description>&lt;p>RecyclerView 的性能核心不是“少创建几个 ViewHolder”这么简单，而是通过多级缓存把不同状态的 ViewHolder 放在不同位置，尽量减少创建、绑定和布局成本。&lt;/p>
&lt;h2 id="核心结论">核心结论
&lt;/h2>&lt;ol>
&lt;li>屏幕内重新布局优先命中 &lt;code>attachedScrap&lt;/code>，通常不需要重新绑定。&lt;/li>
&lt;li>屏幕外短距离滑动优先命中 &lt;code>mCachedViews&lt;/code>，默认容量较小。&lt;/li>
&lt;li>&lt;code>RecycledViewPool&lt;/code> 按 &lt;code>viewType&lt;/code> 复用 ViewHolder，通常需要重新绑定。&lt;/li>
&lt;li>&lt;code>changedScrap&lt;/code> 服务于更新动画和局部刷新。&lt;/li>
&lt;li>Stable IDs 能让 RecyclerView 在位置变化时仍按数据身份匹配 ViewHolder。&lt;/li>
&lt;/ol>
&lt;h2 id="多级缓存模型">多级缓存模型
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">RecyclerView.Recycler
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── attachedScrap
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── 布局过程中临时保存屏幕内 ViewHolder
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── changedScrap
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── 保存发生数据变化、用于动画过渡的 ViewHolder
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── mCachedViews
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── 保存刚离开屏幕的 ViewHolder，按 position 优先复用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── ViewCacheExtension
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── 业务自定义缓存入口，较少使用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── RecycledViewPool
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 按 viewType 分类的共享回收池
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>可以粗略记成：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">越靠前，越接近原位置，复用成本越低；
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">越靠后，越通用，但越可能需要重新绑定。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="attachedscrap">attachedScrap
&lt;/h2>&lt;p>&lt;code>attachedScrap&lt;/code> 用在布局过程中。&lt;/p>
&lt;p>当 RecyclerView 重新布局时，当前可见的 ViewHolder 会先被临时放进 &lt;code>attachedScrap&lt;/code>。布局管理器再次需要某个 position 时，可以直接取回来。&lt;/p>
&lt;p>特点：&lt;/p>
&lt;ol>
&lt;li>主要服务屏幕内重新布局；&lt;/li>
&lt;li>通常按 position 匹配；&lt;/li>
&lt;li>命中后一般不需要重新创建，也不需要重新绑定；&lt;/li>
&lt;li>成本最低。&lt;/li>
&lt;/ol>
&lt;h2 id="changedscrap">changedScrap
&lt;/h2>&lt;p>&lt;code>changedScrap&lt;/code> 主要服务更新动画。&lt;/p>
&lt;p>当调用 &lt;code>notifyItemChanged()&lt;/code> 或发生可动画的数据变化时，旧 ViewHolder 可能会进入 &lt;code>changedScrap&lt;/code>，用于和新状态做动画过渡。&lt;/p>
&lt;p>特点：&lt;/p>
&lt;ol>
&lt;li>和 &lt;code>ItemAnimator&lt;/code> 关系密切；&lt;/li>
&lt;li>可能需要重新绑定；&lt;/li>
&lt;li>用于保留变化前后的 ViewHolder 状态；&lt;/li>
&lt;li>让局部刷新不至于直接闪烁。&lt;/li>
&lt;/ol>
&lt;h2 id="mcachedviews">mCachedViews
&lt;/h2>&lt;p>&lt;code>mCachedViews&lt;/code> 保存刚离开屏幕的 ViewHolder，默认容量通常较小。&lt;/p>
&lt;p>它的价值在于：用户短距离来回滑动时，刚离开的 item 很可能马上回来。这时直接复用可以避免重新绑定。&lt;/p>
&lt;p>特点：&lt;/p>
&lt;ol>
&lt;li>默认容量有限；&lt;/li>
&lt;li>更偏向 position 级别复用；&lt;/li>
&lt;li>命中后可能不需要重新绑定；&lt;/li>
&lt;li>适合处理短距离滚动回退。&lt;/li>
&lt;/ol>
&lt;h2 id="recycledviewpool">RecycledViewPool
&lt;/h2>&lt;p>&lt;code>RecycledViewPool&lt;/code> 是更通用的回收池，按 &lt;code>viewType&lt;/code> 分类。&lt;/p>
&lt;p>进入这里的 ViewHolder 已经和具体 position 脱钩。复用时只保证类型匹配，不保证数据仍然正确，因此通常要重新执行 &lt;code>onBindViewHolder()&lt;/code>。&lt;/p>
&lt;p>适合场景：&lt;/p>
&lt;ol>
&lt;li>长列表复用；&lt;/li>
&lt;li>多个 RecyclerView 共享同类 item；&lt;/li>
&lt;li>ViewHolder 创建成本较高；&lt;/li>
&lt;li>页面中存在嵌套列表。&lt;/li>
&lt;/ol>
&lt;h2 id="stable-ids-的作用">Stable IDs 的作用
&lt;/h2>&lt;p>默认情况下，RecyclerView 更依赖 position 判断 item 身份。数据插入、删除、排序后，position 会变化，这可能影响动画和复用判断。&lt;/p>
&lt;p>启用 Stable IDs 后：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span class="line">&lt;span class="cl">&lt;span class="n">adapter&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">setHasStableIds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">override&lt;/span> &lt;span class="k">fun&lt;/span> &lt;span class="nf">getItemId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">position&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Int&lt;/span>&lt;span class="p">):&lt;/span> &lt;span class="n">Long&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">items&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">position&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="n">id&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>RecyclerView 可以用稳定 ID 判断“这是同一条数据”，即使它的位置变了。&lt;/p>
&lt;p>适合：&lt;/p>
&lt;ol>
&lt;li>数据有天然唯一 ID；&lt;/li>
&lt;li>列表存在插入、删除、排序；&lt;/li>
&lt;li>需要更稳定的变更动画；&lt;/li>
&lt;li>配合 &lt;code>DiffUtil&lt;/code> 或 &lt;code>ListAdapter&lt;/code> 使用。&lt;/li>
&lt;/ol>
&lt;p>注意：Stable ID 必须稳定且唯一。如果 ID 错误，复用错乱会更难排查。&lt;/p>
&lt;h2 id="回看清单">回看清单
&lt;/h2>&lt;ol>
&lt;li>&lt;code>attachedScrap&lt;/code> 解决屏幕内布局复用。&lt;/li>
&lt;li>&lt;code>changedScrap&lt;/code> 解决变化动画和局部刷新。&lt;/li>
&lt;li>&lt;code>mCachedViews&lt;/code> 解决短距离滑动回退。&lt;/li>
&lt;li>&lt;code>RecycledViewPool&lt;/code> 解决跨位置、跨列表的通用复用。&lt;/li>
&lt;li>Stable IDs 用数据身份补足 position 的不稳定性。&lt;/li>
&lt;/ol></description></item></channel></rss>