<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Android Performance</title>
  
  <subtitle>闻道有先后,术业有专攻,如是而已</subtitle>
  <link href="https://androidperformance.com/atom.xml" rel="self"/>
  
  <link href="https://androidperformance.com/"/>
  <updated>2026-04-10T16:29:24.256Z</updated>
  <id>https://androidperformance.com/</id>
  
  <author>
    <name>Gracker</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>SmartPerfetto 架构文章 Q&amp;A：8 个深度技术问答</title>
    <link href="https://androidperformance.com/2026/04/10/SmartPerfetto-Architecture-Deep-Dive-QA/"/>
    <id>https://androidperformance.com/2026/04/10/SmartPerfetto-Architecture-Deep-Dive-QA/</id>
    <published>2026-04-10T03:00:00.000Z</published>
    <updated>2026-04-10T16:29:24.256Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章收集了<a href="/2026/04/10/SmartPerfetto-Architecture-Deep-Dive/">《从 Trace 到洞察：SmartPerfetto AI Agent 的 Harness Engineering 实战》</a>发布后收到的技术问题，以问答形式展开讨论。</p><span id="more"></span><hr><h2 id="Q1：为什么不用-Claude-Code-的标准-Skill-系统，而要自建-YAML-Skill？"><a href="#Q1：为什么不用-Claude-Code-的标准-Skill-系统，而要自建-YAML-Skill？" class="headerlink" title="Q1：为什么不用 Claude Code 的标准 Skill 系统，而要自建 YAML Skill？"></a>Q1：为什么不用 Claude Code 的标准 Skill 系统，而要自建 YAML Skill？</h2><p><strong>提问背景：</strong> Claude Code 的 Skill 系统支持 <code>scripts/</code> 目录放确定性脚本，避免 LLM 泛化。既然可以用 scripts&#x2F; 执行固定的 SQL，为什么还要自建一套 YAML Skill 系统？YAML Skill 是不是本质上是一个让性能工程师按预定义规则执行 SQL 的工具？</p><h3 id="关键区分：两套-Skill-不在同一个层面"><a href="#关键区分：两套-Skill-不在同一个层面" class="headerlink" title="关键区分：两套 Skill 不在同一个层面"></a>关键区分：两套 Skill 不在同一个层面</h3><p>Claude Code Skills 和 SmartPerfetto YAML Skills 解决的是不同阶段的问题：</p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">开发阶段（我写代码时）:</span><br><span class="line">  Claude <span class="built_in">Code</span> + Skills/Hooks → 帮我开发 SmartPerfetto</span><br><span class="line"></span><br><span class="line">运行阶段（用户分析 <span class="keyword">trace</span> 时）:</span><br><span class="line">  SmartPerfetto Backend + YAML Skills → 帮用户分析性能数据</span><br></pre></td></tr></table></figure><p>Claude Code 的 Skill 运行在开发者的终端里，是 CLI 工具的扩展。SmartPerfetto 的 YAML Skill 运行在 Express 后端的 Skill Engine 中，是 Agent 在运行时通过 MCP 工具 <code>invoke_skill</code> 调用的分析单元。两者的执行环境、调用方式、数据流完全不同。</p><h3 id="即使只看「确定性执行」，YAML-Skill-有几个针对性设计"><a href="#即使只看「确定性执行」，YAML-Skill-有几个针对性设计" class="headerlink" title="即使只看「确定性执行」，YAML Skill 有几个针对性设计"></a>即使只看「确定性执行」，YAML Skill 有几个针对性设计</h3><p><strong>1. 参数化 SQL，不是固定脚本</strong></p><p>性能分析的 SQL 不是写死的——同一个 Skill 需要接受不同的参数（进程名、时间范围、帧 ID 列表）：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">steps:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">thread_state_distribution</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">atomic</span></span><br><span class="line">    <span class="attr">sql:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      SELECT state, SUM(dur) as total_dur</span></span><br><span class="line"><span class="string">      FROM thread_state ts</span></span><br><span class="line"><span class="string">      JOIN thread_track tt ON ts.track_id = tt.id</span></span><br><span class="line"><span class="string">      WHERE tt.utid = $&#123;main_thread_utid&#125;</span></span><br><span class="line"><span class="string">        AND ts.ts BETWEEN $&#123;start_ts&#125; AND $&#123;end_ts&#125;</span></span><br><span class="line"><span class="string">      GROUP BY state</span></span><br></pre></td></tr></table></figure><p><code>$&#123;main_thread_utid&#125;</code> 和 <code>$&#123;start_ts&#125;</code> 是 Claude 调用 <code>invoke_skill</code> 时传入的参数。YAML Skill Engine 做参数替换后执行 SQL。如果用 scripts&#x2F;，要么写 shell 脚本接收参数拼 SQL（容易出注入问题），要么写完整的 Python&#x2F;Node 脚本——复杂度比 YAML 高很多。</p><p><strong>2. 自描述的输出格式（DataEnvelope）</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">display:</span></span><br><span class="line">  <span class="attr">level:</span> <span class="string">detail</span></span><br><span class="line">  <span class="attr">columns:</span></span><br><span class="line">    <span class="bullet">-</span> &#123; <span class="attr">name:</span> <span class="string">state</span>, <span class="attr">type:</span> <span class="string">string</span> &#125;</span><br><span class="line">    <span class="bullet">-</span> &#123; <span class="attr">name:</span> <span class="string">total_dur</span>, <span class="attr">type:</span> <span class="string">duration</span> &#125;</span><br></pre></td></tr></table></figure><p>每个 step 声明了输出列的名称和类型。前端根据这个 schema 自动渲染表格——<code>duration</code> 类型自动格式化为 ms，<code>timestamp</code> 类型支持点击跳转到 Perfetto 时间线。scripts&#x2F; 方式的输出是自由文本，前端没法自动渲染。</p><p><strong>3. 可组合（composite + iterator）</strong></p><p>一个 composite Skill 可以引用多个 atomic Skill，iterator 可以遍历数据行逐帧分析。这种组合在 YAML 中是声明式的，Skill Engine 负责编排执行。scripts&#x2F; 方式要实现同样的组合需要自己写编排逻辑。</p><p><strong>4. 面向性能工程师，不是面向开发者</strong></p><p>提问者说对了：YAML Skill 本质上是一个让性能工程师按预定义规则贡献分析逻辑的工具。性能工程师知道该查什么 SQL、该看什么指标，但不一定会写 TypeScript。YAML 格式让他们直接定义 SQL 查询和输出格式，不需要碰后端代码。修改后 DEV 模式刷新浏览器即可生效。</p><h3 id="对比总结"><a href="#对比总结" class="headerlink" title="对比总结"></a>对比总结</h3><table><thead><tr><th>维度</th><th>Claude Code scripts&#x2F;</th><th>SmartPerfetto YAML Skill</th></tr></thead><tbody><tr><td>运行环境</td><td>开发者终端 (CLI)</td><td>Express 后端 (runtime)</td></tr><tr><td>调用者</td><td>开发者通过 <code>/skill</code> 命令</td><td>Agent 通过 <code>invoke_skill</code> MCP 工具</td></tr><tr><td>参数化</td><td>需要自己处理</td><td>&#96;${param</td></tr><tr><td>输出格式</td><td>自由文本</td><td>DataEnvelope (schema-driven)</td></tr><tr><td>前端渲染</td><td>不涉及</td><td>自动表格&#x2F;图表</td></tr><tr><td>组合能力</td><td>手动编排</td><td>composite &#x2F; iterator &#x2F; conditional</td></tr><tr><td>贡献门槛</td><td>需要写脚本</td><td>只写 YAML + SQL</td></tr></tbody></table><p>两者不是替代关系，而是在不同层面解决不同问题。</p><hr><h2 id="Q2：「确定性-灵活性混合」具体是怎么实现的？"><a href="#Q2：「确定性-灵活性混合」具体是怎么实现的？" class="headerlink" title="Q2：「确定性 + 灵活性混合」具体是怎么实现的？"></a>Q2：「确定性 + 灵活性混合」具体是怎么实现的？</h2><p><strong>提问背景：</strong> 文章说「已知场景用 Strategy 文件约束必检项，但每个阶段内的具体查询和深钻方向由 Claude 自主决定」。这个约束和自主之间的边界在哪里？具体是怎么做到的？</p><h3 id="三层机制配合"><a href="#三层机制配合" class="headerlink" title="三层机制配合"></a>三层机制配合</h3><p>这个混合设计靠三层机制配合实现：Strategy 文件定义「必须做什么」，Planning Gate 强制「先计划再执行」，Verifier 事后检查「是否真的做了」。</p><h3 id="第一层：Strategy-文件-—-有硬约束也有软指引"><a href="#第一层：Strategy-文件-—-有硬约束也有软指引" class="headerlink" title="第一层：Strategy 文件 — 有硬约束也有软指引"></a>第一层：Strategy 文件 — 有硬约束也有软指引</h3><p>以滑动分析的 <code>scrolling.strategy.md</code> 为例，它定义了多个分析阶段，但每个阶段的约束强度不同：</p><p><strong>硬约束（必须执行，跳过会触发验证错误）：</strong></p><p>Phase 1.9 根因深钻是最严格的阶段，策略文件里直接用了 🔴 标记和「禁止」字样：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="strong">**Phase 1.9 — 根因深钻（🔴 强制执行，不可跳过）：**</span></span><br><span class="line"></span><br><span class="line">对 <span class="code">`batch_frame_root_cause`</span> 中占比 &gt;15% 的每个 reason<span class="emphasis">_code，</span></span><br><span class="line"><span class="emphasis"><span class="strong">**必须**</span>选最严重的 1 帧执行深钻。</span></span><br><span class="line"><span class="emphasis"><span class="strong">**⛔ 禁止**</span>仅靠 batch_</span>frame<span class="emphasis">_root_</span>cause 的统计分类直接出结论。</span><br><span class="line"></span><br><span class="line">| 条件                    | 深钻动作                                    |</span><br><span class="line">| 任何 reason<span class="emphasis">_code Q4&gt;20% | invoke_</span>skill(&quot;blocking<span class="emphasis">_chain_</span>analysis&quot;, ...) |</span><br><span class="line">| binder<span class="emphasis">_overlap &gt;5ms     | invoke_</span>skill(&quot;binder<span class="emphasis">_root_</span>cause&quot;, ...)       |</span><br><span class="line">| ...</span><br></pre></td></tr></table></figure><p><strong>软指引（建议但可跳过）：</strong></p><p>Phase 1.5（架构感知分支）和 Phase 1.7（根因分支）用的是「建议」「改用」等措辞，Claude 可以根据实际数据决定是否执行：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="strong">**Phase 1.5 — 架构感知分支：**</span></span><br><span class="line"></span><br><span class="line">| 架构      | 调整动作 |</span><br><span class="line">| Flutter   | 改用 flutter<span class="emphasis">_scrolling_</span>analysis |</span><br><span class="line">| WebView   | 注意 CrRendererMain 线程 |</span><br></pre></td></tr></table></figure><p>Strategy 文件的全部内容被原文注入 System Prompt，注入时加了一行硬性说明：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">场景策略（必须严格遵循）</span><br><span class="line">对于以下常见场景，已有验证过的分析流水线。必须完整执行所有阶段，不可跳过。</span><br></pre></td></tr></table></figure><p>Claude 直接在 System Prompt 中看到这些阶段定义、🔴 标记和「禁止」字样。</p><h3 id="第二层：Planning-Gate-—-强制先计划，但不限制计划内容"><a href="#第二层：Planning-Gate-—-强制先计划，但不限制计划内容" class="headerlink" title="第二层：Planning Gate — 强制先计划，但不限制计划内容"></a>第二层：Planning Gate — 强制先计划，但不限制计划内容</h3><p>Claude 在执行任何 SQL 查询或 Skill 调用之前，必须先调用 <code>submit_plan</code> 提交分析计划。没有提交计划就调用 <code>execute_sql</code> 或 <code>invoke_skill</code> 会被直接拒绝：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">requirePlan</span>(<span class="params">toolName: <span class="built_in">string</span></span>): <span class="built_in">string</span> | <span class="literal">null</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (analysisPlanRef.<span class="property">current</span>) <span class="keyword">return</span> <span class="literal">null</span>;  <span class="comment">// 已有计划，放行</span></span><br><span class="line">  <span class="keyword">return</span> <span class="string">`必须先调用 submit_plan 提交分析计划，然后才能使用 <span class="subst">$&#123;toolName&#125;</span>`</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键点在于：<strong>Gate 只要求计划存在，不要求计划和 Strategy 的阶段完全对应。</strong> Claude 可以提交任何结构的计划——它可以把 Phase 1 和 1.5 合并，可以加入 Strategy 没提到的额外步骤，也可以根据初步数据调整深钻方向。</p><p>提交计划时，系统会做场景感知的关键词检查（比如滑动场景检查计划中是否提到了「帧」「jank」等词），但这只是 <strong>warning 级别</strong>——计划即使不包含这些词也会被接受。</p><p>这个设计的目的是：强制 Claude 在动手之前先想清楚要做什么（规划纪律），但不限制它怎么想（规划自由）。</p><h3 id="第三层：Verifier-—-多维度事后检查"><a href="#第三层：Verifier-—-多维度事后检查" class="headerlink" title="第三层：Verifier — 多维度事后检查"></a>第三层：Verifier — 多维度事后检查</h3><p>计划和执行之间可能有偏差——Claude 可能提交了计划但实际跳过了某个关键步骤。Verifier 在分析结束后做多维度事后检查，以启发式行为检查为主，同时补充计划&#x2F;假设&#x2F;场景完整性校验：</p><p><strong>a) 场景完整性检查</strong>——分析输出是否覆盖了场景的核心内容：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 滑动场景：检查是否有显著掉帧但没做 Phase 1.9 深钻</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">&#x27;scrolling&#x27;</span>: &#123;</span><br><span class="line">  <span class="keyword">const</span> hasSignificantJank = <span class="comment">/* 检测文本中是否提到大量掉帧 */</span>;</span><br><span class="line">  <span class="keyword">const</span> hasDeepDrill = <span class="comment">/* 检测是否调用了 blocking_chain / binder_root_cause 等 */</span>;</span><br><span class="line">  <span class="keyword">if</span> (hasSignificantJank &amp;&amp; !hasDeepDrill) &#123;</span><br><span class="line">    issues.<span class="title function_">push</span>(&#123;</span><br><span class="line">      <span class="attr">severity</span>: <span class="string">&#x27;error&#x27;</span>,</span><br><span class="line">      <span class="attr">message</span>: <span class="string">&#x27;滑动分析有掉帧但缺少 Phase 1.9 根因深钻 — reason_code 只是分类标签，不是真正的根因&#x27;</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>b) 假设闭环检查</strong>——所有 <code>submit_hypothesis</code> 是否都有对应的 <code>resolve_hypothesis</code>。</p><p><strong>c) 因果链深度检查</strong>——CRITICAL&#x2F;HIGH 级别的 findings 是否包含足够的因果连接词和机制性术语（启发式文本匹配）。</p><p><strong>d) 可选的 LLM 复核</strong>——用独立的 Haiku 模型做证据支撑度验证（可关闭）。</p><p>如果检查发现 ERROR 级别问题，会触发 Correction Prompt 让 Claude 补做。</p><p>注意 Verifier <strong>不检查 Claude 的计划阶段是否匹配 Strategy 的阶段编号</strong>——它检查的是「关键分析动作是否体现在输出中」，不是「计划格式是否正确」。</p><h3 id="完整的约束光谱"><a href="#完整的约束光谱" class="headerlink" title="完整的约束光谱"></a>完整的约束光谱</h3><p>把三层机制叠在一起，不同阶段的约束强度形成了一个光谱：</p><table><thead><tr><th>阶段</th><th>Strategy 语气</th><th align="center">Planning Gate</th><th align="center">Verifier 检查</th><th align="center">约束强度</th></tr></thead><tbody><tr><td>Phase 1（概览）</td><td>建议</td><td align="center">需要计划</td><td align="center">不单独检查</td><td align="center">中</td></tr><tr><td>Phase 1.5（架构分支）</td><td>建议</td><td align="center">—</td><td align="center">不检查</td><td align="center">低</td></tr><tr><td>Phase 1.7（根因分支）</td><td>建议+条件</td><td align="center">—</td><td align="center">不检查</td><td align="center">低</td></tr><tr><td>Phase 1.9（根因深钻）</td><td>🔴 <strong>必须&#x2F;禁止</strong></td><td align="center">—</td><td align="center"><strong>检查是否调用了深钻工具</strong></td><td align="center"><strong>高</strong></td></tr><tr><td>Phase 2（补充深钻）</td><td>可选</td><td align="center">—</td><td align="center">不检查</td><td align="center">无</td></tr><tr><td>Phase 3（综合结论）</td><td>必须覆盖分布</td><td align="center">—</td><td align="center">检查结论完整性</td><td align="center">中</td></tr></tbody></table><p>而 <code>general.strategy.md</code>（未匹配到场景时的 fallback）则完全是软指引：只给了一个按用户关注方向的路由决策树（CPU → cpu_analysis，内存 → memory_analysis），没有任何必须执行的阶段。Claude 在 general 场景下有完全的自主权。</p><h3 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>Strategy 文件告诉 Claude「分析滑动问题至少要做这几件事」，Planning Gate 确保它先想后做，Verifier 事后检查关键步骤有没有真的做。</strong> 但在这个框架内，具体查什么数据、用哪个工具、按什么顺序，都是 Claude 根据实际数据自主决定的。</p><hr><h2 id="Q3：Agent-和-Workflow-最大的区别在哪里？Agent-的能力边界在哪里、由什么决定？"><a href="#Q3：Agent-和-Workflow-最大的区别在哪里？Agent-的能力边界在哪里、由什么决定？" class="headerlink" title="Q3：Agent 和 Workflow 最大的区别在哪里？Agent 的能力边界在哪里、由什么决定？"></a>Q3：Agent 和 Workflow 最大的区别在哪里？Agent 的能力边界在哪里、由什么决定？</h2><p><strong>提问背景：</strong> 我们在建设 Agent 的过程中，从最初默认 Agent 有能力理解并决策一切给它的 Skill，到现在几乎在 Skill 中写死了一棵决策树，这中间踩坑的出发点都是：「我们认为 Agent 有 xx 能力，但它没有」，导致其输出偏离我们的预期，于是我们不断地给 Skill 加边界，最后变成了一个写死的 Workflow。</p><h3 id="本质区别：决策权在谁手里"><a href="#本质区别：决策权在谁手里" class="headerlink" title="本质区别：决策权在谁手里"></a>本质区别：决策权在谁手里</h3><p>Agent 和 Workflow 不是两个工具、两个框架——它们是同一个光谱的两端：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">写死的 Workflow ◄──────────────────────────────────► 完全自主的 Agent</span><br><span class="line">  │                                                        │</span><br><span class="line">  开发者控制每个分支                               <span class="keyword">LLM </span>决定一切</span><br><span class="line">  │                                                        │</span><br><span class="line">  高确定性、低灵活性                               低确定性、高灵活性</span><br></pre></td></tr></table></figure><table><thead><tr><th>维度</th><th>Workflow</th><th>Agent</th></tr></thead><tbody><tr><td>控制流</td><td>开发者在代码中写死 <code>if/else</code></td><td>LLM 自主选择下一步</td></tr><tr><td>工具选择</td><td>预定义执行顺序</td><td>LLM 根据数据按需选择</td></tr><tr><td>分支条件</td><td>代码中的条件判断</td><td>LLM 推理判断</td></tr><tr><td>失败处理</td><td><code>try/catch</code> + 重试逻辑</td><td>LLM 自我反思 + 换方向</td></tr><tr><td>可预测性</td><td>确定性高</td><td>不确定性高</td></tr><tr><td>适应新场景</td><td>需要开发者加分支</td><td>可以自主探索</td></tr></tbody></table><p><strong>但工程实践中，几乎没有人处在光谱的任何一端。</strong> 纯 Workflow 无法处理未知场景；纯 Agent 在关键步骤上不可靠。实际落地的系统都在光谱的某个中间位置。</p><h3 id="你们踩坑的根因：对-Agent-能力做了全局假设"><a href="#你们踩坑的根因：对-Agent-能力做了全局假设" class="headerlink" title="你们踩坑的根因：对 Agent 能力做了全局假设"></a>你们踩坑的根因：对 Agent 能力做了全局假设</h3><p>「默认 Agent 有能力理解一切 → 发现它做不到 → 不断加约束 → 变成写死的 Workflow」——这个路径的根本问题在于：<strong>对 Agent 的能力做了全局一刀切的判断。</strong></p><p>但 Agent 的能力在不同环节差异巨大：</p><table><thead><tr><th>能力维度</th><th>LLM 可靠度</th><th>该交给谁</th></tr></thead><tbody><tr><td><strong>意图理解</strong>（用户想干什么）</td><td>高 ✅</td><td>Agent（但简单场景可以用关键词匹配替代）</td></tr><tr><td><strong>计划制定</strong>（分几步做、先做什么）</td><td>中等 ⚠️</td><td>需要约束框架：Strategy 文件给框架，LLM 填细节</td></tr><tr><td><strong>数据收集</strong>（该查什么）</td><td>中等 ⚠️</td><td>半自主：Skill 定义了查什么，Agent 决定顺序和参数</td></tr><tr><td><strong>数据推理</strong>（看到数据后归因）</td><td>高 ✅</td><td>Agent——这是 LLM 最大的价值</td></tr><tr><td><strong>精确计算</strong>（数值统计）</td><td>极低 ❌</td><td>工具系统（SQL &#x2F; Skill Engine）</td></tr><tr><td><strong>自我评估</strong>（知道自己对不对）</td><td>低 ⚠️</td><td>外部 Verifier，不信任 Agent 自评</td></tr></tbody></table><p>正确的做法不是「全局选 Agent 或全局选 Workflow」，而是<strong>按环节分配</strong>：</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">场景识别     → Workflow（确定性逻辑，不需要 LLM 参与）</span><br><span class="line">数据收集     → 半 Workflow（<span class="built_in">Skill</span> 定义查什么，<span class="built_in">Agent</span> 决定顺序和参数）</span><br><span class="line">推理归因     → <span class="built_in">Agent</span>（这是 LLM 的核心价值，给足数据就能做好）</span><br><span class="line">输出格式     → Workflow（模板化，确定性）</span><br><span class="line">质量验证     → Workflow（规则检查）+ <span class="built_in">Agent</span>（LLM 复核）</span><br></pre></td></tr></table></figure><h3 id="SmartPerfetto-的做法：约束强度光谱"><a href="#SmartPerfetto-的做法：约束强度光谱" class="headerlink" title="SmartPerfetto 的做法：约束强度光谱"></a>SmartPerfetto 的做法：约束强度光谱</h3><p>SmartPerfetto 没有在 Agent 和 Workflow 之间二选一，而是对不同分析阶段设定了不同的约束强度（详见 Q2）。这里从「能力边界」的角度重新审视这个设计：</p><p><strong>高约束（Phase 1.9 根因深钻）——因为 Agent 在「决定是否深钻」这件事上不可靠：</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># scrolling.strategy.md 中的 Phase 1.9</span></span><br><span class="line"></span><br><span class="line"><span class="strong">**Phase 1.9 — 根因深钻（🔴 强制执行，不可跳过）：**</span></span><br><span class="line"></span><br><span class="line">对 batch<span class="emphasis">_frame_</span>root<span class="emphasis">_cause 中占比 &gt;15% 的每个 reason_</span>code，</span><br><span class="line"><span class="strong">**必须**</span>选最严重的 1 帧执行深钻。</span><br><span class="line"><span class="strong">**⛔ 禁止**</span>仅靠 batch<span class="emphasis">_frame_</span>root<span class="emphasis">_cause 的统计分类直接出结论。</span></span><br></pre></td></tr></table></figure><p>为什么要硬约束？因为我们发现 Agent 有一个系统性偏差：<strong>它倾向于在拿到概览数据后就直接出结论，跳过深钻</strong>。这不是模型不够聪明——Claude 完全有能力做根因深钻——而是模型存在「路径依赖」：概览数据已经包含了统计分类（<code>reason_code</code>），对模型来说「直接用分类标签出结论」比「花 3 轮工具调用做逐帧深钻」的认知成本低得多。</p><p><strong>低约束（Phase 1.5 架构分支）——因为 Agent 在「根据数据选择工具」上足够可靠：</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># scrolling.strategy.md 中的 Phase 1.5</span></span><br><span class="line"></span><br><span class="line"><span class="strong">**Phase 1.5 — 架构感知分支：**</span></span><br><span class="line"></span><br><span class="line">| 架构      | 调整动作 |</span><br><span class="line">| Flutter   | 改用 flutter<span class="emphasis">_scrolling_</span>analysis |</span><br><span class="line">| WebView   | 注意 CrRendererMain 线程 |</span><br></pre></td></tr></table></figure><p>这里用「改用」「注意」等建议性措辞，不强制。因为架构检测的结果（Flutter&#x2F;WebView&#x2F;Standard）已经被确定性代码放进了 system prompt，Agent 看到这个信息后选择正确 Skill 的概率很高。</p><p><strong>零约束（general 场景）——因为 Agent 在「未知场景自主探索」上是唯一选择：</strong></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># general.strategy.md — 只给路由决策树，不给任何必须执行的步骤</span></span><br><span class="line"></span><br><span class="line"><span class="string">场景:</span> <span class="string">general</span></span><br><span class="line"><span class="attr">priority:</span> <span class="number">99</span></span><br><span class="line"></span><br><span class="line"><span class="string">当前查询未匹配到特定场景策略。请根据用户关注的方向，</span></span><br><span class="line"><span class="string">使用以下决策树选择合适的分析路径。</span></span><br></pre></td></tr></table></figure><p>general 场景没有任何硬约束，因为进入 general 意味着用户的问题超出了预定义场景，Workflow 无法处理。此时只能信任 Agent 的自主探索能力。</p><h3 id="Agent-能力边界的决定因素"><a href="#Agent-能力边界的决定因素" class="headerlink" title="Agent 能力边界的决定因素"></a>Agent 能力边界的决定因素</h3><p>Agent 的能力边界不取决于模型参数量或 benchmark 分数，而取决于三个工程因素：</p><p><strong>1. 观测能力——Agent 能”看到”什么数据</strong></p><p>同一个模型，给它 <code>scrolling_analysis</code> Skill 的 L2 结构化帧数据 vs 让它自己写 SQL 查原始表，分析质量差距非常显著。Agent 的上限由你给它的数据工具决定。SmartPerfetto 用 164 个 YAML Skill 封装了领域专家的查询逻辑，Agent 通过 <code>invoke_skill</code> 拿到的是处理过的、结构化的分析数据，而不是原始的百万行 trace 事件。</p><p><strong>2. 约束框架——Agent 在什么范围内决策</strong></p><p>不约束的 Agent 像一个没有任务清单的实习生——知识够但不知道该先做什么。Strategy 文件、Planning Gate、Verifier 多层机制共同定义了 Agent 的决策边界：Strategy 告诉它「至少要做什么」，Planning Gate 强制它「先想后做」，Verifier 事后检查「分析是否充分」（启发式检查 + 假设闭环 + 场景完整性 + 可选的 LLM 复核）。</p><p><strong>3. 反馈质量——Agent 做错后能否被纠正</strong></p><p>Agent 的 findings 中存在相当比例的问题（浅层归因、假阳性、遗漏关键步骤）。单纯依赖模型自我纠错效果有限。SmartPerfetto 用多层验证 + 外部纠错 prompt 来闭环：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Verifier 发现 <span class="built_in">ERROR</span> → 生成 Correction Prompt → 触发 SDK 重试</span><br><span class="line">                       ↑                        ↓</span><br><span class="line">                 Learned patterns ← 累积历史误判模式</span><br></pre></td></tr></table></figure><h3 id="补充：Strategy-文件就是-SOP，这没问题"><a href="#补充：Strategy-文件就是-SOP，这没问题" class="headerlink" title="补充：Strategy 文件就是 SOP，这没问题"></a>补充：Strategy 文件就是 SOP，这没问题</h3><p>有人会指出：<code>scrolling.strategy.md</code> 读起来就像一份 SOP（标准作业程序）——有编号的 Phase、条件表、必做项、甚至直接写了 <code>invoke_skill(&quot;scrolling_analysis&quot;, &#123;...&#125;)</code>。这和「在 Skill 里写死决策树」有什么区别？</p><p><strong>直说：在数据收集阶段，SmartPerfetto 的滑动分析就是一个 Workflow。</strong> Strategy 文件就是 SOP，它把领域专家的分析经验编码成了确定性步骤。这是故意的。</p><p>关键在于理解 SOP 覆盖了什么、没覆盖什么：</p><p><strong>SOP 能覆盖的（数据收集）——Strategy 文件干的事：</strong></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">scrolling</span>.strategy.md:</span><br><span class="line">  <span class="attribute">Phase</span> <span class="number">1</span>:   <span class="string">&quot;调用 scrolling_analysis&quot;</span>              ← 写死了收集什么数据</span><br><span class="line">  <span class="attribute">Phase</span> <span class="number">1</span>.<span class="number">5</span>: <span class="string">&quot;Flutter 改用 flutter_scrolling_analysis&quot;</span> ← 写死了条件分支</span><br><span class="line">  <span class="attribute">Phase</span> <span class="number">1</span>.<span class="number">7</span>: 条件表 → 深钻动作                       ← 写死了 if/then 表</span><br><span class="line">  <span class="attribute">Phase</span> <span class="number">1</span>.<span class="number">9</span>: <span class="string">&quot;🔴 占比&gt;15% 的 reason_code 必须深钻&quot;</span>   ← 写死了必做项</span><br></pre></td></tr></table></figure><p><strong>SOP 写不出来的（推理归因）——Agent 的价值所在：</strong></p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">- </span>47 帧掉帧中，哪些帧是同一根因？（数据聚类）</span><br><span class="line"><span class="bullet">- </span>19 帧 workload_heavy 里，哪帧&quot;最严重&quot;值得深钻？（优先级判断）</span><br><span class="line"><span class="bullet">- </span>深钻发现 Binder 阻塞 23ms + 热降频同时存在，因果方向是什么？（因果推理）</span><br><span class="line"><span class="bullet">- </span>最终结论怎么组织？给 App 开发者 vs 平台工程师的建议如何区分？（表达决策）</span><br></pre></td></tr></table></figure><p><strong>不同场景的 SOP 程度不同：</strong></p><table><thead><tr><th>Strategy 文件</th><th>SOP 程度</th><th>原因</th></tr></thead><tbody><tr><td><code>scrolling.strategy.md</code></td><td><strong>高</strong> — Phase 编号 + 条件表 + 必做项</td><td>滑动分析方法论最成熟，最优数据收集路径已知</td></tr><tr><td><code>startup.strategy.md</code></td><td><strong>中高</strong> — 有 Phase 结构，深钻方向更开放</td><td>启动场景更多样（冷&#x2F;温&#x2F;热、不同瓶颈）</td></tr><tr><td><code>anr.strategy.md</code></td><td><strong>中</strong> — 2-skill pipeline，但根因分析全靠推理</td><td>ANR 根因高度多样</td></tr><tr><td><code>general.strategy.md</code></td><td><strong>低</strong> — 只有路由决策树，无必做项</td><td>未知场景，无法 SOP 化</td></tr></tbody></table><p><code>scrolling.strategy.md</code> 是 SOP 程度最高的，因为滑动分析方法论最成熟。<code>general.strategy.md</code> 几乎没有 SOP，因为用户问题完全不可预测。</p><p><strong>所以正确的理解是：SmartPerfetto &#x3D; 「SOP 驱动的数据收集 + Agent 驱动的推理归因」。</strong></p><p>SOP 解决「分析滑动问题至少要看哪些数据」——这个问题有确定性答案，用 SOP 是对的。Agent 解决「拿到数据后怎么推理因果、怎么组织结论」——这个问题每个 trace 不同，写不成 SOP。</p><p><strong>回到你们的踩坑：问题不是「Skill 变成了 SOP」——数据收集阶段就应该用 SOP。问题是「SOP 吃掉了推理」——如果 SOP 连结论都写死了（”看到 X 就输出 Y”），Agent 就真的退化成 Workflow 了。关键是让 SOP 止步于数据收集，把推理留给 Agent。</strong></p><h3 id="一句话总结-1"><a href="#一句话总结-1" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>Agent 和 Workflow 的区别不是「智能 vs 写死」，而是「决策权的分配方式」。Agent 的能力边界由「观测能力 × 约束框架 × 反馈质量」共同决定。正确的做法是按环节分配决策权——在 Agent 可靠的环节给自主权，在 Agent 不可靠的环节加约束——而不是全局一刀切。</strong></p><hr><h2 id="Q4：Agent-的架构需要从业务视角进行改进吗？"><a href="#Q4：Agent-的架构需要从业务视角进行改进吗？" class="headerlink" title="Q4：Agent 的架构需要从业务视角进行改进吗？"></a>Q4：Agent 的架构需要从业务视角进行改进吗？</h2><p><strong>提问背景：</strong> Agent 的架构有过不同的设计和演进——从最初的 ReAct 架构，到 LangGraph 的节点式架构，不同的 Agent 架构设计会给它带来怎样的影响？在搭建自己的业务 Agent 时，是否需要考虑架构对 Agent 性能的影响？例如 SmartPerfetto 是在 Claude Agent SDK 的基础上基于业务理解加入了不同的 Skill 加载模式。</p><h3 id="三种主流架构的本质差异"><a href="#三种主流架构的本质差异" class="headerlink" title="三种主流架构的本质差异"></a>三种主流架构的本质差异</h3><table><thead><tr><th>架构</th><th>控制流模型</th><th>开发者角色</th><th>适合场景</th></tr></thead><tbody><tr><td><strong>ReAct</strong></td><td>线性循环：Think → Act → Observe → Think…</td><td>定义工具</td><td>工具少、路径短的简单任务</td></tr><tr><td><strong>LangGraph 节点式</strong></td><td>DAG 图：节点&#x3D;步骤，边&#x3D;条件跳转</td><td>设计图结构 + 定义节点 + 写跳转条件</td><td>步骤明确、分支有限的确定性流程</td></tr><tr><td><strong>SDK 原生</strong></td><td>SDK 管理 turn loop，开发者只定义 tools</td><td>定义工具 + 注入上下文</td><td>工具多、路径不可预测、需要 LLM 自主编排</td></tr></tbody></table><p>它们的核心区别在于<strong>「谁来决定下一步做什么」</strong>：</p><ul><li><strong>ReAct</strong>：LLM 在每一步都做完整决策（想什么、做什么、用什么工具），框架只负责转发</li><li><strong>LangGraph</strong>：开发者预定义了所有可能的路径（节点+边），LLM 只在节点内部做局部决策</li><li><strong>SDK 原生</strong>：SDK 管理对话循环，LLM 自主选择工具，开发者通过 system prompt 和工具设计来间接约束</li></ul><h3 id="SmartPerfetto-选择-SDK-原生-自建约束层的原因"><a href="#SmartPerfetto-选择-SDK-原生-自建约束层的原因" class="headerlink" title="SmartPerfetto 选择 SDK 原生 + 自建约束层的原因"></a>SmartPerfetto 选择 SDK 原生 + 自建约束层的原因</h3><p>SmartPerfetto 的架构是 <strong>Claude Agent SDK（SDK 原生）+ 三层约束（Strategy&#x2F;Planning Gate&#x2F;Verifier）</strong>：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Claude Agent SDK 提供:                SmartPerfetto 自建:</span><br><span class="line">├─ Turn <span class="keyword">loop</span>（自动管理多轮对话）      ├─ Scene Classification（场景路由，&lt;<span class="number">1</span>ms）</span><br><span class="line">├─ Tool dispatching（MCP 工具调用）   ├─ Strategy Injection（按场景注入分析策略）</span><br><span class="line">├─ Streaming（SSE 事件流）           ├─ Planning Gate（强制先规划再执行）</span><br><span class="line">├─ <span class="keyword">Session</span> resume（多轮上下文恢复）   ├─ Verifier（事后验证 + 纠错重试）</span><br><span class="line">└─ Sub-agent orchestration           ├─ ArtifactStore（<span class="number">3</span> 级缓存压缩 token）</span><br><span class="line">                                     ├─ Conditional Tool Loading（按场景注入/隐藏工具）</span><br><span class="line">                                     └─ <span class="keyword">Cross</span>-<span class="keyword">Session</span> Memory（模式记忆 + 负面记忆）</span><br></pre></td></tr></table></figure><p><strong>为什么不用 LangGraph？</strong></p><p>性能分析的根因推理路径是<strong>不可预测的</strong>。同样一个「滑动卡顿」，根因可能是：</p><ul><li>Binder 阻塞 → 需要追踪 system_server 端的线程状态</li><li>GPU 渲染慢 → 需要查 GPU frequency 和 fence wait</li><li>GC 暂停 → 需要看 Java heap 和 GC events</li><li>热降频 → 需要查 thermal zone 和 CPU frequency</li><li>锁竞争 → 需要查 monitor contention</li><li>以上多个原因组合</li></ul><p>如果用 LangGraph，你需要为每一种根因路径预定义一个 DAG 节点和跳转条件。性能分析有 21 个 reason_code，每个可以组合深钻——排列组合后的路径数量使得 DAG 图变得不可维护。</p><p>更根本的问题是：<strong>在看到数据之前，你不知道该走哪条路径</strong>。LangGraph 的 DAG 图假设开发者能提前预知所有分支条件，但性能分析的分支条件取决于运行时数据。</p><p><strong>SDK 原生架构的优势：</strong></p><p>LLM 自主选择工具路径，但通过三层约束确保关键步骤不被跳过：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># LangGraph 需要预定义的 DAG:</span></span><br><span class="line">graph.add_edge(<span class="string">&quot;overview&quot;</span>, <span class="string">&quot;check_binder&quot;</span>)</span><br><span class="line">graph.add_edge(<span class="string">&quot;overview&quot;</span>, <span class="string">&quot;check_gpu&quot;</span>)</span><br><span class="line">graph.add_edge(<span class="string">&quot;overview&quot;</span>, <span class="string">&quot;check_gc&quot;</span>)</span><br><span class="line"><span class="comment"># ... 每加一种根因就要改图结构</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># SmartPerfetto 的 Strategy 只需要声明:</span></span><br><span class="line"><span class="comment"># &quot;对占比 &gt;15% 的每个 reason_code，必须选最严重的 1 帧执行深钻&quot;</span></span><br><span class="line"><span class="comment"># Agent 自主决定具体调哪个深钻工具</span></span><br></pre></td></tr></table></figure><h3 id="关键业务导向的架构设计"><a href="#关键业务导向的架构设计" class="headerlink" title="关键业务导向的架构设计"></a>关键业务导向的架构设计</h3><p>以下是 SmartPerfetto 基于业务理解做的架构改进，每一个都直接对应一个业务问题：</p><p><strong>1. 条件化工具加载——减少 Agent 的决策空间</strong></p><p>SmartPerfetto 的 MCP 工具总数最多 20 个（9 个 always-on + 11 个条件注入），但一次分析只注入其中的子集：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// claudeMcpServer.ts — 按模式切换工具集</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (options.<span class="property">lightweight</span>) &#123;</span><br><span class="line">  <span class="comment">// 事实性查询（如&quot;帧率是多少&quot;）：只给 3 个工具</span></span><br><span class="line">  toolEntries = [executeSql, invokeSkill, lookupSqlSchema];</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">// 完整分析：9 个 always-on (含 recall_patterns) + 按上下文条件注入</span></span><br><span class="line">  <span class="comment">// 比较模式 → 注入 compare_skill, execute_sql_on, get_comparison_context</span></span><br><span class="line">  <span class="comment">// 假设管理 → 注入 submit_hypothesis, resolve_hypothesis, flag_uncertainty</span></span><br><span class="line">  <span class="comment">// 规划工具 → 注入 submit_plan, update_plan_phase, revise_plan</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>业务原因：</strong> 工具越多，Agent 选错工具的概率越大。一个只需要快速回答「帧率是多少」的查询，如果看到十几个规划&#x2F;假设&#x2F;比较工具，Agent 可能会过度分析。</p><p><strong>2. Sub-Agent 场景门控——避免不必要的并行开销</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// claudeAgentDefinitions.ts — 只在复杂场景启用 sub-agent</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">ORCHESTRATOR_ONLY_TOOLS</span> = <span class="keyword">new</span> <span class="title class_">Set</span>([</span><br><span class="line">  <span class="string">&#x27;submit_plan&#x27;</span>, <span class="string">&#x27;update_plan_phase&#x27;</span>, <span class="string">&#x27;revise_plan&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;submit_hypothesis&#x27;</span>, <span class="string">&#x27;resolve_hypothesis&#x27;</span>, <span class="string">&#x27;flag_uncertainty&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;compare_skill&#x27;</span>, <span class="string">&#x27;execute_sql_on&#x27;</span>, <span class="string">&#x27;get_comparison_context&#x27;</span>,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Sub-agent 只拿到数据收集工具，不拿规划/假设工具</span></span><br><span class="line"><span class="comment">// 设计原则：sub-agents collect evidence, orchestrator makes diagnosis</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>场景</th><th>Sub-Agent 配置</th><th>原因</th></tr></thead><tbody><tr><td>scrolling</td><td>frame-expert + system-expert</td><td>帧分析和系统分析适合拆分，由 orchestrator 协调</td></tr><tr><td>startup</td><td>startup-expert + system-expert</td><td>启动阶段分析和资源竞争分析适合拆分</td></tr><tr><td>anr</td><td>无 sub-agent</td><td>ANR 是 2-skill 管线，额外 sub-agent 反而增加开销</td></tr></tbody></table><blockquote><p>注：sub-agent 的实际并行性取决于 SDK 内部调度策略，我们按并行采证来设计 prompt，但实际执行可能是串行的。</p></blockquote><p><strong>3. Lightweight vs Full 双模式——快问快答不走完整管线</strong></p><p>当用户问「这个 trace 的帧率是多少」时，不需要走完整的 Planning → Skill → Verification 管线。SmartPerfetto 的 <code>ClaudeRuntime</code> 在入口处做复杂度分类：</p><figure class="highlight isbl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">analyze</span>(<span class="variable">query</span>)</span></span><br><span class="line">  ↓</span><br><span class="line"><span class="variable">queryComplexity</span> === <span class="string">&#x27;quick&#x27;</span></span><br><span class="line">  → <span class="function"><span class="title">analyzeQuick</span>(): <span class="number">3</span> 个工具，无 <span class="variable">Planning</span> <span class="variable">Gate</span>，无 <span class="variable">Verifier</span></span></span><br><span class="line"><span class="function">  → 直接回答事实性问题</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="variable">queryComplexity</span> === <span class="string">&#x27;full&#x27;</span></span></span><br><span class="line"><span class="function">  → 完整管线：<span class="variable">Planning</span> → <span class="variable">Skill</span> → <span class="variable">Verification</span> → <span class="variable">Correction</span></span></span><br><span class="line"><span class="function">  → 系统化分析</span></span><br></pre></td></tr></table></figure><p><strong>业务原因：</strong> 用户的提问中有相当比例是事实性查询（「帧率多少」「有没有 ANR」），走完整管线会不必要地增加延迟。</p><h3 id="对「是否需要从业务角度改进架构」的回答"><a href="#对「是否需要从业务角度改进架构」的回答" class="headerlink" title="对「是否需要从业务角度改进架构」的回答"></a>对「是否需要从业务角度改进架构」的回答</h3><p><strong>需要，但改进方向不是换底层框架（ReAct → LangGraph），而是在现有框架上加业务约束层。</strong> 具体建议：</p><ol><li><p><strong>从你的 Skill 决策树中提取 Strategy 文件</strong>：把写死在代码里的 <code>if/else</code> 决策逻辑，改写为自然语言的分析策略（Markdown 文件），按场景注入 system prompt。这样领域专家可以直接修改分析逻辑，不需要碰代码。</p></li><li><p><strong>加 Planning Gate</strong>：<code>requirePlan()</code> 的实现极其简单（不到 10 行代码），但效果显著——强制 Agent 先想后做，经验上能大幅减少跑偏。</p></li><li><p><strong>加事后 Verifier</strong>：不检查 Agent 的中间步骤是否「正确」（这个很难判断），只检查关键步骤是否「发生了」（这个很容易判断）。</p></li><li><p><strong>按场景&#x2F;复杂度动态调整工具集和约束强度</strong>：不是所有查询都需要同样的分析深度，给简单查询开一条快速通道。</p></li></ol><h3 id="一句话总结-2"><a href="#一句话总结-2" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>架构选择是业务问题，不是技术问题。ReAct&#x2F;LangGraph&#x2F;SDK 只是控制流的不同实现方式——真正影响 Agent 性能的是你在控制流之上搭建的约束层。SmartPerfetto 选择 SDK 原生不是因为它最先进，而是因为性能分析的根因路径不可预测，预定义 DAG 不如让 Agent 在约束框架内自主探索。</strong></p><hr><h2 id="Q5：性能-AI-智能体应该如何做好场景识别？"><a href="#Q5：性能-AI-智能体应该如何做好场景识别？" class="headerlink" title="Q5：性能 AI 智能体应该如何做好场景识别？"></a>Q5：性能 AI 智能体应该如何做好场景识别？</h2><p><strong>提问背景：</strong> 想做场景识别、路由到正确的 Skill，在建设这部分的时候应该更多基于「用户原声」还是「日志」，或者有什么更好的方法？场景识别的几条路径各有问题：代码匹配（关键词匹配会导致筛选结果过大或过小）、LLM 理解（LLM 的理解不一定准确）、日志还原（能筛选有无滑动，但不一定是用户关注的问题）。</p><h3 id="SmartPerfetto-的做法：三层信号、分工明确"><a href="#SmartPerfetto-的做法：三层信号、分工明确" class="headerlink" title="SmartPerfetto 的做法：三层信号、分工明确"></a>SmartPerfetto 的做法：三层信号、分工明确</h3><p>SmartPerfetto 的场景识别不是靠单一信号源，而是三层信号配合，每层解决不同的问题：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Layer <span class="number">1</span>: 用户原声 — 关键词匹配 → 场景类型（scrolling <span class="regexp">/ startup /</span> anr / ...）</span><br><span class="line">Layer <span class="number">2</span>: Trace 数据 — 确定性检测 → 架构信息（Flutter <span class="regexp">/ WebView /</span> Standard / Compose）</span><br><span class="line">Layer <span class="number">3</span>: 数据完整性 — 表存在性检查 → 可用分析维度（有无 GPU 数据、有无热降频数据）</span><br></pre></td></tr></table></figure><p><strong>Layer 1：用户原声（关键词匹配，&lt;1ms）</strong></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// sceneClassifier.ts — 46 行代码，完成了全部场景分类</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">classifyScene</span>(<span class="params">query: <span class="built_in">string</span></span>): <span class="title class_">SceneType</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> scenes = <span class="title function_">getRegisteredScenes</span>();  <span class="comment">// 从 12 个 .strategy.md 的 frontmatter 加载</span></span><br><span class="line">  <span class="keyword">const</span> lower = query.<span class="title function_">toLowerCase</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> sorted = scenes</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function"><span class="params">s</span> =&gt;</span> s.<span class="property">scene</span> !== <span class="string">&#x27;general&#x27;</span>)</span><br><span class="line">    .<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">priority</span> - b.<span class="property">priority</span>);  <span class="comment">// ANR(1) &gt; startup(2) &gt; scrolling(3) &gt; ...</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> scene <span class="keyword">of</span> sorted) &#123;</span><br><span class="line">    <span class="comment">// 先匹配 compound patterns（更具体，如「启动.*慢」）</span></span><br><span class="line">    <span class="keyword">if</span> (scene.<span class="property">compoundPatterns</span>.<span class="title function_">some</span>(<span class="function"><span class="params">p</span> =&gt;</span> p.<span class="title function_">test</span>(query))) <span class="keyword">return</span> scene.<span class="property">scene</span>;</span><br><span class="line">    <span class="comment">// 再匹配单关键词</span></span><br><span class="line">    <span class="keyword">if</span> (scene.<span class="property">keywords</span>.<span class="title function_">some</span>(<span class="function"><span class="params">k</span> =&gt;</span> lower.<span class="title function_">includes</span>(k))) <span class="keyword">return</span> scene.<span class="property">scene</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;general&#x27;</span>;  <span class="comment">// 兜底</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键词定义在每个 Strategy 文件的 YAML frontmatter 中，不是硬编码在 TypeScript 里：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># scrolling.strategy.md frontmatter</span></span><br><span class="line"><span class="attr">keywords:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">滑动</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">卡顿</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">掉帧</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">jank</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">scroll</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">fps</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">帧</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">frame</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">列表</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">流畅</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">fling</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">stuttering</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">dropped</span> <span class="string">frame</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">不流畅</span></span><br><span class="line">  <span class="comment"># ... 共 30+ 关键词</span></span><br></pre></td></tr></table></figure><p><strong>为什么用关键词匹配而不是 LLM？</strong></p><ol><li><strong>成本</strong>：场景分类在每次分析的入口处执行，关键词匹配 &lt;1ms + 0 tokens；LLM 调用 ~500ms + ~500 tokens</li><li><strong>确定性</strong>：分类错误的代价非常高（会注入错误的 Strategy 文件），关键词匹配的行为完全可预测</li><li><strong>足够准确</strong>：在性能分析领域，用户的提问高度格式化——说「滑动卡顿」的就是在问滑动，说「启动慢」的就是在问启动。不需要 LLM 来”理解”</li></ol><p><strong>关键词匹配不够的地方怎么办？</strong></p><p>关键词匹配的确有边界——当用户说「这个 app 为什么慢」时，关键词无法确定是启动慢还是滑动慢。SmartPerfetto 的处理方式是：<strong>匹配不到就 fallback 到 <code>general</code> 场景</strong>，让 Agent 在 general 策略的路由决策树中自主选择方向。</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># general.strategy.md — 没有任何硬约束，只给路由建议</span></span><br><span class="line"></span><br><span class="line"><span class="string">|</span> <span class="string">用户关注方向</span>       <span class="string">|</span> <span class="string">推荐路径</span> <span class="string">|</span></span><br><span class="line"><span class="string">|</span> <span class="string">CPU</span> <span class="string">/</span> <span class="string">调度</span> <span class="string">/</span> <span class="string">线程</span>  <span class="string">|</span> <span class="string">invoke_skill(&quot;cpu_analysis&quot;)</span> <span class="string">|</span></span><br><span class="line"><span class="string">|</span> <span class="string">内存</span> <span class="string">/</span> <span class="string">OOM</span> <span class="string">/</span> <span class="string">泄漏</span>  <span class="string">|</span> <span class="string">invoke_skill(&quot;memory_analysis&quot;)</span> <span class="string">|</span></span><br><span class="line"><span class="string">|</span> <span class="string">不确定方向</span>         <span class="string">|</span> <span class="string">invoke_skill(&quot;scene_reconstruction&quot;)</span> <span class="string">→</span> <span class="string">按场景路由</span> <span class="string">|</span></span><br></pre></td></tr></table></figure><p>这个设计的核心思路是：<strong>不要试图在入口处 100% 准确分类，而是让准确的情况走快速路径（关键词 → Strategy），不确定的情况走探索路径（general → Agent 自主路由）。</strong></p><p><strong>Layer 2：Trace 数据——架构检测（确定性代码）</strong></p><p>场景分类只解决了「用户想分析什么」，但同一个场景（如滑动）在不同渲染架构下的分析路径完全不同：</p><table><thead><tr><th>架构</th><th>渲染管线</th><th>分析差异</th></tr></thead><tbody><tr><td>Standard Android</td><td>UI Thread → RenderThread → SurfaceFlinger</td><td>主线程 + RenderThread 双线分析</td></tr><tr><td>Flutter TextureView</td><td>1.ui → 1.raster → JNISurfaceTexture → RenderThread updateTexImage</td><td>双出图管线，需要分析 Flutter engine 线程 + 纹理桥接</td></tr><tr><td>Flutter SurfaceView</td><td>1.ui → 1.raster → BufferQueue → SurfaceFlinger</td><td>单出图管线，不经过 RenderThread</td></tr><tr><td>WebView</td><td>CrRendererMain → Viz Compositor</td><td>Chromium 渲染管线，线程名不同</td></tr><tr><td>Compose</td><td>UI Thread (Composition) → RenderThread</td><td>和 Standard 类似但有 Composition 阶段</td></tr></tbody></table><p>架构检测委托给 YAML skill <code>rendering_pipeline_detection</code>——它在 SQL 层做线程&#x2F;Slice 信号采集、管线打分、子变体判定，支持 24 种细粒度渲染架构。TypeScript 侧（<code>architectureDetector.ts</code>）只负责调用 skill 和映射结果，不做直接的 if&#x2F;else 判断：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rendering_pipeline_detection skill (SQL)</span><br><span class="line">  → 采集线程信号（<span class="number">1</span>.ui <span class="regexp">/ 1.raster /</span> CrRendererMain / RenderThread ...）</span><br><span class="line">  → 管线打分（FLUTTER_TEXTUREVIEW <span class="regexp">/ WEBVIEW_BLINK /</span> ANDROID_VIEW_STANDARD ...）</span><br><span class="line">  → architectureDetector.ts 映射为 ArchitectureInfo 类型</span><br></pre></td></tr></table></figure><p>检测结果被注入到 system prompt 中（通过 <code>arch-flutter.template.md</code> 等模板），Agent 看到架构信息后选择对应的分析工具。</p><p><strong>Layer 3：数据完整性——能力寄存器</strong></p><p>Trace 采集配置不同，可用的数据维度也不同。有的 trace 没有 GPU frequency 数据，有的没有 thermal zone。SmartPerfetto 在分析开始前探测 18 个数据维度的可用性：</p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">frame_rendering:</span> ✅ (<span class="number">456</span> rows)</span><br><span class="line"><span class="symbol">cpu_scheduling:</span> ✅ (<span class="number">12000</span> rows)</span><br><span class="line"><span class="symbol">gpu:</span> ❌ (无 gpu_frequency counter)</span><br><span class="line"><span class="symbol">thermal_throttling:</span> ✅ (<span class="number">4</span> zones)</span><br><span class="line"><span class="symbol">binder_ipc:</span> ✅ (<span class="number">890</span> transactions)</span><br></pre></td></tr></table></figure><p>这个信息同样注入 system prompt，告诉 Agent 哪些维度可以分析、哪些维度数据缺失。避免 Agent 调用没有数据支撑的 Skill 后得到空结果再换方向——这种试错浪费 1-2 个工具调用的 token。</p><h3 id="对提问中三条路径的评价"><a href="#对提问中三条路径的评价" class="headerlink" title="对提问中三条路径的评价"></a>对提问中三条路径的评价</h3><p><strong>1. 代码匹配（关键词）：可以用，但要设计好兜底</strong></p><p>提问者说「关键词匹配会导致筛选结果过大或过小」。SmartPerfetto 的经验是：</p><ul><li><strong>优先级排序</strong>解决「过大」问题：ANR(1) &gt; startup(2) &gt; scrolling(3)，同时命中多个关键词时取优先级最高的</li><li><strong>Compound patterns</strong> 解决精度问题：<code>/启动.*慢/</code> 比单独匹配「启动」更精确</li><li><strong><code>general</code> 兜底</strong>解决「过小」问题：匹配不到就不猜，交给 Agent 自主探索</li></ul><p><strong>2. LLM 理解：不建议用在分类入口，可以用在兜底路径</strong></p><p>LLM 的分类在 SmartPerfetto 中不是 Layer 1，而是 <code>general</code> 场景中 Agent 的自主路由——这时 Agent 已经拿到了 trace 数据，可以结合数据做更准确的判断。</p><p><strong>3. 日志还原：适合做 Layer 2 的补充信号</strong></p><p>日志能告诉你「trace 中有什么」（有无滑动事件、有无 ANR），但不能告诉你「用户关心什么」。SmartPerfetto 的数据完整性探测就是这个角色——它不参与场景分类，但为 Agent 提供数据可用性信息。</p><h3 id="一句话总结-3"><a href="#一句话总结-3" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>场景识别不要试图用一个信号源解决所有问题。用关键词匹配做快速路由（准确的情况），用 <code>general</code> 兜底做 Agent 自主探索（不确定的情况），用 trace 数据做架构和完整性补充。关键词匹配 + 优先级排序 + compound patterns + 兜底策略，46 行代码就够了。</strong></p><hr><h2 id="Q6：「确定性步骤-AI-自主探索」，如何更好地发挥-AI-自主探索能力？"><a href="#Q6：「确定性步骤-AI-自主探索」，如何更好地发挥-AI-自主探索能力？" class="headerlink" title="Q6：「确定性步骤 + AI 自主探索」，如何更好地发挥 AI 自主探索能力？"></a>Q6：「确定性步骤 + AI 自主探索」，如何更好地发挥 AI 自主探索能力？</h2><p><strong>提问背景：</strong> 目前线上 Skill 运行的时候发现，AI 自主探索、下钻根因容易跑偏，给出不正确的结果。比如 SmartPerfetto 博客中提到的例子——在前期定位到 RenderThread 被 Binder 阻塞后（基于确定性步骤），后面的多个假设形成和验证是纯 AI 发挥吗，还是说针对 Binder 阻塞我们可能给 AI 一些常见的原因作为引导让它自己排查？</p><h3 id="先回答核心问题：不是纯-AI-发挥，也不是写死引导"><a href="#先回答核心问题：不是纯-AI-发挥，也不是写死引导" class="headerlink" title="先回答核心问题：不是纯 AI 发挥，也不是写死引导"></a>先回答核心问题：不是纯 AI 发挥，也不是写死引导</h3><p>SmartPerfetto 的做法是<strong>结构化推理框架 + 按需知识注入</strong>：</p><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">确定性步骤产出数据</span><br><span class="line">  ↓</span><br><span class="line"><span class="built_in">Agent</span> 形成假设（自主，但有推理框架约束）</span><br><span class="line">  ↓</span><br><span class="line"><span class="built_in">Agent</span> 选择验证工具（自主，但有 Strategy 建议）</span><br><span class="line">  ↓</span><br><span class="line">验证结果反馈</span><br><span class="line">  ↓</span><br><span class="line">假设成立 → 深入 / 假设不成立 → 回退换方向</span><br><span class="line">  ↓</span><br><span class="line">Verifier 事后检查</span><br></pre></td></tr></table></figure><p>三个关键机制让 AI 自主探索变得更可靠：</p><h3 id="机制-1：假设管理工具——给推理过程加结构"><a href="#机制-1：假设管理工具——给推理过程加结构" class="headerlink" title="机制 1：假设管理工具——给推理过程加结构"></a>机制 1：假设管理工具——给推理过程加结构</h3><p>SmartPerfetto 提供了 <code>submit_hypothesis</code> 和 <code>resolve_hypothesis</code> 两个 MCP 工具，不是让 Agent 在内心独白中隐式推理，而是<strong>强制外显化</strong>：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Agent 调用:</span><br><span class="line">  <span class="title function_ invoke__">submit_hypothesis</span>(&#123;</span><br><span class="line">    <span class="attr">description</span>: <span class="string">&quot;system_server Binder 响应慢导致 RenderThread 阻塞&quot;</span>,</span><br><span class="line">    <span class="attr">expected_evidence</span>: <span class="string">&quot;system_server 端对应 Binder 事务的 thread_state 显示长时间 Runnable/Sleeping&quot;</span></span><br><span class="line">  &#125;)</span><br><span class="line"></span><br><span class="line">Agent 调用:</span><br><span class="line">  <span class="title function_ invoke__">execute_sql</span>(<span class="string">&quot;SELECT ... FROM thread_state WHERE utid = ... AND ts BETWEEN ...&quot;</span>)</span><br><span class="line"></span><br><span class="line">Agent 调用:</span><br><span class="line">  <span class="title function_ invoke__">resolve_hypothesis</span>(&#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;h1&quot;</span>,</span><br><span class="line">    <span class="attr">outcome</span>: <span class="string">&quot;rejected&quot;</span>,</span><br><span class="line">    <span class="attr">evidence</span>: <span class="string">&quot;system_server 端 Binder 线程状态正常，响应耗时 &lt;2ms，</span></span><br><span class="line"><span class="string">              RenderThread 阻塞原因是 dequeueBuffer 等待 SurfaceFlinger&quot;</span></span><br><span class="line">  &#125;)</span><br></pre></td></tr></table></figure><p><strong>为什么有效？</strong> 假设管理工具迫使 Agent 在行动之前明确声明「我在验证什么」和「我预期看到什么」。这有两个好处：</p><ol><li><strong>防止目标漂移</strong>——Agent 不会在收集数据的过程中忘了自己最初想验证什么</li><li><strong>可审计</strong>——每个假设都有完整记录，Verifier 可以检查是否所有假设都被 resolved</li></ol><h3 id="机制-2：知识注入——不是写死引导，是按需加载领域知识"><a href="#机制-2：知识注入——不是写死引导，是按需加载领域知识" class="headerlink" title="机制 2：知识注入——不是写死引导，是按需加载领域知识"></a>机制 2：知识注入——不是写死引导，是按需加载领域知识</h3><blockquote><p>“针对 Binder 阻塞我们可能给 AI 一些常见的原因作为引导吗？”</p></blockquote><p>是的，但不是写死在 Skill 中，而是通过 <code>lookup_knowledge</code> MCP 工具<strong>按需加载</strong>。Agent 发现 Binder 阻塞后，可以调用：</p><figure class="highlight isbl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">invoke</span> <span class="function"><span class="title">lookup_knowledge</span>(<span class="string">&quot;binder-ipc&quot;</span>)</span></span><br></pre></td></tr></table></figure><p>返回一份 Binder IPC 的知识模板（<code>knowledge-binder-ipc.template.md</code>），包含：</p><ul><li>Binder 事务的典型阻塞原因分类（server 端忙、进程冻结、CPU 调度延迟、oneway 队列满）</li><li>每种原因的排查路径和关键指标</li><li>常见误判场景（如 oneway 事务不会阻塞调用方）</li></ul><p><strong>关键设计：知识是 Agent 主动拉取的，不是系统强制注入的。</strong> 目前共有 8 个知识模板（rendering-pipeline、binder-ipc、gc-dynamics、cpu-scheduler、thermal-throttling、lock-contention、startup-root-causes、data-sources），如果在 system prompt 中预先注入全部模板，会消耗大量 token 且大部分不相关。通过 MCP 工具按需加载，Agent 只在需要时才获取对应领域的背景知识。</p><p>SmartPerfetto 还在 Strategy 文件中提供了<strong>条件化的深钻建议表</strong>，这也是一种引导：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># scrolling.strategy.md Phase 1.9</span></span><br><span class="line"></span><br><span class="line">| 条件                          | 深钻动作 |</span><br><span class="line">| 任何 reason<span class="emphasis">_code Q4&gt;20%       | invoke_</span>skill(&quot;blocking<span class="emphasis">_chain_</span>analysis&quot;, ...) |</span><br><span class="line">| binder<span class="emphasis">_overlap &gt;5ms           | invoke_</span>skill(&quot;binder<span class="emphasis">_root_</span>cause&quot;, ...) |</span><br><span class="line">| cpu<span class="emphasis">_runnable_</span>ratio &gt;30%       | invoke<span class="emphasis">_skill(&quot;cpu_</span>analysis&quot;, ...) |</span><br><span class="line">| thermal<span class="emphasis">_throttle detected     | invoke_</span>skill(&quot;thermal<span class="emphasis">_throttling&quot;, ...) |</span></span><br><span class="line"><span class="emphasis">| gc_</span>pause<span class="emphasis">_total &gt;10ms          | invoke_</span>skill(&quot;gc<span class="emphasis">_analysis&quot;, ...) |</span></span><br></pre></td></tr></table></figure><p>这个表不是写死的决策树——它是给 Agent 的<strong>查找表</strong>。Agent 看到数据后，根据数据中的指标值决定走哪一行。如果数据不匹配任何一行，Agent 可以自主探索。</p><h3 id="机制-3：ReAct-Reasoning-Nudge——在工具返回时触发反思"><a href="#机制-3：ReAct-Reasoning-Nudge——在工具返回时触发反思" class="headerlink" title="机制 3：ReAct Reasoning Nudge——在工具返回时触发反思"></a>机制 3：ReAct Reasoning Nudge——在工具返回时触发反思</h3><p>在数据工具（<code>execute_sql</code> &#x2F; <code>invoke_skill</code>）成功返回的<strong>前几次</strong>调用中，SmartPerfetto 在结果末尾附加一段推理提示：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// claudeMcpServer.ts</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">REASONING_NUDGE</span> = <span class="string">&#x27;\n\n[REFLECT] 在执行下一步之前：&#x27;</span> +</span><br><span class="line">  <span class="string">&#x27;这个数据的关键发现是什么？是否支持/反驳你的假设？&#x27;</span> +</span><br><span class="line">  <span class="string">&#x27;如有重要推断，请用 submit_hypothesis 或 write_analysis_note 记录。&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只在前 N 次数据工具调用时附加，之后停止（控制 token 成本）</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">REASONING_NUDGE_MAX_CALLS</span> = <span class="number">4</span>;</span><br></pre></td></tr></table></figure><p><strong>成本极低（~20 tokens&#x2F;次，前 4 次共 ~80 tokens），但效果显著。</strong> 之所以不是全程附加，是为了在分析后半段控制 token 开销——前几次 nudge 已经建立了「收到数据 → 先反思 → 再行动」的模式。没有这个 nudge，Agent 倾向于连续调用工具而不停下来思考——收集了 5 次数据但没有形成任何中间结论，最后的总结质量很差。</p><h3 id="用图片中的-Binder-例子走一遍完整流程"><a href="#用图片中的-Binder-例子走一遍完整流程" class="headerlink" title="用图片中的 Binder 例子走一遍完整流程"></a>用图片中的 Binder 例子走一遍完整流程</h3><figure class="highlight sqf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>. 先看总览 → 发现 <span class="number">47</span> 帧卡顿，P90 = <span class="number">23.5</span>ms</span><br><span class="line">   [确定性：invoke_skill(<span class="string">&quot;scrolling_analysis&quot;</span>)]</span><br><span class="line"></span><br><span class="line"><span class="number">2</span>. 根据总览决定方向 → <span class="number">40</span>% 卡在 APP 阶段，优先看 App 侧</span><br><span class="line">   [确定性：数据驱动，Strategy 文件建议]</span><br><span class="line"></span><br><span class="line"><span class="number">3</span>. 选代表帧深钻 → Frame #<span class="number">234</span> RenderThread 被 Binder 阻塞 <span class="number">23</span>ms</span><br><span class="line">   [确定性：invoke_skill(<span class="string">&quot;jank_frame_detail&quot;</span>)]</span><br><span class="line"></span><br><span class="line">--- 以下进入 AI 自主探索 ---</span><br><span class="line"></span><br><span class="line"><span class="number">4</span>. <span class="built_in">Agent</span> 主动加载知识：lookup_knowledge(<span class="string">&quot;binder-ipc&quot;</span>)</span><br><span class="line">   → 获取 Binder 阻塞常见原因分类表</span><br><span class="line">   [AI 决策 + 知识引导]</span><br><span class="line"></span><br><span class="line"><span class="number">5</span>. <span class="built_in">Agent</span> 形成假设：submit_hypothesis(<span class="string">&quot;system_server Binder 响应慢&quot;</span>)</span><br><span class="line">   预期证据：server 端 thread_state 长时间 Sleeping/Runnable</span><br><span class="line">   [AI 推理，假设工具强制外显]</span><br><span class="line"></span><br><span class="line"><span class="number">6</span>. <span class="built_in">Agent</span> 验证：execute_sql(<span class="string">&quot;查 Binder 对端 thread_state&quot;</span>)</span><br><span class="line">   → 发现 system_server CPU 调度延迟，不是 Binder 响应慢</span><br><span class="line">   [AI 自主工具调用]</span><br><span class="line"></span><br><span class="line"><span class="number">7</span>. [REFLECT] nudge 触发反思</span><br><span class="line">   <span class="built_in">Agent</span>: <span class="string">&quot;假设 1 不成立，server 端 thread_state 显示 Runnable 排队，</span></span><br><span class="line"><span class="string">          真正原因是 CPU 调度延迟&quot;</span></span><br><span class="line">   → resolve_hypothesis(outcome: <span class="string">&quot;refined&quot;</span>,</span><br><span class="line">       evidence: <span class="string">&quot;system_server CPU 调度延迟导致 Binder 线程排队&quot;</span>)</span><br><span class="line">   [AI 推理 + 知识模板中的排查路径]</span><br><span class="line"></span><br><span class="line"><span class="number">8</span>. <span class="built_in">Agent</span> 深入：execute_sql(<span class="string">&quot;查 CPU frequency + thermal zone&quot;</span>)</span><br><span class="line">   → 发现热降频，CPU 大核被限频到 <span class="number">50</span>%</span><br><span class="line">   [AI 自主选择下一步深钻方向]</span><br><span class="line"></span><br><span class="line"><span class="number">9</span>. 综合结论：RenderThread Binder 阻塞 ← system_server CPU 调度延迟 ← 热降频</span><br><span class="line">   [AI 归纳，形成 WHY 链]</span><br><span class="line"></span><br><span class="line">--- Verifier 事后检查 ---</span><br><span class="line"></span><br><span class="line"><span class="number">10</span>. Verifier 启发式检查：</span><br><span class="line">    - 文本模式匹配：结论中是否体现深钻分析（而非仅引用概览数据）</span><br><span class="line">    - 假设闭环：所有 submit_hypothesis 是否都有对应的 resolve_hypothesis</span><br><span class="line">    - 场景完整性：滑动分析是否包含帧/卡顿相关内容</span><br><span class="line">    - 因果链启发式：是否有足够的因果连接词和机制性术语</span><br><span class="line">    [确定性：启发式规则检查，非精确验证]</span><br></pre></td></tr></table></figure><p>注意 Step 4-9 全部是 AI 自主探索，但受到三个约束：</p><ul><li><strong>假设工具</strong>迫使推理外显化（Step 5, 7）</li><li><strong>知识注入</strong>提供领域排查路径（Step 4）</li><li><strong>REFLECT nudge</strong>在前几次工具返回后触发反思（Step 7）</li></ul><h3 id="如何让-AI-自主探索更可靠：四个实操建议"><a href="#如何让-AI-自主探索更可靠：四个实操建议" class="headerlink" title="如何让 AI 自主探索更可靠：四个实操建议"></a>如何让 AI 自主探索更可靠：四个实操建议</h3><p><strong>1. 给数据，不给结论</strong></p><p>Skill 应该返回结构化数据（帧耗时、线程状态分布、阻塞函数列表），而不是已经得出的结论（「RenderThread 阻塞是因为 Binder」）。让 AI 自己从数据中推理结论，比让它基于别人给的结论做进一步分析更可靠。</p><p><strong>2. 给框架，不给路径</strong></p><p>Strategy 文件应该定义「必须做什么」（Phase 1.9 必须深钻），而不是「怎么做」（先查 A 再查 B 再查 C）。Agent 在有框架约束的情况下自主选择路径，比在没有任何约束下自由探索要可靠得多，又比写死路径灵活得多。</p><p><strong>3. 给知识，不给答案</strong></p><p>知识模板应该包含「可能的原因分类和排查方法」，而不是「如果看到 X 就是 Y」。前者帮助 Agent 建立推理框架，后者把 Agent 变回 Workflow。</p><p><strong>4. 验证行为，不验证结论</strong></p><p>Verifier 应该用启发式规则检查「分析输出是否体现了关键动作」（结论中是否有深钻分析痕迹、假设是否都已 resolved、因果链是否有足够深度），而不是试图判断「结论是否正确」（这个你在离线评估中用 LLM Judge 做，不应该在运行时做）。注意这是文本模式匹配级别的启发式检查，不是精确的工具调用日志审计。</p><h3 id="一句话总结-4"><a href="#一句话总结-4" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>AI 自主探索的可靠性不是靠「写死引导」来保证的，而是靠三个机制：假设管理工具让推理外显化、按需知识注入提供领域排查框架、ReAct nudge 防止盲目工具调用。关键原则是「给数据不给结论、给框架不给路径、给知识不给答案」。</strong></p><hr><h2 id="Q7：每一轮的-Prompt-是怎么拼接的？"><a href="#Q7：每一轮的-Prompt-是怎么拼接的？" class="headerlink" title="Q7：每一轮的 Prompt 是怎么拼接的？"></a>Q7：每一轮的 Prompt 是怎么拼接的？</h2><p><strong>提问背景：</strong> 大模型 Agent 的输出质量很大程度取决于 system prompt 的设计。SmartPerfetto 在每次分析时是如何构造 prompt 的？不同场景、不同轮次之间 prompt 有什么变化？token 预算怎么控制？</p><h3 id="总体设计：四层分层拼接-缓存优化"><a href="#总体设计：四层分层拼接-缓存优化" class="headerlink" title="总体设计：四层分层拼接 + 缓存优化"></a>总体设计：四层分层拼接 + 缓存优化</h3><p>SmartPerfetto 的 system prompt 不是一个静态字符串，而是由 <code>buildSystemPrompt()</code> 函数（<code>claudeSystemPrompt.ts:260</code>）在每次 SDK 查询前<strong>动态拼接</strong>的。拼接遵循一个核心原则：</p><blockquote><p><strong>按「稳定性」排序——越不容易变化的内容越靠前，越动态的内容越靠后。</strong></p></blockquote><p>这个设计的原因是 <strong>Anthropic API 的自动缓存机制</strong>：system prompt 超过 1024 tokens 时，API 会自动对 prompt 前缀做缓存。如果把不变的部分放在最前面，多轮对话中大部分 prompt 可以命中缓存，显著降低延迟和成本：</p><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Same <span class="built_in">trace</span> + same <span class="built_in">scene</span>:     ~<span class="number">4000</span> <span class="built_in">tokens</span> cached (~<span class="number">80</span><span class="symbol">%</span> savings)</span><br><span class="line">Same <span class="built_in">trace</span> + different <span class="built_in">scene</span>: ~<span class="number">800</span> <span class="built_in">tokens</span> cached  (~<span class="number">18</span><span class="symbol">%</span> savings)</span><br><span class="line">Different <span class="built_in">trace</span>:              ~<span class="number">400</span> <span class="built_in">tokens</span> cached   (~<span class="number">8</span><span class="symbol">%</span> savings)</span><br></pre></td></tr></table></figure><h3 id="四层拼接结构"><a href="#四层拼接结构" class="headerlink" title="四层拼接结构"></a>四层拼接结构</h3><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">┌───────────────────────────────────────────────────────┐</span><br><span class="line">│  Tier <span class="number">1</span>: STATIC（进程生命周期内不变）                    │</span><br><span class="line">│  ┌─────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ prompt-role<span class="selector-class">.template</span><span class="selector-class">.md</span>        (~<span class="number">200</span> tokens)    │  │</span><br><span class="line">│  │ → 角色定义：Android 性能分析专家                    │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ prompt-output-format<span class="selector-class">.template</span><span class="selector-class">.md</span> (~<span class="number">850</span> tokens)  │  │</span><br><span class="line">│  │ → 输出格式：<span class="selector-attr">[CRITICAL]</span>/<span class="selector-attr">[HIGH]</span>/<span class="selector-attr">[MEDIUM]</span>/<span class="selector-attr">[LOW]</span>     │  │</span><br><span class="line">│  │ → 根因推理链格式、Mermaid 因果链规则               │  │</span><br><span class="line">│  │ → Slice 嵌套规则、CPU 频率估算指南                 │  │</span><br><span class="line">│  └─────────────────────────────────────────────────┘  │</span><br><span class="line">├───────────────────────────────────────────────────────┤</span><br><span class="line">│  Tier <span class="number">2</span>: PER-TRACE（同一 trace 内稳定）                 │</span><br><span class="line">│  ┌─────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 架构信息            (~<span class="number">150</span> tokens)                │  │</span><br><span class="line">│  │ → <span class="string">&quot;Flutter TextureView, 置信度 92%&quot;</span>              │  │</span><br><span class="line">│  │ → + arch-flutter<span class="selector-class">.template</span><span class="selector-class">.md</span> 架构指南            │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 焦点应用             (~<span class="number">100</span> tokens)               │  │</span><br><span class="line">│  │ → <span class="string">&quot;com.example.app (主焦点), 帧数 456&quot;</span>           │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 数据完整性           (~<span class="number">200</span> tokens, 可被丢弃)      │  │</span><br><span class="line">│  │ → 只报告缺失/不足的维度，可用维度不报告            │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ SQL 知识库参考       (~<span class="number">300</span> tokens, 可被丢弃)      │  │</span><br><span class="line">│  │ → 从 Perfetto stdlib 索引匹配到的表/视图/函数     │  │</span><br><span class="line">│  └─────────────────────────────────────────────────┘  │</span><br><span class="line">├───────────────────────────────────────────────────────┤</span><br><span class="line">│  Tier <span class="number">3</span>: PER-QUERY（场景/查询变化时变化）               │</span><br><span class="line">│  ┌─────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 方法论 + 场景策略     (~<span class="number">1200</span> tokens)             │  │</span><br><span class="line">│  │ → prompt-methodology<span class="selector-class">.template</span><span class="selector-class">.md</span>                │  │</span><br><span class="line">│  │ → &#123;&#123;sceneStrategy&#125;&#125; = scrolling<span class="selector-class">.strategy</span><span class="selector-class">.md</span>     │  │</span><br><span class="line">│  │   或 startup/anr/general 等 <span class="number">12</span> 套策略之一        │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 子代理协作指南        (~<span class="number">200</span> tokens, 可被丢弃)     │  │</span><br><span class="line">│  │ → 何时委托 vs 直接调用                           │  │</span><br><span class="line">│  │ → 滑动场景专用并行证据收集指南                     │  │</span><br><span class="line">│  └─────────────────────────────────────────────────┘  │</span><br><span class="line">├───────────────────────────────────────────────────────┤</span><br><span class="line">│  Tier <span class="number">4</span>: PER-INTERACTION（每次查询都可能变化）           │</span><br><span class="line">│  ┌─────────────────────────────────────────────────┐  │</span><br><span class="line">│  │ 用户选区上下文        (~<span class="number">300</span> tokens, 不可丢弃)     │  │</span><br><span class="line">│  │ → 时间范围选区 (selection-area<span class="selector-class">.template</span>.md)      │  │</span><br><span class="line">│  │ → 或 Slice 选区 (selection-slice<span class="selector-class">.template</span>.md)   │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 比较模式上下文        (条件注入)                   │  │</span><br><span class="line">│  │ → 双 trace 对比方法论 + 工具说明                  │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 对话上下文            (~<span class="number">500</span> tokens)              │  │</span><br><span class="line">│  │ → 分析笔记 (≤<span class="number">10</span> 条, 按优先级排序)                │  │</span><br><span class="line">│  │ → 之前的 findings (≤<span class="number">10</span> 条)                       │  │</span><br><span class="line">│  │ → 已知实体 (用于 drill-down)                     │  │</span><br><span class="line">│  │ → 对话摘要 (跨轮压缩, ≤<span class="number">2000</span> tokens)             │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ SQL 踩坑记录          (≤<span class="number">5</span> 条, 可被丢弃)          │  │</span><br><span class="line">│  │ → ERROR → BAD SQL → FIX SQL                    │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 历史分析经验          (跨会话模式记忆, 可被丢弃)   │  │</span><br><span class="line">│  │ 历史踩坑记录          (跨会话负面记忆, 可被丢弃)   │  │</span><br><span class="line">│  ├─────────────────────────────────────────────────┤  │</span><br><span class="line">│  │ 历史分析计划          (≤<span class="number">3</span> 轮, 可被丢弃)           │  │</span><br><span class="line">│  │ → 各阶段 ✓/⊘/○ 状态 + 摘要                     │  │</span><br><span class="line">│  └─────────────────────────────────────────────────┘  │</span><br><span class="line">└───────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h3 id="模板加载与变量替换"><a href="#模板加载与变量替换" class="headerlink" title="模板加载与变量替换"></a>模板加载与变量替换</h3><p>所有 prompt 内容都在 Markdown 文件中定义（详见 Q1），TypeScript 只做加载和变量替换：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// strategyLoader.ts — 模板系统</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 加载模板（DEV 模式跳过缓存，刷新浏览器即生效）</span></span><br><span class="line"><span class="title function_">loadPromptTemplate</span>(<span class="string">&#x27;prompt-methodology&#x27;</span>)  <span class="comment">// → strategies/prompt-methodology.template.md</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 加载场景策略（从 YAML frontmatter 中提取 keywords，body 作为策略内容）</span></span><br><span class="line"><span class="title function_">getStrategyContent</span>(<span class="string">&#x27;scrolling&#x27;</span>)  <span class="comment">// → strategies/scrolling.strategy.md 的 Markdown body</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 变量替换</span></span><br><span class="line"><span class="title function_">renderTemplate</span>(methodologyTemplate, &#123; sceneStrategy &#125;)</span><br><span class="line"><span class="comment">// 将 &#123;&#123;sceneStrategy&#125;&#125; 替换为 scrolling.strategy.md 的内容</span></span><br></pre></td></tr></table></figure><p><strong>模板文件清单：</strong></p><table><thead><tr><th>类别</th><th>文件</th><th>用途</th></tr></thead><tbody><tr><td><strong>静态模板</strong></td><td><code>prompt-role.template.md</code></td><td>角色定义</td></tr><tr><td></td><td><code>prompt-output-format.template.md</code></td><td>输出格式规则（91 行）</td></tr><tr><td></td><td><code>prompt-quick.template.md</code></td><td>快速模式精简 prompt</td></tr><tr><td><strong>方法论</strong></td><td><code>prompt-methodology.template.md</code></td><td>分析方法论（含 <code>&#123;&#123;sceneStrategy&#125;&#125;</code> 占位符）</td></tr><tr><td><strong>架构指南</strong></td><td><code>arch-standard.template.md</code></td><td>Standard Android 渲染指导</td></tr><tr><td></td><td><code>arch-flutter.template.md</code></td><td>Flutter 引擎指导</td></tr><tr><td></td><td><code>arch-compose.template.md</code></td><td>Jetpack Compose 指导</td></tr><tr><td></td><td><code>arch-webview.template.md</code></td><td>WebView 指导</td></tr><tr><td><strong>选区模板</strong></td><td><code>selection-area.template.md</code></td><td>时间范围选区（<code>&#123;&#123;startNs&#125;&#125;</code>, <code>&#123;&#123;endNs&#125;&#125;</code>…）</td></tr><tr><td></td><td><code>selection-slice.template.md</code></td><td>Slice 选区（<code>&#123;&#123;eventId&#125;&#125;</code>, <code>&#123;&#123;ts&#125;&#125;</code>…）</td></tr><tr><td><strong>比较模式</strong></td><td><code>comparison-methodology.template.md</code></td><td>双 trace 对比方法论</td></tr><tr><td><strong>场景策略</strong></td><td>12 个 <code>*.strategy.md</code></td><td>scrolling&#x2F;startup&#x2F;anr&#x2F;memory&#x2F;…</td></tr><tr><td><strong>知识模板</strong></td><td>8 个 <code>knowledge-*.template.md</code></td><td>按需加载的领域知识（非 prompt 注入）</td></tr><tr><td><strong>辅助模板</strong></td><td><code>prompt-complexity-classifier.template.md</code></td><td>Quick&#x2F;Full 分流判定（不注入 prompt，但决定走哪条路径）</td></tr></tbody></table><h3 id="Token-预算管理"><a href="#Token-预算管理" class="headerlink" title="Token 预算管理"></a>Token 预算管理</h3><p><strong>预算上限：</strong> 4500 tokens（<code>MAX_PROMPT_TOKENS</code>）。纠错重试时，如果检测到 SDK auto-compact（对话历史被自动压缩），会将预算降到 3000 tokens 以留出空间；否则复用原始 prompt。</p><p><strong>Token 估算方法：</strong> 混合中英文估算——中文字符按 1.5 tokens&#x2F;字，ASCII 按 0.3 tokens&#x2F;字。这是粗略近似，但用于预算管理足够准确。</p><p><strong>超预算时的渐进丢弃策略：</strong></p><p>当拼接完成后 token 数超出预算，按优先级从低到高依次丢弃整个 section：</p><figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">丢弃顺序（最先丢弃 → 最后丢弃）：</span><br><span class="line">1. Perfetto SQL 知识库参考  → Agent 可以用 lookup_sql_schema 工具替代</span><br><span class="line">2. Trace 数据完整度        → 有帮助但 Agent 运行时也能发现数据缺失</span><br><span class="line">3. 历史分析经验            → 跨会话模式记忆，非关键</span><br><span class="line">4. 历史踩坑记录            → 跨会话负面记忆，可丢弃</span><br><span class="line">5. SQL 踩坑记录            → 锦上添花</span><br><span class="line">6. 子代理协作              → 只在<span class="built_in"> sub-agent </span>启用时有用</span><br><span class="line">7. 历史分析计划            → 补充性上下文</span><br></pre></td></tr></table></figure><p><strong>永不丢弃的内容：</strong></p><ul><li>角色定义、输出格式（Tier 1 静态）</li><li>架构信息、焦点应用（Tier 2 per-trace）</li><li>方法论 + 场景策略（Tier 3 核心）</li><li>用户选区上下文（用户的显式意图）</li><li>对话上下文（之前的 findings 和分析笔记）</li></ul><h3 id="上下文构建的完整流程"><a href="#上下文构建的完整流程" class="headerlink" title="上下文构建的完整流程"></a>上下文构建的完整流程</h3><p>在 <code>ClaudeRuntime.analyze()</code> 中，prompt 拼接前需要经过二十多个准备阶段（Phase）来收集所有上下文：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Phase</span> <span class="number">0</span>:   选区上下文日志</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">0</span>.<span class="number">5</span>: 焦点应用检测（<span class="number">3</span> 种方法：battery_stats / oom_adj / frame_timeline）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">1</span>:   Skill 执行器初始化</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">2</span>:   架构检测（LRU 缓存，同 trace 只检测一次）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">2</span>.<span class="number">5</span>: 厂商检测（OEM 定制化，LRU 缓存）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">2</span>.<span class="number">8</span>: 比较模式上下文（双 trace 模式）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">2</span>.<span class="number">9</span>: 数据完整性探测（<span class="number">18</span> 个维度，~<span class="number">50</span>ms）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">3</span>:   会话上下文 + 对话历史</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">4</span>:   实体存储（drill-down 引用）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">5</span>:   场景分类（关键词匹配，&lt;<span class="number">1</span>ms）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">5</span>.<span class="number">5</span>: 跨会话模式记忆匹配</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">6</span>:   ArtifactStore + 分析笔记</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">6</span>.<span class="number">5</span>: 分析计划（当前 + 历史）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">6</span>.<span class="number">6</span>: Watchdog 反馈引用</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">6</span>.<span class="number">7</span>: 假设状态</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">6</span>.<span class="number">8</span>: 不确定性标记</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">7</span>:   SQL 错误追踪</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">8</span>:   MCP Server 创建（注入以上所有状态）</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">9</span>:   (已移除)</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">10</span>:  SQL 知识库上下文</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">11</span>:  Sub-agent 定义</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">12</span>:  SQL error-fix pairs</span><br><span class="line"><span class="attribute">Phase</span> <span class="number">13</span>:  → buildSystemPrompt(context) → 最终 prompt</span><br></pre></td></tr></table></figure><p>所有 Phase 的结果汇入 <code>ClaudeAnalysisContext</code> 对象，传给 <code>buildSystemPrompt()</code> 做最终拼接。</p><h3 id="Quick-vs-Full-双模式"><a href="#Quick-vs-Full-双模式" class="headerlink" title="Quick vs Full 双模式"></a>Quick vs Full 双模式</h3><p>不是所有查询都需要完整的 4500-token prompt。当用户问事实性问题（如「帧率是多少」）时，SmartPerfetto 使用精简的 quick prompt：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// buildQuickSystemPrompt() — ~1500 tokens</span></span><br><span class="line"><span class="comment">// 加载 prompt-quick.template.md</span></span><br><span class="line"><span class="comment">// 只注入 &#123;&#123;architectureContext&#125;&#125; 和 &#123;&#123;focusAppContext&#125;&#125;</span></span><br><span class="line"><span class="comment">// 无方法论、无场景策略、无对话上下文</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>维度</th><th>Quick Mode</th><th>Full Mode</th></tr></thead><tbody><tr><td>目标 tokens</td><td>~1500</td><td>~4500</td></tr><tr><td>场景策略</td><td>无</td><td>12 套之一</td></tr><tr><td>方法论</td><td>无</td><td>prompt-methodology.template.md</td></tr><tr><td>对话上下文</td><td>无</td><td>findings + notes + entity + summary</td></tr><tr><td>Planning Gate</td><td>无</td><td>有</td></tr><tr><td>Verifier</td><td>无</td><td>有</td></tr><tr><td>适用场景</td><td>「帧率多少」「有没有 ANR」</td><td>「分析滑动卡顿」「分析启动性能」</td></tr></tbody></table><h3 id="多轮对话中-Prompt-的变化"><a href="#多轮对话中-Prompt-的变化" class="headerlink" title="多轮对话中 Prompt 的变化"></a>多轮对话中 Prompt 的变化</h3><p>在多轮分析中（用户追问或 drill-down），prompt 的变化取决于 <strong>SDK session 是否命中 resume</strong>：</p><p><strong>第 1 轮：</strong> 无对话上下文、无历史计划、无分析笔记</p><p><strong>第 2 轮起 — SDK session resume 命中（4 小时内）：</strong></p><ul><li>SDK 内部已持有完整对话历史，<strong>不重复注入</strong> <code>previousFindings</code> 和 <code>conversationSummary</code></li><li>但仍注入：分析笔记（≤10 条）、实体上下文（drill-down 引用）、历史计划（≤3 轮）</li><li>Tier 1-3 保持不变，命中 ~80% 缓存</li></ul><p><strong>第 2 轮起 — SDK session 过期或不可用：</strong></p><ul><li>手动注入上一轮的 findings 为「之前的分析发现」（≤10 条）</li><li>手动注入对话摘要（<code>sessionContext.generatePromptContext(2000)</code>，≤2000 tokens）</li><li>分析笔记、实体上下文、历史计划同上</li></ul><p><strong>纠错重试轮：</strong> 如果检测到 SDK auto-compact（对话历史被自动压缩），token 预算从 4500 降到 3000，渐进丢弃会更激进地移除非关键 section。如果没有发生 auto-compact，复用原始 system prompt。</p><h3 id="一个具体例子：滑动分析的-Prompt-拼接"><a href="#一个具体例子：滑动分析的-Prompt-拼接" class="headerlink" title="一个具体例子：滑动分析的 Prompt 拼接"></a>一个具体例子：滑动分析的 Prompt 拼接</h3><p>用户输入 <code>&quot;分析滑动卡顿&quot;</code>，Flutter TextureView 架构，第 1 轮：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-attr">[Tier 1]</span> prompt-role<span class="selector-class">.template</span><span class="selector-class">.md</span>                    → <span class="string">&quot;你是 Android 性能分析专家...&quot;</span></span><br><span class="line"><span class="selector-attr">[Tier 1]</span> prompt-output-format<span class="selector-class">.template</span><span class="selector-class">.md</span>            → 输出格式规则</span><br><span class="line"><span class="selector-attr">[Tier 2]</span> <span class="string">&quot;架构: Flutter TextureView, 置信度 92%&quot;</span>     → + arch-flutter<span class="selector-class">.template</span><span class="selector-class">.md</span></span><br><span class="line"><span class="selector-attr">[Tier 2]</span> <span class="string">&quot;焦点应用: com.example.app (主焦点)&quot;</span>        → 帧数 + 检测方法</span><br><span class="line"><span class="selector-attr">[Tier 2]</span> <span class="string">&quot;数据完整性: gpu ❌ 疑似未采集&quot;</span>              → 只报缺失/不足，可用的不报</span><br><span class="line"><span class="selector-attr">[Tier 2]</span> SQL 知识库: android_frames, slice_self_dur   → stdlib 匹配结果</span><br><span class="line"><span class="selector-attr">[Tier 3]</span> prompt-methodology + scrolling<span class="selector-class">.strategy</span><span class="selector-class">.md</span>   → Phase <span class="number">1</span>→<span class="number">1.5</span>→<span class="number">1.9</span>→<span class="number">3</span> 完整策略</span><br><span class="line"><span class="selector-attr">[Tier 4]</span> (无选区、无对话上下文、无历史计划)</span><br><span class="line"></span><br><span class="line">预估: ~<span class="number">3200</span> tokens, 在 <span class="number">4500</span> 预算内, 无需丢弃</span><br></pre></td></tr></table></figure><p>用户追问 <code>&quot;深入分析第 3 帧&quot;</code>，第 2 轮（SDK session resume 命中）：</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[<span class="meta">Tier 1-3</span>] 与第 <span class="number">1</span> 轮相同（命中 ~<span class="number">80</span>% 缓存）</span><br><span class="line">[<span class="meta">Tier 4</span>] 对话上下文 (SDK 已持有对话历史，不重复注入 findings):</span><br><span class="line">  - 分析笔记: <span class="string">&quot;⚠️ [假设] 主要 jank 原因是 RenderThread Binder 阻塞&quot;</span></span><br><span class="line">  - 实体上下文: frame<span class="meta">#3 的 ID 和时间范围</span></span><br><span class="line">  - 历史计划: <span class="string">&quot;✓ Phase 1 概览, ✓ Phase 1.9 深钻, ○ Phase 3 结论&quot;</span></span><br><span class="line">  (previousFindings 和 conversationSummary 由 SDK session 内部管理，不在 prompt 中重复)</span><br><span class="line"></span><br><span class="line">预估: ~<span class="number">3400</span> tokens, 在预算内</span><br></pre></td></tr></table></figure><h3 id="一句话总结-5"><a href="#一句话总结-5" class="headerlink" title="一句话总结"></a>一句话总结</h3><p><strong>Prompt 按「稳定性」四层排序（Static → Per-Trace → Per-Query → Dynamic），利用 API 前缀缓存实现多轮对话 ~80% token 节省。模板系统让领域专家可以直接编辑分析策略而不碰 TypeScript。超预算时按优先级渐进丢弃，但永远保留角色定义、场景策略和用户选区——这三者决定了分析的方向和范围。</strong></p><hr><h2 id="Q8：SmartPerfetto-有哪些-Skill？"><a href="#Q8：SmartPerfetto-有哪些-Skill？" class="headerlink" title="Q8：SmartPerfetto 有哪些 Skill？"></a>Q8：SmartPerfetto 有哪些 Skill？</h2><p><strong>提问背景：</strong> SmartPerfetto 的分析能力由 YAML Skill 承载。完整的 Skill 清单可以帮助理解系统的分析覆盖范围。</p><h3 id="总览"><a href="#总览" class="headerlink" title="总览"></a>总览</h3><table><thead><tr><th>分类</th><th>数量</th><th>说明</th></tr></thead><tbody><tr><td>Atomic</td><td>87</td><td>单步检测&#x2F;统计，一条或几条 SQL 完成</td></tr><tr><td>Composite</td><td>29</td><td>组合多个 atomic skill，支持 iterator&#x2F;conditional</td></tr><tr><td>Deep</td><td>2</td><td>深度剖析（callstack、CPU profiling）</td></tr><tr><td>Pipeline</td><td>28</td><td>渲染管线检测 + 教学（24+ 种架构）</td></tr><tr><td>Module</td><td>18</td><td>模块化配置：app&#x2F;framework&#x2F;hardware&#x2F;kernel</td></tr><tr><td><strong>合计</strong></td><td><strong>164</strong></td><td></td></tr></tbody></table><hr><h3 id="Atomic-Skills（87-个）"><a href="#Atomic-Skills（87-个）" class="headerlink" title="Atomic Skills（87 个）"></a>Atomic Skills（87 个）</h3><p>单步数据提取和检测，是所有高层 Skill 的构建基础。</p><p><strong>帧渲染与掉帧：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>consumer_jank_detection</td><td>从 SF 消费端角度检测真正的掉帧（per-layer buffer 枯竭）</td></tr><tr><td>frame_blocking_calls</td><td>识别每个掉帧帧期间的阻塞调用（GC、Binder、锁、IO）</td></tr><tr><td>frame_production_gap</td><td>检测帧生产间隙：连续帧之间的 gap 超过 1.5× VSync</td></tr><tr><td>frame_pipeline_variance</td><td>检测帧时长抖动与高方差区间</td></tr><tr><td>render_pipeline_latency</td><td>分解帧渲染全链路各阶段耗时</td></tr><tr><td>render_thread_slices</td><td>分析 RenderThread 的时间片分布</td></tr><tr><td>app_frame_production</td><td>分析应用主线程的帧生产情况</td></tr><tr><td>sf_frame_consumption</td><td>分析 SurfaceFlinger 消费帧的情况</td></tr><tr><td>sf_composition_in_range</td><td>分析 SurfaceFlinger 合成延迟</td></tr><tr><td>sf_layer_count_in_range</td><td>统计时间范围内 SF 活跃图层数量</td></tr><tr><td>present_fence_timing</td><td>分析 Present Fence 时序，检测实际显示延迟</td></tr><tr><td>game_fps_analysis</td><td>针对游戏场景的帧率分析，支持固定帧率模式</td></tr></tbody></table><p><strong>VSync 与刷新率：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>vsync_period_detection</td><td>检测 VSync 周期，返回刷新率和置信度</td></tr><tr><td>vsync_config</td><td>从 trace 中解析实际的 VSync 周期和刷新率设置</td></tr><tr><td>vsync_alignment_in_range</td><td>分析帧与 VSync 信号的对齐情况</td></tr><tr><td>vsync_phase_alignment</td><td>分析输入事件与 VSync 的相位关系，定位跟手延迟瓶颈</td></tr><tr><td>vrr_detection</td><td>检测设备是否使用可变刷新率（VRR&#x2F;LTPO&#x2F;Adaptive Sync）</td></tr></tbody></table><p><strong>CPU 与调度：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>cpu_topology_detection</td><td>从 cpufreq 动态检测 CPU 大小核拓扑</td></tr><tr><td>cpu_topology_view</td><td>创建可复用 SQL VIEW <code>_cpu_topology</code></td></tr><tr><td>cpu_slice_analysis</td><td>分析 CPU 时间片分布（动态拓扑检测）</td></tr><tr><td>cpu_load_in_range</td><td>分析指定时间范围内各 CPU 核心的负载情况</td></tr><tr><td>cpu_cluster_load_in_range</td><td>计算大核簇和小核簇的整体 CPU 负载百分比</td></tr><tr><td>cpu_freq_timeline</td><td>分析各 CPU 核心的频率变化时间线</td></tr><tr><td>cpu_throttling_in_range</td><td>检测 CPU 热控限频情况</td></tr><tr><td>sched_latency_in_range</td><td>分析线程调度等待时间分布，检测 CPU 争抢</td></tr><tr><td>scheduling_analysis</td><td>分析线程调度延迟（Runnability）</td></tr><tr><td>task_migration_in_range</td><td>分析线程在大小核之间的迁移频率</td></tr><tr><td>thread_affinity_violation</td><td>检测主线程&#x2F;RenderThread 的高频迁核行为</td></tr><tr><td>thermal_predictor</td><td>基于 CPU 频率趋势预测热限频风险</td></tr><tr><td>cache_miss_impact</td><td>统计 cache-miss 计数器并评估波动</td></tr></tbody></table><p><strong>GPU：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>gpu_render_in_range</td><td>分析 GPU 渲染耗时和 Fence 等待</td></tr><tr><td>gpu_freq_in_range</td><td>分析 GPU 频率变化情况</td></tr><tr><td>gpu_metrics</td><td>分析 GPU 频率、利用率和渲染性能</td></tr><tr><td>gpu_power_state_analysis</td><td>分析 GPU 频率状态切换，识别降频压力与抖动</td></tr></tbody></table><p><strong>主线程分析：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>main_thread_states_in_range</td><td>统计区间内主线程状态、阻塞函数与占比</td></tr><tr><td>main_thread_slices_in_range</td><td>统计区间内主线程切片耗时分布</td></tr><tr><td>main_thread_sched_latency_in_range</td><td>统计主线程 Runnable 等待时间分布</td></tr><tr><td>main_thread_file_io_in_range</td><td>统计区间内主线程文件 IO 相关切片耗时</td></tr></tbody></table><p><strong>Binder IPC：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>binder_in_range</td><td>分析指定时间范围内的 Binder 事务</td></tr><tr><td>binder_blocking_in_range</td><td>分析同步 Binder 调用中对端进程的响应延迟</td></tr><tr><td>binder_root_cause</td><td>对慢 Binder 事务进行服务端&#x2F;客户端阻塞原因归因</td></tr><tr><td>binder_storm_detection</td><td>检测 Binder 事务风暴：短时间内过多 IPC 调用</td></tr></tbody></table><p><strong>锁与同步：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>lock_contention_in_range</td><td>分析指定时间范围内的锁竞争情况</td></tr><tr><td>futex_wait_distribution</td><td>统计 futex&#x2F;mutex 锁等待分布与耗时</td></tr></tbody></table><p><strong>启动专用（19 个）：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>startup_events_in_range</td><td>查询启动事件及 TTID&#x2F;TTFD 指标</td></tr><tr><td>startup_slow_reasons</td><td>启动慢原因（Google 官方分类 + 自检）v3.0</td></tr><tr><td>startup_critical_tasks</td><td>自动识别启动区间内所有活跃线程，按 CPU 时间排序</td></tr><tr><td>startup_thread_blocking_graph</td><td>利用 waker_utid 构建线程间的 block&#x2F;wakeup 关系图</td></tr><tr><td>startup_jit_analysis</td><td>分析 JIT 编译线程对启动速度的影响</td></tr><tr><td>startup_cpu_placement_timeline</td><td>按时间桶分析主线程核类型变化，检测启动初期被困小核</td></tr><tr><td>startup_freq_rampup</td><td>分析冷启动初期 CPU 频率爬升速度，检测升频延迟</td></tr><tr><td>startup_binder_pool_analysis</td><td>分析启动期间 Binder 线程池利用率和饱和度</td></tr><tr><td>startup_hot_slice_states</td><td>分析启动区间内 Top N 热点 Slice 的线程状态分布</td></tr><tr><td>startup_main_thread_states_in_range</td><td>统计启动阶段主线程 Running&#x2F;Runnable&#x2F;Blocked 占比</td></tr><tr><td>startup_main_thread_slices_in_range</td><td>统计启动阶段主线程切片热点</td></tr><tr><td>startup_binder_in_range</td><td>统计启动阶段 Binder 调用分布</td></tr><tr><td>startup_main_thread_file_io_in_range</td><td>统计启动阶段主线程文件 IO</td></tr><tr><td>startup_sched_latency_in_range</td><td>统计启动阶段主线程 Runnable 等待时延</td></tr><tr><td>startup_main_thread_sync_binder_in_range</td><td>统计启动阶段主线程同步 Binder 耗时</td></tr><tr><td>startup_main_thread_binder_blocking_in_range</td><td>分析启动阶段主线程同步 Binder 阻塞明细</td></tr><tr><td>startup_breakdown_in_range</td><td>统计启动阶段各归因原因耗时占比</td></tr><tr><td>startup_gc_in_range</td><td>统计启动阶段 GC 切片及主线程占比</td></tr><tr><td>startup_class_loading_in_range</td><td>统计启动阶段类加载切片耗时</td></tr></tbody></table><p><strong>内存与 GC：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>gc_events_in_range</td><td>查询给定进程的 GC 事件和可选时间范围</td></tr><tr><td>memory_pressure_in_range</td><td>分析指定时间范围内的内存压力指标</td></tr><tr><td>page_fault_in_range</td><td>分析 Page Fault 和内存回收对性能的影响</td></tr></tbody></table><p><strong>输入与触摸：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>input_events_in_range</td><td>提取区间内的原始输入事件，分析分发延迟</td></tr><tr><td>input_to_frame_latency</td><td>测量每个 MotionEvent 到对应帧 present 的延迟</td></tr><tr><td>touch_to_display_latency</td><td>测量从触摸到帧渲染的端到端延迟</td></tr><tr><td>scroll_response_latency</td><td>测量滚动手势从输入到首帧渲染的响应延迟</td></tr></tbody></table><p><strong>系统与设备：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>system_load_in_range</td><td>分析系统整体 CPU 利用率和进程活跃度</td></tr><tr><td>device_state_snapshot</td><td>采集 trace 期间的设备环境信息（屏幕、电量、温度等）</td></tr><tr><td>device_state_timeline</td><td>追踪设备状态随时间的变化</td></tr><tr><td>wakelock_tracking</td><td>追踪 Wake Lock 持有情况，检测电池功耗异常</td></tr></tbody></table><p><strong>其他：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>blocking_chain_analysis</td><td>分析主线程阻塞链：谁阻塞了主线程？唤醒者在做什么？</td></tr><tr><td>anr_main_thread_blocking</td><td>深度分析 ANR 中主线程阻塞原因</td></tr><tr><td>anr_context_in_range</td><td>提取第一个 ANR 事件数据用作时间窗口锚点</td></tr><tr><td>app_lifecycle_in_range</td><td>追踪 Activity&#x2F;Fragment 生命周期事件</td></tr><tr><td>compose_recomposition_hotspot</td><td>检测 Jetpack Compose 重组热点</td></tr><tr><td>webview_v8_analysis</td><td>分析 WebView V8 引擎：GC、脚本编译、执行时间</td></tr><tr><td>rendering_pipeline_detection</td><td>识别应用渲染管线类型（24 种细粒度检测）</td></tr><tr><td>pipeline_key_slices_overlay</td><td>查询管线关键 Slice 的 ts&#x2F;dur 用于时间线 overlay</td></tr></tbody></table><hr><h3 id="Composite-Skills（29-个）"><a href="#Composite-Skills（29-个）" class="headerlink" title="Composite Skills（29 个）"></a>Composite Skills（29 个）</h3><p>组合多个 atomic skill，支持 iterator（逐帧&#x2F;逐事件深钻）和 conditional（条件分支）。</p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>scrolling_analysis</td><td>滑动分析主入口：概览 → 帧列表 → 根因分类 → 逐帧诊断</td></tr><tr><td>flutter_scrolling_analysis</td><td>Flutter 特定帧分析，使用 Flutter 线程模型</td></tr><tr><td>jank_frame_detail</td><td>分析特定掉帧的详细原因：深钻 jank 原因和根因分类</td></tr><tr><td>startup_analysis</td><td>启动分析主入口：Iterator 模式、大小核分析、四象限</td></tr><tr><td>startup_detail</td><td>分析单个启动事件：主线程耗时、Binder、CPU 大小核占比</td></tr><tr><td>anr_analysis</td><td>ANR v3.0 分析：系统问题 vs 应用问题、分类处理</td></tr><tr><td>anr_detail</td><td>单个 ANR 事件详情：四象限、Binder 依赖、死锁检测</td></tr><tr><td>cpu_analysis</td><td>CPU 分析：时间分布、大小核分析、调度链路</td></tr><tr><td>gpu_analysis</td><td>GPU 分析：频率分布、内存使用、帧渲染关联</td></tr><tr><td>memory_analysis</td><td>内存分析：GC 事件、GC 与帧关联、线程状态</td></tr><tr><td>gc_analysis</td><td>GC 分析：基于 stdlib android_garbage_collection_events</td></tr><tr><td>binder_analysis</td><td>Binder 深度分析：事务基础、线程状态</td></tr><tr><td>binder_detail</td><td>单个 Binder 事务详情：CPU 大小核、四象限、阻塞原因</td></tr><tr><td>thermal_throttling</td><td>温度监控、热节流检测、CPU 频率相关性</td></tr><tr><td>lock_contention_analysis</td><td>锁竞争多维度分析：基于 android.monitor_contention</td></tr><tr><td>surfaceflinger_analysis</td><td>SF 帧合成性能：GPU&#x2F;HWC 合成比例、慢合成检测</td></tr><tr><td>click_response_analysis</td><td>点击响应分析：基于 stdlib android_input_events</td></tr><tr><td>click_response_detail</td><td>单个慢输入事件详情：延迟分解、四象限、主线程阻塞</td></tr><tr><td>scroll_session_analysis</td><td>单个完整滑动区间：Touch 阶段 vs Fling 阶段 FPS</td></tr><tr><td>navigation_analysis</td><td>Activity&#x2F;Fragment 跳转性能：生命周期、转场动画</td></tr><tr><td>lmk_analysis</td><td>LMK 分析：原因分布、时间线、频率</td></tr><tr><td>dmabuf_analysis</td><td>DMA Buffer 分析：分配、释放、泄漏检测</td></tr><tr><td>block_io_analysis</td><td>Block IO 分析：设备级统计、队列深度、长耗时 IO</td></tr><tr><td>io_pressure</td><td>IO 阻塞数据检测、IO Wait 时间、严重度评估</td></tr><tr><td>suspend_wakeup_analysis</td><td>休眠&#x2F;唤醒分析：时间分布、唤醒源排行</td></tr><tr><td>network_analysis</td><td>网络分析：流量概览、应用流量、协议分布</td></tr><tr><td>irq_analysis</td><td>硬中断和软中断的频率、耗时、嵌套情况</td></tr><tr><td>scene_reconstruction</td><td>通过用户输入和屏幕状态还原用户操作场景</td></tr><tr><td>state_timeline</td><td>四泳道连续状态时间线：设备&#x2F;用户&#x2F;应用&#x2F;系统</td></tr></tbody></table><hr><h3 id="Deep-Skills（2-个）"><a href="#Deep-Skills（2-个）" class="headerlink" title="Deep Skills（2 个）"></a>Deep Skills（2 个）</h3><p>深度剖析，通常需要更长的执行时间。</p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>cpu_profiling</td><td>CPU 性能剖析：使用热点和调度效率深度分析</td></tr><tr><td>callstack_analysis</td><td>Running 状态下的调用栈热点分析</td></tr></tbody></table><hr><h3 id="Pipeline-Skills（28-个）"><a href="#Pipeline-Skills（28-个）" class="headerlink" title="Pipeline Skills（28 个）"></a>Pipeline Skills（28 个）</h3><p>渲染管线检测 + 教学。每个 pipeline skill 对应一种渲染架构，包含管线描述、关键线程、性能指标和优化建议。</p><table><thead><tr><th>Skill ID</th><th>渲染架构</th></tr></thead><tbody><tr><td>pipeline_android_view_standard_blast</td><td>Android 12+ 标准 HWUI + BLASTBufferQueue</td></tr><tr><td>pipeline_android_view_standard_legacy</td><td>Android 12 前标准 HWUI + Legacy BufferQueue</td></tr><tr><td>pipeline_android_view_software</td><td>CPU Skia 软件渲染，无 RenderThread</td></tr><tr><td>pipeline_android_view_mixed</td><td>View + SurfaceView 混合渲染</td></tr><tr><td>pipeline_android_view_multi_window</td><td>同进程多窗口（Dialog&#x2F;PopupWindow）</td></tr><tr><td>pipeline_android_pip_freeform</td><td>画中画和自由窗口模式</td></tr><tr><td>pipeline_compose_standard</td><td>Jetpack Compose + HWUI RenderThread</td></tr><tr><td>pipeline_flutter_textureview</td><td>Flutter PlatformView 降级模式</td></tr><tr><td>pipeline_flutter_surfaceview_skia</td><td>Flutter + Skia 引擎（JIT Shader）</td></tr><tr><td>pipeline_flutter_surfaceview_impeller</td><td>Flutter + Impeller 引擎（预编译 Shader）</td></tr><tr><td>pipeline_webview_gl_functor</td><td>传统 WebView，App RenderThread 同步等待</td></tr><tr><td>pipeline_webview_surface_control</td><td>现代 WebView + Viz&#x2F;OOP-R 独立合成</td></tr><tr><td>pipeline_webview_textureview_custom</td><td>X5&#x2F;UC 等定制 WebView 内核</td></tr><tr><td>pipeline_webview_surfaceview_wrapper</td><td>WebView 全屏视频包装模式</td></tr><tr><td>pipeline_chrome_browser_viz</td><td>Chrome Viz 合成器，多进程架构</td></tr><tr><td>pipeline_opengl_es</td><td>直接 OpenGL ES &#x2F; EGL 渲染</td></tr><tr><td>pipeline_vulkan_native</td><td>原生 Vulkan 渲染</td></tr><tr><td>pipeline_angle_gles_vulkan</td><td>ANGLE: OpenGL ES → Vulkan 翻译层</td></tr><tr><td>pipeline_game_engine</td><td>Unity&#x2F;Unreal&#x2F;Godot 等游戏引擎</td></tr><tr><td>pipeline_surfaceview_blast</td><td>独立 SurfaceView + BLAST 同步</td></tr><tr><td>pipeline_textureview_standard</td><td>SurfaceTexture 纹理采样&#x2F;合成模式</td></tr><tr><td>pipeline_camera_pipeline</td><td>Camera2&#x2F;HAL3 多流相机渲染</td></tr><tr><td>pipeline_video_overlay_hwc</td><td>HWC 视频层硬件加速叠加</td></tr><tr><td>pipeline_hardware_buffer_renderer</td><td>Android 14+ HBR API 直接 Buffer 渲染</td></tr><tr><td>pipeline_surface_control_api</td><td>NDK SurfaceControl 直接事务提交</td></tr><tr><td>pipeline_variable_refresh_rate</td><td>VRR&#x2F;ARR + FrameTimeline 动态刷新率</td></tr><tr><td>pipeline_imagereader_pipeline</td><td>ImageReader API：ML 推理、录屏、自定义相机</td></tr><tr><td>pipeline_software_compositing</td><td>SF CPU 软件合成回退（GPU 不可用时）</td></tr></tbody></table><blockquote><p>注：<code>_base.skill.yaml</code> 是 Pipeline Skill 的基础模板文件，不注册为可用 Skill，不计入总数。</p></blockquote><hr><h3 id="Module-Skills（18-个）"><a href="#Module-Skills（18-个）" class="headerlink" title="Module Skills（18 个）"></a>Module Skills（18 个）</h3><p>模块化分析配置，按层级组织。Agent 通过 <code>list_skills</code> 发现并按需调用。</p><p><strong>硬件层（5 个）：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>cpu_module</td><td>CPU 频率、热节流和电源状态</td></tr><tr><td>gpu_module</td><td>GPU 渲染、频率和显存使用</td></tr><tr><td>memory_module</td><td>内存带宽、LMK、dmabuf、PSI、缺页</td></tr><tr><td>thermal_module</td><td>温度传感器、热节流检测、冷却策略</td></tr><tr><td>power_module</td><td>Wake Lock、CPU idle、电源模式、休眠&#x2F;唤醒</td></tr></tbody></table><p><strong>框架层（6 个）：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>surfaceflinger_module</td><td>帧渲染时序、卡顿原因、GPU 合成</td></tr><tr><td>choreographer_module</td><td>VSync 信号、doFrame 回调、帧生产管线</td></tr><tr><td>ams_module</td><td>应用生命周期、进程管理、启动时序</td></tr><tr><td>wms_module</td><td>窗口动画、Activity 转场、多窗口</td></tr><tr><td>art_module</td><td>GC、JIT 编译和内存分配</td></tr><tr><td>input_module</td><td>触摸延迟、输入派发和点击响应</td></tr></tbody></table><p><strong>内核层（4 个）：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>scheduler_module</td><td>线程调度延迟、CPU 利用率、大小核分配</td></tr><tr><td>binder_module</td><td>跨进程调用、阻塞事务、调用延迟</td></tr><tr><td>lock_contention_module</td><td>Mutex&#x2F;Futex、Java monitor、死锁检测</td></tr><tr><td>filesystem_module</td><td>Block IO、文件操作、数据库、SharedPreferences</td></tr></tbody></table><p><strong>应用层（3 个）：</strong></p><table><thead><tr><th>Skill ID</th><th>一句话描述</th></tr></thead><tbody><tr><td>launcher_module</td><td>主屏性能、应用启动、小部件更新</td></tr><tr><td>systemui_module</td><td>状态栏、通知栏、快速设置、导航栏</td></tr><tr><td>third_party_module</td><td>第三方应用性能、卡顿和资源使用</td></tr></tbody></table><hr><h3 id="Skill-之间的关系"><a href="#Skill-之间的关系" class="headerlink" title="Skill 之间的关系"></a>Skill 之间的关系</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Module <span class="title function_">Skills</span> <span class="params">(配置层)</span></span><br><span class="line">  └→ 定义分析范围和关注点</span><br><span class="line"></span><br><span class="line">Composite <span class="title function_">Skills</span> <span class="params">(编排层)</span></span><br><span class="line">  ├→ 引用多个 Atomic Skills</span><br><span class="line">  ├→ Iterator: 逐帧/逐事件遍历深钻</span><br><span class="line">  └→ Conditional: 按数据条件分支</span><br><span class="line"></span><br><span class="line">Atomic <span class="title function_">Skills</span> <span class="params">(执行层)</span></span><br><span class="line">  └→ 直接执行 SQL，返回 DataEnvelope</span><br><span class="line"></span><br><span class="line">Pipeline <span class="title function_">Skills</span> <span class="params">(知识层)</span></span><br><span class="line">  └→ 渲染管线教学 + 检测</span><br><span class="line"></span><br><span class="line">Deep <span class="title function_">Skills</span> <span class="params">(剖析层)</span></span><br><span class="line">  └→ Callstack / CPU profiling 深度分析</span><br></pre></td></tr></table></figure><p><strong>Agent 的典型调用路径（以滑动分析为例）：</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">invoke_skill(&quot;scrolling_analysis&quot;)          ← Composite，内部调用多个 <span class="keyword">Atomic</span></span><br><span class="line">  → consumer_jank_detection                 ← <span class="keyword">Atomic</span>，检测掉帧</span><br><span class="line">  → 逐帧 iterator → jank_frame_detail      ← Composite，每帧深钻</span><br><span class="line">    → main_thread_states_in_range           ← <span class="keyword">Atomic</span></span><br><span class="line">    → binder_blocking_in_range              ← <span class="keyword">Atomic</span></span><br><span class="line">    → frame_blocking_calls                  ← <span class="keyword">Atomic</span></span><br></pre></td></tr></table></figure><hr><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android 性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这篇文章收集了&lt;a href=&quot;/2026/04/10/SmartPerfetto-Architecture-Deep-Dive/&quot;&gt;《从 Trace 到洞察：SmartPerfetto AI Agent 的 Harness Engineering 实战》&lt;/a&gt;发布后收到的技术问题，以问答形式展开讨论。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="AI Agent" scheme="https://androidperformance.com/tags/AI-Agent/"/>
    
    <category term="SmartPerfetto" scheme="https://androidperformance.com/tags/SmartPerfetto/"/>
    
    <category term="MCP" scheme="https://androidperformance.com/tags/MCP/"/>
    
  </entry>
  
  <entry>
    <title>从 Trace 到洞察：SmartPerfetto AI Agent 的 Harness Engineering 实战</title>
    <link href="https://androidperformance.com/2026/04/10/SmartPerfetto-Architecture-Deep-Dive/"/>
    <id>https://androidperformance.com/2026/04/10/SmartPerfetto-Architecture-Deep-Dive/</id>
    <published>2026-04-10T02:00:00.000Z</published>
    <updated>2026-04-10T16:29:32.819Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章记录了 SmartPerfetto 从零到可用过程中的关键技术决策——为什么选这个方案而不是那个，哪些地方踩了坑，踩完之后怎么调整的。</p><span id="more"></span><h2 id="为什么做这个工具"><a href="#为什么做这个工具" class="headerlink" title="为什么做这个工具"></a>为什么做这个工具</h2><p>我做了多年 Android 性能优化。日常工作中大量时间花在 Perfetto trace 分析上——Perfetto 是 Google 开源的系统级 trace 工具，采集帧渲染、线程调度、CPU 频率、Binder 通信等数据，几乎是 Android 性能分析的标准工具。它的 trace_processor 引擎把 trace 加载到一个嵌入式 SQLite 数据库中，支持用 SQL 查询。</p><p>分析 trace 的过程是高度重复的：找到问题区间、查帧数据、看线程状态、追阻塞链、关联系统指标。每次做的事情类似，但每个 trace 的细节不同。这种「流程固定、细节变化」的工作特点很适合 AI Agent 来处理——把固定流程中的数据收集和初步归因自动化，人来做最后的判断和确认。</p><p>SmartPerfetto 就是这个尝试的产物。它在 Perfetto UI 上加了一个 AI 分析面板，用户用自然语言提问（如「分析滑动性能」），背后由 Claude Agent 通过 MCP（Model Context Protocol，Anthropic 提出的工具调用协议）调用 trace_processor 执行 SQL，自主完成多轮数据收集和分析。</p><p>写这篇文章的目的，是把构建过程中的工程决策和教训记录下来。从最初的「直接调 API」到现在的最多 20 个 MCP 工具（9 常驻 + 11 条件注入）+ 164 个 YAML Skill + 三层验证体系，中间的每个设计选择都有具体的反例在推动——试过不行才换的方案。这些踩坑记录对做 AI Agent 应用或者做 Android 性能工具的工程师可以直接借鉴。</p><h2 id="开篇：同一个-Trace，两条分析路径"><a href="#开篇：同一个-Trace，两条分析路径" class="headerlink" title="开篇：同一个 Trace，两条分析路径"></a>开篇：同一个 Trace，两条分析路径</h2><p>一个滑动 trace，120Hz 设备，用户反馈列表滑动偶尔卡顿。打开 Perfetto 看到惯性滑动阶段有 18 帧掉帧，其中 3 帧 Full 级（~60ms，120Hz 设备的单帧预算是 8.33ms）。</p><blockquote><p><strong>掉帧（Jank）：</strong> Perfetto 的 frame_timeline track 记录每帧渲染耗时。超过 VSync 周期（120Hz 下为 8.33ms）用户会感知卡顿。<code>jank_type</code> 字段区分掉帧类型：App 侧超时、SurfaceFlinger 合成延迟、Buffer Stuffing（BufferQueue 队列背压）等。</p></blockquote><p><strong>路径 A：手动分析</strong></p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">1.</span> 打开 Perfetto UI，拖动时间轴找到滑动区间</span><br><span class="line"><span class="bullet">2.</span> 展开 frame<span class="emphasis">_timeline track，逐帧检查哪些超过 VSync 周期</span></span><br><span class="line"><span class="emphasis">3. 18 帧掉帧——逐帧点开，展开 thread_</span>state 切片，查看主线程在做什么</span><br><span class="line"><span class="bullet">4.</span> 帧 1：Sleeping，手动查 waker<span class="emphasis">_utid → system_</span>server（Android 系统核心进程，托管 AMS/WMS 等系统服务）Binder 回来慢</span><br><span class="line">   帧 2：Running，但在 Choreographer#doFrame 里卡了 → RecyclerView onBind 过重</span><br><span class="line">   帧 3：Sleeping + Running 交替 → dequeueBuffer 等 SurfaceFlinger 合成</span><br><span class="line">   ... (还有 15 帧需要逐一检查)</span><br><span class="line"><span class="bullet">5.</span> 关联 CPU 频率 track，确认是否有热降频或 governor 升频延迟</span><br><span class="line"><span class="bullet">6.</span> 检查是否有 GC pause、Lock contention、Binder 超时</span><br><span class="line"><span class="bullet">7.</span> 汇总证据，组织结论</span><br></pre></td></tr></table></figure><blockquote><p><strong>thread_state</strong> 记录线程的调度状态（Running &#x2F; Runnable &#x2F; Sleeping &#x2F; Uninterruptible Sleep 等）。不同状态指向不同的排查方向——Runnable 通常提示 CPU 调度层面的问题，Sleeping 通常提示等待&#x2F;阻塞层面的问题。<code>waker_utid</code> 字段记录了唤醒线程的源线程 ID，可以辅助追踪跨进程的阻塞链。</p></blockquote><p>第 3-4 步是主要工作量——18 帧掉帧，每帧都需要展开 thread_state、追踪阻塞原因、关联 CPU 调度。分析过程是逐帧串行的：每帧的下钻路径可能不同（Binder? 锁? GC? IO?），全部看完再汇总。</p><p><strong>路径 B：SmartPerfetto Agent</strong></p><p>用户输入 <code>&quot;分析滑动性能&quot;</code>，以下是 Agent 实际执行的操作（来自 session log <code>session_agent-1774679540422</code>）：</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">classifyScene(<span class="string">&quot;分析滑动性能&quot;</span>) → scrolling  (&lt;<span class="number">1</span>ms, 关键词匹配)</span><br><span class="line">buildSystemPrompt() → <span class="number">4500</span> tokens (scrolling.strategy.md 注入)</span><br><span class="line"></span><br><span class="line">submit_plan(&#123;</span><br><span class="line">  phases: [<span class="string">&quot;p0: 架构+时间范围&quot;</span>, <span class="string">&quot;p1: 滑动概览+掉帧列表&quot;</span>,</span><br><span class="line">           <span class="string">&quot;p1_9: 根因深钻&quot;</span>, <span class="string">&quot;p3: 综合结论&quot;</span>],</span><br><span class="line">  successCriteria: <span class="string">&quot;WHY 链 ≥2 级，可操作优化建议&quot;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">invoke_skill(<span class="string">&quot;scrolling_analysis&quot;</span>, &#123;process_name: <span class="string">&quot;com.example...&quot;</span>&#125;)</span><br><span class="line">  → <span class="number">18</span> 帧掉帧全部提取，每帧附带:</span><br><span class="line">    - 耗时、jank_type、reason_code (App<span class="regexp">/SF/</span>GPU)</span><br><span class="line">    - 主线程状态分布 (Running<span class="regexp">/Sleeping/</span>IO)</span><br><span class="line">    - 阻塞函数 top-<span class="number">3</span></span><br><span class="line">  → ArtifactStore: art_1 (~<span class="number">440</span> tokens 引用)</span><br><span class="line"></span><br><span class="line">fetch_artifact(<span class="string">&quot;art_1&quot;</span>, <span class="string">&quot;rows&quot;</span>, <span class="number">0</span>, <span class="number">18</span>)  → 获取全部 <span class="number">18</span> 帧的结构化数据</span><br><span class="line">invoke_skill(<span class="string">&quot;frame_blocking_calls&quot;</span>, &#123;frame_ids: [<span class="number">3</span>, <span class="number">7</span>, <span class="number">12</span>]&#125;)</span><br><span class="line">  → <span class="number">3</span> 帧代表帧的阻塞链: dequeueBuffer <span class="regexp">/ Binder /</span> GC</span><br><span class="line">lookup_knowledge(<span class="string">&quot;rendering-pipeline&quot;</span>)  → 加载渲染管线参考资料</span><br><span class="line"></span><br><span class="line">输出结论:</span><br><span class="line">  [HIGH] 惯性滑动阶段 <span class="number">18</span> 帧卡顿，<span class="number">3</span> 次 Full 级 ~<span class="number">60</span>ms 掉帧</span><br><span class="line">  [MEDIUM] <span class="number">14</span> 帧 Buffer Stuffing (App 按时完成渲染，但 BufferQueue 满导致呈现延迟)</span><br><span class="line">  [MEDIUM] Vulkan Shader 首帧编译 + CPU 冷频 (<span class="number">18.66</span>ms, 超预算 <span class="number">2.2</span>×)</span><br></pre></td></tr></table></figure><p><strong>Metrics 快照</strong> (来自 <code>logs/metrics/</code>)：16 次工具调用，0 次失败，SQL 平均 652ms。</p><p>下图展示了一次完整分析的请求生命周期——从用户输入到最终结论的每一步：</p><p><img src="/images/smartperfetto-data-flow.png" alt="请求生命周期"></p><p>两条路径的分析步骤相同——查帧数据 → 定位 jank → 追踪阻塞链 → 关联系统状态 → 归纳结论。</p><p>差异在于：手动分析逐帧串行，每帧需要手动展开和追踪；Agent 通过 <code>scrolling_analysis</code> Skill 用一条 SQL 批量获取全部 18 帧的结构化数据，再选代表帧深钻阻塞链。</p><p>Agent 的分析结果同时落地到 Perfetto UI 上：</p><ul><li><strong>Auto-Pin</strong>：Agent 提到的关键帧和 Slice 自动标记在时间线上</li><li><strong>点击跳转</strong>：结论中的时间戳和帧 ID 支持点击跳转到 Perfetto 对应位置</li><li><strong>数据表格</strong>：18 帧的完整性能数据以结构化格式渲染为可排序、可筛选的表格</li></ul><p><strong>运行截图：以 SmartPerfetto 前端以 Perfetto 插件的形式存在</strong></p><p><img src="/images/smartperfetto-ui-overview.png" alt="SmartPerfetto UI 全貌"></p><p><strong>运行截图：滑动分析的时候详细分析每一个掉帧的地方， 点击最左边那个剪头可以展开</strong></p><p><img src="/images/smartperfetto-jank-detail.png" alt="滑动分析掉帧详情"></p><p><strong>运行截图：滑动分析结论</strong></p><!-- 图片暂缺: 滑动分析结论截图 --><p><strong>运行截图：滑动分析结论，代表帧分析</strong></p><p><img src="/images/smartperfetto-jank1.png" alt="代表帧分析 1"></p><p><strong>运行截图：滑动分析结论，代表帧分析</strong></p><p><img src="/images/smartperfetto-jank2.png" alt="代表帧分析 2"></p><p><strong>运行截图：每一轮分析都有单独的分析 report，内容与前端显示的一致（更详细一些）</strong></p><p><img src="/images/image-20260330001618627.png" alt="分析报告"></p><!-- 图片暂缺: 分析结论截图 --><p>分析结论、数据表格和 Perfetto 时间线在同一个界面上。Agent 完成批量数据收集和初步归因后，工程师在 Perfetto UI 上确认关键发现。</p><blockquote><p>需要说明的是，当前 Agent 在复杂边界情况下仍然需要人的判断（后文会具体讨论误诊问题）。这篇文章记录的是构建这个 Agent 背后的工程决策过程。</p></blockquote><hr><h2 id="第一部分：为什么-LLM-不能直接分析-Trace？"><a href="#第一部分：为什么-LLM-不能直接分析-Trace？" class="headerlink" title="第一部分：为什么 LLM 不能直接分析 Trace？"></a>第一部分：为什么 LLM 不能直接分析 Trace？</h2><p>在开始讨论架构之前，需要先回答一个根本问题：为什么不能直接把 trace 数据发给 LLM 让它分析？这个问题的答案决定了 SmartPerfetto 整个架构的出发点。</p><h3 id="数据规模：trace-文件装不进上下文"><a href="#数据规模：trace-文件装不进上下文" class="headerlink" title="数据规模：trace 文件装不进上下文"></a>数据规模：trace 文件装不进上下文</h3><p>一个实际的 Perfetto trace 的数据规模是这样的：</p><table><thead><tr><th>维度</th><th>典型值</th></tr></thead><tbody><tr><td>Trace 文件大小</td><td>50MB - 500MB</td></tr><tr><td>事件数量</td><td>百万 ~ 千万级</td></tr><tr><td>序列化为文本后</td><td>数 GB</td></tr><tr><td>Claude 最大 context</td><td>~200K tokens（约 150KB 文本）</td></tr></tbody></table><p>两者差了好几个数量级。即使是一个较小的 50MB trace，里面的 slice（函数调用记录）、counter（CPU 频率采样点）、thread_state（线程调度状态）等数据序列化后也远超 LLM 的上下文容量。</p><p>这就意味着 LLM 不可能直接「看到」trace 数据。它必须通过工具按需查询——先用 SQL 找到需要的数据子集（比如某个时间范围内某个线程的状态分布），拿到查询结果后再做分析。这个约束从根本上决定了 SmartPerfetto 必须是一个 <strong>工具驱动</strong> 的 Agent 架构，而不是把数据喂进 prompt 的简单方案。</p><h3 id="精确计算：LLM-不擅长处理数值"><a href="#精确计算：LLM-不擅长处理数值" class="headerlink" title="精确计算：LLM 不擅长处理数值"></a>精确计算：LLM 不擅长处理数值</h3><p>性能分析的日常工作围绕精确数值展开：帧耗时的 P50 &#x2F; P90 &#x2F; P99 分位数、VSync 周期检测（需要对 VSYNC-sf 间隔取中位数并吸附到标准刷新率）、CPU 利用率的百分比计算、各线程状态的时间占比。</p><p>LLM 处理这类数值计算时经常出错。一个实际例子：早期测试中，Claude 把 16.7ms 的帧耗时判断为「正常，未超过 VSync 周期」——它按 60Hz（16.67ms）的帧预算来算了。但这个 trace 采集自一台 120Hz 设备，单帧预算应该是 8.33ms，16.7ms 实际上超预算了一倍。这类错误看起来很小，但在性能分析中会导致完全相反的结论。</p><p>数值计算必须由工具完成——SQL 的 <code>AVG()</code>、<code>PERCENTILE()</code> 和 YAML Skill 中预定义的统计逻辑，保证每次计算结果一致且精确。</p><h3 id="领域知识：LLM-知道但不会用"><a href="#领域知识：LLM-知道但不会用" class="headerlink" title="领域知识：LLM 知道但不会用"></a>领域知识：LLM 知道但不会用</h3><p>Android 的渲染管线复杂度是很多开发者没有预期到的。最常见的三种渲染路径是：标准 HWUI 管线（HWUI 是 Android 默认的硬件加速渲染引擎，应用的 View 绘制指令在主线程生成，由 RenderThread 提交给 GPU，最终经 SurfaceFlinger 合成到屏幕）、Flutter 的双线程模型（1.ui → 1.raster，不走 RenderThread）、以及 WebView 的 Chromium 管线（CrRendererMain 线程负责渲染）。除此之外还有 Jetpack Compose、游戏引擎、相机管线等。SmartPerfetto 的架构检测系统目前识别 24+ 种渲染管线，不同管线的 jank 分析需要查看不同的线程和指标——这也是为什么架构检测是分析的第一步。</p><p>卡顿的根因可能跨线程（主线程阻塞 → 原因在 RenderThread）、跨进程（App 等待 → system_server 的 WindowManagerService 响应慢）、甚至跨硬件层（CPU 调度到小核 → 算力不足 → 帧超时）。</p><p>LLM 的训练数据中包含这些概念——它「知道」什么是 RenderThread，什么是 Binder，什么是 SurfaceFlinger。但面对一个具体的 trace，它缺乏将这些知识<strong>按场景分阶段运用</strong>的能力。比如分析滑动卡顿时，需要先检查帧级数据（哪些帧掉了、掉帧类型是什么），再针对占比最高的根因类型选择不同的深钻路径（App 侧阻塞走 blocking_chain_analysis，合成端延迟走 SurfaceFlinger 分析）。这种分步骤、有条件分支的分析流程，需要通过策略注入来引导。</p><h3 id="可靠性：错误率在实际运行中偏高"><a href="#可靠性：错误率在实际运行中偏高" class="headerlink" title="可靠性：错误率在实际运行中偏高"></a>可靠性：错误率在实际运行中偏高</h3><p>即使解决了数据访问问题，直接让 LLM 产出性能分析结论仍然面临可靠性问题。在 SmartPerfetto 的实际运行中，我观察到几类典型的输出问题：</p><ul><li><strong>幻觉</strong>：生成 trace 中不存在的数据或指标</li><li><strong>遗漏</strong>：漏掉关键检查项（比如分析启动性能时不检查 JIT 编译和类加载的影响）</li><li><strong>浅层归因</strong>：停在「主线程忙」的层面，不继续追踪是忙在 futex（锁竞争）、binder_wait（跨进程等待）还是 GC pause</li><li><strong>结论不一致</strong>：同一份 trace 分析两次，得到不同的严重等级判定</li></ul><p>后文第二部分会详细讨论这个问题——agentv3 上线 18 天后的质量审查显示，约 30% 的 Agent 结论包含不同程度的误判。</p><h3 id="SmartPerfetto-的分工设计"><a href="#SmartPerfetto-的分工设计" class="headerlink" title="SmartPerfetto 的分工设计"></a>SmartPerfetto 的分工设计</h3><p>基于这四个问题，SmartPerfetto 的架构按以下方式分工：</p><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">LLM <span class="comment">(Claude)</span> 负责:              工具系统负责:</span><br><span class="line">├─ 理解用户意图                  ├─ SQL 精确查询 <span class="comment">(trace_processor)</span></span><br><span class="line">├─ 制定分析计划                  ├─ 数值计算与统计 <span class="comment">(Skill 内置)</span></span><br><span class="line">├─ 推理因果关系                  ├─ 渲染架构检测 <span class="comment">(24+ 管线)</span></span><br><span class="line">├─ 跨领域关联分析                ├─ 分层数据提取 <span class="comment">(L1-L4)</span></span><br><span class="line">├─ 生成结构化结论                ├─ Perfetto stdlib 查询</span><br><span class="line">└─ 自然语言交互                  └─ 数据摘要与压缩 <span class="comment">(Artifact Store)</span></span><br><span class="line"></span><br><span class="line">连接层: MCP Protocol — 最多 <span class="number">20</span> 个工具 <span class="comment">(9 常驻 + 11 条件)</span></span><br><span class="line">策略层: <span class="number">12</span> 套场景策略 <span class="comment">(.strategy.md)</span></span><br><span class="line">质量层: <span class="number">3</span> 层验证 + SQL 纠错学习</span><br></pre></td></tr></table></figure><p>LLM 做推理和表达，工具做查询和计算。连接两者的是 MCP（Model Context Protocol，Anthropic 提出的工具调用协议）——Claude 通过标准 MCP 接口调用 trace_processor 执行 SQL、调用 YAML Skill 做结构化分析、查询 Perfetto stdlib 模块。分析结果通过 SSE（Server-Sent Events）实时推送到 Perfetto UI 前端。</p><p>支撑这个分工的工程基础设施包括：场景路由（根据用户问题注入不同的分析策略）、数据压缩（控制返回给 LLM 的数据量）、质量验证（拦截 LLM 的领域误判）。后面几个部分展开讨论每个部分的设计过程。</p><p>下图是完整的系统架构，展示了从用户请求到分析结论的 4 个阶段：</p><p><img src="/images/smartperfetto-architecture-overview.png" alt="系统架构总览"></p><hr><h2 id="第二部分：从-Workflow-到-Agent"><a href="#第二部分：从-Workflow-到-Agent" class="headerlink" title="第二部分：从 Workflow 到 Agent"></a>第二部分：从 Workflow 到 Agent</h2><h3 id="Workflow-和-Agent-的区别"><a href="#Workflow-和-Agent-的区别" class="headerlink" title="Workflow 和 Agent 的区别"></a>Workflow 和 Agent 的区别</h3><p>Anthropic 在 2024 年 12 月发表的<a href="https://www.anthropic.com/research/building-effective-agents">《Building Effective Agents》</a>（作者 Erik Schluntz、Barry Zhang）中，将 AI 系统分为两类：</p><ul><li><strong>Workflow（工作流）</strong>：LLM 和工具通过预定义的代码路径进行编排。每一步做什么、下一步走哪里，都由开发者事先定义好。</li><li><strong>Agent（智能体）</strong>：LLM 动态主导自身流程和工具使用，自主决定如何完成任务。</li></ul><p>这个区分的实际意义在于灵活性和可控性的权衡。Workflow 提供可预测性，适合步骤固定的任务；Agent 提供灵活性，适合需要根据中间数据调整方向的开放式问题。Andrew Ng 的描述很准确：不需要二元地判断一个系统是不是 Agent，而是把它看作不同程度的 Agent 化。SmartPerfetto 的 agentv2 和 agentv3 分别对应这个光谱的两端。</p><h3 id="为什么性能分析需要-Agent-而不是-Pipeline"><a href="#为什么性能分析需要-Agent-而不是-Pipeline" class="headerlink" title="为什么性能分析需要 Agent 而不是 Pipeline"></a>为什么性能分析需要 Agent 而不是 Pipeline</h3><p>性能分析不是一个「给输入得输出」的固定流程，它是一个探索性的推理过程。以一个实际的滑动分析为例：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">1</span>. 先看总览 → 发现 <span class="number">47</span> 帧卡顿，P90 = <span class="number">23</span>.<span class="number">5</span>ms</span><br><span class="line"><span class="attribute">2</span>. 根据总览决定方向 → <span class="number">40</span>% 卡在 APP 阶段，优先看 APP 侧</span><br><span class="line"><span class="attribute">3</span>. 选代表帧深钻 → Frame #<span class="number">234</span> 的 RenderThread 被 Binder 阻塞 <span class="number">23</span>ms</span><br><span class="line"><span class="attribute">4</span>. 形成假设 → <span class="string">&quot;可能是 system_server 的 Binder 响应慢&quot;</span></span><br><span class="line"><span class="attribute">5</span>. 验证假设 → 查 Binder 对端的 thread_state，发现 system_server CPU 调度延迟</span><br><span class="line"><span class="attribute">6</span>. 假设如果不成立 → 回退，换方向（比如改查 GPU 或 GC）</span><br><span class="line"><span class="attribute">7</span>. 综合所有发现，形成结论</span><br></pre></td></tr></table></figure><p>每一步决策都依赖前一步的结果——无法在分析开始前就确定所有步骤。Pipeline 无法处理「这个 trace 的问题可能在 GPU，也可能在 GC，需要根据中间数据动态选择下钻方向」这种需求。</p><p>SmartPerfetto 的设计是确定性和灵活性的混合：已知场景（滑动、启动、ANR 等）用 Strategy 文件约束必检项，保证不遗漏；但每个阶段内的具体查询和深钻方向由 Claude 自主决定。未匹配的场景则完全交给 Claude 自主探索。</p><h3 id="agentv2：一个典型的-Workflow"><a href="#agentv2：一个典型的-Workflow" class="headerlink" title="agentv2：一个典型的 Workflow"></a>agentv2：一个典型的 Workflow</h3><p>agentv2 使用 DeepSeek 作为后端，采用 Governance Pipeline 架构——通过 planner &#x2F; executor &#x2F; synthesizer 三阶段编排，本质上是预定义的多步骤工作流（历史 commit <code>6d80aefb</code>: “Replace the 13-step agentv2 governance pipeline with Claude-as-orchestrator”）。</p><p>这个架构在标准 Android 应用的滑动分析上工作得不错，但遇到非标准情况就出问题了。比如 Flutter 应用的 trace 里没有标准的 frame_timeline 数据，管线拿到空结果后继续执行后续步骤，最终输出基于空数据的结论。</p><h3 id="agentv3：迁移到-Agent-架构"><a href="#agentv3：迁移到-Agent-架构" class="headerlink" title="agentv3：迁移到 Agent 架构"></a>agentv3：迁移到 Agent 架构</h3><p>2026 年 3 月 2 日（commit <code>6d80aefb</code>），我切换到 Claude Agent SDK。Claude 接收工具定义和策略后，自主决定调用什么工具、按什么顺序、查什么数据。</p><p>一个 AI Agent 通常具备以下特征，agentv3 的实现对照如下：</p><table><thead><tr><th>特征</th><th>SmartPerfetto 中的实现</th><th>代码位置</th></tr></thead><tbody><tr><td>自主性</td><td>Agent 自主决定调用哪个工具、按什么顺序</td><td><code>claudeRuntime.ts</code></td></tr><tr><td>推理能力</td><td>每次工具调用后追加 REASONING_NUDGE 触发显式反思</td><td><code>claudeMcpServer.ts:84</code></td></tr><tr><td>工具使用</td><td>最多 20 个 MCP 工具调用 trace_processor</td><td>9 常驻 + 11 条件</td></tr><tr><td>规划能力</td><td>submit_plan + requirePlan() 门控</td><td>轻量模式关闭</td></tr><tr><td>反思能力</td><td>3 层 Verifier + Correction Prompt (max 2 轮)</td><td><code>claudeVerifier.ts</code></td></tr><tr><td>错误恢复</td><td>SQL 纠错学习 + 跨会话误判模式学习</td><td>跨文件</td></tr><tr><td>记忆</td><td>短期: Analysis Notes &#x2F; Artifact Store；长期: Pattern Memory &#x2F; SQL Fix Pairs</td><td>7 层记忆</td></tr></tbody></table><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">age<span class="symbol">ntv2</span> <span class="comment">(Workflow)</span>: 固定管线 → 每步预定义 → 意外数据 = 错误结论</span><br><span class="line">age<span class="symbol">ntv3</span> <span class="comment">(Agent)</span>:    动态计划 → 自主调用工具 → 意外数据 = 调整计划</span><br></pre></td></tr></table></figure><h3 id="迁移后的-9-轮审查"><a href="#迁移后的-9-轮审查" class="headerlink" title="迁移后的 9 轮审查"></a>迁移后的 9 轮审查</h3><p>从 3 月 2 日到 3 月 20 日，经历了 9 轮架构审查。其中影响最大的几轮：</p><table><thead><tr><th>轮次</th><th>日期</th><th>主要发现</th></tr></thead><tbody><tr><td>Round 1</td><td>3&#x2F;2</td><td>初始 SDK 集成后 12 个修复——SQL 知识库没接入 System Prompt，jank_frame_detail 中 CPU 核数硬编码为 4</td></tr><tr><td>Round 3</td><td>3&#x2F;12</td><td>架构接线审计——12 处「实现了但没接上」的断连，比如验证管线在 0 findings 时被跳过</td></tr><tr><td>Round 7</td><td>3&#x2F;15</td><td>Perfetto Stdlib 集成——预加载模块 4→22，Schema Index 708→761</td></tr><tr><td>Round 9</td><td>3&#x2F;20</td><td>18 天真实 trace 后的生产质量审查——3 P0 + 4 P1 + 5 P2，催生了三层验证系统</td></tr></tbody></table><h3 id="冷启动-4-层联动-Bug"><a href="#冷启动-4-层联动-Bug" class="headerlink" title="冷启动 4 层联动 Bug"></a>冷启动 4 层联动 Bug</h3><p>2026 年 3 月 19 日（commit <code>d5a1d7b3</code>），发现冷启动被错误分类为热启动。追踪后发现这是一个跨 4 层的联动问题：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Layer <span class="title function_">A</span> <span class="params">(Perfetto Stdlib)</span>: bindApplication 的 ts 比 launchingActivity 早 ~98ms → 被过滤器排除</span><br><span class="line">Layer <span class="title function_">B</span> <span class="params">(Skill 逻辑)</span>:      startup_events_in_range 的时间过滤与 Layer A 不兼容</span><br><span class="line">Layer <span class="title function_">C</span> <span class="params">(<span class="number">10</span> 个下游 Skill)</span>:  冗余的 startup_type 过滤条件 → 重分类后返回 <span class="number">0</span> 行</span><br><span class="line">Layer <span class="title function_">D</span> <span class="params">(质量门禁)</span>:         startup_analysis 的过滤规则和重分类逻辑不同步</span><br></pre></td></tr></table></figure><p>修复规模：重写 10 个下游 Skill，新增 4 个启动分析 Skill。这个问题说明在 Skill 依赖链中，上游的一个字段语义错误会逐层放大。</p><h3 id="Ghost-MCP-Query-—-异步生命周期错配"><a href="#Ghost-MCP-Query-—-异步生命周期错配" class="headerlink" title="Ghost MCP Query — 异步生命周期错配"></a>Ghost MCP Query — 异步生命周期错配</h3><p>2026 年 4 月 7 日（commit <code>a0ad63ba</code>）抓到的另一类跨层 Bug：分析超时之后，session 已经清理、SSE 流已经收尾，但 trace_processor 的 stderr 仍然在 90 秒后陆续吐出 <code>no such table: cpu_frequency_counters</code> &#x2F; <code>no such column: ts</code> 这类错误——孤儿日志，没有 owner 可以归因到任何 session，前面错误 4 的两条纠错对就是从这堆 stderr 里反查出来的。</p><p>根因在 SDK Query 的异步生命周期：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Layer <span class="title function_">A</span> <span class="params">(Claude Agent SDK)</span>:    SDK 内部的 AsyncIterator 还在生产 message</span><br><span class="line">Layer <span class="title function_">B</span> <span class="params">(claudeRuntime.ts)</span>:    timeout 触发，<span class="keyword">break</span> 出 <span class="keyword">for</span>-await 循环</span><br><span class="line">Layer <span class="title function_">C</span> <span class="params">(MCP 工具队列)</span>:         SDK 子进程没收到任何关闭信号，</span><br><span class="line">                                把队列里剩下的 invoke_skill / execute_sql 继续派发</span><br><span class="line">Layer <span class="title function_">D</span> <span class="params">(trace_processor)</span>:     执行这些<span class="string">&quot;幽灵查询&quot;</span>，错误也照常返回——</span><br><span class="line">                                但此时 session 已经清理，没人在听</span><br></pre></td></tr></table></figure><p><code>AsyncGenerator.return()</code> 和 <code>break</code> 只对消费侧生效，<strong>不会反向通知生产侧的外部资源</strong>（SDK 子进程 + MCP 工具执行队列）。修法是把 <code>sdkQueryWithRetry</code> 的返回类型从单个 AsyncIterable 改成 <code>&#123; stream, close &#125;</code> 二元组，timeout &#x2F; 异常 &#x2F; finally 三个路径都显式调一次 <code>close()</code>，让 SDK 主动 abort 子进程：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// claudeRuntime.ts</span></span><br><span class="line"><span class="keyword">const</span> &#123; stream, close &#125; = <span class="title function_">sdkQueryWithRetry</span>(&#123; ... &#125;);</span><br><span class="line"><span class="keyword">const</span> timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123; <span class="title function_">close</span>(); <span class="comment">/* abort SDK 子进程 */</span> &#125;, <span class="variable constant_">AGENT_TIMEOUT</span>);</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">for</span> <span class="keyword">await</span> (<span class="keyword">const</span> message <span class="keyword">of</span> stream) &#123; ... &#125;</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">  <span class="built_in">clearTimeout</span>(timer);</span><br><span class="line">  <span class="title function_">close</span>();   <span class="comment">// 即使正常退出也兜底关一次</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个 Bug 之所以隐蔽，是因为表面症状（孤儿 SQL 错误日志）和真实根因（异步资源生命周期错配）相隔很远——错误信息长得像「Agent 写错 SQL」，但其实是「Agent 早就停了，是 SDK 子进程没停」。在 Agent 应用中，”break 一个循环” 经常没有想象中那么干净；任何长生命周期的异步资源都需要显式的 close 通道，而不是依赖 <code>for-await</code> 的自动析构。</p><hr><h2 id="第三部分：三个关键的工程决策"><a href="#第三部分：三个关键的工程决策" class="headerlink" title="第三部分：三个关键的工程决策"></a>第三部分：三个关键的工程决策</h2><h3 id="决策-1：Scene-Classification-—-从全量注入到按需加载"><a href="#决策-1：Scene-Classification-—-从全量注入到按需加载" class="headerlink" title="决策 1：Scene Classification — 从全量注入到按需加载"></a>决策 1：Scene Classification — 从全量注入到按需加载</h3><p>一开始我把 12 个场景（scrolling &#x2F; startup &#x2F; ANR &#x2F; interaction &#x2F; pipeline &#x2F; game &#x2F; memory 等）的分析策略全部塞进 System Prompt，总计 15000+ tokens。逻辑是：Claude 应该知道所有场景的分析方法，这样不管用户问什么都能应对。</p><p>实际运行后发现 Claude 会混淆不同场景的术语——在分析滑动时引用了启动阶段的指标，把 VSync 间隔（帧间时序）和 bindApplication（进程初始化）搞混。根本原因是不同场景的术语存在大量重叠，「帧」在滑动场景里是渲染帧，在启动场景里是首帧显示，12 套策略同时出现时 LLM 无法区分上下文。</p><p>解决方式是做场景分类，每次只注入一套策略：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// sceneClassifier.ts — 12 场景, &lt;1ms 执行</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">classifyScene</span>(<span class="params">query: <span class="built_in">string</span></span>): <span class="title class_">SceneType</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> scenes = <span class="title function_">getRegisteredScenes</span>(); <span class="comment">// 从 .strategy.md frontmatter 加载</span></span><br><span class="line">  <span class="keyword">const</span> sorted = scenes</span><br><span class="line">    .<span class="title function_">filter</span>(<span class="function"><span class="params">s</span> =&gt;</span> s.<span class="property">scene</span> !== <span class="string">&#x27;general&#x27;</span>)</span><br><span class="line">    .<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">priority</span> - b.<span class="property">priority</span>); <span class="comment">// ANR(1) → startup(2) → scrolling(3)</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> scene <span class="keyword">of</span> sorted) &#123;</span><br><span class="line">    <span class="keyword">if</span> (scene.<span class="property">compound_patterns</span>.<span class="title function_">some</span>(<span class="function"><span class="params">p</span> =&gt;</span> p.<span class="title function_">test</span>(query))) <span class="keyword">return</span> scene.<span class="property">scene</span>;</span><br><span class="line">    <span class="keyword">if</span> (scene.<span class="property">keywords</span>.<span class="title function_">some</span>(<span class="function"><span class="params">k</span> =&gt;</span> lower.<span class="title function_">includes</span>(k))) <span class="keyword">return</span> scene.<span class="property">scene</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">&#x27;general&#x27;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键词和优先级声明在每个 <code>.strategy.md</code> 的 YAML frontmatter 中，不硬编码在代码里：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># scrolling.strategy.md</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">scene:</span> <span class="string">scrolling</span></span><br><span class="line"><span class="attr">priority:</span> <span class="number">3</span></span><br><span class="line"><span class="attr">keywords:</span> [<span class="string">滑动</span>, <span class="string">掉帧</span>, <span class="string">jank</span>, <span class="string">scroll</span>, <span class="string">fps</span>, <span class="string">帧率</span>, <span class="string">卡顿</span>]</span><br><span class="line"><span class="attr">compound_patterns:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;(?:分析|看看|检查).*(?:滑动|滚动|列表)&quot;</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p>添加新场景只需新建一个 <code>.strategy.md</code> 文件。DEV 模式下支持热加载，修改后刷新浏览器即可生效。</p><p>调整之后 System Prompt 从 ~15000 tokens 降到 ~4500 tokens，策略混淆的问题没有再出现。新增场景也从改代码变成了新建一个 <code>.md</code> 文件。</p><p>当多轮对话积累了较多上下文（分析笔记、历史计划、模式记忆等），System Prompt 可能重新超过 4500 token 预算。这时按优先级逐个丢弃低价值段落：SQL 知识库参考（Claude 可以用 <code>lookup_sql_schema</code> 工具按需查询）→ 历史分析经验 → 历史踩坑记录 → SQL 纠错对 → 子代理协作指引 → 历史分析计划。核心段落（角色、方法论、场景策略、输出格式）不会被丢弃。</p><h3 id="决策-2：Artifact-Store-—-控制返回给-LLM-的数据量"><a href="#决策-2：Artifact-Store-—-控制返回给-LLM-的数据量" class="headerlink" title="决策 2：Artifact Store — 控制返回给 LLM 的数据量"></a>决策 2：Artifact Store — 控制返回给 LLM 的数据量</h3><p>决策 1 解决了 System Prompt 的膨胀问题。但即使场景策略只注入了一套，Agent 在执行过程中每次调用 Skill 仍然会产生大量数据（200+ 行帧数据），这些数据全部放进上下文带来新的问题。</p><p>早期版本把 Skill 执行结果（比如 200 行帧数据、487 行阻塞分析）完整返回给 Claude。每个 Skill 结果约 3000 tokens，一次分析调用 5-8 个 Skill，仅 Skill 数据就占 15000-24000 tokens。</p><p>token 成本是一方面，更意外的发现是：数据越多，Claude 的输出质量反而越差。面对 200 行帧数据时，它倾向于逐行描述（「帧 1 耗时 12.3ms，帧 2 耗时 15.7ms…」）而不是做模式归纳。我猜测原因是上下文中充斥大量数字后，LLM 的注意力被分散了。</p><p>解决方式是把 Skill 结果存入 ArtifactStore，返回给 Claude 的只有紧凑引用（~440 tokens）——行数、列名和摘要信息。需要详情时，Claude 通过 <code>fetch_artifact</code> 按需分页获取。完整数据通过独立的 SSE（Server-Sent Events）通道发送给前端渲染，不经过 LLM。</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">invoke_skill(&quot;scrolling_analysis&quot;) 执行结果</span><span class="punctuation">:</span></span><br><span class="line">  <span class="attribute">├── 前端</span><span class="punctuation">:</span> <span class="string">全量 DataEnvelope (200 行) → SSE → UI 表格渲染</span></span><br><span class="line">  <span class="attribute">│         (DataEnvelope</span><span class="punctuation">:</span> <span class="string">自描述的数据合约，包含列名、类型、交互动作，</span></span><br><span class="line">  <span class="attribute">│          前端根据 schema 自动渲染表格/图表，不需要针对每个 Skill 写代码)</span></span><br><span class="line"><span class="attribute">  └── Claude</span><span class="punctuation">:</span> <span class="string">紧凑引用 (~440 tokens)</span></span><br><span class="line">              <span class="attribute">&quot;scrolling_analysis 完成. 概要</span><span class="punctuation">:</span> <span class="string">347 帧, jank 率 10.6%</span></span><br><span class="line">               <span class="attribute">art_1 (详情</span><span class="punctuation">:</span> <span class="string">fetch_artifact(&#x27;art_1&#x27;, &#x27;rows&#x27;, 0, 20))&quot;</span></span><br></pre></td></tr></table></figure><p><strong>fetch_artifact 的三个粒度：</strong></p><table><thead><tr><th>级别</th><th>返回内容</th><th>约 tokens</th></tr></thead><tbody><tr><td><code>summary</code></td><td>行数 + 列名 + 首行样本</td><td>~50</td></tr><tr><td><code>rows</code></td><td>分页数据 (offset&#x2F;limit)</td><td>~200-500</td></tr><tr><td><code>full</code></td><td>完整原始数据</td><td>~3000</td></tr></tbody></table><p>调整后每个 Skill 的 token 成本从 ~3000 降到 ~440，8 个 Skill 从 ~24000 降到 ~3520 tokens。Claude 的输出从逐行描述变成了模式归纳，前端仍然能拿到完整数据做表格渲染。</p><!-- TODO: 贴真实截图 --><!-- 截图 5: Artifact Store 效果对比 — 左: Claude 收到的紧凑引用文本, 右: 前端渲染的完整数据表格 --><h3 id="决策-3：三层验证-—-从真实误判中学到的"><a href="#决策-3：三层验证-—-从真实误判中学到的" class="headerlink" title="决策 3：三层验证 — 从真实误判中学到的"></a>决策 3：三层验证 — 从真实误判中学到的</h3><p>agentv3 上线 18 天后，我做了一次系统性的质量审查（2026 年 3 月 20 日，commit <code>da63eaf9</code>）。统计结果让我意外：约 30% 的 Agent 结论包含不同程度的误判。</p><p>以下是实际遇到的误判案例：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[案例 <span class="number">1</span>] <span class="symbol">Agent</span> 将 <span class="symbol">VSync</span> 对齐偏移标记为 <span class="symbol">CRITICAL</span></span><br><span class="line">实际情况: 现代高刷设备（<span class="number">90</span><span class="symbol">Hz</span>/<span class="number">120</span><span class="symbol">Hz</span>/<span class="number">144</span><span class="symbol">Hz</span>）的 <span class="symbol">VSync</span> 间隔本身就不是完全固定的，</span><br><span class="line">存在正常的微小偏移（±<span class="number">0.5</span>ms 量级）。<span class="symbol">Agent</span> 把这种正常偏移当成了异常</span><br><span class="line"></span><br><span class="line">[案例 <span class="number">2</span>] <span class="symbol">Agent</span> 将 <span class="symbol">Buffer</span> <span class="symbol">Stuffing</span> 帧计入掉帧统计</span><br><span class="line">实际情况: <span class="symbol">Buffer</span> <span class="symbol">Stuffing</span> 表示 <span class="symbol">App</span> 按时完成了渲染，但 <span class="symbol">BufferQueue</span> 队列满导致</span><br><span class="line">生产侧背压。这不是 <span class="symbol">App</span> 逻辑问题，不应直接算作 <span class="symbol">App</span> 侧掉帧。</span><br><span class="line"><span class="symbol">SmartPerfetto</span> 通过双信号检测处理：默认排除，但如果实际呈现间隔 &gt; <span class="number">1.5</span>x <span class="symbol">VSync</span></span><br><span class="line">则仍计入感知掉帧</span><br><span class="line"></span><br><span class="line">[案例 <span class="number">3</span>] <span class="symbol">Agent</span> 将单帧耗时异常标记为 <span class="symbol">CRITICAL</span></span><br><span class="line">实际情况: 孤立的单帧异常不构成模式，需要确认是否重复出现</span><br><span class="line"></span><br><span class="line">[案例 <span class="number">4</span>] <span class="symbol">Agent</span> 将主线程 <span class="symbol">Sleeping</span> 占 <span class="number">35</span><span class="comment">% (469ms) 标记为 MEDIUM</span></span><br><span class="line">实际情况: 在启动总时长中，<span class="number">469</span>ms 的主线程睡眠占比已经很高，应标记为 <span class="symbol">HIGH</span></span><br></pre></td></tr></table></figure><p>这些误判有一个共同特点：它们不是逻辑错误，而是 <strong>领域经验的缺失</strong>。高刷设备上 VSync 微小偏移是正常的、Buffer Stuffing 的延迟发生在管线队列层面而非 App 逻辑、单帧异常不构成模式——这些判断依赖对 Android 图形栈的深入理解，Claude 的训练数据对这些细节覆盖不足。</p><p>认识到这一点后，我建立了三层递进验证：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Layer <span class="number">1</span>: 启发式检查 (无 <span class="keyword">LLM </span>调用)</span><br><span class="line">  — 正则匹配已知误判模式（VSync 偏移标 CRITICAL、<span class="keyword">Buffer </span>Stuffing 算掉帧、单帧标 CRITICAL）</span><br><span class="line"></span><br><span class="line">Layer <span class="number">2</span>: Plan 遵从检查 (无 <span class="keyword">LLM </span>调用)</span><br><span class="line">  — 对照 <span class="keyword">submit_plan </span>的步骤，检查结论是否覆盖了所有计划阶段</span><br><span class="line"></span><br><span class="line">Layer <span class="number">3</span>: 独立模型审查 (使用 Haiku)</span><br><span class="line">  — 用不同模型检查每个发现是否有数据证据支持，因果链是否完整</span><br></pre></td></tr></table></figure><p>验证发现严重问题时，生成 Correction Prompt 让 Claude 修正结论（最多 2 轮）。</p><p><strong>跨会话学习：</strong> 确认的误判模式被持久化到 <code>logs/learned_misdiagnosis_patterns.json</code>，下次分析时自动注入 System Prompt。例如系统学到了：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;keywords&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;R008&quot;</span><span class="punctuation">,</span> <span class="string">&quot;TTID&quot;</span><span class="punctuation">,</span> <span class="string">&quot;超出&quot;</span><span class="punctuation">,</span> <span class="string">&quot;LOW&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;TTID 超出标记为 LOW，但 TTID(1912ms) 超出 dur_ms(1338ms) 43%，</span></span><br><span class="line"><span class="string">              应标记为 MEDIUM 或更高&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;occurrences&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>注：学习到的误判模式不会立即生效。代码中要求 <code>occurrences &gt;= 2</code> 才会进入有效模式集——首次记录只是标记，同一模式第二次出现时才会注入到后续分析的 System Prompt 中，避免孤立事件造成过度矫正。</p></blockquote><hr><h2 id="第四部分：为什么不用标准的-Skill-系统？"><a href="#第四部分：为什么不用标准的-Skill-系统？" class="headerlink" title="第四部分：为什么不用标准的 Skill 系统？"></a>第四部分：为什么不用标准的 Skill 系统？</h2><h3 id="从-SOP-到-YAML-Skill-的设计选择"><a href="#从-SOP-到-YAML-Skill-的设计选择" class="headerlink" title="从 SOP 到 YAML Skill 的设计选择"></a>从 SOP 到 YAML Skill 的设计选择</h3><p>做性能分析的团队一般都有自己的 SOP（标准操作流程）：滑动卡顿怎么查、启动慢怎么分析、ANR 怎么定位。SOP 通常是一份文档或检查清单，有经验的工程师照着做，新人跟着学。</p><p>Anthropic 的 Claude Code 有一套 Skills 系统，本质上是参数化的 Prompt 模板——注入上下文后提交给 Agent 执行。一个自然的想法是把性能分析 SOP 写成这种 Prompt 模板，让 Claude 按 SOP 执行。</p><p>我一开始也走了这条路。给 Claude 的 Prompt 是：「查询 frame_timeline 表，找出 jank 帧，分析主线程在 jank 帧期间的状态分布。」</p><p>Claude 理解意图没问题，但每次生成的 SQL 不一样。有时候 JOIN 路径写对了（<code>slice → thread_track → thread</code>），有时候直接写 <code>slice.utid</code>——这个列不存在。查出来的结果格式也不固定，有时候 3 列有时候 5 列，前端渲染没法做。</p><p>原因很简单：SOP 是给人看的，工程师看到「查 frame_timeline」知道具体该写什么 SQL。LLM 对 Perfetto 的 SQL schema 理解不完整（这些 schema 在训练数据中覆盖有限），每次从 SOP 文本到 SQL 的翻译过程都会引入方差。</p><p>SmartPerfetto 的 YAML Skill 采用了不同的思路——不是 Prompt 模板，而是声明式的 SQL 执行单元：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># YAML Skill: SQL 预定义，结果格式固定</span></span><br><span class="line"><span class="attr">steps:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">id:</span> <span class="string">thread_state_distribution</span></span><br><span class="line">    <span class="attr">type:</span> <span class="string">atomic</span></span><br><span class="line">    <span class="attr">sql:</span> <span class="string">|</span></span><br><span class="line"><span class="string">      SELECT state, SUM(dur) as total_dur,</span></span><br><span class="line"><span class="string">             ROUND(SUM(dur) * 100.0 / SUM(SUM(dur)) OVER(), 2) as pct</span></span><br><span class="line"><span class="string">      FROM thread_state ts</span></span><br><span class="line"><span class="string">      JOIN thread_track tt ON ts.track_id = tt.id</span></span><br><span class="line"><span class="string">      WHERE tt.utid = $&#123;main_thread_utid&#125;</span></span><br><span class="line"><span class="string">        AND ts.ts BETWEEN $&#123;frame_start&#125; AND $&#123;frame_end&#125;</span></span><br><span class="line"><span class="string">      GROUP BY state ORDER BY total_dur DESC</span></span><br><span class="line"><span class="string"></span>    <span class="attr">display:</span></span><br><span class="line">      <span class="attr">level:</span> <span class="string">detail</span></span><br><span class="line">      <span class="attr">columns:</span></span><br><span class="line">        <span class="bullet">-</span> &#123; <span class="attr">name:</span> <span class="string">state</span>, <span class="attr">type:</span> <span class="string">string</span> &#125;</span><br><span class="line">        <span class="bullet">-</span> &#123; <span class="attr">name:</span> <span class="string">total_dur</span>, <span class="attr">type:</span> <span class="string">duration</span> &#125;</span><br><span class="line">        <span class="bullet">-</span> &#123; <span class="attr">name:</span> <span class="string">pct</span>, <span class="attr">type:</span> <span class="string">percentage</span> &#125;</span><br></pre></td></tr></table></figure><p>两种方式的核心区别在于「谁来写 SQL」。Prompt 模板让 LLM 每次动态生成 SQL，结果格式不可预测，无法做回归测试；YAML Skill 预定义了 SQL 和输出 schema，参数替换后执行，结果格式固定，可以稳定地回归测试和前端渲染。</p><table><thead><tr><th>维度</th><th>Prompt 模板 (SOP 式)</th><th>YAML Skill (声明式执行)</th></tr></thead><tbody><tr><td>SQL 来源</td><td>LLM 每次动态生成</td><td>YAML 预定义，参数替换</td></tr><tr><td>结果格式</td><td>每次可能不同</td><td>固定的列名和类型</td></tr><tr><td>可回归测试</td><td>不支持</td><td>6 条 trace 回归测试全通过</td></tr><tr><td>前端渲染</td><td>需要解析自由文本</td><td>Schema-driven 表格&#x2F;图表</td></tr><tr><td>可组合</td><td>不支持</td><td>composite skill 调用 atomic skill</td></tr><tr><td>厂商适配</td><td>需要写不同 Prompt</td><td><code>.override.yaml</code> 覆写 SQL</td></tr></tbody></table><p>最终的分工是：Claude 负责理解意图、选择 Skill、推理归因；YAML Skill 负责精确的 SQL 查询和结构化输出。Claude 通过 <code>invoke_skill</code> 调用 Skill，Skill 返回结构化数据，Claude 基于数据做判断。</p><h3 id="为什么不把每个-Skill-暴露为独立的-MCP-Tool？"><a href="#为什么不把每个-Skill-暴露为独立的-MCP-Tool？" class="headerlink" title="为什么不把每个 Skill 暴露为独立的 MCP Tool？"></a>为什么不把每个 Skill 暴露为独立的 MCP Tool？</h3><p>一个自然的问题是：为什么不直接把 87 个 atomic 分析能力注册为 87 个 MCP Tool，让 Claude 直接调用？</p><p>实际试过会发现一个问题：MCP 的 tool list 会随着工具数量线性增长。87 个工具意味着每次 API 调用都要在请求中附带 87 个工具的描述（名称、参数 schema、使用说明），这个固定开销会占据大量 token。更重要的是，当 Claude 面对 87 个工具时，它的选择准确率会下降——工具太多，它不知道该用哪个。</p><p>SmartPerfetto 的设计是 Claude 只看到 2 个和 Skill 相关的 MCP Tool：</p><ul><li><code>invoke_skill(skillId, params)</code> — 执行指定的 Skill</li><li><code>list_skills(category?)</code> — 按场景类别查询可用的 Skill 列表</li></ul><p>通过 <code>list_skills(category=&quot;scrolling&quot;)</code> 按需发现能力，再用 <code>invoke_skill</code> 调用。<strong>2 个 MCP Tool 封装了 160+ 个分析能力，工具列表的 token 开销是固定的。</strong></p><p>另一个好处是 YAML 格式降低了贡献门槛。性能分析专家如果对某个分析场景有经验，可以直接写 YAML Skill 定义 SQL 查询和输出格式，不需要懂 TypeScript 或修改后端代码。修改后在开发模式下刷新浏览器即可生效（热加载），迭代周期在秒级。</p><h3 id="Skill-系统的结构"><a href="#Skill-系统的结构" class="headerlink" title="Skill 系统的结构"></a>Skill 系统的结构</h3><p>Skill 数量从项目初期的十几个增长到现在的 164 个，增长的驱动力不是「尽可能多」，而是分析实践中不断遇到新的场景需要覆盖——比如最初只有标准 HWUI 的帧分析，后来遇到 Flutter 应用需要专门的 Skill，再遇到厂商差异需要 override，再遇到启动分析中 JIT、class loading、Binder pool 各自需要独立的检测逻辑。</p><p>当前的 Skill 按类型分布如下：</p><table><thead><tr><th>类型</th><th>数量</th><th>位置</th><th>说明</th></tr></thead><tbody><tr><td><strong>Atomic</strong></td><td>87</td><td><code>skills/atomic/</code></td><td>单一检测能力（VSync 周期、CPU 拓扑、GPU 频率、GC 事件等）</td></tr><tr><td><strong>Composite</strong></td><td>29</td><td><code>skills/composite/</code></td><td>多步组合分析（如 scrolling_analysis 编排多个 atomic Skill）</td></tr><tr><td><strong>Pipeline</strong></td><td>28</td><td><code>skills/pipelines/</code></td><td>渲染管线检测 + 教学（24+ 种 Android 渲染架构识别）</td></tr><tr><td><strong>Module</strong></td><td>18</td><td><code>skills/modules/</code></td><td>按模块分类的分析（app &#x2F; framework &#x2F; hardware &#x2F; kernel）</td></tr><tr><td><strong>Deep</strong></td><td>2</td><td><code>skills/deep/</code></td><td>深度分析（CPU profiling、callstack 分析）</td></tr></tbody></table><p>另有 <code>skills/vendors/</code> 下 8 个厂商的 <code>.override.yaml</code>（Pixel &#x2F; Samsung &#x2F; Xiaomi &#x2F; Honor &#x2F; OPPO &#x2F; Vivo &#x2F; Qualcomm &#x2F; MTK），覆盖通用 Skill 中的厂商特定 SQL。</p><h3 id="分层结果"><a href="#分层结果" class="headerlink" title="分层结果"></a>分层结果</h3><p>早期 Skill 的输出是平铺的——一个 Skill 返回一张大表，200 行帧数据混在一起，用户打开就看到全量数据，没有层次感。实际使用中发现工程师的阅读习惯是：先看概要（掉帧率多少、P90 多少），再决定要不要展开看详情，再针对具体帧深钻。</p><p>现在 Skill 的输出按层级组织，前端渐进式渲染：</p><figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">summary</span>  — <span class="string">&quot;47 帧卡顿, P90=23.5ms, SEVERE 占 12%&quot;</span></span><br><span class="line">  │            聚合指标，快速了解全貌</span><br><span class="line">  ▼</span><br><span class="line"><span class="title">key</span>      — 关键数据（最重要的指标和发现）</span><br><span class="line">  │            高亮展示</span><br><span class="line">  ▼</span><br><span class="line"><span class="title">detail</span>   — 完整的数据列表 (frame_id, duration, jank_<span class="keyword">type</span>)</span><br><span class="line">  │            可展开的数据表格</span><br><span class="line">  ▼</span><br><span class="line"><span class="title">hidden</span>   — 辅助数据（中间计算结果，默认折叠）</span><br><span class="line">               按需展开查看</span><br></pre></td></tr></table></figure><p>Skill 的每个 step 通过 <code>display.level</code> 声明自己的展示层级（实际使用最多的是 <code>detail</code> — 240 处、<code>key</code> — 170 处、<code>summary</code> — 81 处）。前端根据 <code>DataEnvelope</code> 中的列类型（<code>timestamp</code>、<code>duration</code>、<code>percentage</code>、<code>bytes</code> 等）和交互动作（<code>navigate_timeline</code> 跳转到 trace 位置、<code>navigate_range</code> 选中时间范围、<code>copy</code> 复制数据）自动渲染表格和图表——新增一个 Skill，前端不需要写额外的代码。这是 164 个 Skill 而前端代码量仍然可控的关键。</p><h3 id="Step-类型"><a href="#Step-类型" class="headerlink" title="Step 类型"></a>Step 类型</h3><p>最初所有 Skill 都只有一种 step：执行一条 SQL。后来遇到需要组合多个 Skill 的场景（比如 scrolling_analysis 需要先查帧数据，再对每个 jank 帧做阻塞分析），以及需要遍历数据行的场景（逐帧诊断），逐步扩展了 step 类型：</p><table><thead><tr><th>Step 类型</th><th>说明</th><th>使用频次</th></tr></thead><tbody><tr><td><code>atomic</code></td><td>单条 SQL 查询，最基础的 step 类型</td><td>最常用</td></tr><tr><td><code>skill</code></td><td>引用另一个 Skill 的结果，用于组合分析中复用已有能力</td><td>56 处</td></tr><tr><td><code>iterator</code></td><td>遍历数据行，对每行执行子查询</td><td>5 个 composite Skill 中使用</td></tr><tr><td><code>diagnostic</code></td><td>诊断步骤，生成结构化的诊断结论</td><td>38 处</td></tr><tr><td><code>parallel</code></td><td>并行执行多个 step（代码已支持，尚未在 Skill 中使用）</td><td>0</td></tr><tr><td><code>conditional</code></td><td>根据条件选择分支（代码已支持，尚未在 Skill 中使用）</td><td>0</td></tr></tbody></table><p><code>iterator</code> 是逐帧分析的核心——比如对 18 个 jank 帧中最严重的 8 个，逐一执行 blocking_chain_analysis，每帧独立分析阻塞原因。<code>parallel</code> 和 <code>conditional</code> 在类型系统中已定义，目前还没有 Skill 使用——这是因为当前的分析场景用 <code>skill</code> 引用 + <code>iterator</code> 遍历已经能覆盖，后续引入更复杂的场景（如多路并行数据采集）时会用到。</p><h3 id="领域-Skill-举例"><a href="#领域-Skill-举例" class="headerlink" title="领域 Skill 举例"></a>领域 Skill 举例</h3><p>以下几个例子说明为什么需要这么多专用 Skill——每个 Skill 背后都有一个「通用方案处理不了」的具体问题。</p><h4 id="Consumer-Jank-Detection-—-框架标记-≠-用户感知"><a href="#Consumer-Jank-Detection-—-框架标记-≠-用户感知" class="headerlink" title="Consumer Jank Detection — 框架标记 ≠ 用户感知"></a>Consumer Jank Detection — 框架标记 ≠ 用户感知</h4><p>框架的 <code>jank_type</code> 标记不等于用户感知的掉帧。存在 Hidden Jank——框架标记 <code>jank_type=&#39;None&#39;</code> 但用户感知到卡。原因是框架的判定口径和用户的实际感知之间存在差异。</p><p>SmartPerfetto 用独立的 <code>consumer_jank_detection</code> Skill 做掉帧判定：通过 VSYNC-sf 间隔的中位数估算实际 VSync 周期，再用 1.5 倍 VSync 周期作为阈值，基于相邻帧的 present_ts 差值（帧实际显示到屏幕的时间戳）判断是否掉帧。不依赖框架标记。</p><h4 id="阻塞链分析-—-跨线程、跨进程的根因追踪"><a href="#阻塞链分析-—-跨线程、跨进程的根因追踪" class="headerlink" title="阻塞链分析 — 跨线程、跨进程的根因追踪"></a>阻塞链分析 — 跨线程、跨进程的根因追踪</h4><p>一帧掉帧的根因可能涉及多层因果链：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">帧 <span class="number">42</span> 耗时 <span class="number">62</span>ms (预算 <span class="number">8</span>.<span class="number">33</span>ms)</span><br><span class="line">  └→ 主线程被阻塞 <span class="number">35</span>ms</span><br><span class="line">      └→ 阻塞在 futex_wait (锁竞争)</span><br><span class="line">          └→ 锁持有者是 <span class="keyword">Binder </span>线程</span><br><span class="line">              └→ <span class="keyword">Binder </span>线程在等 system_server 响应</span><br></pre></td></tr></table></figure><p><code>blocking_chain_analysis</code> Skill 用 3 步 SQL 提供这条链的关键线索：主线程状态分布（Running &#x2F; Sleeping &#x2F; IO 各占多少）→ 唤醒者追踪（通过 waker_utid 找到是谁唤醒了主线程）→ 阻塞函数汇总（futex &#x2F; binder_wait &#x2F; io_schedule 各累计多少时间）。这种跨层分析用通用 Prompt 让 Claude 自己写 SQL 很难稳定实现。</p><h4 id="Flutter-架构分支-—-不同渲染模式需要不同分析逻辑"><a href="#Flutter-架构分支-—-不同渲染模式需要不同分析逻辑" class="headerlink" title="Flutter 架构分支 — 不同渲染模式需要不同分析逻辑"></a>Flutter 架构分支 — 不同渲染模式需要不同分析逻辑</h4><p>Flutter 的两种渲染模式涉及不同的线程，分析时需要看不同的目标：</p><table><thead><tr><th>模式</th><th>Jank 分析目标线程</th><th align="center">是否经过宿主 RenderThread</th></tr></thead><tbody><tr><td><strong>TextureView</strong> (双管线)</td><td>1.ui + 1.raster + RenderThread</td><td align="center">是</td></tr><tr><td><strong>SurfaceView</strong> (单管线)</td><td>1.ui + 1.raster</td><td align="center">否</td></tr></tbody></table><p>如果用标准 HWUI 的分析逻辑去分析 Flutter SurfaceView 应用，会把 1.raster 线程的耗时错误归因到 RenderThread。SmartPerfetto 通过架构检测（24+ 种渲染管线）自动识别 Flutter 应用并切换到专用的 <code>flutter_scrolling_analysis</code> Skill。</p><p>但「自动识别 Flutter」本身也踩了坑（commit <code>355df8ee</code>，4&#x2F;6）。早期的 pipeline 检测器是给每种架构单独打分，分数最高的胜出——结果 Flutter TextureView 的 trace 经常被误判为 STANDARD。原因是 Flutter TextureView 的宿主侧仍然走 HWUI 管线（<code>Choreographer#doFrame</code> &#x2F; <code>DrawFrame</code> &#x2F; <code>RenderThread</code>），这些信号同时被 STANDARD 和 TEXTUREVIEW 两个分类吸收。STANDARD 的信号覆盖面更广（trace 里几乎一定有 Choreographer 帧），总分常常压过专属的 TEXTUREVIEW，把 Flutter 应用误分到 STANDARD。同样的问题也出在 WeChat Skyline（被 WEBVIEW 吸收）和游戏引擎（被 STANDARD&#x2F;MIXED 吸收）上。</p><p>修法不是调权重，而是给特化 pipeline 加 <code>exclude_if</code>：TEXTUREVIEW 一旦看到 Flutter <code>1.ui</code> &#x2F; <code>1.raster</code> 信号就直接屏蔽 STANDARD 分类；STANDARD_LEGACY&#x2F;MIXED&#x2F;SURFACEVIEW_BLAST 看到 Game Engine 信号就互斥；OPENGL_ES 看到 WebView&#x2F;Game 信号就互斥。<strong>24+ 种 pipeline 不能各打各的分，需要一个「特化 → 通用」的优先级链。</strong> 这是「pipeline 多了之后必须做相互排斥」的典型例子——也是为什么 Skill 数量增长到 160+ 之后，光「正确路由到哪个 Skill」本身就成了独立的工程问题。</p><h4 id="厂商覆写-—-同一指标在不同平台上的字段名不同"><a href="#厂商覆写-—-同一指标在不同平台上的字段名不同" class="headerlink" title="厂商覆写 — 同一指标在不同平台上的字段名不同"></a>厂商覆写 — 同一指标在不同平台上的字段名不同</h4><p>高通、联发科、Google Tensor 的 trace 中，相同指标的字段名不同（比如 GPU 频率在高通叫 <code>gpufreq</code>，联发科可能叫 <code>gpu_freq_khz</code>）。<code>.override.yaml</code> 让同一个 Skill 在不同平台上自动适配 SQL，不需要为每个厂商写独立 Skill。</p><hr><h2 id="第五部分：SQL-工程"><a href="#第五部分：SQL-工程" class="headerlink" title="第五部分：SQL 工程"></a>第五部分：SQL 工程</h2><p>前面讨论的 Skill 系统最终都落到 SQL 查询上——每个 Skill 的 step 执行的是预定义的 SQL。SQL 是 SmartPerfetto 的核心——所有性能数据的获取最终都通过 SQL 查询 trace_processor 完成。这部分展开讨论 SQL 层面的几个工程问题：查询模式设计、官方 stdlib 复用、Schema 索引、结果压缩和纠错学习。</p><h3 id="SQL-查询模式：时间区间-JOIN-和递归分桶"><a href="#SQL-查询模式：时间区间-JOIN-和递归分桶" class="headerlink" title="SQL 查询模式：时间区间 JOIN 和递归分桶"></a>SQL 查询模式：时间区间 JOIN 和递归分桶</h3><p>Perfetto trace 的数据本质上是带时间戳和持续时长的事件流。性能分析中最常见的操作是<strong>判断两个事件在时间上是否重叠</strong>——比如某帧渲染期间，主线程有没有被 Binder 调用阻塞。</p><p>YAML Skill 中大量使用的核心 SQL 模式是<strong>时间区间 JOIN</strong>——判断两个事件是否在时间上重叠。下面这条 SQL 的业务含义是：对于每个掉帧，找出在这帧渲染期间同时发生的阻塞调用（如 GC、Binder、锁），并计算它们重叠了多少毫秒：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 业务含义：掉帧帧和阻塞调用的时间重叠分析</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  jf.frame_id,</span><br><span class="line">  b.name <span class="keyword">as</span> blocking_call,</span><br><span class="line">  <span class="comment">-- 计算精确的重叠时长（纳秒级）</span></span><br><span class="line">  ROUND((<span class="built_in">MIN</span>(b.ts <span class="operator">+</span> b.dur, jf.ts <span class="operator">+</span> jf.dur) <span class="operator">-</span> <span class="built_in">MAX</span>(b.ts, jf.ts)) <span class="operator">/</span> <span class="number">1e6</span>, <span class="number">2</span>) <span class="keyword">as</span> overlap_ms</span><br><span class="line"><span class="keyword">FROM</span> jank_frames jf</span><br><span class="line"><span class="keyword">JOIN</span> blocking_calls b</span><br><span class="line">  <span class="keyword">ON</span> b.ts <span class="operator">&lt;</span> jf.ts <span class="operator">+</span> jf.dur       <span class="comment">-- 阻塞调用的开始 &lt; 帧的结束</span></span><br><span class="line">  <span class="keyword">AND</span> b.ts <span class="operator">+</span> b.dur <span class="operator">&gt;</span> jf.ts       <span class="comment">-- 阻塞调用的结束 &gt; 帧的开始</span></span><br><span class="line"><span class="keyword">HAVING</span> overlap_ms <span class="operator">&gt;</span> <span class="number">0.5</span>           <span class="comment">-- 过滤掉不足 0.5ms 的微小重叠</span></span><br></pre></td></tr></table></figure><blockquote><p>这里的 <code>MIN(end1, end2) - MAX(start1, start2)</code> 是计算两个区间重叠长度的标准公式。在 Perfetto trace 中，时间戳精度到纳秒，这种区间 JOIN 能精确到 0.001ms 的粒度。</p></blockquote><p>另一个常用模式是<strong>递归 CTE 做时间分桶</strong>。比如分析启动过程中 CPU 大核&#x2F;小核的使用分布变化：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 递归生成时间桶（最多 30 个，防止递归失控）</span></span><br><span class="line"><span class="keyword">WITH</span> <span class="keyword">RECURSIVE</span> buckets <span class="keyword">AS</span> (</span><br><span class="line">  <span class="keyword">SELECT</span> <span class="number">0</span> <span class="keyword">as</span> idx, $&#123;start_ts&#125; <span class="keyword">as</span> bucket_start,</span><br><span class="line">         <span class="built_in">MIN</span>($&#123;start_ts&#125; <span class="operator">+</span> bucket_ns, $&#123;end_ts&#125;) <span class="keyword">as</span> bucket_end</span><br><span class="line">  <span class="keyword">UNION</span> <span class="keyword">ALL</span></span><br><span class="line">  <span class="keyword">SELECT</span> idx <span class="operator">+</span> <span class="number">1</span>, bucket_end, <span class="built_in">MIN</span>(bucket_end <span class="operator">+</span> bucket_ns, $&#123;end_ts&#125;)</span><br><span class="line">  <span class="keyword">FROM</span> buckets <span class="keyword">WHERE</span> bucket_end <span class="operator">&lt;</span> $&#123;end_ts&#125; <span class="keyword">AND</span> idx <span class="operator">&lt;</span> <span class="number">29</span></span><br><span class="line">)</span><br><span class="line"><span class="comment">-- 每个时间桶内，统计大核 vs 小核的调度时间</span></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  ROUND(<span class="built_in">SUM</span>(<span class="keyword">CASE</span> <span class="keyword">WHEN</span> core_type <span class="keyword">IN</span> (<span class="string">&#x27;prime&#x27;</span>,<span class="string">&#x27;big&#x27;</span>,<span class="string">&#x27;medium&#x27;</span>)</span><br><span class="line">    <span class="keyword">THEN</span> overlap_dur <span class="keyword">ELSE</span> <span class="number">0</span> <span class="keyword">END</span>) <span class="operator">/</span> <span class="number">1e6</span>, <span class="number">2</span>) <span class="keyword">as</span> big_core_ms,</span><br><span class="line">  ROUND(<span class="built_in">SUM</span>(<span class="keyword">CASE</span> <span class="keyword">WHEN</span> core_type <span class="operator">=</span> <span class="string">&#x27;little&#x27;</span></span><br><span class="line">    <span class="keyword">THEN</span> overlap_dur <span class="keyword">ELSE</span> <span class="number">0</span> <span class="keyword">END</span>) <span class="operator">/</span> <span class="number">1e6</span>, <span class="number">2</span>) <span class="keyword">as</span> little_core_ms</span><br><span class="line"><span class="keyword">FROM</span> buckets b</span><br><span class="line"><span class="keyword">LEFT</span> <span class="keyword">JOIN</span> main_thread_sched ms <span class="keyword">ON</span> ms.ts <span class="operator">&lt;</span> b.bucket_end <span class="keyword">AND</span> ms.ts <span class="operator">+</span> ms.dur <span class="operator">&gt;</span> b.bucket_start</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> b.idx</span><br></pre></td></tr></table></figure><blockquote><p><code>_cpu_topology</code> 是 Perfetto stdlib 提供的视图，把 CPU 核心分类为 prime &#x2F; big &#x2F; medium &#x2F; little。递归 CTE 限制最多 30 个桶，防止在极长 trace 上递归失控。</p></blockquote><p>这些 SQL 模式被封装在 YAML Skill 中，通过 <code>$&#123;param|default&#125;</code> 语法接受参数。Claude 不需要自己写这些复杂的时间区间 JOIN——它调用 <code>invoke_skill</code> 传入时间范围和进程名，Skill 负责执行预定义的 SQL 并返回结构化结果。</p><h3 id="Perfetto-Stdlib-复用"><a href="#Perfetto-Stdlib-复用" class="headerlink" title="Perfetto Stdlib 复用"></a>Perfetto Stdlib 复用</h3><p>Perfetto 官方维护了一套 SQL 标准库（stdlib），提供了大量预定义的视图和函数。比如 <code>android_frames</code> 视图封装了帧渲染数据的多表关联逻辑，<code>_android_critical_blocking_calls</code> 内部表汇总了关键阻塞调用。直接使用这些官方抽象，比手写 SQL 从底层表关联要稳定得多。</p><p>SmartPerfetto 对 stdlib 的集成经历了几轮迭代——其中一次回退还把「优化的方向」整个翻了过来：</p><ul><li><p><strong>初始阶段：</strong> 只预加载了 4 个 stdlib 模块（android.frames.timeline、android.binder、android.startup.startups、android.input），大部分 Skill 的 SQL 直接查底层表。优点是启动快，缺点是 Skill 里到处自己手写多表 JOIN</p></li><li><p><strong>Round 7 (3&#x2F;15)：</strong> 把预加载集扩展到 22 个模块，包括 <code>linux.cpu.utilization</code>、<code>android.garbage_collection</code>、<code>android.oom_adjuster</code>、<code>slices.with_context</code>，覆盖 CPU&#x2F;GC&#x2F;OOM&#x2F;slice 等常用维度。当时的逻辑是：trace 加载时一次性把所有常用 stdlib 模块批量 <code>INCLUDE</code>，后续 Skill 查询零开销</p></li><li><p><strong>回退到 lazy 加载 (4&#x2F;1, commit <code>0afeb60f</code>)：</strong> 22 模块的 eager preload 在生产中翻车了——200MB+ 的大 trace 上，启动时并发 <code>INCLUDE</code> 22 个模块会同时占用 trace_processor_shell 的 RPC 连接，触发 <code>socket hang up</code>。<strong>根因是 trace_processor_shell 是单线程的 SQLite 引擎，最不擅长并发 INCLUDE 这种「批量 schema mutation」负载。</strong> 最终的修法是把 eager preload 收回，只保留 3 个 Tier-0 模块，且改成首次 query 时 lazy + 串行加载 + 最多 3 次重试：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// workingTraceProcessor.ts</span></span><br><span class="line"><span class="comment">// Tier 0: absolute minimum stdlib modules needed for any analysis to start.</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CRITICAL_STDLIB_MODULES</span> = [</span><br><span class="line">  <span class="string">&#x27;android.frames.timeline&#x27;</span>,    <span class="comment">// 19 个 skill 引用 — frame/jank 基础</span></span><br><span class="line">  <span class="string">&#x27;android.startup.startups&#x27;</span>,   <span class="comment">// 16 个 skill 引用 — startup 基础</span></span><br><span class="line">  <span class="string">&#x27;android.binder&#x27;</span>,             <span class="comment">// 22 个 skill 引用 — IPC/blocking 基础</span></span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>这 3 个是按「skill 引用次数」筛出来的最高频依赖。其余 stdlib 模块改由 Skill YAML 的 <code>prerequisites</code> 段或 SQL 里显式的 <code>INCLUDE PERFETTO MODULE</code> 在第一次用到时按需声明</p></li><li><p><strong>按需发现：</strong> <code>perfettoStdlibScanner.ts</code> 扫描 Perfetto 源码目录自动发现所有可用模块，通过 <code>list_stdlib_modules</code> MCP 工具让 Claude 按需 <code>INCLUDE</code> 非预加载的模块</p></li></ul><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// perfettoStdlibScanner.ts — 扫描 perfetto/src/trace_processor/perfetto_sql/stdlib/</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">scanDirectory</span>(<span class="params">dir: <span class="built_in">string</span>, prefix: <span class="built_in">string</span></span>): <span class="built_in">string</span>[] &#123;</span><br><span class="line">  <span class="comment">// 递归扫描 .sql 文件，转换为模块路径格式 (如 &quot;android.frames&quot;)</span></span><br><span class="line">  <span class="comment">// 排除 prelude 目录（这些是自动加载的，不需要手动 INCLUDE）</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这次回退的教训和文章前面的「数据越多 Claude 输出反而越差」是同一类的——<strong>「在系统启动时把所有可能用到的资源都准备好」是直觉上最优、实际上最差的策略</strong>。无论是给 LLM 的上下文还是给 trace_processor 的 stdlib，先 lazy + 按需加载，等真正出现性能瓶颈再考虑预热，几乎总是更稳的选择。</p><p>另一个独立的教训是：使用 stdlib 的 <code>android_garbage_collection_events</code> 视图比自己 JOIN <code>slice</code> + <code>thread</code> + <code>process</code> 表查 GC 事件要稳定得多——因为 GC 事件的 slice name 在不同 Android 版本中有变化（<code>concurrent mark sweep</code> vs <code>young concurrent copying</code> vs <code>HeapTaskDaemon</code>），stdlib 已经处理了这些兼容性问题。但 stdlib 视图自己也有坑（列名前缀、模块未自动加载），后面 SQL 纠错那一节会展开讲。</p><h3 id="SQL-Schema-Index：让-Claude-知道有什么表可以查"><a href="#SQL-Schema-Index：让-Claude-知道有什么表可以查" class="headerlink" title="SQL Schema Index：让 Claude 知道有什么表可以查"></a>SQL Schema Index：让 Claude 知道有什么表可以查</h3><p>Perfetto trace_processor 包含数百个表和视图，加上 stdlib 的模块，Claude 不可能全部记住。<code>lookup_sql_schema</code> MCP 工具提供了一个搜索接口，让 Claude 按关键词查找相关的表、视图和函数定义。</p><p>底层是一个从 Perfetto 源码自动生成的索引文件（<code>perfettoSqlIndex.light.json</code>），包含 <strong>761 个模板</strong>，每个模板记录了名称、类别、类型（table&#x2F;view&#x2F;function）、列定义和参数。</p><p>查询时使用分词匹配 + 评分排序：</p><ul><li>名称&#x2F;类别&#x2F;描述中包含完整搜索词 → 高分</li><li>多词查询按 token 分别匹配 → 匹配 ≥50% 的 token 才算相关</li><li>表名的下划线分段支持前缀匹配（”frame_time” 匹配 “frame_timeline_slice”）</li><li>返回 top 30 结果</li></ul><p>配合 <code>sqlKnowledgeBase.ts</code> 的意图映射，还支持双语查询：用户输入「卡顿」会映射到 <code>[&#39;jank&#39;, &#39;frame&#39;, &#39;dropped&#39;]</code> 等搜索词，输入「启动」会映射到 <code>[&#39;android_startups&#39;, &#39;launch&#39;, &#39;time_to_display&#39;]</code>。多个意图同时命中时，分数叠加——比如查询「启动帧卡顿」同时触发 startup 和 jank 两个意图，匹配到两者交集的模板分数最高。</p><h3 id="SQL-结果压缩"><a href="#SQL-结果压缩" class="headerlink" title="SQL 结果压缩"></a>SQL 结果压缩</h3><p>当 Claude 使用 <code>execute_sql</code> 直接查询时，可以传入 <code>summary=true</code> 参数触发结果压缩。压缩逻辑在 <code>sqlSummarizer.ts</code> 中实现：</p><p><strong>数值列：</strong> 计算 min、max、avg 和分位数（P50 &#x2F; P90 &#x2F; P95 &#x2F; P99），让 Claude 了解数据分布，不需要看原始行。</p><p><strong>字符串列：</strong> 统计 top 5 值及其出现次数，了解数据的类别分布。</p><p><strong>样本行选择：</strong> 从完整结果中选 10 行有代表性的样本。选择策略是：如果数据中有 <code>dur</code>、<code>latency</code>、<code>jank</code>、<code>count</code> 等和性能相关的列，按该列降序排列取 top 10（最严重的数据通常最有分析价值）；如果没有明确的性能指标列，等间距采样。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 200 行原始结果 (~3000 tokens) 压缩为:</span></span><br><span class="line">&#123;</span><br><span class="line">  &quot;totalRows&quot;: <span class="number">200</span>,</span><br><span class="line">  &quot;columnStats&quot;: [</span><br><span class="line">    &#123; &quot;column&quot;: &quot;dur_ms&quot;, &quot;type&quot;: &quot;numeric&quot;,</span><br><span class="line">      &quot;min&quot;: <span class="number">2.1</span>, &quot;max&quot;: <span class="number">67.3</span>, &quot;avg&quot;: <span class="number">12.8</span>,</span><br><span class="line">      &quot;p50&quot;: <span class="number">9.2</span>, &quot;p90&quot;: <span class="number">23.5</span>, &quot;p95&quot;: <span class="number">35.1</span>, &quot;p99&quot;: <span class="number">62.0</span> &#125;,</span><br><span class="line">    &#123; &quot;column&quot;: &quot;jank_type&quot;, &quot;type&quot;: &quot;string&quot;,</span><br><span class="line">      &quot;topValues&quot;: [</span><br><span class="line">        &#123; &quot;value&quot;: &quot;App Deadline Missed&quot;, &quot;count&quot;: <span class="number">87</span> &#125;,</span><br><span class="line">        &#123; &quot;value&quot;: &quot;Buffer Stuffing&quot;, &quot;count&quot;: <span class="number">45</span> &#125;,</span><br><span class="line">        &#123; &quot;value&quot;: &quot;None&quot;, &quot;count&quot;: <span class="number">68</span> &#125;</span><br><span class="line">      ] &#125;</span><br><span class="line">  ],</span><br><span class="line">  &quot;sampleRows&quot;: [ <span class="comment">/* 10 行最严重的帧数据 */</span> ]</span><br><span class="line">&#125;</span><br><span class="line"><span class="operator">/</span><span class="operator">/</span> <span class="operator">~</span><span class="number">500</span> tokens，压缩率 <span class="operator">~</span><span class="number">85</span><span class="operator">%</span></span><br></pre></td></tr></table></figure><p>这和前面提到的 Artifact Store 配合使用——Artifact Store 压缩的是 Skill 结果（invoke_skill 返回的数据），SQL Summarizer 压缩的是 Claude 直接执行 SQL 时的结果。两层压缩覆盖了 Agent 获取数据的两条路径。</p><h3 id="SQL-纠错学习"><a href="#SQL-纠错学习" class="headerlink" title="SQL 纠错学习"></a>SQL 纠错学习</h3><p>Claude 对 Perfetto 的 SQL schema 不完全熟悉，会写出有错误的查询。以下是实际记录的典型错误（来自 <code>logs/sql_learning/error_fix_pairs.json</code>）：</p><p><strong>错误 1：JOIN 了不存在的列</strong></p><p>Perfetto 的 <code>slice</code> 表没有直接的 <code>utid</code> 列。要关联 slice 和 thread，需要经过 <code>thread_track</code> 中间表：<code>slice.track_id → thread_track.id → thread_track.utid → thread.utid</code>。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 错误: no such column: s.utid</span></span><br><span class="line"><span class="keyword">SELECT</span> s.ts, s.name <span class="keyword">FROM</span> slice s</span><br><span class="line"><span class="keyword">JOIN</span> thread t <span class="keyword">ON</span> s.utid <span class="operator">=</span> t.utid</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修正: 通过 thread_track 中间表</span></span><br><span class="line"><span class="keyword">SELECT</span> s.ts, s.name <span class="keyword">FROM</span> slice s</span><br><span class="line"><span class="keyword">JOIN</span> thread_track tt <span class="keyword">ON</span> s.track_id <span class="operator">=</span> tt.id</span><br><span class="line"><span class="keyword">JOIN</span> thread t <span class="keyword">ON</span> tt.utid <span class="operator">=</span> t.utid</span><br></pre></td></tr></table></figure><p><strong>错误 2：列名歧义</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 错误: ambiguous column name: name (slice 和 process 都有 name 列)</span></span><br><span class="line"><span class="keyword">SELECT</span> name, ts <span class="keyword">FROM</span> slice s <span class="keyword">JOIN</span> process p <span class="keyword">ON</span> ...</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修正: 加表名前缀</span></span><br><span class="line"><span class="keyword">SELECT</span> s.name, s.ts <span class="keyword">FROM</span> slice s <span class="keyword">JOIN</span> process p <span class="keyword">ON</span> ...</span><br></pre></td></tr></table></figure><p><strong>错误 3：对 counter 表的数据模型理解有误</strong></p><p>Perfetto 的 <code>counter</code> 表存储的是采样点（时间戳 + 值），不是区间数据，没有 <code>dur</code> 列。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 错误: no such column: c.dur</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">SUM</span>(c.value <span class="operator">*</span> c.dur) <span class="keyword">FROM</span> counter c</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修正: 使用简单平均值或 LEAD 窗口函数</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">AVG</span>(c.value) <span class="keyword">FROM</span> counter c <span class="keyword">WHERE</span> ...</span><br></pre></td></tr></table></figure><p><strong>错误 4：stdlib 表名 &#x2F; 列名陷阱</strong></p><p>这两条都是 commit <code>05922e67</code> 加进去的——发现源是「无主孤儿 stderr」：分析早就结束了，trace_processor 还在喷错误日志，反查回去才定位到 Agent 在 dynamic SQL 里反复踩同样的坑。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 错误 4a: no such table: cpu_frequency_counters</span></span><br><span class="line"><span class="comment">-- 这是 stdlib 表，不在 lazy 预加载的 3 个 Tier-0 模块里</span></span><br><span class="line"><span class="keyword">SELECT</span> ts, cpu, freq <span class="keyword">FROM</span> cpu_frequency_counters <span class="keyword">WHERE</span> ...</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修正: 先 INCLUDE 模块再查</span></span><br><span class="line">INCLUDE PERFETTO <span class="keyword">MODULE</span> linux.cpu.frequency;</span><br><span class="line"><span class="keyword">SELECT</span> ts, cpu, freq <span class="keyword">FROM</span> cpu_frequency_counters <span class="keyword">WHERE</span> ...</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 错误 4b: no such column: ts</span></span><br><span class="line"><span class="comment">-- android_garbage_collection_events 用 gc_ 前缀的列名，不是常规的 ts/dur</span></span><br><span class="line"><span class="keyword">SELECT</span> ts<span class="operator">/</span><span class="number">1e6</span>, dur<span class="operator">/</span><span class="number">1e6</span> <span class="keyword">FROM</span> android_garbage_collection_events</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 修正: 用 gc_ts / gc_dur / gc_running_dur / gc_runnable_dur</span></span><br><span class="line"><span class="keyword">SELECT</span> gc_ts<span class="operator">/</span><span class="number">1e6</span>, gc_dur<span class="operator">/</span><span class="number">1e6</span>, gc_running_dur<span class="operator">/</span><span class="number">1e6</span></span><br><span class="line"><span class="keyword">FROM</span> android_garbage_collection_events</span><br></pre></td></tr></table></figure><p>stdlib 视图作者经常会用领域前缀的列名来避开多表 JOIN 时的 <code>ambiguous column</code> 问题，但 Claude 默认假设的是 <code>ts/dur</code> 这种通用约定。这种「stdlib 自己的命名习惯 vs 通用 SQL 习惯」的冲突没法靠 schema introspection 完全自动解决——<code>lookup_sql_schema</code> 工具能告诉 Claude 表存在和有哪些列，但不能预测「这次 Claude 一定会想当然地写 <code>ts</code>」。只能靠纠错对累积来兜底。</p><p>这些错误的检测和学习机制是这样的：当 SQL 执行失败时，错误信息和 SQL 被暂存；当后续有 SQL 执行成功时，系统通过 Jaccard 相似度匹配（排除 SQL 结构关键词如 SELECT&#x2F;FROM&#x2F;WHERE，以及 Perfetto 通用 token 如 utid&#x2F;dur&#x2F;slice）判断是否是同一查询的修正版本。匹配阈值 &gt;30%，时间窗口 60 秒。匹配成功则生成 error→fix 对并持久化到磁盘。</p><p>新分析开始时，最近 10 条纠错对加载到 System Prompt，Claude 在写 SQL 之前就能看到这些已知的坑。纠错对设置 30 天 TTL，过期自动清理——Perfetto 的 SQL schema 会随版本更新变化。</p><hr><h2 id="第六部分：开发过程本身的-Harness-演进"><a href="#第六部分：开发过程本身的-Harness-演进" class="headerlink" title="第六部分：开发过程本身的 Harness 演进"></a>第六部分：开发过程本身的 Harness 演进</h2><p>最后一个部分稍微跳出产品本身，聊一下开发过程。SmartPerfetto 是用 AI 辅助开发的——从第一行代码到现在，Claude Code 是主要的编程工具。回顾这三个月，我使用 AI 辅助开发的方式本身也经历了几次迭代，和 SmartPerfetto 从 agentv2 到 agentv3 的演进有相似的逻辑。</p><h3 id="AI-辅助开发的几个阶段"><a href="#AI-辅助开发的几个阶段" class="headerlink" title="AI 辅助开发的几个阶段"></a>AI 辅助开发的几个阶段</h3><p>先简要说明涉及的工具和概念：</p><ul><li><strong>Claude Code</strong>：Anthropic 的 CLI 工具，可以在终端中与 Claude 对话，Claude 能直接读写文件、执行命令。我在开发中一直开启 <code>--dangerously-skip-permissions</code>（危险模式）和 bypass permissions，让 Claude 无需逐次确认即可自主执行文件编辑、命令运行、Git 操作等。这大幅提升了迭代速度——Claude 可以连续执行「改代码 → 跑测试 → 看结果 → 修复 → 再跑」的完整循环而不被权限弹窗打断，代价是需要开发者对 Claude 的操作有足够信任和事后审查</li><li><strong>Claude Agent SDK</strong>：Anthropic 提供的 Agent 开发框架，SmartPerfetto 的 agentv3 后端基于它构建。SDK 封装了多轮对话管理、MCP 工具调用循环、上下文自动压缩（auto-compact）等能力，开发者定义工具集和 System Prompt，SDK 驱动 Claude 自主完成多轮分析</li><li><strong>Plan Mode</strong>：Claude Code 的规划模式，AI 先输出结构化实施方案（要改哪些文件、改什么、依赖关系），人审查确认后再执行代码修改</li><li><strong>SuperPower</strong>：Claude Code 的第三方插件生态，通过 MCP Server 为 Claude Code 注入额外能力。SmartPerfetto 开发中使用了 Chrome DevTools Protocol 插件（直接操控浏览器截图、调试前端）、Playwright 插件（自动化 UI 测试和截图）等。这些插件让 Claude Code 的能力从代码编辑扩展到了浏览器交互和可视化验证</li><li><strong>Codex + Codex MCP</strong>：Codex 是 OpenAI 的代码推理模型。通过 Codex MCP Server 集成到 Claude Code 中后，Claude 可以在对话过程中直接调用 Codex 做独立审查——把实施方案发给 Codex，Codex 以只读方式访问代码库，从架构合理性、边界情况、遗漏风险三个角度给出反馈，整个过程不需要离开 Claude Code 的工作流</li><li><strong>Agent Team</strong>：Claude Code 支持启动多个子 Agent 并行工作，每个 Agent 可以有独立的工具集和角色定义</li><li><strong>Skills &#x2F; Hooks</strong>：Claude Code 的扩展机制，Skills 是可复用的任务模板（如 <code>/commit</code>、<code>/simplify</code>），Hooks 是在特定事件（如工具调用前后）自动执行的脚本</li></ul><h3 id="我的实际演进过程"><a href="#我的实际演进过程" class="headerlink" title="我的实际演进过程"></a>我的实际演进过程</h3><p><strong>阶段 1：直接对话</strong></p><p>最早期的开发方式是在 Claude Code 中直接描述需求，让 AI 修改代码。类似于结对编程中一个人说、一个人写。这个阶段人需要逐行审查每次修改，因为 AI 对项目上下文的理解有限，经常做出不符合整体架构的局部修改。</p><p><strong>阶段 2：Plan Mode（SuperPower）</strong></p><p>开始使用 Plan Mode 后，工作流变成：我描述需求 → AI 输出结构化的实施方案（要改哪些文件、每个文件改什么、改动顺序和依赖关系）→ 我审查方案 → 确认后 AI 执行。这把 review 的重心从「逐行看代码」转移到了「审查架构方案」，效率明显提升。</p><p><strong>阶段 3：Plan Mode（SuperPower） + 引入同行 Review（Codex）</strong></p><p>单靠一个 AI 生成方案，容易出现盲区。我开始在 Plan Mode 的方案确定后，把方案发给 Codex 做独立审查。Codex 以只读方式访问代码库，从架构合理性、边界情况、遗漏风险三个角度给反馈。这相当于在 AI 开发流程中引入了 code review 环节。</p><p>文章前面提到的 9 轮架构审查，大部分都经过了这个流程。以 Perfetto Stdlib 集成为例（Round 7，3 月 15 日），Codex 审查了 3 轮，累计提出 36 条反馈，其中涉及 stdlib 模块预加载策略、Schema Index 的缓存失效机制等我在方案中遗漏的问题。</p><p><strong>阶段 4：Harness 化的工程流水线</strong></p><p>到后期，开发流程变成了：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">1. 我确定需求和架构方向</span><br><span class="line">2. Claude Code 在 Plan Mode 下输出实施方案</span><br><span class="line">3. Codex 以只读模式审查方案，提出反馈</span><br><span class="line">4. 我评估 Codex 的反馈（不盲从，约 20% 的建议会被驳回并说明理由）</span><br><span class="line">5. Claude Code 按修正后的方案执行代码修改</span><br><span class="line">6. 自动运行完整测试：</span><br><span class="line">   - npx tsc --noEmit (类型检查)</span><br><span class="line">   - npm run test:scene-trace-regression (6 条 trace 回归，验证 Skill 数据产出)</span><br><span class="line">   - npm run validate:skills + validate:strategies (Skill/策略合约校验)</span><br><span class="line">   - 对于启动/滑动/Flutter 相关的改动，还会跑真实 Trace 的 E2E Agent 分析：</span><br><span class="line">     用 verifyAgentSseScrolling.ts 脚本加载真实 trace 文件，</span><br><span class="line">     发起完整的 Agent 分析会话，检查 SSE 事件流、工具调用序列、</span><br><span class="line">     最终结论是否覆盖策略中定义的必检项。</span><br><span class="line">     比如滑动场景会检查 Agent 是否执行了 Phase 1.9 根因深钻，</span><br><span class="line">     Flutter 场景会检查 Agent 是否正确识别了 TextureView/SurfaceView 架构</span><br><span class="line">     并调用了 flutter_scrolling_analysis 而不是标准的 scrolling_analysis。</span><br><span class="line">     这一步验证的不是 Skill 能不能跑通，而是 Agent 在面对真实数据时</span><br><span class="line">     的推理路径和结论质量是否符合预期。</span><br><span class="line">     <span class="comment">&lt;!-- <span class="doctag">TODO:</span> 贴真实截图 --&gt;</span></span><br><span class="line">     <span class="comment">&lt;!-- 截图 6: E2E 测试输出 — verifyAgentSseScrolling.ts 的终端输出，展示 SSE 事件统计和通过/失败状态 --&gt;</span></span><br><span class="line">7. 测试不通过 → 分析失败原因（读 session log + metrics）→ 修复 → 重新测试</span><br><span class="line">8. 测试通过 → /simplify (代码整理)</span><br><span class="line">9. 最终 Codex review 确认</span><br></pre></td></tr></table></figure><p>这个流程中，人的介入集中在第 1 步（需求和架构决策）和第 4 步（评估 review 反馈）。代码细节、测试执行、格式整理由工程流水线完成。</p><h3 id="和-SmartPerfetto-架构的对应关系"><a href="#和-SmartPerfetto-架构的对应关系" class="headerlink" title="和 SmartPerfetto 架构的对应关系"></a>和 SmartPerfetto 架构的对应关系</h3><p>回过头看，我的 AI 辅助开发流程和 SmartPerfetto 的 Agent 分析流程在结构上是相似的：</p><table><thead><tr><th>维度</th><th>SmartPerfetto Agent 分析</th><th>我的 AI 辅助开发</th></tr></thead><tbody><tr><td>意图理解</td><td>Scene Classifier 识别场景</td><td>我确定需求方向</td></tr><tr><td>策略注入</td><td>.strategy.md 注入分析方法论</td><td>Plan Mode 输出实施方案</td></tr><tr><td>执行</td><td>MCP 工具调用 SQL&#x2F;Skill</td><td>Claude Code 执行代码修改</td></tr><tr><td>质量验证</td><td>三层 Verifier (启发式+Plan+Haiku)</td><td>回归测试 + Codex review</td></tr><tr><td>纠正循环</td><td>Correction Prompt 让 Claude 修正</td><td>测试失败 → 分析 → 修复 → 重跑</td></tr><tr><td>跨会话学习</td><td>Pattern Memory + SQL 纠错</td><td>CLAUDE.md 规则积累 + memory 系统</td></tr></tbody></table><p>两个系统的演进方向也一致：<strong>人的介入从执行层逐步上移到决策层。</strong> SmartPerfetto 从固定管线（人定义每一步）到自主推理（人定义目标和约束）；我的开发方式从逐行 review 到审查架构方案。</p><p>这不是偶然——Harness Engineering 的核心就是构建足够的工程基础设施（测试、验证、review），使得人可以信任 AI 的执行结果，把注意力放在更高层的决策上。</p><p>下图汇总了 SmartPerfetto 的 Harness Engineering 全景——从输入路由到跨会话学习：</p><p><img src="/images/smartperfetto-eleven-layers.png" alt="Harness Engineering 全景"></p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>回顾这三个月的迭代，从 agentv2 的 13 步固定管线到 agentv3 的自主推理，从约 30% 误判率到三层验证，从 15000 tokens 的 System Prompt 到 4500 tokens 的按需加载——每一步变化都有具体的失败经历在推动。</p><p>做完这个项目之后，我对 AI Agent 应用开发有两个体会。</p><p>第一个是：主要工作量不在 LLM API 调用本身，而在围绕 LLM 的工程基础设施：</p><ul><li>System Prompt 怎么组织，才能让 LLM 不混淆上下文？→ 场景分类 + 按需加载 + Token 预算</li><li>怎么控制 LLM 的执行顺序，让它先想再做？→ Planning Gate + Hypothesis 提交</li><li>返回多少数据给 LLM 合适？→ Artifact Store，给摘要而不是全量</li><li>怎么发现和拦截 LLM 的领域误判？→ 三层验证 + Correction 循环</li><li>怎么保证数据查询的精度？→ YAML Skill (声明式 SQL) + SQL 纠错学习</li><li>怎么适配不同渲染架构和芯片平台？→ 架构检测 + 厂商覆写</li></ul><p>第二个体会是：<strong>Agent 的「环境」比 prompt 的措辞重要得多。</strong> agentv3 初期我花了不少时间调整 System Prompt 的用词和格式，后来发现真正影响 Agent 输出质量的不是 prompt 怎么写，而是给它什么工具、返回什么数据、施加什么约束。三个具体的例子：</p><ul><li>加了 <code>submit_plan</code> 门控后，Claude 不再没有方向地查 SQL（之前会出现连续 <code>SELECT * FROM slice</code> → <code>SELECT * FROM thread</code> 的无目的查询），分析路径变得有组织</li><li>加了 ArtifactStore 后，Claude 接收到的数据从 200 行降到摘要引用，推理的聚焦度明显提升</li><li>加了 <code>lookup_knowledge</code> 工具后，根因分析的深度从「主线程阻塞」推进到「Binder 对端 system_server 因 CPU 被调度到小核导致响应延迟」</li></ul><p>这些改进都不是通过调整 prompt 文字实现的，而是通过改变 Agent 的工具集和数据环境实现的。如果我要给做 AI Agent 应用的工程师一个建议，就是把精力放在工具设计和数据控制上，而不是 prompt engineering 上。</p><h3 id="后续方向"><a href="#后续方向" class="headerlink" title="后续方向"></a>后续方向</h3><p>当前的 SmartPerfetto 是一个交互式分析工具，还远远没有达到可以发布的程度，所以目前还是<strong>闭源</strong>的，由我个人在负责开发。后续的工程方向包括：</p><ul><li><strong>厂商深度接入</strong> — 当前 8 个厂商的 <code>.override.yaml</code> 只覆盖了核心 Skill。更多厂商专属指标（高通 Snapdragon Profiler 数据、联发科 MAGT 信号、三星 GameOptimizing 服务）需要逐一对接</li><li><strong>CI 集成 + 批处理</strong> — 从交互式分析到 CI Pipeline 中自动分析每次构建的性能回归。包括无人值守模式、结果对比基线、自动标记 regression</li><li><strong>E2E 验证框架</strong> — 当前的 6 条 trace 回归测试验证 Skill 产出数据的正确性，但不验证 Agent 的结论质量。需要建立 E2E 验证：给定 trace + 已知根因 → 检查 Agent 是否正确定位</li><li><strong>代码库接入</strong> — 将 trace 中的 slice&#x2F;function 映射回源码位置，结合 git blame 定位变更引入点</li></ul><p><strong>在合适的时候本工具会开源处理（因为各个大厂内部都在做了，所以开源出来大家集思广益，共同开发），对进度感兴的同学可以加我微信进群聊或者私聊。</strong></p><hr><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android 性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这篇文章记录了 SmartPerfetto 从零到可用过程中的关键技术决策——为什么选这个方案而不是那个，哪些地方踩了坑，踩完之后怎么调整的。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="AI Agent" scheme="https://androidperformance.com/tags/AI-Agent/"/>
    
    <category term="SmartPerfetto" scheme="https://androidperformance.com/tags/SmartPerfetto/"/>
    
    <category term="MCP" scheme="https://androidperformance.com/tags/MCP/"/>
    
  </entry>
  
  <entry>
    <title>OpenClaw 常见问题解答：Token 消耗、能干什么、本地模型、隐私安全、使用体验</title>
    <link href="https://androidperformance.com/2026/03/10/OpenClaw-FAQ/"/>
    <id>https://androidperformance.com/2026/03/10/OpenClaw-FAQ/</id>
    <published>2026-03-10T00:00:00.000Z</published>
    <updated>2026-04-10T16:37:52.271Z</updated>
    
    <content type="html"><![CDATA[<p>昨天那篇 OpenClaw 实战复盘发出去之后，后台留言和群里讨论最多的就是这几个方向的问题：Token 消耗、能干什么、本地模型、隐私安全、使用体验。今天挑出被问频率最高的五个，一个一个说清楚。</p><p><img src="/../images/OpenClaw-FAQ/cover.png" alt="OpenClaw 常见问题解答"></p><span id="more"></span><h2 id="1-Token-消耗到底多少？贵不贵？"><a href="#1-Token-消耗到底多少？贵不贵？" class="headerlink" title="1. Token 消耗到底多少？贵不贵？"></a>1. Token 消耗到底多少？贵不贵？</h2><p>先说结论：没你想的那么贵，但也不是零成本。我前后用了两套方案，一套云端大厂套餐，一套国产性价比路线，感受差异还是挺明显的。</p><p><img src="/../images/OpenClaw-FAQ/q1-token-cost.png" alt="Token 消耗与成本"></p><h3 id="前期：OpenAI-Pro-套餐（200-刀-x2F-月）"><a href="#前期：OpenAI-Pro-套餐（200-刀-x2F-月）" class="headerlink" title="前期：OpenAI Pro 套餐（200 刀&#x2F;月）"></a>前期：OpenAI Pro 套餐（200 刀&#x2F;月）</h3><p>一开始接的是 OpenAI Pro，量大管饱，用着确实省心。不过这个套餐不是只给 OpenClaw 用的——我日常写代码也走这个额度，实际消耗大概一半一半。跑了段时间下来，5 小时速率上限从来没碰过，Weekly 上限也没撞到，说明我这种”重度使用但不是 7×24 不停歇”的用法，200 刀的额度是够用的。</p><h3 id="后期：智谱-GLM5-Coding-Plan（年费-¥1608）"><a href="#后期：智谱-GLM5-Coding-Plan（年费-¥1608）" class="headerlink" title="后期：智谱 GLM5 Coding Plan（年费 ¥1608）"></a>后期：智谱 GLM5 Coding Plan（年费 ¥1608）</h3><p>Pro 到期后，我切到了智谱 GLM5 Coding Plan，走的是国产模型路线。我买的时候赶上了首购优惠价 ¥980&#x2F;年，但 **2026 年 2 月智谱调整了套餐价格，取消了首购优惠，整体涨幅约 30%**。</p><h4 id="2026-年-3-月最新套餐价格"><a href="#2026-年-3-月最新套餐价格" class="headerlink" title="2026 年 3 月最新套餐价格"></a>2026 年 3 月最新套餐价格</h4><table><thead><tr><th>套餐</th><th>季费</th><th>年费（季费×4）</th><th>月均</th><th>适用场景</th></tr></thead><tbody><tr><td>GLM Coding Lite</td><td>¥132</td><td>¥528</td><td>¥44</td><td>轻度用户</td></tr><tr><td>GLM Coding Pro</td><td>¥402</td><td>¥1608</td><td>¥134</td><td>重度用户（推荐）</td></tr><tr><td>GLM Coding Max</td><td>¥1266</td><td>¥5064</td><td>¥422</td><td>团队&#x2F;企业</td></tr></tbody></table><p>我用的相当于 Pro 档位，涨价前 ¥980&#x2F;年，现在 ¥1608&#x2F;年。实际跑下来体验还行，定时任务、巡检、日报周报、知识库整理这些高频场景都能稳稳撑住，没出现过限额不够用的情况。</p><h4 id="GLM-5-API-按量计费（如果不用套餐）"><a href="#GLM-5-API-按量计费（如果不用套餐）" class="headerlink" title="GLM-5 API 按量计费（如果不用套餐）"></a>GLM-5 API 按量计费（如果不用套餐）</h4><p>如果你不想买套餐，也可以按量付费。GLM-5 的 API 价格：</p><table><thead><tr><th>模型</th><th>输入价格</th><th>输出价格</th></tr></thead><tbody><tr><td>GLM-5（0-32K）</td><td>¥4 &#x2F; 百万 Tokens</td><td>¥18 &#x2F; 百万 Tokens</td></tr><tr><td>GLM-5（32K+）</td><td>¥6 &#x2F; 百万 Tokens</td><td>¥22 &#x2F; 百万 Tokens</td></tr><tr><td>GLM-5-Code（0-32K）</td><td>¥6 &#x2F; 百万 Tokens</td><td>¥28 &#x2F; 百万 Tokens</td></tr></tbody></table><p>算笔账：我 7 天烧了 2.4 亿 Token，如果按量付费光这 7 天就要花大约 ¥2400，而套餐年费才 ¥1608——也就是说<strong>一整年的套餐价还不到 7 天按量费用的零头</strong>。所以我的建议很直接：<strong>重度用户直接上套餐，轻度用户可以先按量试试水深</strong>。</p><h4 id="国内-Coding-Plan-对比（2026-年-3-月）"><a href="#国内-Coding-Plan-对比（2026-年-3-月）" class="headerlink" title="国内 Coding Plan 对比（2026 年 3 月）"></a>国内 Coding Plan 对比（2026 年 3 月）</h4><p>这三家是对 OpenClaw 支持最好的国内模型：</p><table><thead><tr><th>套餐</th><th>智谱 GLM</th><th>MiniMax</th><th>Kimi</th></tr></thead><tbody><tr><td><strong>入门档</strong></td><td>Lite ¥44&#x2F;月（¥528&#x2F;年）</td><td>Starter ¥70&#x2F;月（¥700&#x2F;年）</td><td>Moderato ¥133&#x2F;月</td></tr><tr><td><strong>进阶档</strong></td><td>Pro ¥134&#x2F;月（¥1608&#x2F;年）</td><td>Plus ¥140&#x2F;月（¥1400&#x2F;年）</td><td>Allegretto ¥99&#x2F;月</td></tr><tr><td><strong>高级档</strong></td><td>Max ¥422&#x2F;月（¥5064&#x2F;年）</td><td>Max ¥350&#x2F;月（¥3500&#x2F;年）</td><td>Vivace ¥199&#x2F;月</td></tr><tr><td><strong>主力模型</strong></td><td>GLM-5</td><td>MiniMax M2.5</td><td>Kimi K2.5</td></tr><tr><td><strong>特点</strong></td><td>国产最强，OpenClaw 默认支持</td><td>性价比高，API 稳定</td><td>长上下文强，2026 年转 token 计费</td></tr><tr><td><strong>OpenClaw 支持</strong></td><td>⭐⭐⭐ 原生支持</td><td>⭐⭐⭐ 原生支持</td><td>⭐⭐ 需配置</td></tr></tbody></table><p>我的选择是 GLM Coding Pro（¥1608&#x2F;年），原因是 OpenClaw 对智谱的支持最成熟，API 稳定，中文能力强。性价比之选是 MiniMax Plus（¥1400&#x2F;年），比 GLM Pro 便宜 200 块，能力接近。</p><blockquote><p>⚠️ 以上价格为 2026 年 3 月查询结果，各家会不定期调整，购买前请以官网为准。</p></blockquote><h3 id="真实数据：7-天烧了-2-4-亿-Token"><a href="#真实数据：7-天烧了-2-4-亿-Token" class="headerlink" title="真实数据：7 天烧了 2.4 亿 Token"></a>真实数据：7 天烧了 2.4 亿 Token</h3><p>我查了下智谱后台：<strong>过去 7 天，GLM5 消耗了约 2.4 亿 Token</strong>。数字看起来挺吓人的，但要注意几点：这 2.4 亿是纯云端 GLM5 的消耗，本地模型处理的那部分并不算在里面；Coding Plan 年费 1608 元，2.4 亿 Token 分摊到 7 天的话平均每天大约 3400 万 Token；如果按量付费 GLM5 大约 0.01 元&#x2F;千 Token，光这 7 天就要花 2400 元——而 Coding Plan 全年才 1608 元，套餐的性价比不言而喻。所以真实的费用结构是这样：<strong>云端干高质量任务，本地干高频脏活，搭配下来成本远没有想象中夸张。</strong> 完全不想花钱的话也可以只用本地模型起步，但能力上限和响应质量确实会差一截。如果愿意每月花一杯咖啡的钱接个云端模型，整体体验会好很多，尤其是长文写作和复杂推理的场景。</p><p><img src="/../images/OpenClaw-FAQ/token-usage.png" alt="Token 消耗统计"></p><h3 id="心态：别太功利"><a href="#心态：别太功利" class="headerlink" title="心态：别太功利"></a>心态：别太功利</h3><p>说到费用，我想多聊两句心态。养了这么久 OpenClaw（我家小朋友叫它”虾哥”），我最大的感触是：<strong>别一开始就想着变现、ROI 这些功利性的东西</strong>。AI 时代才刚刚开始，前期投资是必须的。你现在投入的时间和金钱可能暂时看不到直接收益，但会慢慢转化成你对 AI 能力边界的理解、对 Agent 协作的体感、对”什么场景能用 AI 解决”的直觉判断力。这些东西短期内没法量化，但长期来看价值巨大。</p><p>我的态度一直是：养虾就当给自己买了个大玩具，千金难买我开心。每天看它自己跑任务、写文章、整理知识库，这件事本身就已经很有成就感了。以后能变现当然好，但不能用”现在能不能变现”来决定要不要开始。<strong>先养起来，比什么都重要。</strong></p><h2 id="2-OpenClaw-到底能干什么？给几个实际的例子"><a href="#2-OpenClaw-到底能干什么？给几个实际的例子" class="headerlink" title="2. OpenClaw 到底能干什么？给几个实际的例子"></a>2. OpenClaw 到底能干什么？给几个实际的例子</h2><p>上一篇实战复盘写得比较详细，但我知道很多人没耐心从头看到尾。这里直接捞几个我觉得最有价值、最能说明问题的场景出来。</p><p><img src="/../images/OpenClaw-FAQ/q2-features.png" alt="OpenClaw 能干什么"></p><h3 id="我正在用的"><a href="#我正在用的" class="headerlink" title="我正在用的"></a>我正在用的</h3><table><thead><tr><th>场景</th><th>说明</th><th>价值</th></tr></thead><tbody><tr><td><strong>每日论文精读</strong></td><td>自动抓 Android&#x2F;AI 论文 → 翻译 → 精读笔记，落盘到 Obsidian</td><td>跟踪前沿，零人工</td></tr><tr><td><strong>GitHub 仓库监控</strong></td><td>巡检 Issue&#x2F;PR 变化，重要更新主动通知</td><td>我监控的是 Perfetto，不用每天刷</td></tr><tr><td><strong>知识库建设</strong></td><td>公众号&#x2F;X&#x2F;博客自动归档、结构化</td><td>1760 篇 Markdown 持续增长</td></tr><tr><td><strong>内容回顾</strong></td><td>每天推送值得重看的内容</td><td>解决”收藏了再也没看过”</td></tr><tr><td><strong>Android 动态汇总</strong></td><td>自动汇总领域动态，提取关键话题</td><td>不用刷信息流</td></tr></tbody></table><p>核心价值在于，OpenClaw 最值钱的不是”帮你回答一个问题”，而是把那些你明知很重要、但凭意志力很难长期坚持的事情变成系统默认帮你执行的后台任务。</p><hr><h3 id="进阶案例-1：批量导入-NotebookLM-进行深度分析"><a href="#进阶案例-1：批量导入-NotebookLM-进行深度分析" class="headerlink" title="进阶案例 1：批量导入 NotebookLM 进行深度分析"></a>进阶案例 1：批量导入 NotebookLM 进行深度分析</h3><p><strong>NotebookLM</strong> 是 Google 推出的 AI 研究助手，可以把你的各种资料（PDF、网页、文档）变成一个可对话的知识库。它最大的亮点是能自动生成<strong>播客式音频概述</strong>——两个 AI 主持人用轻松聊天的形式帮你总结资料要点，通勤路上戴着耳机听非常香。但 NotebookLM 有个明显的痛点：<strong>手动添加资料很麻烦</strong>，需要一个一个上传文件、一条一条粘贴链接，资料一多就让人抓狂。OpenClaw 刚好可以帮你把这个流程自动化掉。</p><h4 id="实际操作"><a href="#实际操作" class="headerlink" title="实际操作"></a>实际操作</h4><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">我：帮我研究一下 Android 15 的性能优化特性。</span><br><span class="line"><span class="bullet">    1.</span> 搜索相关资料（官方文档、博客、论文）</span><br><span class="line"><span class="bullet">    2.</span> 整理成 10-15 个高质量来源</span><br><span class="line"><span class="bullet">    3.</span> 批量导入到 NotebookLM</span><br><span class="line"><span class="bullet">    4.</span> 生成播客概述</span><br></pre></td></tr></table></figure><p>OpenClaw 会自动搜集 Android 15 性能优化相关的官方文档、技术博客和论文，筛选掉水文和营销号只保留真正有价值的内容，然后通过 API 或浏览器自动化把资料一键导入 NotebookLM，最后触发播客生成。</p><h4 id="为什么这个组合很香？"><a href="#为什么这个组合很香？" class="headerlink" title="为什么这个组合很香？"></a>为什么这个组合很香？</h4><table><thead><tr><th>单独用 NotebookLM</th><th>OpenClaw + NotebookLM</th></tr></thead><tbody><tr><td>手动找资料、手动上传</td><td>自动搜集、批量导入</td></tr><tr><td>资料零散、质量参差不齐</td><td>筛选过的高质量来源</td></tr><tr><td>一个项目搞完就结束</td><td>可以定期更新、持续追踪</td></tr></tbody></table><p>本质上就是把”信息搜集和筛选”这种费时费力的脏活外包给 OpenClaw，让 NotebookLM 专注于它最擅长的”深度分析和结构化输出”。</p><hr><h3 id="进阶案例-2：扔一个链接，自动抓取所有关联内容并落盘"><a href="#进阶案例-2：扔一个链接，自动抓取所有关联内容并落盘" class="headerlink" title="进阶案例 2：扔一个链接，自动抓取所有关联内容并落盘"></a>进阶案例 2：扔一个链接，自动抓取所有关联内容并落盘</h3><p>这是我日常用得最多的场景之一，几乎每天都会触发。刷推或者看公众号的时候看到一篇好文章，里面提到了几篇论文、几个 GitHub 仓库、几个相关链接、几本推荐的书籍。以前的做法你一定不陌生：收藏 → 告诉自己回头看 → 忘记 → 再也没看过。现在有了 OpenClaw，做法变了：</p><figure class="highlight nestedtext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">我：https://example.com/awesome-article</span><br><span class="line">    把这篇文章里提到的所有内容都抓下来：</span><br><span class="line">    <span class="bullet">-</span> <span class="string">论文 → 下载 PDF</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">GitHub 仓库 → star + 克隆到本地</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">相关链接 → 抓取正文</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">书籍 → 记录到待读清单</span></span><br><span class="line">    最后整理成一份 Markdown 落盘到 Obsidian</span><br></pre></td></tr></table></figure><p>OpenClaw 会读取原文、识别所有链接和引用，然后分类处理：arXiv&#x2F;论文链接下载 PDF 到 <code>论文/</code> 目录，GitHub 链接判断是否值得 star、重要的克隆到本地，普通网页抓取正文转成 Markdown，书籍链接提取书名加入待读清单。最后生成一份包含原文摘要、所有关联内容链接和简介、落盘位置的索引 Markdown，自动保存到 <code>Personal-Knowledge/source/</code> 目录。<strong>一个链接，一条指令，全套内容自动归位。</strong> 以后想回顾的时候打开 Obsidian 搜一下关键词就行，所有关联资料都已经按类型归好位了。</p><h3 id="如果你是-Android-开发者"><a href="#如果你是-Android-开发者" class="headerlink" title="如果你是 Android 开发者"></a>如果你是 Android 开发者</h3><p>这是我目前还没做但正在规划中的方向，列出来看看有没有跟你产生共鸣的场景：</p><table><thead><tr><th>痛点</th><th>OpenClaw 能做什么</th><th>适用</th></tr></thead><tbody><tr><td><strong>Trace 分析门槛高</strong></td><td>上传 Perfetto&#x2F;Systrace → AI 辅助解读 → 生成”人话版”报告</td><td>App 性能优化、系统 UX 性能</td></tr><tr><td><strong>版本适配清单</strong></td><td>自动抓 Android Release Notes → 生成适配 checklist → 对比代码标记检查点</td><td>App 开发者（每年必做）</td></tr><tr><td><strong>Issue 堆积难筛选</strong></td><td>自动分类（bug&#x2F;feature&#x2F;question）→ 优先级判断 → 提取关键信息</td><td>开源维护者、团队协作</td></tr><tr><td><strong>Code Review 耗时</strong></td><td>检测常见问题模式（空指针、资源泄漏、主线程 IO）→ 生成 Review checklist</td><td>所有团队</td></tr><tr><td><strong>技术债务遗忘</strong></td><td>定期扫描 TODO&#x2F;FIXME → 按优先级排序 → 定期提醒”该还债了”</td><td>所有项目</td></tr><tr><td><strong>Benchmark 没人看</strong></td><td>定期跑 benchmark → 自动对比历史 → 异常告警（启动 +500ms）</td><td>性能优化团队</td></tr><tr><td><strong>内部文档找不到</strong></td><td>基于知识库回答”之前遇到过吗？””设计文档在哪？”</td><td>所有团队</td></tr><tr><td><strong>上游变更追踪</strong></td><td>监控 AOSP&#x2F;厂商代码变更 → 提醒需要合并的点</td><td>系统开发者</td></tr></tbody></table><p>这些能力并不是开箱即用的，需要根据你自己的业务场景做定制开发。但 OpenClaw 的架构天然支持这些方向的扩展——<strong>关键是先把基础设施养起来，再根据实际需求慢慢迭代</strong>。</p><h3 id="一句话总结"><a href="#一句话总结" class="headerlink" title="一句话总结"></a>一句话总结</h3><p>论文每天读、仓库每天盯、知识每天理、日报每天写。它真正做到的是，把”需要长期坚持”这件反人性的事情变成了”系统在后台默默帮你执行”。</p><h2 id="3-本地模型可以干什么？"><a href="#3-本地模型可以干什么？" class="headerlink" title="3. 本地模型可以干什么？"></a>3. 本地模型可以干什么？</h2><p>本地模型的定位并不是替代云端大模型，而是专门负责那些脏活、累活和高频重复的任务。一句话概括分工：云端”想得深”，本地”跑得勤”。</p><p><img src="/../images/OpenClaw-FAQ/q3-local-models.png" alt="云端 + 本地分工"></p><h3 id="本地模型承担了多少？"><a href="#本地模型承担了多少？" class="headerlink" title="本地模型承担了多少？"></a>本地模型承担了多少？</h3><p>我的实际运行数据：</p><table><thead><tr><th>任务类型</th><th>模型</th><th>占比</th></tr></thead><tbody><tr><td>巡检、监控、批处理</td><td>本地 Qwen3.5-27B</td><td>30%</td></tr><tr><td>状态检查、轻量分类</td><td>本地 Qwen3.5-4B</td><td>20%</td></tr><tr><td>极轻任务</td><td>本地 Qwen3.5-2B</td><td>10%</td></tr><tr><td>云端失败降级</td><td>本地 Qwen3.5-9B</td><td>5%</td></tr><tr><td>长文、精修、复盘</td><td>云端 GLM5</td><td>15%</td></tr><tr><td>日报周报、审计</td><td>云端 GLM5</td><td>10%</td></tr><tr><td>论文精读、ClawFeed</td><td>云端 GLM5</td><td>10%</td></tr></tbody></table><p><strong>按任务数量算，本地承担了 60-70%；按 Token 消耗算，本地承担了 80%+。</strong></p><h3 id="我的配置"><a href="#我的配置" class="headerlink" title="我的配置"></a>我的配置</h3><p>硬件是一台 Mac Studio（M 系列 Apple Silicon），上面部署了多个规格的本地模型：Qwen3.5-2B&#x2F;4B&#x2F;9B 使用 MLX 8bit 量化，负责轻量任务和云端故障时的容灾降级；Qwen3.5-27B 通过 Ollama 部署，承担高频结构化任务，是本地的主力干活模型。顺便提一句，Apple Silicon 上用 MLX 框架做推理的效率明显比 Ollama 高，尤其是跑小模型的时候差距更明显。</p><h3 id="分工原则"><a href="#分工原则" class="headerlink" title="分工原则"></a>分工原则</h3><table><thead><tr><th>本地适合</th><th>云端更强</th></tr></thead><tbody><tr><td>高频结构化任务（RSS、队列处理）</td><td>长文写作与精修</td></tr><tr><td>巡检与监控</td><td>复杂推理与判断</td></tr><tr><td>数据预处理（不离开本机）</td><td>高质量翻译与解读</td></tr><tr><td>云端降级接管</td><td>论文精读、深度分析</td></tr></tbody></table><p>至于成本，硬件本来就是已经购入的设备，电费增加也不多，所以本地模型的<strong>边际成本接近零</strong>。</p><h3 id="烧-Token-的速度"><a href="#烧-Token-的速度" class="headerlink" title="烧 Token 的速度"></a>烧 Token 的速度</h3><p>即使有本地模型分担，<strong>云端 Token 还是烧得飞快</strong>。我的配置是主模型 GPT-5.4 + 干活主力 GLM5 + 本地苦力 Qwen3.5 系列（27B&#x2F;9B&#x2F;4B&#x2F;2B）。所以我的建议是<strong>直接上量大管饱的 Coding Plan 套餐</strong>，预算充裕的话可以选 GPT Pro，追求性价比的话 MiniMax 或者 Kimi 都是不错的选择。按量付费的话，以这个消耗速度你一定会心疼的。</p><h3 id="备用通道"><a href="#备用通道" class="headerlink" title="备用通道"></a>备用通道</h3><p>虾哥前期运行偶尔会出现不稳定的情况，这时候我会用 Claude Code（Remote Control）远程连过去手动排查和救火。这种情况出现的频率不高，但有备用通道在手里心里踏实很多。</p><h3 id="分工逻辑"><a href="#分工逻辑" class="headerlink" title="分工逻辑"></a>分工逻辑</h3><p>打个比方，本地模型是”勤奋的蓝领工人”，云端模型是”聪明的高级顾问”。OpenClaw 做的事情就是帮你把这套分工协作的逻辑彻底自动化了。</p><h2 id="4-隐私和安全怎么保护？"><a href="#4-隐私和安全怎么保护？" class="headerlink" title="4. 隐私和安全怎么保护？"></a>4. 隐私和安全怎么保护？</h2><p>这个问题被问得非常多，也确实是每个人在决定养虾之前必须认真想清楚的事情。</p><p><img src="/../images/OpenClaw-FAQ/q4-security.png" alt="隐私与安全"></p><h3 id="核心原则"><a href="#核心原则" class="headerlink" title="核心原则"></a>核心原则</h3><p><strong>敏感数据不离开本机。</strong> 这是我给自己划的底线。具体的做法也很简单直接：</p><ol><li><strong>敏感文件不上云端</strong>：公司代码、内部文档、个人隐私相关的内容，一律只走本地模型处理</li><li><strong>本地优先</strong>：能在本地解决的任务，绝不走云端 API</li><li><strong>最小权限原则</strong>：OpenClaw 只能访问我明确授权过的目录和工具，其他一概不给权限</li></ol><h3 id="技术层面的防护"><a href="#技术层面的防护" class="headerlink" title="技术层面的防护"></a>技术层面的防护</h3><p><strong>命令执行白名单</strong>方面，<code>safeBins</code> 配置严格限制了它能执行的命令范围，只允许 <code>ls</code>、<code>cat</code>、<code>grep</code>、<code>find</code>、<code>git</code> 这些基础只读命令，而像 <code>rm -rf</code> 这样的危险操作会被 <code>deniedFlags</code> 直接拦截。也就是说即使有人尝试通过提示注入诱导它执行恶意操作，命令层面也根本跑不通。</p><p><strong>来源校验</strong>方面，<code>commands.ownerAllowFrom</code> 和 <code>channels.allowFrom</code> 做了来源白名单，只允许我自己的账号发送系统级指令，即便在群聊里有其他人@它也不会响应任何敏感操作。</p><p>另外还有<strong>配置文件哈希校验</strong>，每天自动对 <code>openclaw.json</code> 生成 SHA256 哈希值并与基线值对比，一旦检测到配置文件被篡改系统会立刻触发告警通知。加上<strong>定期巡检</strong>，每天自动扫描 workspace 目录下的所有文件，检测是否存在私钥、密码、助记词等敏感信息，一旦发现就立即告警通知绝不静默忽略。</p><h3 id="运行层面的隔离"><a href="#运行层面的隔离" class="headerlink" title="运行层面的隔离"></a>运行层面的隔离</h3><ul><li>OpenClaw 跑在独立的 Mac Studio 上，不和我日常开发的机器混用</li><li>敏感账号（公司邮箱、内部系统）不接入 OpenClaw</li><li>微信公众号等对外发布渠道，只开放”草稿保存”，不开放”直接发布”</li></ul><h3 id="信任边界"><a href="#信任边界" class="headerlink" title="信任边界"></a>信任边界</h3><p>我现在的策略是：<strong>OpenClaw 能看到的东西，不会比我主动发给云端 AI 的内容更敏感。</strong> 你想想看，日常用 ChatGPT、Claude 的时候也会上传文件、发截图，那些数据的暴露面其实更大、更不可控。OpenClaw 反而是相对受控的——它的权限范围、它的行为日志、它的所有操作记录全部都在我本地机器上，随时可以审计。</p><h3 id="安全的本质"><a href="#安全的本质" class="headerlink" title="安全的本质"></a>安全的本质</h3><p>安全从来不是 AI Agent 自带的默认行为，而是需要你在部署之前主动设计和配置出来的。养虾之前先把笼子扎紧，这个顺序千万不能反。</p><h2 id="5-使用体验：手机随时操控，数据不会丢"><a href="#5-使用体验：手机随时操控，数据不会丢" class="headerlink" title="5. 使用体验：手机随时操控，数据不会丢"></a>5. 使用体验：手机随时操控，数据不会丢</h2><p>这一条其实被问得不算多，但我个人觉得它是 OpenClaw 用起来真正”顺手”的关键所在，值得单独拎出来说说。</p><p><img src="/../images/OpenClaw-FAQ/q5-experience.png" alt="使用体验"></p><h3 id="Telegram：一个主-bot-若干群聊"><a href="#Telegram：一个主-bot-若干群聊" class="headerlink" title="Telegram：一个主 bot + 若干群聊"></a>Telegram：一个主 bot + 若干群聊</h3><p>我的用法是这样：主 bot 用私聊做日常对话、发指令、问问题；不同的 daily task 转发到不同的群——Android 群放技术简报、论文精读、GitHub 动态，Daily 群放日报、周报、系统通知，其他群按需分流。为什么要分群？原因有三个：</p><ol><li><strong>信息不混杂</strong>：技术内容和日常运维日志分开管理，想看哪个类别就进哪个群</li><li><strong>通知可控</strong>：重要的群打开通知提醒，不重要的直接静音，避免信息轰炸</li><li><strong>协作方便</strong>：有些群可以拉同事一起加入，共享信息源</li></ol><h3 id="数据落盘到-Obsidian"><a href="#数据落盘到-Obsidian" class="headerlink" title="数据落盘到 Obsidian"></a>数据落盘到 Obsidian</h3><p>Telegram 在这个体系里只是”前端展示层”，真正重要的内容最终都会落盘到 Obsidian。这种架构设计意味着三件事：</p><ul><li><strong>不会丢</strong>：Telegram 的消息会随着时间被刷掉，但 Obsidian 里的 Markdown 文件会一直留存</li><li><strong>可检索</strong>：Obsidian 的全文搜索能力远比 Telegram 的聊天记录搜索好用</li><li><strong>可整理</strong>：后期可以随时对内容重新组织结构、打标签、建立双向链接</li></ul><p>我现在 Obsidian 里的结构：</p><ul><li><code>OpenClaw定时任务/</code>：所有定时任务的输出</li><li><code>论文/</code>：论文精读三件套</li><li><code>Personal-Knowledge/source/</code>：知识库（1760 篇）</li><li><code>X 文章/</code>：归档的高价值 X 内容</li></ul><h3 id="手机随时访问"><a href="#手机随时访问" class="headerlink" title="手机随时访问"></a>手机随时访问</h3><p>这一点的重要性比我最初想象的要大得多。出去玩的时候掏出手机打开 Telegram 就能直接给虾哥发指令，跟在电脑前操作没有区别；想看今天的日报打开 Obsidian 手机端文件已经通过 iCloud 同步过来了；遇到紧急情况需要调整任务也不用专门开电脑，手机上就能完成操作。我现在的日常习惯是早上醒来先刷一下 Telegram，看看昨晚虾哥跑了哪些任务、有没有异常，有问题的直接在手机上回复处理。外出的时候也能随时看一眼运行状态，心里有数。</p><p><img src="/../images/OpenClaw-FAQ/status-card.png" alt="OpenClaw 状态卡片"></p><h3 id="架构总结"><a href="#架构总结" class="headerlink" title="架构总结"></a>架构总结</h3><p><strong>前端是 Telegram，后端是 Obsidian。</strong> Telegram 解决的是”随时随地都能触达”的问题，Obsidian 解决的是”数据永远不会丢”的问题。两者结合在一起，才构成了一个真正好用的 24&#x2F;7 全天候助手体验。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ol><li><a href="https://www.androidperformance.com/2026/03/08/OpenClaw-Local-Practice/">我把 OpenClaw 跑在本地三周后，发现它根本不是聊天机器人</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android 性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;昨天那篇 OpenClaw 实战复盘发出去之后，后台留言和群里讨论最多的就是这几个方向的问题：Token 消耗、能干什么、本地模型、隐私安全、使用体验。今天挑出被问频率最高的五个，一个一个说清楚。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/../images/OpenClaw-FAQ/cover.png&quot; alt=&quot;OpenClaw 常见问题解答&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="AI" scheme="https://androidperformance.com/categories/AI/"/>
    
    
    <category term="OpenClaw" scheme="https://androidperformance.com/tags/OpenClaw/"/>
    
    <category term="AI" scheme="https://androidperformance.com/tags/AI/"/>
    
    <category term="效率工具" scheme="https://androidperformance.com/tags/%E6%95%88%E7%8E%87%E5%B7%A5%E5%85%B7/"/>
    
    <category term="本地模型" scheme="https://androidperformance.com/tags/%E6%9C%AC%E5%9C%B0%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>我把 OpenClaw 跑在本地三周后，发现它根本不是聊天机器人</title>
    <link href="https://androidperformance.com/2026/03/08/OpenClaw-Local-Practice/"/>
    <id>https://androidperformance.com/2026/03/08/OpenClaw-Local-Practice/</id>
    <published>2026-03-08T10:00:00.000Z</published>
    <updated>2026-04-10T16:36:37.726Z</updated>
    
    <content type="html"><![CDATA[<p>最近这段时间，我一直在本地重度使用 OpenClaw。最开始我也把它当成一个 AI 工具，但真正把它接进 Telegram、Obsidian、定时任务、本地模型和内容工作流之后，我发现自己完全想错了——<strong>它最厉害的地方，不是回答问题，而是开始替你持续工作。</strong> 它能接消息、调工具、跑定时任务、调用不同模型、沉淀长期记忆、把结果回写 Obsidian，还能把复杂任务分发给别的 Agent。你如果只把它当聊天机器人，那基本等于只用了它 20% 的能力。</p><span id="more"></span><p>对我这种既做 Android 系统性能优化、又要做内容、运营社群、维护知识库、跟进项目、写代码的人来说，OpenClaw 最值钱的地方只有一句话：<strong>它让我第一次有了”AI 不只是回答我，而是真的在替我持续推进工作”的感觉。</strong></p><p><img src="/../images/OpenClaw-Local-Practice/01-scene-openclaw-control-center.png" alt="OpenClaw 工作流总览"></p><h2 id="1-我是什么时候开始真正用-OpenClaw-的？"><a href="#1-我是什么时候开始真正用-OpenClaw-的？" class="headerlink" title="1. 我是什么时候开始真正用 OpenClaw 的？"></a>1. 我是什么时候开始真正用 OpenClaw 的？</h2><p>按本地环境的初始化痕迹看，我是在 <strong>2026 年 2 月 16 日晚上</strong>开始把 OpenClaw 真正落到自己机器上的。这不是”装来看看”的那种体验，而是一路接进自己的日常工作流：消息入口、定时任务、Obsidian 知识库、日报周报、GitHub 监控、论文精读、内容素材整理、创作与复盘。所以我现在已经不把它当”一个工具”，而是把它当成一个本地 AI 调度层、一个定时执行系统、一个长期记忆系统、一个内容与知识的自动整理器，以及一个能和 Claude Code 配合工作的 AI 中枢。</p><h2 id="2-对一个-Android-系统工程师来说，OpenClaw-到底有什么用？"><a href="#2-对一个-Android-系统工程师来说，OpenClaw-到底有什么用？" class="headerlink" title="2. 对一个 Android 系统工程师来说，OpenClaw 到底有什么用？"></a>2. 对一个 Android 系统工程师来说，OpenClaw 到底有什么用？</h2><p>如果你本职是 Android 工程、系统优化、性能分析、稳定性治理，OpenClaw 最有价值的，不是”替你答一道题”，而是帮你处理那些<strong>持续重复、跨工具、跨时间、跨上下文</strong>的工作。我现在主要把它用在四类事情上。</p><h3 id="第一类：信息流自动化"><a href="#第一类：信息流自动化" class="headerlink" title="第一类：信息流自动化"></a>第一类：信息流自动化</h3><p>每日技术简报、每日信息简报、RSS 抓取、高价值内容筛选、GitHub Issue &#x2F; PR 监控、每日论文精读——这些事情以前不是不会做，而是<strong>太碎了，做着做着就断</strong>。OpenClaw 的价值就是把这些动作从”靠意志力”变成”靠系统默认执行”。</p><h3 id="第二类：知识库持续整理"><a href="#第二类：知识库持续整理" class="headerlink" title="第二类：知识库持续整理"></a>第二类：知识库持续整理</h3><p>我平时会收很多内容：技术文章、公众号、X 长文、RSS、项目资料、自己的思考。以前最大的问题是：收藏了但没整理，整理了但找不到，找到了也很难复用。OpenClaw 接进来之后，会持续做增量整理、结构修复、高价值归档、内容回顾和记忆维护。这件事对工程师特别重要，因为成长靠的不是”看过很多”，而是<strong>以后还能拿出来继续用</strong>。</p><h3 id="第三类：日报、复盘和周检"><a href="#第三类：日报、复盘和周检" class="headerlink" title="第三类：日报、复盘和周检"></a>第三类：日报、复盘和周检</h3><p>这是很多人最容易忽略，但最有复利的一部分。我现在让 OpenClaw 长期跑每日早间提醒、今天干了啥日报、日终复盘、周沉淀、项目周审、三层记忆维护、冥想 &#x2F; evolution log。说白了，OpenClaw 最让我上瘾的一点就是：<strong>它把那些”我知道很重要，但人很难长期坚持”的事情，变成了系统自动帮我做。</strong></p><h3 id="第四类：工程协作"><a href="#第四类：工程协作" class="headerlink" title="第四类：工程协作"></a>第四类：工程协作</h3><p>它也能参与工程工作，比如跟踪 GitHub 仓库变化、巡检 issue &#x2F; PR、整理代码上下文、调用其他 coding agent 干活。但这里我要先说结论：<strong>OpenClaw 不是为了替代 Claude Code，而是为了编排 Claude Code。</strong> Claude Code 更像一个”坐在终端里的顶级程序员”；OpenClaw 更像一个”AI 运营系统”。</p><h2 id="3-为什么我旁边常备-Claude-Code？"><a href="#3-为什么我旁边常备-Claude-Code？" class="headerlink" title="3. 为什么我旁边常备 Claude Code？"></a>3. 为什么我旁边常备 Claude Code？</h2><p>因为 OpenClaw 早期真的会挂。我说得更直白一点：升级后会报错、模型配置会出问题、定时任务会异常、某些工具链会失灵、某些路径写入会失败、旧会话和新配置不一定同步。这时候如果你只有 OpenClaw 自己，很多问题会非常烦，但如果旁边有个 Claude Code，事情就简单很多。</p><p>我的实际分工很清楚：<strong>OpenClaw</strong> 负责调度、记忆、定时、归档、消息、工作流；<strong>Claude Code</strong> 负责修 bug、查日志、看报错、做复杂代码改动、升级后救火。简而言之，Claude Code 像顶级程序员，OpenClaw 像 AI 运营系统。</p><p><img src="/../images/OpenClaw-Local-Practice/04-comparison-openclaw-vs-claudecode.png" alt="Claude Code 与 OpenClaw 协作"></p><h2 id="4-我的本地配置是什么？"><a href="#4-我的本地配置是什么？" class="headerlink" title="4. 我的本地配置是什么？"></a>4. 我的本地配置是什么？</h2><p>我现在这套机器是 Mac Studio M1 Ultra，64GB 内存，48 核 GPU。这套配置对本地 Agent 工作流非常舒服，因为它允许我同时跑 OpenClaw 常驻服务、Ollama 本地模型、Obsidian、浏览器自动化、以及各种脚本和知识库任务。我现在不是单模型打法，而是<strong>云端 + 本地混合分工</strong>。</p><h3 id="模型搭配思路很简单"><a href="#模型搭配思路很简单" class="headerlink" title="模型搭配思路很简单"></a>模型搭配思路很简单</h3><p>高质量任务上云（长文、周报、复杂总结、关键判断），高频脏活本地跑（结构化任务、巡检、批处理、日常 worker）。我本地主要是这套分层：2B 做状态检查，4B 做轻量巡检，9B 是高频主力，27B &#x2F; 35B 负责更重的本地任务。对我这台机器来说，<strong>9B 是最划算的日常工作马</strong>。</p><h2 id="5-OpenClaw-最重要的，不只是模型，而是那几份核心文件"><a href="#5-OpenClaw-最重要的，不只是模型，而是那几份核心文件" class="headerlink" title="5. OpenClaw 最重要的，不只是模型，而是那几份核心文件"></a>5. OpenClaw 最重要的，不只是模型，而是那几份核心文件</h2><p>很多人第一次接触 OpenClaw，会把注意力放在模型、工具、命令行上。但我现在越来越觉得，它真正强大的地方反而是那几份被文件化的规则。我自己最看重的是这几类文件：</p><ul><li><code>SOUL.md</code>：定义它的性格、风格、做事姿态</li><li><code>USER.md</code>：定义它到底在帮谁、目标是什么</li><li><code>AGENTS.md</code>：定义启动流程、记忆规则、安全边界、群聊行为</li><li><code>MEMORY.md + memory/YYYY-MM-DD.md</code>：一个存长期记忆，一个存每天发生了什么</li><li><code>TOOLS.md</code>：记录这台机器独有的本地环境信息</li></ul><p>所以我现在对 OpenClaw 的理解是：<strong>它不是靠一大段系统提示词活着，而是靠一套”文件化人格 + 文件化记忆 + 文件化规则”活着。</strong></p><p><img src="/../images/OpenClaw-Local-Practice/02-framework-core-files.png" alt="OpenClaw 核心文件框架"></p><h2 id="6-Skill-机制为什么重要？"><a href="#6-Skill-机制为什么重要？" class="headerlink" title="6. Skill 机制为什么重要？"></a>6. Skill 机制为什么重要？</h2><p>很多人把 Skill 理解成”插件”，但我觉得更准确的理解是<strong>能力包</strong>。一个 Skill 往往不只是多一个按钮，而是把适合什么场景、怎么调用工具、是否依赖脚本、有哪些边界和注意事项、在什么情况下该用或不该用这些东西一起打包了。比如 Obsidian、coding-agent、RSS、技能审计这些能力，一旦装对，OpenClaw 就不再只是聊天，而是真的开始干活。但我的建议也很明确：<strong>不要为了炫技乱装 Skill。</strong> 先装核心能力，真正跑两天确认稳定再继续扩；涉及高权限或外联的 Skill，先审计再决定要不要上。</p><h2 id="7-Telegram-这块，单-Agent-多群聊能用，但后期最好按职责拆"><a href="#7-Telegram-这块，单-Agent-多群聊能用，但后期最好按职责拆" class="headerlink" title="7. Telegram 这块，单 Agent 多群聊能用，但后期最好按职责拆"></a>7. Telegram 这块，单 Agent 多群聊能用，但后期最好按职责拆</h2><p>如果你只在私聊里用 OpenClaw，其实还没完全体会到它的架构价值。它真正好玩的地方之一，是同一套系统可以接 Telegram，而且不一定只能对应一种工作方式。</p><h3 id="方案-A：单-Agent-多群聊"><a href="#方案-A：单-Agent-多群聊" class="headerlink" title="方案 A：单 Agent 多群聊"></a>方案 A：单 Agent 多群聊</h3><p>优点是简单：配置快、起步成本低、前期验证最省事。但缺点也很明显：上下文容易串味，不同群的语气和任务容易混在一起，写作、日报、技术答疑很容易互相污染。</p><h3 id="方案-B：按职责拆成多个-Agent"><a href="#方案-B：按职责拆成多个-Agent" class="headerlink" title="方案 B：按职责拆成多个 Agent"></a>方案 B：按职责拆成多个 Agent</h3><p>这才是我现在更认可的方式。比如 <code>main</code> 负责私聊主会话，<code>daily</code> 负责定时任务、日报、巡检、知识库类工作，<code>writer</code> 负责写作和内容精修类任务，专题 Agent 负责某个群、某条业务线或某个项目。这样做的好处是上下文隔离更清楚、角色边界更明确、不同群不会互相污染、后期扩展更轻松。我的建议是：<strong>前期可以单 Agent 多群聊，后期一定要按职责拆。</strong></p><p><img src="/../images/OpenClaw-Local-Practice/03-flowchart-workflow-loop.png" alt="Telegram 多 Agent 架构"></p><h2 id="8-安全不是附录，而是前置条件"><a href="#8-安全不是附录，而是前置条件" class="headerlink" title="8. 安全不是附录，而是前置条件"></a>8. 安全不是附录，而是前置条件</h2><p>这件事我非常建议一开始就讲透。因为 OpenClaw 一旦开始接本地文件系统、浏览器、命令执行、外部消息入口、本地和云端模型混合调用，它就不再是一个”无害聊天框”了，而是一个真正有行动能力的系统。这个时候，安全边界一定要先立住。</p><p>我现在比较认同的几条原则是：</p><ul><li><strong>长期记忆要分层</strong>：像 <code>MEMORY.md</code> 这种更私人、更稳定的信息，最好只在主会话里使用，不要在群聊上下文里乱读乱用</li><li><strong>外部发送要谨慎</strong>：邮件、公开发帖、社交平台发布，最好默认需要确认，不要自动对外表达半成品</li><li><strong>群聊不要越权</strong>：在群里它是参与者，不是代言人，更不是你本人</li><li><strong>Skill 不要乱装</strong>：尤其是涉及联网、执行脚本、读本地路径的能力，先审计再说</li><li><strong>本地小模型别无脑给大权限</strong>：尤其是接网页、执行命令、读写路径时，要清楚工作区边界和工具边界</li></ul><p>如果你后面还要接更多外部能力、浏览器自动化或者海外模型，通常还需要稳定的<strong>科学上网</strong>环境，但能力越多越要先把边界设计好。</p><h2 id="9-还有一个特别关键的问题：它到底留下了什么成果？"><a href="#9-还有一个特别关键的问题：它到底留下了什么成果？" class="headerlink" title="9. 还有一个特别关键的问题：它到底留下了什么成果？"></a>9. 还有一个特别关键的问题：它到底留下了什么成果？</h2><p>如果文章只写”它能做什么”，读者还是容易觉得这是一套概念系统。真正有说服力的，其实是<strong>这套系统已经在 Obsidian 里沉淀出了什么成果物</strong>。截至我写这篇文章时，我的 Obsidian 里已经能直接看到这些成果：</p><p><img src="/../images/OpenClaw-Local-Practice/05-infographic-obsidian-artifacts-wall.png" alt="Obsidian 成果墙"></p><ul><li><code>OpenClaw定时任务/</code> 下有 <strong>214 篇 Markdown</strong>，说明日报、周检、归档、巡检已经不是口号，而是持续产出</li><li><code>Personal-Knowlodge/source/</code> 下有 <strong>1760 篇 Markdown</strong>，知识库已经不是空目录，而是真在增长</li><li><code>X 文章/</code> 下有 <strong>115 篇 Markdown</strong>，高价值外部内容已经沉淀成长期素材库</li><li><code>论文/</code> 下有 <strong>4 个标准化论文目录</strong>，每个目录都按”原 PDF + 翻译 + 精读”来组织</li><li><code>小说工坊/夜航之上（分章）/</code> 下有 <strong>58 个章节相关文件</strong>，它已经进入长期创作工作流，而不只是技术任务</li><li><code>Ebook/</code> 里已经有实际 EPUB 成果，不只是笔记，还有可交付格式</li></ul><p>如果你看几个具体例子，会更有感觉：<code>OpenClaw定时任务/每日论文精读（Android+AI）/2026-03-08-每日论文精读（Android+AI）.md</code>、<code>Personal-Knowlodge/source/2026-03-08_wechat_Android_JNI原理分析.md</code>、<code>论文/AI-2026-03-08-Agentic-Reasoning-Framework/01-paper.pdf + 02-翻译.md + 03-精读.md</code>、<code>小说工坊/夜航之上（分章）/第018章-光标闪烁.md</code>。这也是我现在最看重 OpenClaw 的地方：<strong>它不是做完就没了，而是在不断把工作变成资产。</strong></p><h2 id="10-我踩过哪些坑？"><a href="#10-我踩过哪些坑？" class="headerlink" title="10. 我踩过哪些坑？"></a>10. 我踩过哪些坑？</h2><p>这部分非常重要，因为它决定你会不会半路放弃。</p><h3 id="坑-1：它初期真的没那么稳"><a href="#坑-1：它初期真的没那么稳" class="headerlink" title="坑 1：它初期真的没那么稳"></a>坑 1：它初期真的没那么稳</h3><p>你要接受一个现实：OpenClaw 很强，但并不等于”零维护”。我踩过的坑包括升级后报错、模型 allowlist 没配对、模型切换后旧会话不生效、定时任务能跑但落盘失败、外部路径权限问题、浏览器策略问题等等。</p><h3 id="坑-2：Node-环境不要混"><a href="#坑-2：Node-环境不要混" class="headerlink" title="坑 2：Node 环境不要混"></a>坑 2：Node 环境不要混</h3><p>我后面把运行环境统一切到了 <strong>Homebrew Node</strong>，不再混用 nvm。这个动作非常有必要，不然后面升级、重启、路径都容易乱。</p><h3 id="坑-3：Obsidian-外部路径别乱写"><a href="#坑-3：Obsidian-外部路径别乱写" class="headerlink" title="坑 3：Obsidian 外部路径别乱写"></a>坑 3：Obsidian 外部路径别乱写</h3><p>这是我踩得很深的一个坑。后来我收敛成非常明确的规则：</p><ul><li>必须绝对路径</li><li>最稳是 <code>exec + python/pathlib</code> 落盘</li><li>不要想当然地直接 <code>write/edit</code></li><li>同日内容尽量追加，不要覆盖</li></ul><h3 id="坑-4：自动化不是越多越好"><a href="#坑-4：自动化不是越多越好" class="headerlink" title="坑 4：自动化不是越多越好"></a>坑 4：自动化不是越多越好</h3><p>一开始很容易上头，这个也想自动化、那个也想自动化。但最后你会发现任务太多就会带来重复、冲突、噪音、token 成本上升，结果反而没人真正看。所以更好的策略不是”全自动化”，而是先抓最有复利的几条主线。</p><h2 id="11-什么样的人适合用-OpenClaw？"><a href="#11-什么样的人适合用-OpenClaw？" class="headerlink" title="11. 什么样的人适合用 OpenClaw？"></a>11. 什么样的人适合用 OpenClaw？</h2><p>我觉得最适合的是三类人。<strong>第一类是有持续输入和输出需求的人</strong>，比如工程师、独立开发者、技术博主、社群运营者、研究型创作者。<strong>第二类是愿意折腾工作流的人</strong>，如果你对”自动化、系统化、结构化”天然感兴趣，OpenClaw 会越用越顺手。<strong>第三类是想把 AI 真正接进工作流的人</strong>，如果你不是只想问几个问题，而是真的想做持续监控、自动整理、主动提醒、定时产出、长期记忆、跨平台内容协同，那 OpenClaw 会很有用。</p><h2 id="12-什么样的人不太适合？"><a href="#12-什么样的人不太适合？" class="headerlink" title="12. 什么样的人不太适合？"></a>12. 什么样的人不太适合？</h2><p>也要说实话。只想把它当 ChatGPT 替代品的人、不想维护环境不想看日志的人、没有持续工作流的人，这三类人不一定用不好，但大概率感受不到它真正的价值。</p><h2 id="13-硬件怎么推荐？"><a href="#13-硬件怎么推荐？" class="headerlink" title="13. 硬件怎么推荐？"></a>13. 硬件怎么推荐？</h2><p>如果你问我推荐什么机器，我会分三档说：</p><ul><li><strong>入门档</strong>：16GB 内存，主要跑云端模型，本地只做少量辅助</li><li><strong>实用档</strong>：32GB 内存，能跑一部分本地模型，可以承接中轻量任务</li><li><strong>舒服档</strong>：64GB 及以上，Mac Studio 或高配工作站级机器，云端 + 本地混合长期常驻</li></ul><p>如果你真想把 OpenClaw 跑成”长期常驻、云端+本地混合、多任务并行”的系统，<strong>64GB 这一档体验会明显更稳</strong>。</p><h2 id="14-我现在对-OpenClaw-的最终判断"><a href="#14-我现在对-OpenClaw-的最终判断" class="headerlink" title="14. 我现在对 OpenClaw 的最终判断"></a>14. 我现在对 OpenClaw 的最终判断</h2><p>如果只让我用一句话总结，我会这么说：<strong>OpenClaw 最迷人的地方，不是它更会回答问题，而是它开始替你持续推进工作。</strong> 对 Android 系统工程师来说，它最有价值的地方不是取代你写代码，而是帮你把输入、整理、归档、跟进、输出、复盘、记忆这些本来散落在各处的事情串起来。它不是零门槛工具，它会挂、会报错、会踩坑，但一旦你把它跑顺，你会很明显地感受到：<strong>以前是你在推着工作流走，后来是系统在推着你往前走。</strong> 这就是我觉得它最值得折腾的地方。</p><h2 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h2><p>如果你本身就是工程师、创作者，或者正在尝试把 AI 接进真实工作流，而不是只把它当聊天窗口，那 OpenClaw 值得你认真折腾一次。它不一定适合所有人，但一旦跑顺，带来的不是”对话效率提升”，而是<strong>工作流层面的复利</strong>。</p><h2 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h2><ol><li><a href="https://www.androidperformance.com/2026/03/10/OpenClaw-FAQ/">OpenClaw 常见问题解答：Token 消耗、能干什么、本地模型、隐私安全、使用体验</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android 性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近这段时间，我一直在本地重度使用 OpenClaw。最开始我也把它当成一个 AI 工具，但真正把它接进 Telegram、Obsidian、定时任务、本地模型和内容工作流之后，我发现自己完全想错了——&lt;strong&gt;它最厉害的地方，不是回答问题，而是开始替你持续工作。&lt;/strong&gt; 它能接消息、调工具、跑定时任务、调用不同模型、沉淀长期记忆、把结果回写 Obsidian，还能把复杂任务分发给别的 Agent。你如果只把它当聊天机器人，那基本等于只用了它 20% 的能力。&lt;/p&gt;</summary>
    
    
    
    <category term="AI" scheme="https://androidperformance.com/categories/AI/"/>
    
    
    <category term="OpenClaw" scheme="https://androidperformance.com/tags/OpenClaw/"/>
    
    <category term="AI" scheme="https://androidperformance.com/tags/AI/"/>
    
    <category term="AI Agent" scheme="https://androidperformance.com/tags/AI-Agent/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 10 - Binder 调度与锁竞争</title>
    <link href="https://androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/"/>
    <id>https://androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/</id>
    <published>2025-11-16T07:33:30.000Z</published>
    <updated>2026-02-07T05:24:49.187Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Perfetto 系列的第十篇文章，聚焦 Binder 这一 Android 跨进程通信的核心机制。Binder 承载着大部分系统服务与应用的交互，也常常是性能瓶颈的源头。本文站在系统开发与性能调优的视角，结合 <code>linux.ftrace</code>（binder tracepoints + sched）、<code>thread_state</code> 轨道，以及 ART 的 Java Monitor Contention（通过 atrace 的 <code>dalvik</code> 类别采集）等信号，给出一套可直接落地的诊断流程，帮助初学者和进阶开发者定位耗时、线程池压力与锁竞争等问题。</p><p>本系列的目标，就是通过 Perfetto 这个工具，从一个全新的图形化视角，来审视 Android 系统的整体运行，同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章，但总是对繁杂的调用链感到困惑，或者记不住具体的执行流程。那么通过 Perfetto，将这些流程可视化，你可能会对系统有更深入、更直观的理解。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列文章</a></li><li><a href="#basics">Binder 基础与案例</a></li><li><a href="#setup">Perfetto 观测准备</a><ul><li><a href="#other-tools">其他 Binder 分析工具</a></li></ul></li><li><a href="#workflow">Binder 分析工作流</a><ul><li><a href="#workflow-latency">步骤一：定位事务耗时</a></li><li><a href="#workflow-threadpool">步骤二：评估线程池与 Oneway 队列</a></li><li><a href="#workflow-lock">步骤三：排查锁竞争</a></li></ul></li><li><a href="#platform-features">最新平台特性与优化建议</a></li><li><a href="#summary">总结</a></li><li><a href="#refs">参考</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h2 id="Perfetto-系列文章"><a href="#Perfetto-系列文章" class="headerlink" title="Perfetto 系列文章"></a>Perfetto 系列文章</h2><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p><a id="basics"></a></p><h2 id="Binder-基础与案例"><a href="#Binder-基础与案例" class="headerlink" title="Binder 基础与案例"></a>Binder 基础与案例</h2><p>对于首次接触 Binder 的读者，理解它的角色和参与者至关重要。可以先把 Binder 粗暴地理解成“跨进程的函数调用”：你在一个进程里像调用本地接口一样写代码，真正的调用和数据传输则由 Binder 帮你完成。整体上它是 Android 的主力跨进程通信（IPC）机制，核心包含四个组件：</p><ol><li><strong>Client</strong>：应用线程通过 <code>IBinder.transact()</code> 发起调用，将 <code>Parcel</code> 序列化的数据写入内核。</li><li><strong>Service（Server）</strong>：通常运行在 SystemServer 或其他进程中，通过 <code>Binder.onTransact()</code> 读取 <code>Parcel</code> 并执行业务逻辑。</li><li><strong>Binder Driver</strong>：内核模块 <code>/dev/binder</code> 负责线程池调度、缓冲区管理、优先级继承等，是连接双方的“信使”。</li><li><strong>Thread Pool</strong>：服务端通常维护一组 Binder 线程。需要注意的是，<strong>线程池并不是一开始就创建满的</strong>，而是按需创建。Java 层默认最大线程数约为 15 个 Binder 工作线程（不含主线程），Native 层通过 <code>ProcessState</code> 也可以配置最大线程数（默认值通常也是 15）。当所有 Binder 线程都忙碌时，新的请求就会在驱动层排队等待空闲线程。</li></ol><p><strong>为什么需要 Binder？</strong></p><p>Android 采用多进程架构来隔离应用、提升安全性与稳定性。每个 APK 运行在独立的用户空间，当需要访问系统能力（相机、位置、通知等）时，必须跨进程调用 Framework 或 SystemServer。</p><p>传统 IPC 方案的局限：</p><table><thead><tr><th>IPC 方式</th><th>问题</th></tr></thead><tbody><tr><td>Socket</td><td>开销大，缺少身份校验</td></tr><tr><td>Pipe</td><td>仅支持父子进程，单向通信</td></tr><tr><td>共享内存</td><td>需要额外的同步机制，缺少访问控制</td></tr></tbody></table><p>Binder 在内核层解决了这些问题，提供了三个关键能力：一是<strong>身份与权限</strong>（基于 UID&#x2F;PID 校验，确保调用方合法）；二是<strong>同步与异步调用</strong>（同步模式下 Client 等待 Server 返回，这是最常见的模式，而异步模式下 Client 发送后立即返回，适用于通知、状态上报等场景）；三是<strong>优先级继承</strong>（当高优先级 Client 调用低优先级 Server 时，Server 会临时提升优先级，避免优先级反转问题）。</p><p>因此，当应用进程在启动阶段通过 <code>IActivityManager#attachApplication()</code> 把自己“挂到” SystemServer 时，底层必然借助 Binder 把调用安全、可靠地传递给 <code>system_server</code>。</p><h3 id="从-App-开发者视角的案例"><a href="#从-App-开发者视角的案例" class="headerlink" title="从 App 开发者视角的案例"></a>从 App 开发者视角的案例</h3><p>假设我们在 Trace 里关注到 <code>AIDL::java::IActivityManager::attachApplication::server</code>。它对应的是应用进程通过 <code>IActivityManager#attachApplication(...)</code> 发起的一次<strong>同步</strong> Binder 调用，服务端实现位于 <code>system_server</code> 的 <code>ActivityManagerService</code>。调用路径可以概括为：首先，在 <strong>Proxy 侧</strong>，应用进程通过 <code>ActivityManager.getService()</code> 拿到一个 <code>IActivityManager</code> 的代理对象（<code>BinderProxy</code>）；然后进行<strong>序列化</strong>，调用 <code>attachApplication(...)</code> 时，代理会把参数写入 <code>Parcel</code>，执行 <code>transact()</code>；接着是<strong>内核传输</strong>，Binder 驱动将该事务排入 <code>system_server</code> 的 Binder 线程队列，并<strong>唤醒</strong>一个空闲线程（例如 <code>Binder:1460_5</code>）；随后在 <strong>Stub 侧</strong>，<code>ActivityManagerService</code>（Stub）所在的线程被唤醒，读取参数并进入 <code>attachApplication</code> 的处理流程；最后是<strong>返回</strong>阶段，Service 处理完毕，将结果写入 <code>Parcel</code>，驱动唤醒原 App 线程，App 线程从 <code>waitForResponse()</code> 返回继续执行。</p><p>在 Perfetto 中，这条链路会显示为：<strong>Android Binder &#x2F; Transactions</strong> 轨道上的一次事务（如果 trace 中能解析到 AIDL 信息，Slice 名称会类似 <code>AIDL::java::IActivityManager::attachApplication::client/server</code>，或在 SQL 中体现为 <code>aidl_name=IActivityManager</code>、<code>method_name=attachApplication</code>）；App 线程在 <code>thread_state</code> 里处于 <code>S</code> (Sleeping) 状态（同步调用时常见），且 <code>blocked_function</code> 通常涉及 <code>binder_thread_read</code> &#x2F; <code>epoll_wait</code> &#x2F; <code>ioctl(BINDER_WRITE_READ)</code>；SystemServer 的 Binder 线程出现 <code>Running</code> 切片；以及 <strong>Flow 箭头</strong>（Perfetto 会用箭头把 Client 的 <code>transact</code> 和 Server 的处理线程连接起来）。</p><p><img src="/images/image-20260207113358540.webp" alt="image-20260207113358540"></p><p><a id="setup"></a></p><h2 id="Perfetto-观测准备"><a href="#Perfetto-观测准备" class="headerlink" title="Perfetto 观测准备"></a>Perfetto 观测准备</h2><p>要在 Perfetto 中诊断 Binder，需要提前准备好数据源与 Trace 配置。</p><h3 id="数据源与轨道总览"><a href="#数据源与轨道总览" class="headerlink" title="数据源与轨道总览"></a>数据源与轨道总览</h3><p>Binder 分析需要把「事务事件」和「线程调度&#x2F;阻塞&#x2F;锁」串起来。录制侧主要依赖 <code>linux.ftrace</code>（包含 Binder tracepoints、调度事件以及可选的 atrace 类别），再配合少量元数据源（进程&#x2F;线程命名映射）。</p><p><strong><code>linux.ftrace</code>（内核层 + atrace）</strong> 是最通用、最基础的数据源，兼容所有 Android 版本。它直接读取内核的 ftrace 事件，包括 <code>binder_transaction</code>（事务开始）、<code>binder_transaction_received</code>（服务端收到事务）、<code>binder_transaction_alloc_buf</code>（缓冲区分配，诊断 TransactionTooLarge）等；再配合调度相关事件（<code>sched_switch</code>、<code>sched_waking</code>）即可还原出 “Client 发起调用 → 内核唤醒 Server 线程 → Server 处理 → 返回” 的链路。<br>另外，<code>linux.ftrace</code> 里还可以开启 atrace 类别来补充用户态 Slice：<code>binder_driver</code>&#x2F;<code>am</code>&#x2F;<code>wm</code> 等有助于解释系统服务语义；<code>dalvik</code> 则用于采集 ART 的 <strong>monitor contention</strong>（Java <code>synchronized</code> 竞争），从而在 UI 里出现 <strong>Thread &#x2F; Lock contention</strong> 相关轨道。</p><p><strong><code>linux.process_stats</code>（元数据）</strong> 用于把 PID&#x2F;TID 映射成进程名&#x2F;线程名，方便在 UI 和 SQL 中阅读与过滤。开销极低，建议常开。</p><blockquote><p><strong>说明</strong>：Perfetto UI 中的 <strong>Android Binder &#x2F; Transactions</strong>、<strong>Android Binder &#x2F; Oneway Calls</strong> 轨道，以及 PerfettoSQL 标准库中的 <code>android.binder</code> &#x2F; <code>android.monitor_contention</code> 模块，都是在 trace processor 侧基于上述原始事件解析&#x2F;聚合出来的，并不是需要额外开启的“录制数据源”。</p></blockquote><h3 id="Trace-Config-推荐"><a href="#Trace-Config-推荐" class="headerlink" title="Trace Config 推荐"></a>Trace Config 推荐</h3><p>以下配置兼顾了兼容性与新特性，建议作为标准的 Binder 分析模板。将配置保存为 <code>binder_config.pbtx</code> 即可使用：</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"># ============================================================</span><br><span class="line"># Binder 分析专用 Perfetto 配置</span><br><span class="line"># 适用范围：Android <span class="number">10</span>+（Android <span class="number">12</span>+ 在路径/权限上更省心）</span><br><span class="line"># ============================================================</span><br><span class="line"></span><br><span class="line"># --- 缓冲区与时长设置 ---</span><br><span class="line">buffers &#123;</span><br><span class="line">  size_kb: <span class="number">65536</span>          # <span class="number">64</span>MB 缓冲区，适合中等复杂度场景</span><br><span class="line">  fill_policy: RING_BUFFER</span><br><span class="line">&#125;</span><br><span class="line">duration_ms: <span class="number">15000</span>        # <span class="number">15</span> 秒抓取时长，可根据需要调整</span><br><span class="line"></span><br><span class="line"># --- 数据源 <span class="number">1</span>：linux.ftrace (内核 + atrace) ---</span><br><span class="line"># 最通用的数据源：Binder tracepoints + sched 事件 +（可选）atrace 类别</span><br><span class="line">data_sources &#123;</span><br><span class="line">  config &#123;</span><br><span class="line">    name: <span class="string">&quot;linux.ftrace&quot;</span></span><br><span class="line">    ftrace_config &#123;</span><br><span class="line">      # Binder 核心事件</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_transaction&quot;</span>           # 事务开始</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_transaction_received&quot;</span>  # 服务端收到事务</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_transaction_alloc_buf&quot;</span> # 缓冲区分配（诊断 TransactionTooLarge）</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_set_priority&quot;</span>          # 优先级继承</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_lock&quot;</span>                  # 内核锁（通常可省略）</span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_locked&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;binder/binder_unlock&quot;</span></span><br><span class="line"></span><br><span class="line">      # 调度事件（串联 Client/Server 线程）</span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_switch&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_waking&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_wakeup&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_blocked_reason&quot;</span>          # 阻塞原因</span><br><span class="line"></span><br><span class="line">      # 可选：应用层 Trace 点（需要 atrace）</span><br><span class="line">      atrace_categories: <span class="string">&quot;binder_driver&quot;</span>   # Binder 驱动层</span><br><span class="line">      atrace_categories: <span class="string">&quot;sched&quot;</span>           # 调度</span><br><span class="line">      atrace_categories: <span class="string">&quot;am&quot;</span>              # ActivityManager</span><br><span class="line">      atrace_categories: <span class="string">&quot;wm&quot;</span>              # WindowManager</span><br><span class="line">      atrace_categories: <span class="string">&quot;dalvik&quot;</span>          # Java Monitor Contention（锁竞争）</span><br><span class="line">      # atrace_categories: <span class="string">&quot;view&quot;</span>          # 如需分析 UI 可开启</span><br><span class="line"></span><br><span class="line">      # 如需抓取应用侧 atrace Slice（例如 doFrame / 自定义 Trace），可指定：</span><br><span class="line">      # atrace_apps: <span class="string">&quot;你的应用包名&quot;</span></span><br><span class="line">      # 或者抓全部（Trace 体积会变大）：</span><br><span class="line">      # atrace_apps: <span class="string">&quot;*&quot;</span></span><br><span class="line"></span><br><span class="line">      # 符号化内核调用栈</span><br><span class="line">      symbolize_ksyms: <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">      # 优化调度事件存储，减少 Trace 体积</span><br><span class="line">      compact_sched &#123;</span><br><span class="line">        enabled: <span class="literal">true</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"># --- 数据源 <span class="number">2</span>：linux.process_stats (进程信息) ---</span><br><span class="line"># 提供进程名、PID 等基础信息</span><br><span class="line">data_sources &#123;</span><br><span class="line">  config &#123;</span><br><span class="line">    name: <span class="string">&quot;linux.process_stats&quot;</span></span><br><span class="line">    process_stats_config &#123;</span><br><span class="line">      scan_all_processes_on_start: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="配置项说明"><a href="#配置项说明" class="headerlink" title="配置项说明"></a>配置项说明</h4><table><thead><tr><th>数据源</th><th>作用</th><th>Android 版本要求</th><th>开销</th></tr></thead><tbody><tr><td><code>linux.ftrace</code> (binder&#x2F;*)</td><td>内核层 Binder 事件</td><td>所有版本</td><td>低</td></tr><tr><td><code>linux.ftrace</code> (sched&#x2F;*)</td><td>调度事件，串联线程唤醒</td><td>所有版本</td><td>中</td></tr><tr><td><code>linux.ftrace</code> (atrace: dalvik&#x2F;…)</td><td>Framework Slice + Java Monitor Contention</td><td>所有版本（字段随版本演进）</td><td>低-中</td></tr><tr><td><code>linux.process_stats</code></td><td>进程名称和 PID 映射</td><td>所有版本</td><td>极低</td></tr></tbody></table><blockquote><p><strong>提示</strong>：本文的 Binder 分析工作流只依赖 <code>linux.ftrace</code>（binder tracepoints + sched + dalvik），因此 Android 12&#x2F;13&#x2F;14+ 的抓取思路基本一致。不同版本的 UI 字段名可能略有差异，遇到差异时推荐用 Perfetto SQL（stdlib）做校验。</p></blockquote><h3 id="快速上手：3-步抓取与查看-Binder-Trace"><a href="#快速上手：3-步抓取与查看-Binder-Trace" class="headerlink" title="快速上手：3 步抓取与查看 Binder Trace"></a>快速上手：3 步抓取与查看 Binder Trace</h3><ol><li><p><strong>抓取 Trace</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 推送配置</span></span><br><span class="line">adb push binder_config.pbtx /data/local/tmp/</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开始抓取</span></span><br><span class="line">adb shell perfetto --txt -c /data/local/tmp/binder_config.pbtx \</span><br><span class="line">    -o /data/misc/perfetto-traces/trace.pftrace</span><br><span class="line"></span><br><span class="line"><span class="comment"># ... 操作手机复现卡顿 ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 取出文件</span></span><br><span class="line">adb pull /data/misc/perfetto-traces/trace.pftrace .</span><br></pre></td></tr></table></figure></li><li><p><strong>打开 Trace</strong>：访问 <a href="https://ui.perfetto.dev/">ui.perfetto.dev</a>，拖入 trace 文件。</p></li><li><p><strong>添加关键视图</strong>：</p><ul><li>左侧点击 <strong>Tracks</strong> → <strong>Add new track</strong></li><li>搜索 “Binder”，添加 <strong>Android Binder &#x2F; Transactions</strong> 和 <strong>Android Binder &#x2F; Oneway Calls</strong></li><li>搜索 “Lock”，添加 <strong>Thread &#x2F; Lock contention</strong>（如果有数据）</li></ul></li></ol><p><a id="other-tools"></a></p><h3 id="其他-Binder-分析工具"><a href="#其他-Binder-分析工具" class="headerlink" title="其他 Binder 分析工具"></a>其他 Binder 分析工具</h3><p>除了 Perfetto，还可以用两个工具辅助定位：<code>am trace-ipc</code>（系统自带）和 <code>binder-trace</code>（开源，能力更强但门槛更高）。</p><h4 id="am-trace-ipc：Java-层-Binder-调用追踪"><a href="#am-trace-ipc：Java-层-Binder-调用追踪" class="headerlink" title="am trace-ipc：Java 层 Binder 调用追踪"></a>am trace-ipc：Java 层 Binder 调用追踪</h4><p><code>am trace-ipc</code> 用于追踪 Java 层 Binder 调用堆栈。系统会在目标进程开启 Binder stack tracking（<code>BinderProxy.transact()</code> 路径），在停止时导出文本统计。优点是<strong>零配置</strong>、无需 root。</p><p>基本用法很简单，就是”开始 → 操作 → 停止导出”三步：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 开始追踪（系统会记录符合条件进程的 Binder 调用，通常以可调试进程为主）</span></span><br><span class="line">adb shell am trace-ipc start</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 在手机上执行你要分析的操作（比如启动某个应用、触发卡顿场景等）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 停止追踪并导出结果到文件</span></span><br><span class="line">adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 把结果文件拉到电脑上查看</span></span><br><span class="line">adb pull /data/local/tmp/ipc-trace.txt</span><br></pre></td></tr></table></figure><p>导出结果是纯文本，示例如下：</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Traces <span class="keyword">for</span> process: com<span class="selector-class">.example</span><span class="selector-class">.app</span></span><br><span class="line">Count: <span class="number">15</span></span><br><span class="line">java<span class="selector-class">.lang</span><span class="selector-class">.Throwable</span></span><br><span class="line">    at android<span class="selector-class">.os</span><span class="selector-class">.BinderProxy</span><span class="selector-class">.transact</span>(BinderProxy<span class="selector-class">.java</span>:xxx)</span><br><span class="line">    at android<span class="selector-class">.app</span>.IActivityManager<span class="variable">$Stub</span><span class="variable">$Proxy</span><span class="selector-class">.startActivity</span>(...)</span><br><span class="line">    at android<span class="selector-class">.app</span><span class="selector-class">.Instrumentation</span><span class="selector-class">.execStartActivity</span>(...)</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure><p>它会按进程分组，列出调用堆栈和次数（Count），适合快速回答“调了哪些服务、调了多少次”。</p><p><strong>与 Perfetto 配合使用</strong>：Perfetto 看时间线与线程关系，<code>trace-ipc</code> 补“具体是哪个 Java 调用点发起调用”。</p><p>适用场景：怀疑卡顿&#x2F;ANR 与频繁 IPC 有关，或需要定位具体 Java 发起点。</p><h4 id="binder-trace：实时-Binder-消息解析"><a href="#binder-trace：实时-Binder-消息解析" class="headerlink" title="binder-trace：实时 Binder 消息解析"></a>binder-trace：实时 Binder 消息解析</h4><p><a href="https://github.com/foundryzero/binder-trace">binder-trace</a> 可以实时拦截并解析 Binder 消息，常被称为“Binder 的 Wireshark”，能看到接口、方法及部分参数。</p><p>它基于 Frida 动态注入，通常需要 root（或模拟器）和 frida-server，本地需 Python 3.9+。示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 追踪指定应用的 Binder 通信（-d 指定设备，-n 指定进程名，-a 指定 Android 版本）</span></span><br><span class="line">binder-trace -d emulator-5554 -n com.example.app -a 11</span><br></pre></td></tr></table></figure><p>它支持按接口&#x2F;方法&#x2F;事务类型过滤，适合安全研究和逆向分析这类“看消息内容”的场景。日常性能排查通常仍以 Perfetto + <code>am trace-ipc</code> 为主。</p><p><a id="workflow"></a></p><h2 id="Binder-分析工作流"><a href="#Binder-分析工作流" class="headerlink" title="Binder 分析工作流"></a>Binder 分析工作流</h2><p>拿到 Trace 后，不要直接在大海捞针。推荐按照“找目标 → 看耗时 → 查线程 → 找锁”的顺序进行。</p><p><a id="workflow-latency"></a></p><h3 id="步骤一：定位事务耗时"><a href="#步骤一：定位事务耗时" class="headerlink" title="步骤一：定位事务耗时"></a>步骤一：定位事务耗时</h3><p>分析的第一步是找到你关心的那次 Binder 调用。在 Perfetto 中有几种常用的定位方式：如果你已经知道是哪个进程发起的调用，可以直接在 <code>Transactions</code> 轨道里找到你的 App 进程作为 <code>Client</code> 的区域；如果你知道调用的接口名或方法名，可以按 <code>/</code> 键打开搜索框，输入 AIDL 接口名（如 <code>IActivityManager</code>）、方法名（如 <code>attachApplication</code>），或者直接输入完整的 Slice 名（如 <code>AIDL::java::IActivityManager::attachApplication::server</code>）来快速定位；如果你是在排查 UI 卡顿问题，最直接的方式是先看 UI 线程的 <code>thread_state</code> 轨道，找到处于 <code>S</code>（Sleeping）状态且时长较长的片段——如果这段时间主线程几乎没有在执行代码，那很可能就是在等待 Binder 调用返回，这里就是分析的起点。</p><p>选中一个 Transaction Slice 后，右侧的 Details 面板会显示这次事务的详细信息（Client&#x2F;Server 线程、时间戳、耗时等）。不同版本的 Perfetto UI 字段名可能略有差异，但你可以用 Perfetto SQL 的 <code>android_binder_txns</code> 来统一理解几个关键耗时：</p><ul><li><code>client_dur</code>：客户端端到端耗时（同步调用时基本等同于“我在等这次 Binder 返回”的时间）</li><li><code>server_dur</code>：服务端从开始处理到（同步时）发出 reply 的 wall clock 时长</li><li><code>dispatch_dur = server_ts - client_ts</code>：从客户端发起到服务端真正开始处理的延迟（常包含排队&#x2F;线程可用性&#x2F;调度影响）</li></ul><p>下面这段 SQL 可以直接在 Perfetto UI 的 SQL 页面运行（用于快速找出最慢的同步事务，并拆出派发延迟与服务端耗时）：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">INCLUDE PERFETTO <span class="keyword">MODULE</span> android.binder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  aidl_name,</span><br><span class="line">  method_name,</span><br><span class="line">  client_process,</span><br><span class="line">  client_thread,</span><br><span class="line">  client_dur <span class="operator">/</span> <span class="number">1e6</span> <span class="keyword">AS</span> client_ms,</span><br><span class="line">  server_process,</span><br><span class="line">  server_thread,</span><br><span class="line">  server_dur <span class="operator">/</span> <span class="number">1e6</span> <span class="keyword">AS</span> server_ms,</span><br><span class="line">  (server_ts <span class="operator">-</span> client_ts) <span class="operator">/</span> <span class="number">1e6</span> <span class="keyword">AS</span> dispatch_ms</span><br><span class="line"><span class="keyword">FROM</span> android_binder_txns</span><br><span class="line"><span class="keyword">WHERE</span> is_sync</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> client_dur <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><p><img src="/images/image-20260207110047680.webp" alt="image-20260207110047680"></p><p>理解这些耗时之间的关系非常重要，因为它直接决定了你下一步应该往哪个方向深挖。如果 <code>client_dur</code> 很长但 <code>server_dur</code> 很短，通常说明慢主要不在服务端处理，而是在派发&#x2F;排队（<code>dispatch_dur</code> 会很大），这时应该优先检查服务端线程池与调度情况（步骤二）。如果 <code>server_dur</code> 本身就很长，说明服务端处理慢，这时你需要跳转到服务端的 Binder 线程，看它在这段时间里到底在干什么——是在跑业务代码、等锁、还是等 IO。</p><p><a id="workflow-threadpool"></a></p><h3 id="步骤二：评估线程池与-Oneway-队列"><a href="#步骤二：评估线程池与-Oneway-队列" class="headerlink" title="步骤二：评估线程池与 Oneway 队列"></a>步骤二：评估线程池与 Oneway 队列</h3><p>如果步骤一的分析发现耗时主要不在服务端处理，而是在”排队”上，那就需要进一步检查 Binder 线程池的状态了。在深入分析之前，先回答一个经常被问到的问题：**”每个进程大概会有多少个 Binder 线程？system_server 的 Binder 线程池规模大致是什么量级？什么情况下会’耗尽’？”**</p><h4 id="system-server-的-Binder-线程池规模"><a href="#system-server-的-Binder-线程池规模" class="headerlink" title="system_server 的 Binder 线程池规模"></a>system_server 的 Binder 线程池规模</h4><p>在上游 AOSP（Android 14&#x2F;15）中，Binder 线程池的设计思路是：<strong>按需增长、可配置、没有单一固定数字</strong>。</p><ul><li><strong>线程池是按需增长的</strong>：每个服务端进程在 Binder 驱动中维护一个线程池，实际线程数会根据负载按需增减，上限由内核中的 <code>max_threads</code> 字段和用户态 <code>ProcessState#setThreadPoolMaxThreadCount()</code> 等配置共同决定。</li><li><strong>典型上限取决于进程角色</strong>：普通应用进程的 Binder 工作线程上限通常在 <strong>15</strong> 左右（libbinder 默认值）；但 <code>system_server</code> 会在启动时显式把上限调高，AOSP 当前代码中设置为 <strong>31</strong>。因此，<code>system_server</code> 并不等同于“默认十几条线程”。<br>某些厂商 ROM 或定制内核会根据自身负载模型，把上限调大或调小（例如调到几十条线程），因此你在不同设备上通过 <code>ps -T system_server</code>、<code>top -H</code> 或 Perfetto 数 <code>Binder:</code> 线程时，看到的具体数字可能会有差异。</li><li><strong>以实际观测为准，而不是死记一个数字</strong>：在 Perfetto 里，更推荐的做法是直接展开某个进程，看有多少个 <code>Binder:xxx_y</code> 线程轨道，以及它们在抓 Trace 期间的活跃程度，以此来评估线程池的“规模”和“繁忙度”。</li></ul><h4 id="Binder-线程数、缓冲区与“Binder-耗尽”"><a href="#Binder-线程数、缓冲区与“Binder-耗尽”" class="headerlink" title="Binder 线程数、缓冲区与“Binder 耗尽”"></a>Binder 线程数、缓冲区与“Binder 耗尽”</h4><p>在性能分析中，大家提到“Binder 个数”时，往往会混在一起谈三类不同的资源限制：</p><p><strong>Binder 线程池耗尽</strong>是指某个进程内所有 Binder 工作线程都处于 <code>Running</code> &#x2F; <code>D</code> &#x2F; <code>S</code> 等忙碌状态，没有空闲线程可以被驱动唤醒处理新事务。其现象包括 Client 线程在 <code>thread_state</code> 轨道里长时间停留在 <code>S</code> 状态（调用栈停在 <code>ioctl(BINDER_WRITE_READ)</code> &#x2F; <code>epoll_wait</code>），并且在 SQL 里可以观察到大量事务的 <code>dispatch_dur</code>（<code>server_ts - client_ts</code>）显著偏大——说明请求在服务端真正开始处理之前就已经卡在“等线程&#x2F;等调度”上了。对于 <code>system_server</code> 这类关键进程，线程池被打满意味着系统服务响应能力下降，很容易放大为<strong>全局卡顿或 ANR</strong>。</p><p><strong>Binder 事务缓冲区耗尽</strong>涉及每个进程在 Binder 驱动里的一块有限大小的共享缓冲区（典型值约 <strong>1MB 量级</strong>），用于承载正在传输的 <code>Parcel</code> 数据。典型场景包括一次事务传输过大的对象（如大 Bitmap、超长字符串、大数组等），以及大量并发事务尚未被消费完，导致缓冲区中堆积了太多尚未释放的 <code>Parcel</code>。可能的结果包括内核日志中出现 <code>binder_transaction_alloc_buf</code> 失败、Java 层抛出 <code>TransactionTooLargeException</code>，以及后续事务在驱动层长时间排队甚至失败（看起来像是“Binder 被用光了”）。解决这类问题的思路<strong>不是</strong>通过“多开线程”，而是控制单次传输的数据量（拆包、分页、流式协议），并对大块数据优先使用 <code>SharedMemory</code> &#x2F; 文件 &#x2F; <code>ParcelFileDescriptor</code> 等机制。</p><p><strong>Binder 引用表 &#x2F; 对象数量</strong>方面，Binder 驱动会为每个进程维护引用表和节点对象，这些也有上限，但在大多数实际场景中，很少首先撞到这里。常见风险是长时间持有大量 Binder 引用却不释放，更多体现为<strong>内存&#x2F;稳定性问题</strong>，而不是 UI 卡顿。</p><p>在 Perfetto 里分析时，可以带着一个判断框架：<br><strong>“现在的慢，是因为线程池被打满，还是事务过大&#x2F;缓冲区被用光？”</strong><br>前者主要看 **Binder 线程数与它们的 <code>thread_state</code>**，以及事务的 <code>dispatch_dur</code>（<code>server_ts - client_ts</code>，可近似理解为派发&#x2F;排队延迟）；后者则关注 <strong>单次事务的大小、并发事务数量和是否伴随 <code>TransactionTooLargeException</code> &#x2F; <code>binder_transaction_alloc_buf</code> 相关日志</strong>。</p><hr><p>现在回到我们的分析场景：</p><p>Binder 线程池的繁忙程度直接决定了服务的并发处理能力。对于同步事务来说，如果服务端 Binder 线程长期处于 <code>Running</code> 或 <code>Uninterruptible Sleep (D)</code> 状态，新的请求就会在内核里排队，客户端线程会长时间阻塞在 <code>ioctl(BINDER_WRITE_READ)</code> &#x2F; <code>epoll_wait</code>，主线程在 <code>thread_state</code> 上通常表现为长段 <code>S</code>（Sleeping）。</p><p>在 Perfetto 中诊断线程池问题，优先看两个信号：<strong>Binder 线程是否长期满载</strong>，以及事务的 **<code>dispatch_dur</code> 是否显著大于 <code>server_dur</code>**（判读方式与步骤一一致）。</p><p><strong>关于 Oneway 调用在 Perfetto 中的识别</strong>：同步调用（Two-way）和异步调用（Oneway）在 Perfetto 中的表现有明显区别，学会区分它们对分析很有帮助。同步调用时，客户端会阻塞等待（<code>thread_state</code> 显示 <code>S</code>），Perfetto 通常会画出 transaction → reply 的 Flow；而 Oneway 调用客户端发完就返回、几乎无阻塞，Flow 只有单向的 transaction，没有 reply 回来。另外，Oneway 调用的 Slice 名称后面可能会带 <code>[oneway]</code> 标记；在 SQL 里也可以通过 <code>android_binder_txns.is_sync = 0</code> 来过滤 Oneway。</p><p>在分析 Oneway 相关问题时，重点关注两件事：一是服务端的队列深度（如果同一 <code>IBinder</code> 对象上的 Oneway 请求堆积，后续请求的实际执行时机会被不断延后）；二是是否存在批量发送的模式（短时间内大量 Oneway 调用会形成”尖峰”，在 Perfetto 中表现为服务端 Binder 线程上密集排列的短 Slice）。</p><p><img src="/images/image-20260207130758344.webp" alt="image-20260207130758344"></p><p>值得一提的是，SystemServer 的 Binder 线程不仅要处理来自各个 App 的请求，还要处理系统内部的调用（比如 AMS 调 WMS、WMS 调 SurfaceFlinger 等）。如果某个”行为不端”的 App 在短时间内疯狂发送 Oneway 请求，可能会把某个系统服务的 Oneway 队列塞满，进而影响到其他 App 的异步回调时延，造成全局性的卡顿感。</p><p><a id="workflow-lock"></a></p><h3 id="步骤三：排查锁竞争"><a href="#步骤三：排查锁竞争" class="headerlink" title="步骤三：排查锁竞争"></a>步骤三：排查锁竞争</h3><p>如果你跳转到服务端的 Binder 线程，发现它在处理你的请求期间长时间处于 <code>S</code>（Sleeping）或 <code>D</code>（Disk Sleep &#x2F; Uninterruptible Sleep）状态，那通常意味着它在等待某个资源——要么是在等锁，要么是在等 IO。锁竞争是 SystemServer 中非常常见的性能瓶颈来源，因为 SystemServer 里运行着大量服务，它们之间共享很多全局状态，而这些状态往往通过 <code>synchronized</code> 锁来保护。</p><p><strong>Java 锁（Monitor Contention）</strong> 是最常见的情况。SystemServer 中有不少全局锁，比如 WindowManagerService 的 <code>mGlobalLock</code>、ActivityManagerService 的一些内部锁等。当多个线程同时需要访问被这些锁保护的资源时，就会产生竞争。在 Perfetto 中，如果你看到某个 Binder 线程状态为 <code>S</code>，并且 <code>blocked_function</code> 字段包含 <code>futex</code> 相关的符号（如 <code>futex_wait</code>），那基本可以确定是在等 Java 锁。要进一步确认是在等哪个锁、被谁持有，可以查看 <code>Lock contention</code> 轨道。Perfetto 会把锁竞争的关系可视化出来：用连接线标出 Owner（持有锁的线程，比如 <code>android.display</code> 线程）和 Waiter（等待锁的线程，比如处理你请求的 <code>Binder:123_1</code>）。点击 Contention Slice，还可以在 Details 面板里看到锁对象的类名（比如 <code>com.android.server.wm.WindowManagerGlobalLock</code>），这对于理解问题的根源非常有帮助。</p><p><img src="/images/image-20260207130953951.webp" alt="image-20260207130953951"></p><p><strong>Native 锁（Mutex &#x2F; RwLock）</strong> 的情况相对少见一些，但在某些场景下也会遇到。表现形式类似：线程状态为 <code>D</code> 或 <code>S</code>，但调用栈里出现的是 <code>__mutex_lock</code>、<code>pthread_mutex_lock</code>、<code>rwsem</code> 等 Native 层的符号，而不是 Java 的 <code>futex_wait</code>。分析这类问题通常需要结合 <code>sched_blocked_reason</code> 事件来看线程具体在等什么，属于比较进阶的内容，这里就不展开了。</p><h4 id="使用-SQL-统计-system-server-中的-Java-Monitor-Contention（可选）"><a href="#使用-SQL-统计-system-server-中的-Java-Monitor-Contention（可选）" class="headerlink" title="使用 SQL 统计 system_server 中的 Java Monitor Contention（可选）"></a>使用 SQL 统计 system_server 中的 Java Monitor Contention（可选）</h4><p>PerfettoSQL 标准库已经提供了解析后的 <code>android_monitor_contention</code> 表（由 ART 的 monitor contention 相关 Slice 解析而来），建议优先使用它来做统计，而不是手工解析 slice 名称字符串：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">INCLUDE PERFETTO <span class="keyword">MODULE</span> android.monitor_contention;</span><br><span class="line"></span><br><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  process_name,</span><br><span class="line">  blocked_thread_name <span class="keyword">AS</span> waiter_thread,</span><br><span class="line">  blocking_thread_name <span class="keyword">AS</span> owner_thread,</span><br><span class="line">  (dur <span class="operator">/</span> <span class="number">1e6</span>) <span class="keyword">AS</span> dur_ms,</span><br><span class="line">  (waiter_count <span class="operator">+</span> <span class="number">1</span>) <span class="keyword">AS</span> waiter_threads,</span><br><span class="line">  short_blocked_method,</span><br><span class="line">  short_blocking_method,</span><br><span class="line">  blocked_src,</span><br><span class="line">  blocking_src</span><br><span class="line"><span class="keyword">FROM</span> android_monitor_contention</span><br><span class="line"><span class="keyword">WHERE</span> process_name <span class="operator">=</span> <span class="string">&#x27;system_server&#x27;</span></span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> dur <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">50</span>;</span><br></pre></td></tr></table></figure><blockquote><p><strong>提示</strong>：如果查不到数据，请确认抓取时 <code>atrace_categories</code> 包含 <code>dalvik</code>，并且问题场景中确实发生了 monitor contention。</p></blockquote><p><img src="/images/image-20260207105941270.webp" alt="image-20260207105941270"></p><p><a id="platform-features"></a></p><h2 id="最新平台特性与优化建议"><a href="#最新平台特性与优化建议" class="headerlink" title="最新平台特性与优化建议"></a>最新平台特性与优化建议</h2><p>随着 Android 版本演进，Binder 在性能与稳定性上也持续增强。理解这些机制有助于解释 Perfetto 现象并指导优化。</p><p><strong>Binder Freeze（Android 12+）</strong>：Cached 进程被冻结后几乎不获得 CPU。对其发起同步 Binder 调用会被拒绝，并可能触发目标进程终止；异步（<code>oneway</code>）事务通常先缓冲，待解冻后处理。</p><p><strong>Frozen-callee 回调策略（Android 14+ 常见）</strong>：可用 <code>RemoteCallbackList</code> 的 policy（<code>DROP</code>、<code>ENQUEUE_MOST_RECENT</code>、<code>ENQUEUE_ALL</code>）控制冻结期间回调堆积，降低解冻后的抖动与压力。</p><p><strong>Binder Heavy Hitter Watcher</strong>：用于识别短时间内占比异常高的 Binder 调用热点。启用方式、阈值和输出渠道依版本与设备配置而定。</p><p><strong>给开发者的一些建议</strong>：</p><p>关于 <strong>Oneway</strong>：只在确实不需要返回值和完成时机时使用（如日志、状态通知）。把同步调用硬改成 Oneway 往往只会把等待转移到服务端队列，并引入时序问题。</p><p>关于 <strong>大数据传输</strong>：避免直接走 Binder（尤其是 Bitmap）。单进程 Binder 缓冲区约 1MB，容易触发 <code>TransactionTooLargeException</code>；应改用 <code>SharedMemory</code>、文件或 <code>ParcelFileDescriptor</code>。</p><p>关于 <strong>主线程调用</strong>：不要在 UI 线程调用耗时不可控的 Binder 服务；若必须调用，请放到后台线程，完成后再回主线程更新 UI。</p><p><a id="summary"></a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Perfetto 是分析 Binder 问题的高效工具。核心方法是：用 <code>linux.ftrace</code> 抓取 binder&#x2F;sched&#x2F;dalvik 信号，在 UI 中沿 Flow 串联 Client 与 Server，再结合 <code>client_dur</code> &#x2F; <code>server_dur</code> &#x2F; <code>dispatch_dur</code>、线程状态和锁竞争，区分“排队慢”“处理慢”“等锁”。</p><p>遇到难解释的 UI 卡顿或 ANR 时，可按“主线程是否在等 Binder → 服务端是否排队&#x2F;处理慢&#x2F;等锁”的顺序排查。再结合 CPU、调度、渲染等信号，通常能更快定位根因。</p><p><a id="refs"></a></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol><li><a href="https://paul.pub/android-binder-driver/">理解Android Binder机制1&#x2F;3：驱动篇</a></li><li><a href="https://perfetto.dev/docs/analysis/stdlib-docs#android-binder">PerfettoSQL stdlib - android.binder</a></li><li><a href="https://perfetto.dev/docs/data-sources/cpu-scheduling">Perfetto Documentation - Ftrace</a></li><li><a href="https://cs.android.com/android/platform/superproject/+/main:frameworks/native/libs/binder/">Android Source - Binder</a></li><li><a href="https://developer.android.com/reference/android/os/Parcel">Android Developers - Parcel and Bundle</a></li><li><a href="https://github.com/foundryzero/binder-trace">binder-trace - Wireshark for Binder</a></li><li><a href="https://www.cnblogs.com/wanghongzhu/p/15069884.html">am trace-ipc 源码分析</a></li></ol><p>* </p><p><a id="about"></a></p><h2 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h2><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a></li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a></li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a></li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Perfetto 系列的第十篇文章，聚焦 Binder 这一 Android 跨进程通信的核心机制。Binder 承载着大部分系统服务与应用的交互，也常常是性能瓶颈的源头。本文站在系统开发与性能调优的视角，结合 &lt;code&gt;linux.ftrace&lt;/code&gt;（binder tracepoints + sched）、&lt;code&gt;thread_state&lt;/code&gt; 轨道，以及 ART 的 Java Monitor Contention（通过 atrace 的 &lt;code&gt;dalvik&lt;/code&gt; 类别采集）等信号，给出一套可直接落地的诊断流程，帮助初学者和进阶开发者定位耗时、线程池压力与锁竞争等问题。&lt;/p&gt;
&lt;p&gt;本系列的目标，就是通过 Perfetto 这个工具，从一个全新的图形化视角，来审视 Android 系统的整体运行，同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章，但总是对繁杂的调用链感到困惑，或者记不住具体的执行流程。那么通过 Perfetto，将这些流程可视化，你可能会对系统有更深入、更直观的理解。&lt;/p&gt;</summary>
    
    
    
    <category term="Android性能优化" scheme="https://androidperformance.com/categories/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="性能优化" scheme="https://androidperformance.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    <category term="Binder" scheme="https://androidperformance.com/tags/Binder/"/>
    
    <category term="线程优化" scheme="https://androidperformance.com/tags/%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%8C%96/"/>
    
    <category term="卡顿优化" scheme="https://androidperformance.com/tags/%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%96/"/>
    
    <category term="ANR分析" scheme="https://androidperformance.com/tags/ANR%E5%88%86%E6%9E%90/"/>
    
    <category term="页面响应" scheme="https://androidperformance.com/tags/%E9%A1%B5%E9%9D%A2%E5%93%8D%E5%BA%94/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 9 - CPU 信息解读</title>
    <link href="https://androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/"/>
    <id>https://androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/</id>
    <published>2025-11-12T02:00:00.000Z</published>
    <updated>2026-02-07T05:17:47.837Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Perfetto 系列的第九篇文章，主题是 Perfetto 中的 CPU 信息分析。Perfetto 提供了远超 Systrace 的数据可视化与分析能力，理解 CPU 相关信息是定位性能瓶颈、分析功耗问题的基础。</p><p>本系列的目标，就是通过 Perfetto 这个工具，从一个全新的图形化视角，来审视 Android 系统的整体运行，同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章，但总是对繁杂的调用链感到困惑，或者记不住具体的执行流程。那么通过 Perfetto，将这些流程可视化，你可能会对系统有更深入、更直观的理解。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列文章</a></li><li><a href="#overview">Perfetto 中的 CPU 信息概览</a></li><li><a href="#traceconfig">抓取 CPU 信息所需要的 Trace Config</a></li><li><a href="#biglittle">CPU 核心架构：big.LITTLE</a></li><li><a href="#scheduling">CPU 调度</a></li><li><a href="#frequency">CPU Frequency (CPU 频率) 深度解析</a></li><li><a href="#eas">Linux 内核调度策略：选核与迁移</a></li><li><a href="#practices-sql">实战与 SQL</a></li><li><a href="#summary">总结</a></li></ul><p><a id="series"></a></p><h2 id="Perfetto-系列文章"><a href="#Perfetto-系列文章" class="headerlink" title="Perfetto 系列文章"></a>Perfetto 系列文章</h2><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p><a id="overview"></a></p><h2 id="Perfetto-中的-CPU-信息概览"><a href="#Perfetto-中的-CPU-信息概览" class="headerlink" title="Perfetto 中的 CPU 信息概览"></a>Perfetto 中的 CPU 信息概览</h2><p>在 Perfetto UI 中，CPU 相关的信息通常分组置于顶部，是性能分析的起点。主要包含以下三个核心轨道：</p><ul><li><strong>CPU Scheduling (CPU 调度)</strong>: 显示在每个时间点，各个 CPU 核心上正在执行的线程。</li><li><strong>CPU Frequency (CPU 频率)</strong>: 显示每个 CPU 核心或核心簇的频率变化情况。</li><li><strong>CPU Idle (CPU 空闲状态)</strong>: 显示每个 CPU 核心进入的低功耗状态 (C-States)。</li></ul><p><img src="/images/Android-Perfetto-09-CPU/image-20251112214415666.webp" alt="image-20251112214415666"></p><p>通过分析 CPU 相关的信息，可以解答以下关键性能问题， 或者进行竞品分析：</p><ul><li>应用主线程为何没有执行？是否被其他线程抢占？</li><li>某个任务执行缓慢的原因是什么？是否被调度到了低性能核心？</li><li>在特定场景下，CPU 频率是否受限？</li><li>应用在后台时，CPU 是否有效进入了深度睡眠状态？</li></ul><p><a id="traceconfig"></a></p><h2 id="抓取-CPU-信息所需要的-Trace-Config"><a href="#抓取-CPU-信息所需要的-Trace-Config" class="headerlink" title="抓取 CPU 信息所需要的 Trace Config"></a>抓取 CPU 信息所需要的 Trace Config</h2><p>为了采集到本文分析所需的所有 CPU 数据，你需要一个正确的 <code>TraceConfig</code>。不正确的配置会导致某些轨道（如 CPU 频率）或某些内核事件（如唤醒事件）丢失。以下是 Perfetto 官方文档推荐的、用于通用 CPU 分析的配置。你可以将其添加到你的  Perfetto 的 Config 中，抓 Trace 的时候使用。</p><figure class="highlight protobuf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">data_sources &#123;</span><br><span class="line">  config &#123;</span><br><span class="line">    name: <span class="string">&quot;linux.ftrace&quot;</span></span><br><span class="line">    ftrace_config &#123;</span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_process_exit&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_process_free&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;task/task_newtask&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;task/task_rename&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_switch&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;power/suspend_resume&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_blocked_reason&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_wakeup&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_wakeup_new&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_waking&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_process_exit&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;sched/sched_process_free&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;task/task_newtask&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;task/task_rename&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;power/cpu_frequency&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;power/cpu_idle&quot;</span></span><br><span class="line">      ftrace_events: <span class="string">&quot;power/suspend_resume&quot;</span></span><br><span class="line">      symbolize_ksyms: <span class="literal">true</span></span><br><span class="line">      disable_generic_events: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources &#123;</span><br><span class="line">  config &#123;</span><br><span class="line">    name: <span class="string">&quot;linux.process_stats&quot;</span></span><br><span class="line">    process_stats_config &#123;</span><br><span class="line">      scan_all_processes_on_start: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">data_sources &#123;</span><br><span class="line">  config &#123;</span><br><span class="line">    name: <span class="string">&quot;linux.sys_stats&quot;</span></span><br><span class="line">    sys_stats_config &#123;</span><br><span class="line">      cpufreq_period_ms: <span class="number">250</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这个配置启用了包括 <code>sched</code>（调度）、<code>power</code>（频率和空闲）、<code>task</code>（任务生命周期）在内的关键 ftrace 事件，是进行深度 CPU 分析的基础。</p><p><a id="biglittle"></a></p><h2 id="CPU-核心架构：big-LITTLE"><a href="#CPU-核心架构：big-LITTLE" class="headerlink" title="CPU 核心架构：big.LITTLE"></a>CPU 核心架构：big.LITTLE</h2><p>在深入分析前，必须了解现代手机 SoC 的 CPU 核心架构。目前主流的移动处理器普遍采用 <code>big.LITTLE</code> 异构多核架构，或其变种，如 <code>big.Medium.LITTLE</code>（大中小核）。</p><ul><li><strong>小核 (LITTLE cores)</strong>: 针对低功耗设计，频率较低，用于处理后台任务、轻量级计算，以保证续航。</li><li><strong>大核 (big cores)</strong>: 针对高性能设计，频率更高，功耗也更大，用于处理用户交互、游戏、应用启动等重负载场景。</li><li><strong>超大核 (Prime Core)</strong>: 部分旗舰芯片会有一个频率极高的超大核，用于应对最严苛的单核性能挑战。</li></ul><p>在 Perfetto 的 CPU 轨道中，核心通常从 0 开始编号。例如，在一个典型的八核处理器中，<code>CPU 0-3</code> 可能为小核，<code>CPU 4-6</code> 为大核，<code>CPU 7</code> 为超大核。<strong>识别核心类型对于性能分析至关重要</strong>：一个计算密集型任务如果长时间运行在小核上，其耗时必然远超预期。分析时，需要将线程的运行核心与其任务属性进行匹配，以判断调度器（Scheduler）的行为是否符合预期。</p><p>下面是一个典型的 4+4 的 CPU ：</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112214626810.webp" alt="image-20251112214626810"></p><p>下面是一个典型的 4+3+1 的 CPU （ MTK 的天玑 9500、9400 ，以及高通骁龙高端系列）：</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112215105550.webp" alt="image-20251112215105550"></p><p>下面是一个 5+2 的 CPU （高通 8Elite 1 阉割版，8Elite 1 的标准版 0-5 是小核，6-7 是大核，这里就不放图了）</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112214833158.webp" alt="image-20251112214833158"></p><p>一般通过查阅 CPU 的 spec 就可以知道他的大中小核心的架构，或者 cat 对应的 CPU 节点也可以，这里就不再赘述了。</p><p><a id="scheduling"></a></p><h2 id="CPU-调度"><a href="#CPU-调度" class="headerlink" title="CPU 调度"></a>CPU 调度</h2><p><code>CPU Scheduling</code> 轨道是最常用且最重要的部分，它可视化了 Linux 内核调度器的决策过程。其数据来源于内核 ftrace 中的 <code>sched/sched_switch</code> 事件。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112215332310.webp" alt="CPU 调度区域"></p><p>每个 CPU 核心对应一行独立的轨迹。轨道上的不同色块，代表了在该时间片上，特定线程正在该 CPU 核心上运行。</p><ul><li>UI 细节：点击 CPU 切片，详情面板会显示该次调度的 <code>cpu</code>、<code>end_state</code>、<code>priority</code>、所属 <code>process/thread</code> 等；向下展开进程还能看到每个线程的独立轨道，便于跟踪单个线程的状态演化（参考 <a href="https://perfetto.dev/docs/data-sources/cpu-scheduling">官方文档</a>）。</li></ul><p><img src="/images/Android-Perfetto-09-CPU/image-20251112215540626.webp" alt="image-20251112215540626"></p><h3 id="线程状态深度解析"><a href="#线程状态深度解析" class="headerlink" title="线程状态深度解析"></a>线程状态深度解析</h3><p>理解 Linux 的线程状态是进行性能优化的前提。在 Perfetto 中，选中一个线程，下方的 <code>Current State</code> 面板会显示其当前状态。这些状态信息来源于 Perfetto 解析得到的 <code>thread_state</code>&#x2F;<code>thread_state_slice</code> 表。</p><h4 id="Running-绿色"><a href="#Running-绿色" class="headerlink" title="Running (绿色)"></a>Running (绿色)</h4><p><strong>状态定义</strong>：绿色代表线程正在 CPU 上执行代码。这是唯一真正在消耗 CPU 资源进行计算的状态。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112215705017.webp" alt="image-20251112215705017"></p><p><strong>分析要点</strong>：</p><ul><li><strong>执行时长</strong>：过长的 <code>Running</code> 状态，尤其是在关键线程上，通常意味着密集的计算任务，例如复杂的算法或循环。这会增加任务耗时，并可能阻塞其他线程的执行。</li><li><strong>运行核心</strong>：结合 CPU 的核心架构（例如 big.LITTLE）进行分析。一个计算密集型任务是否被调度到了预期的性能核心（大核）上，是评估调度策略是否合理的重要依据。</li><li><strong>运行频率</strong>：线程的实际执行速度也受 CPU 频率影响。即使线程运行在大核上，如果因为温控等原因导致降频，其性能表现也会下降。因此需要结合 <code>CPU Frequency</code> 轨道进行综合分析。</li></ul><h4 id="R-Runnable-x2F-可运行"><a href="#R-Runnable-x2F-可运行" class="headerlink" title="R (Runnable &#x2F; 可运行)"></a>R (Runnable &#x2F; 可运行)</h4><p><strong>状态定义</strong>：线程已具备所有运行条件，正在等待调度器分配 CPU 核心。在 Perfetto 的线程私有轨道中，<code>Runnable</code> 状态通常以浅绿色或白色条显示。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112220248837.webp" alt="image-20251112220248837"></p><p><strong>分析要点</strong>：</p><ul><li><strong><code>Runnable</code> 与卡顿</strong>：对于 UI 线程这类对响应时间敏感的线程，长时间处于 <code>Runnable</code> 状态是造成卡顿的直接原因。它意味着线程无法及时获得 CPU 时间来处理任务（如 UI 绘制），从而导致掉帧。</li></ul><p><strong><code>Runnable</code> 的三种类型</strong>:<br>仔细观察会发现，线程进入 <code>Runnable</code> 状态的“前身”各不相同。根据内核的调度时机，我们可以将其分为三种情况：</p><ol><li><p><strong>从睡眠中唤醒-Wake-up</strong>：这是最常见的一种。线程因等待的资源（如锁、I&#x2F;O、Binder 回复）已经就绪，从 <code>S</code> 或 <code>D</code> 状态被唤醒，进入 <code>Runnable</code> 状态，等待被调度器选中执行。</p></li><li><p><strong>用户抢占-User Preemption</strong>：指线程的运行时间片用完，或出现更高优先级的任务，导致调度器在<strong>从内核态返回用户态时</strong>（如系统调用、中断返回后）决定换下当前线程。此时，被换下的线程从 <code>Running</code> 变为 <code>Runnable</code>。在底层的 <code>sched_switch</code> trace 中，它的 <code>prev_state</code> 标记为 <code>R</code>。</p></li><li><p><strong>内核抢占-Kernel Preemption</strong>：指一个更高优先级的任务或中断，在当前线程<strong>正在执行内核态代码期间</strong>，就“强行”将其打断，使其让出 CPU。这种情况通常意味着一次更紧急的调度。此时，被换下的线程从 <code>Running</code> 变为 <code>Runnable (Preempted)</code>。在底层 trace 中，它的 <code>prev_state</code> 标记为 <code>R+</code>，Perfetto 据此进行了解析和展示。</p></li></ol><p>理解这三种类型的区别，有助于更精细地判断调度延迟的原因。例如，大量的 <code>Runnable (Preempted)</code> 可能暗示着系统中存在频繁的、高优先级的唤醒源，导致关键线程在内核态中被频繁打断，或者当时的 CPU 已经满载了（比较常见的情况），这时候优先级比较低的 Task 就很容易被其他优先级高的 Task 抢占，被迫让出 CPU 。如果你的关键 Task 总是被抢占，那就需要调整优先级。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112220534899.webp" alt="image-20251112220534899"></p><h4 id="S-Sleep-x2F-可中断睡眠"><a href="#S-Sleep-x2F-可中断睡眠" class="headerlink" title="S (Sleep &#x2F; 可中断睡眠)"></a>S (Sleep &#x2F; 可中断睡眠)</h4><p><strong>状态定义</strong>：线程因等待某个事件而进入睡眠，可以被信号中断。这是最常见的睡眠状态，通常情况下是良性的，因为它在等待期间不消耗 CPU 资源。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112221122170.webp" alt="image-20251112221122170"></p><p><strong>分析要点</strong>：</p><ul><li><strong>等待的资源</strong>：如果关键线程（如 UI 线程）睡眠时间过长，同样会引发性能问题。常见的等待原因包括：<ul><li><strong>锁竞争</strong>：等待获取一个 <code>mutex</code> (Java 锁或 native futex)。</li><li><strong>Binder 通信</strong>：等待另一个进程通过 Binder 调用返回结果。</li><li><strong>I&#x2F;O 操作</strong>：等待网络 socket 数据 (<code>epoll_wait</code>)。</li><li><strong>显式休眠</strong>：代码中调用了 <code>Thread.sleep()</code> 或 <code>Object.wait()</code>。</li></ul></li><li><strong>依赖分析</strong>：在 Perfetto 中，选中 CPU 区域被唤醒正在 Running 的 Task，就会有 UI 标识他是被哪个 CPU 上的哪个 Task 唤醒的，这有助于快速定位线程间的依赖关系。结合函数调用栈，可以进一步定位导致睡眠的具体代码。<br><img src="/images/Android-Perfetto-09-CPU/image-20251112221703122.webp" alt="image-20251112221703122"></li></ul><h4 id="D-Uninterruptible-Sleep-x2F-不可中断睡眠"><a href="#D-Uninterruptible-Sleep-x2F-不可中断睡眠" class="headerlink" title="D (Uninterruptible Sleep &#x2F; 不可中断睡眠)"></a>D (Uninterruptible Sleep &#x2F; 不可中断睡眠)</h4><p><strong>状态定义</strong>：线程在等待硬件 I&#x2F;O 操作完成，期间不能被任何信号中断。此状态旨在保护进程与设备交互过程中的数据一致性。在 Perfetto 中，该状态通常显示为橙色或红色，是需要重点关注的信号。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112220738143.webp" alt="image-20251112220738143"></p><p><strong>分析要点</strong>：</p><ul><li><strong>严重的性能瓶颈</strong>：长时间的 <code>D</code> 状态意味着线程被完全阻塞，无法响应任何事件。如果发生在 UI 线程，极易导致 ANR。</li><li><strong>常见原因</strong>：<ol><li><strong>磁盘 I&#x2F;O</strong>：频繁或单次大量的文件读写操作。</li><li><strong>内存压力</strong>：系统物理内存不足，导致频繁的页面换入&#x2F;换出 (swap)，其本质是高频的磁盘 I&#x2F;O。</li><li><strong>内核驱动问题</strong>：部分内核驱动的实现缺陷也可能导致线程陷入 <code>D</code> 状态。</li></ol></li><li><strong>排查方向</strong>：在 <code>Current State</code> 面板中，如果 <code>D</code> 状态伴有 <code>(iowait)</code> 标记，则明确表示在等待 I&#x2F;O。需要检查应用的 I&#x2F;O 模式，评估其合理性，例如是否将耗时 I&#x2F;O 操作置于主线程，或是否存在可优化的空间（如减少 I&#x2F;O 次数和数据量）。</li></ul><h3 id="唤醒关系分析"><a href="#唤醒关系分析" class="headerlink" title="唤醒关系分析"></a>唤醒关系分析</h3><p>线程间的依赖关系是性能分析中的一个难点。一个线程长时间 <code>Sleep</code>，关键在于找出它在“等谁”。Perfetto 提供了强大的唤醒关系可视化功能。</p><ul><li><strong>UI 操作</strong>：在 Perfetto 的 CPU 区域中，用鼠标左键单击选中一个处于 <code>Running</code>   状态的线程 Task 。Perfetto 会自动绘制一条从“唤醒者”到“被唤醒者”的依赖箭头，并高亮显示唤醒源所在的线程切片。<br><img src="/images/Android-Perfetto-09-CPU/image-20251112221703122.webp" alt="image-20251112221703122"></li><li><strong>底层原理</strong>：该功能依赖于内核的 <code>sched_wakeup</code> ftrace 事件。当线程 T1 释放了某个资源（如解锁、完成 Binder 调用），而线程 T2 正在等待该资源时，内核会将 T2 标记为 <code>Runnable</code> 状态，并记录下 T1 -&gt; T2 的这次唤醒事件。Perfetto 解析这些事件，从而构建出线程间的依赖链。</li></ul><p>通过唤醒分析，可以清晰地追踪复杂的调用链，例如：UI 线程等待一个 Binder 调用 -&gt; Binder 线程执行任务 -&gt; Binder 线程等待另一个锁 -&gt; 持锁线程释放锁并唤醒 Binder 线程 -&gt; Binder 线程完成任务并唤醒 UI 线程。整个过程中的瓶颈点将一目了然。</p><h4 id="调度唤醒与延迟分析"><a href="#调度唤醒与延迟分析" class="headerlink" title="调度唤醒与延迟分析"></a>调度唤醒与延迟分析</h4><p>当线程 A 在 <code>wait()</code> 上挂起时，它进入 <code>S</code>（Sleeping）并从 CPU 运行队列移除。线程 B 调用 <code>notify()</code> 后，内核会把线程 A 转换为 <code>R</code>（Runnable）。此时线程 A“有资格”被放回某个 CPU 的运行队列，但这并不意味着“立刻”运行。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112222240991.webp" alt="image-20251112222240991"></p><p>常见等待原因包括：</p><ul><li>所有 CPU 都在忙：线程 A 需要等待空出队列槽位（或当前运行的线程优先级更高）。</li><li>存在空闲 CPU，但迁移需时：负载均衡器将线程迁移到其他 CPU 需要一定时间窗口。</li></ul><p>除非使用实时优先级，大多数 Linux 调度器配置并非严格的“work‑conserving”。调度器有时会“等待”当前 CPU 线程自然空闲，以避免跨 CPU 迁移带来的额外开销与功耗。这会在 <code>R</code> 状态下形成可观测的“排队延迟”。结合 <code>sched_waking</code>&#x2F;<code>sched_wakeup_new</code> 可更精确地刻画“被唤醒”到“真正入队&#x2F;运行”之间的时间段（参考官方文档：<code>sched_waking / sched_wakeup</code> 的差异与适用场景）。</p><ul><li>在目标线程的 <code>thread_state</code> 轨道中筛选 <code>state=R</code> 的切片，用作“调度延迟”的直接证据。</li><li>同步对照同一 CPU 的其它重负载线程与 IRQ&#x2F;SoftIRQ 轨迹，验证是否存在时间重叠的抢占或高优先级压制。</li><li>若频繁在 <code>R</code> 状态排队并以 <code>end_state=R+</code> 收尾，视为非自愿抢占严重，需评估优先级、放置与负载均衡策略。</li></ul><h4 id="sched-waking-与-sched-wakeup-的差异与非严格-work-conserving"><a href="#sched-waking-与-sched-wakeup-的差异与非严格-work-conserving" class="headerlink" title="sched_waking 与 sched_wakeup 的差异与非严格 work-conserving"></a>sched_waking 与 sched_wakeup 的差异与非严格 work-conserving</h4><ul><li><code>sched_waking</code> 在线程被标记为可运行（R）时就会发出，<code>sched_wakeup</code> 与跨 CPU 唤醒有关，可能记录在源或目的 CPU 上；对大多数延迟分析而言，仅 <code>sched_waking</code> 已足够（参见官方说明）。</li><li>多数 Linux 调度配置在通用优先级下并非严格“work-conserving”。调度器有时会“等待当前 CPU 空闲”以避免跨核迁移带来的额外开销与功耗，这会造成 R 状态下的等待时间（排队延迟）并非异常，而是权衡后的结果（参见 <a href="https://perfetto.dev/docs/data-sources/cpu-scheduling">Perfetto CPU Scheduling</a>）。</li></ul><h3 id="用户态与内核态：sys-切片定位空火焰图"><a href="#用户态与内核态：sys-切片定位空火焰图" class="headerlink" title="用户态与内核态：sys_* 切片定位空火焰图"></a>用户态与内核态：sys_* 切片定位空火焰图</h3><ul><li><code>Running</code> 的绿色切片未必都是应用代码在忙。若线程陷入单个长系统调用如 <code>sys_read</code>、<code>sys_futex</code>，用户态采样火焰图可能几乎为空。</li><li>若 UI 线程某段 <code>sched_slice</code> 很长，而 CPU 火焰图热点很少或几乎没有：<ol><li>打开该线程的 <code>slice</code> 视图，查找是否存在长时间 <code>sys_*</code> 切片；</li><li>若存在：瓶颈多在 I&#x2F;O 或同步原语，优先检查 I&#x2F;O 路径、锁粒度与访问模式；</li><li>若不存在：回到火焰图，继续剖析用户态热点函数。</li></ol></li><li>建议：合并 I&#x2F;O、切换异步、优化锁竞争、降低系统调用频率与单次数据量。</li></ul><h3 id="CPU-时间与墙上时间"><a href="#CPU-时间与墙上时间" class="headerlink" title="CPU 时间与墙上时间"></a>CPU 时间与墙上时间</h3><p><img src="/images/Android-Perfetto-09-CPU/image-20251112223105996.webp" alt="image-20251112223105996"></p><ul><li><code>Wall</code> 是切片从开始到结束的真实世界时间，<code>CPU</code> 是真正在 CPU 上运行的时间。</li><li>二者关系：<code>Wall = CPU + Runnable + Sleep</code>。<ol><li>选中目标切片（如 <code>Choreographer#doFrame</code>），比较 <code>Wall</code> 与 <code>CPU</code>；</li><li>若 <code>Wall ≈ CPU</code>，则为计算过重，使用火焰图定位热点；\n  3) 若 <code>Wall &gt;&gt; CPU</code>，则为调度或依赖等待，查看 <code>thread_state</code> 的 <code>R/S/D</code> 分布与唤醒链。</li></ol></li></ul><p><a id="frequency"></a></p><h2 id="CPU-Frequency-CPU-频率-深度解析"><a href="#CPU-Frequency-CPU-频率-深度解析" class="headerlink" title="CPU Frequency (CPU 频率) 深度解析"></a>CPU Frequency (CPU 频率) 深度解析</h2><p>CPU 频率直接影响代码执行速度，同时也与功耗正相关。<code>CPU Frequency</code> 轨道显示了每个核心在特定时间的运行频率（<code>scaling_cur_freq</code>）。但更重要的是理解其背后的限制因素：</p><h3 id="影响-CPU-频率的核心因素"><a href="#影响-CPU-频率的核心因素" class="headerlink" title="影响 CPU 频率的核心因素"></a>影响 CPU 频率的核心因素</h3><ol><li><p><strong>任务负载 (Task Utilization)</strong>: 这是最主要的驱动因素。现代 Android 系统多采用 <code>schedutil</code> 作为 <code>cpufreq</code> 调频策略。它直接关联调度器（Scheduler），根据线程的“繁忙”程度（<code>utilization</code>）来决定频率。一个高负载的线程（<code>util</code> 很高）会促使 <code>schedutil</code> 请求更高的频率，反之亦然。</p></li><li><p><strong>场景策略 (Power HAL)</strong>: Android Framework 通过 Power HAL 向底层传递当前的系统状态，即“场景”。例如，在应用启动、游戏、或触摸屏幕时，Power HAL 会向内核请求更高的性能，这通常通过抬高 CPU 的<strong>地板频 (Floor &#x2F; <code>scaling_min_freq</code>)</strong> 和&#x2F;或 <strong>天花板频 (Ceiling &#x2F; <code>scaling_max_freq</code>)</strong> 来实现，确保 CPU 能快速响应。</p></li><li><p><strong>温度控制 (Thermal Throttling)</strong>: 这是拥有最高优先级的限制。当设备温度（来自电池、CPU、NPU 等传感器）超过预设阈值时，温控系统会强制降低 CPU 的<strong>天花板频</strong>，以减少发热，保护硬件。此时，即使有高负载任务，CPU 频率也无法提升，是导致游戏掉帧、应用卡顿的常见外部原因。</p></li><li><p><strong>功耗限制与省电模式</strong>: 在低电量或开启省电模式时，系统同样会通过降低天花板频、限制最高性能输出来延长续航。</p></li></ol><p>因此，当发现一个重任务在运行时 CPU 频率上不去，不仅要看当前频率，更要关注 <code>scaling_max_freq</code> 是否被限制了。这通常意味着性能瓶颈的根源不在于应用代码本身，而在于系统的温控或功耗策略。</p><p><img src="/images/Android-Perfetto-09-CPU/image-20251112223237018.webp" alt="image-20251112223237018"></p><p>上图是 CPU 频率区域的标识图，鼠标放上去就可以看到当前的频率，CPU 频率变化很快。图中 CPU 有带颜色和不带颜色的部分，带颜色标识当前 CPU 有 Task 在跑，不带颜色说明当前 CPU 是空的，无 Task。</p><h3 id="频率数据采集与平台差异"><a href="#频率数据采集与平台差异" class="headerlink" title="频率数据采集与平台差异"></a>频率数据采集与平台差异</h3><ul><li>两种获取方式：<ul><li>事件驱动：启用 <code>power/cpu_frequency</code>，在内核 cpufreq 驱动变更频率时记录事件。并非所有平台支持，在多数 ARM SoC 上可靠，在大量现代 Intel 平台上常无数据。</li><li>轮询采样：启用 <code>linux.sys_stats</code> 并设置 <code>cpufreq_period_ms &gt; 0</code>，定期读取 <code>/sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq</code>，ARM&#x2F;Intel 均可用。建议与事件驱动组合使用以补齐“初始频率快照”。</li></ul></li><li>Android 设备常以“簇”为单位调频，常见现象是同簇内多个 CPU 同步变频。</li><li>已知问题：<ul><li>事件仅在频率变化时产生，短 Trace 或稳定场景可能出现左侧“空白”。此时需依赖轮询补足。</li><li>某些 UI 版本在未捕获 Idle 状态时不渲染 cpufreq 轨道，但数据仍可通过查询得到。</li><li>参考：<code>CPU frequency and idle states</code> 官方说明。</li></ul></li></ul><h3 id="big-LITTLE：相同频率不等于相同性能或能耗"><a href="#big-LITTLE：相同频率不等于相同性能或能耗" class="headerlink" title="big.LITTLE：相同频率不等于相同性能或能耗"></a>big.LITTLE：相同频率不等于相同性能或能耗</h3><ul><li>在异构 CPU 上，同为 2.0GHz，小核与大核的实际算力和能耗并不等价。频率必须结合核心类型理解。</li><li>核心容量（capacity）与 IPC：大核通常具备更宽的乱序执行、更多执行端口、更大的缓存与更激进的预取&#x2F;分支预测，单位周期完成的指令数（IPC）更高；同频下，大核完成同样工作所需时间更短、能量更少。</li><li>簇级 DVFS（cluster-level DVFS）：移动 SoC 多以“簇”为单位调频。小核簇的高频并不等同于大核簇的中频性能输出；不同簇的电压-频率-能量曲线不同，同一频点无法横向类比“每瓦性能”。</li><li>内存&#x2F;缓存&#x2F;互联瓶颈：热点若受制于内存带宽、LLC 命中率或系统互联（NoC），单纯提升小核频率收益有限；大核更大的缓存&#x2F;更强预取可显著降低同任务的访存等待，体现“同频不同效”。</li><li>能效曲线非线性：小核在接近最高频时常进入能效陡降区（电压抬升使边际能耗激增），而大核在中等频点可能达到“更高单位能效”。同频比较忽略了“电压-频率-能量”的三维权衡。</li></ul><h4 id="P-States-与-governor"><a href="#P-States-与-governor" class="headerlink" title="P-States 与 governor"></a>P-States 与 governor</h4><ul><li>CPU 频率并非连续值，而是离散的性能状态 P-States。常用 governor 为 <code>schedutil</code>，依据任务利用率等信号选择合适 P-State，并联动电压调节。</li><li>UI 上的频率变化，实质是 governor 在不同 P-State 之间切换。高频不必然更快或更省电，需要结合核簇类型与当时的上限&#x2F;下限约束理解。</li><li>核验步骤：<ol><li>在 <code>CPU Frequency</code> 观察频率的上下限是否被拉高&#x2F;压低（如场景策略&#x2F;温控限制 <code>scaling_max_freq</code>）。 </li><li>在 <code>CPU Scheduling</code> 查看关键线程运行在哪类核心（结合设备的核编号划分）。</li><li>若“高频 + 小核 + 仍慢”，优先考虑选核&#x2F;访存瓶颈，而非简单“频率不够”。</li></ol></li><li>建议结合 Power HAL 场景、热管理与任务画像（CPU 密集或访存密集）综合优化，避免单纯以频率作为调参目标。参考官方文档： <a href="https://perfetto.dev/docs/data-sources/cpu-scheduling">Perfetto CPU Scheduling</a>。</li></ul><p><a id="eas"></a></p><h2 id="Linux-内核调度策略：选核与迁移"><a href="#Linux-内核调度策略：选核与迁移" class="headerlink" title="Linux 内核调度策略：选核与迁移"></a>Linux 内核调度策略：选核与迁移</h2><p>理解了线程状态和频率后，我们还需要深入了解 Linux 内核调度器（在 Android 中主要是 EAS - Energy Aware Scheduling）的两个核心行为：如何为任务挑选一个 CPU 核心，以及为何要将任务从一个核心迁移到另一个。</p><h3 id="选核逻辑-Task-Placement"><a href="#选核逻辑-Task-Placement" class="headerlink" title="选核逻辑 (Task Placement)"></a>选核逻辑 (Task Placement)</h3><p>当一个线程从 <code>Sleep</code> 状态被唤醒，或一个新线程被创建时，EAS 调度器需要为它选择一个最合适的 CPU 核心。其核心目标是在满足任务性能需求的前提下，尽可能地降低系统功耗。决策流程大致如下：</p><ol><li><strong>评估任务负载 (Task Utilization)</strong>: 调度器会评估该线程的 <code>util</code>，即它需要多少计算资源。这是一个动态调整的值，反映了线程历史上的繁忙程度。</li><li><strong>寻找“足够”的核心</strong>: 调度器会遍历所有可用的 CPU 核心，比较任务的 <code>util</code> 和每个核心的 <code>capacity</code>（容量&#x2F;最大计算能力）。大核的 <code>capacity</code> 远高于小核。调度器会寻找 <code>capacity</code> &gt; <code>util</code> 的核心，即能“装下”这个任务的核心。</li><li><strong>寻找“最节能”的核心</strong>: 在所有满足 <code>capacity</code> 要求的核心中，调度器会利用预置在内核中的“能量模型”（Energy Model）进行计算。这个模型知道每个核心在每个频率下运行的功耗。调度器会选择一个能让整个系统（包括任务本身和其他正在运行的任务）总功耗最低的 CPU 核心作为最终选择。</li></ol><p><strong>简而言之</strong>：EAS 的目标不是为任务找一个最快的核，而是找一个“刚刚好够用”且“最省电”的核。</p><h3 id="核心迁移逻辑-Task-Migration"><a href="#核心迁移逻辑-Task-Migration" class="headerlink" title="核心迁移逻辑 (Task Migration)"></a>核心迁移逻辑 (Task Migration)</h3><p>将任务从一个 CPU 核心移动到另一个，是调度器进行动态调优的关键手段。主要发生在以下情况：</p><ol><li><p><strong>负载均衡 (Load Balancing)</strong>: 这是最常见的迁移原因。调度器会周期性地检查系统是否处于“负载不均衡”状态。例如，<code>CPU-1</code>（小核）上挤满了高负载任务导致其利用率饱和，而 <code>CPU-7</code>（大核）却很空闲。此时，调度器会判定系统失衡，并将 <code>CPU-1</code> 上的某个高负载任务“拉到”（pull）<code>CPU-7</code> 上运行，以恢复平衡，提升性能。</p></li><li><p><strong>唤醒时迁移 (Wake-up Migration)</strong>: 当一个线程睡醒时，调度器会重新评估它的最佳核心。如果这个线程在睡眠期间 <code>util</code> 发生了变化（例如，一个后台下载线程突然收到了一个大文件下载任务，<code>util</code> 飙升），或者原先运行的核心现在非常繁忙，调度器就可能在唤醒时直接为它选择一个更合适的新核心，而不是让它在原来的地方排队。</p></li></ol><p>理解选核与迁移逻辑，有助于我们判断调度器的行为是否“异常”。例如，一个明显的前台 UI 线程被不合理地长时间限制在小核上，或者在大小核之间过于频繁地“反复横跳”，都可能暗示着系统调度策略或任务优先级设置存在问题。</p><p>当然目前 Android 厂商针对调度器有做非常多的客制化，关键 Task（Critical Task）通常会占用更多的 CPU ，甚至更容易上大核。另外还有很多绑核策略、签核策略，导致大家看到的每个厂商的现象都不一样。这也是各个厂商的核心竞争力（比如 Oppo 的蜂鸟引擎）。</p><p><a id="practices-sql"></a></p><h2 id="实战与-SQL"><a href="#实战与-SQL" class="headerlink" title="实战与 SQL"></a>实战与 SQL</h2><h3 id="使用-SQL-进行量化分析"><a href="#使用-SQL-进行量化分析" class="headerlink" title="使用 SQL 进行量化分析"></a>使用 SQL 进行量化分析</h3><p>Perfetto 内置的 SQL 查询引擎是其强大功能之一，允许开发者对 Trace 数据进行精确的聚合、筛选和分析。以下是一些常用的 CPU 分析查询。</p><h3 id="1-计算各进程的-CPU-总时长"><a href="#1-计算各进程的-CPU-总时长" class="headerlink" title="1. 计算各进程的 CPU 总时长"></a>1. 计算各进程的 CPU 总时长</h3><p>此查询统计每个进程在所有 CPU 上运行的总时长，按降序排列，用于快速定位消耗 CPU 资源最多的进程。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  process.name,</span><br><span class="line">  <span class="built_in">sum</span>(dur) <span class="operator">/</span> <span class="number">1e9</span> <span class="keyword">AS</span> total_cpu_time_s</span><br><span class="line"><span class="keyword">FROM</span> sched</span><br><span class="line"><span class="keyword">JOIN</span> thread <span class="keyword">ON</span> sched.utid <span class="operator">=</span> thread.utid</span><br><span class="line"><span class="keyword">JOIN</span> process <span class="keyword">ON</span> thread.upid <span class="operator">=</span> process.upid</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> process.name</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> total_cpu_time_s <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h3 id="2-分析单个线程的时间状态分布"><a href="#2-分析单个线程的时间状态分布" class="headerlink" title="2. 分析单个线程的时间状态分布"></a>2. 分析单个线程的时间状态分布</h3><p>此查询基于 <code>thread_state</code> 表，可用于分析特定线程（示例为 <code>surfaceflinger</code>）在各种状态下的时间分布，从而判断其主要瓶颈。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  <span class="keyword">CASE</span> state</span><br><span class="line">    <span class="keyword">WHEN</span> <span class="string">&#x27;Running&#x27;</span> <span class="keyword">THEN</span> <span class="string">&#x27;Running&#x27;</span></span><br><span class="line">    <span class="keyword">WHEN</span> <span class="string">&#x27;R&#x27;</span> <span class="keyword">THEN</span> <span class="string">&#x27;Runnable&#x27;</span></span><br><span class="line">    <span class="keyword">WHEN</span> <span class="string">&#x27;S&#x27;</span> <span class="keyword">THEN</span> <span class="string">&#x27;Interruptible Sleep&#x27;</span></span><br><span class="line">    <span class="keyword">WHEN</span> <span class="string">&#x27;D&#x27;</span> <span class="keyword">THEN</span> <span class="string">&#x27;Uninterruptible Sleep&#x27;</span></span><br><span class="line">    <span class="keyword">ELSE</span> state</span><br><span class="line">  <span class="keyword">END</span> <span class="keyword">AS</span> human_state,</span><br><span class="line">  <span class="built_in">sum</span>(dur) <span class="operator">/</span> <span class="number">1e6</span> <span class="keyword">AS</span> total_time_ms</span><br><span class="line"><span class="keyword">FROM</span> thread_state</span><br><span class="line"><span class="keyword">WHERE</span> utid <span class="operator">=</span> (<span class="keyword">SELECT</span> utid <span class="keyword">FROM</span> thread <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;surfaceflinger&#x27;</span> LIMIT <span class="number">1</span>)</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> human_state</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> total_time_ms <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><h3 id="3-查找特定时间段内-CPU-消耗最高的线程"><a href="#3-查找特定时间段内-CPU-消耗最高的线程" class="headerlink" title="3. 查找特定时间段内 CPU 消耗最高的线程"></a>3. 查找特定时间段内 CPU 消耗最高的线程</h3><p>此查询用于分析特定场景（如应用启动的 2-5s 时间段）中，消耗 CPU 时间最长的线程。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  thread.name,</span><br><span class="line">  <span class="built_in">sum</span>(dur) <span class="operator">/</span> <span class="number">1e9</span> <span class="keyword">AS</span> cpu_time_s</span><br><span class="line"><span class="keyword">FROM</span> sched</span><br><span class="line"><span class="keyword">JOIN</span> thread <span class="keyword">ON</span> sched.utid <span class="operator">=</span> thread.utid</span><br><span class="line"><span class="comment">-- 时间戳单位为纳秒，可以自己加上 WHERE ts &gt; 2e9 AND ts &lt; 5e9 来取某一段时间</span></span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> thread.name</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> cpu_time_s <span class="keyword">DESC</span></span><br><span class="line">LIMIT <span class="number">20</span>;</span><br></pre></td></tr></table></figure><h3 id="4-查看线程在各-CPU-核心上的运行时间分布"><a href="#4-查看线程在各-CPU-核心上的运行时间分布" class="headerlink" title="4. 查看线程在各 CPU 核心上的运行时间分布"></a>4. 查看线程在各 CPU 核心上的运行时间分布</h3><p>此查询有助于了解一个线程的 CPU 亲和性，以及它是否在预期的大小核上运行。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  cpu,</span><br><span class="line">  <span class="built_in">sum</span>(dur) <span class="operator">/</span> <span class="number">1e6</span> <span class="keyword">AS</span> time_on_cpu_ms</span><br><span class="line"><span class="keyword">FROM</span> sched</span><br><span class="line"><span class="keyword">WHERE</span> utid <span class="operator">=</span> (<span class="keyword">SELECT</span> utid <span class="keyword">FROM</span> thread <span class="keyword">WHERE</span> name <span class="operator">=</span> <span class="string">&#x27;system_server&#x27;</span> LIMIT <span class="number">1</span>)</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> cpu</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> cpu;</span><br></pre></td></tr></table></figure><h3 id="5-计算各进程的-CPU-利用率"><a href="#5-计算各进程的-CPU-利用率" class="headerlink" title="5. 计算各进程的 CPU 利用率"></a>5. 计算各进程的 CPU 利用率</h3><p>此查询计算了每个进程在整个 Trace 时间范围内的 CPU 利用率。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span></span><br><span class="line">  process.name <span class="keyword">AS</span> process_name,</span><br><span class="line">  <span class="number">100</span> <span class="operator">*</span> <span class="built_in">sum</span>(dur) <span class="operator">/</span> <span class="built_in">CAST</span>(TRACE_END() <span class="operator">-</span> TRACE_START() <span class="keyword">AS</span> <span class="type">REAL</span>) <span class="keyword">AS</span> cpu_utilization_percent</span><br><span class="line"><span class="keyword">FROM</span> sched</span><br><span class="line"><span class="keyword">JOIN</span> thread <span class="keyword">ON</span> thread.utid <span class="operator">=</span> sched.utid</span><br><span class="line"><span class="keyword">JOIN</span> process <span class="keyword">ON</span> process.upid <span class="operator">=</span> thread.upid</span><br><span class="line"><span class="keyword">GROUP</span> <span class="keyword">BY</span> process.name</span><br><span class="line"><span class="keyword">ORDER</span> <span class="keyword">BY</span> cpu_utilization_percent <span class="keyword">DESC</span>;</span><br></pre></td></tr></table></figure><p><a id="summary"></a></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>对 Perfetto 中 CPU 信息的熟练分析，是 Android 性能优化的关键技能。通过深度理解 <strong>核心架构</strong>、<strong>线程状态</strong>、<strong>唤醒关系</strong>、<strong>频率限制</strong> 和 <strong>C-State</strong>，并结合强大的 <strong>SQL 查询</strong>进行量化分析，开发者可以精准地定位并解决各类性能与功耗问题。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Perfetto 系列的第九篇文章，主题是 Perfetto 中的 CPU 信息分析。Perfetto 提供了远超 Systrace 的数据可视化与分析能力，理解 CPU 相关信息是定位性能瓶颈、分析功耗问题的基础。&lt;/p&gt;
&lt;p&gt;本系列的目标，就是通过 Perfetto 这个工具，从一个全新的图形化视角，来审视 Android 系统的整体运行，同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章，但总是对繁杂的调用链感到困惑，或者记不住具体的执行流程。那么通过 Perfetto，将这些流程可视化，你可能会对系统有更深入、更直观的理解。&lt;/p&gt;</summary>
    
    
    
    <category term="Android性能优化" scheme="https://androidperformance.com/categories/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="性能优化" scheme="https://androidperformance.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
    <category term="线程优化" scheme="https://androidperformance.com/tags/%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%8C%96/"/>
    
    <category term="卡顿优化" scheme="https://androidperformance.com/tags/%E5%8D%A1%E9%A1%BF%E4%BC%98%E5%8C%96/"/>
    
    <category term="ANR分析" scheme="https://androidperformance.com/tags/ANR%E5%88%86%E6%9E%90/"/>
    
    <category term="页面响应" scheme="https://androidperformance.com/tags/%E9%A1%B5%E9%9D%A2%E5%93%8D%E5%BA%94/"/>
    
    <category term="电量优化" scheme="https://androidperformance.com/tags/%E7%94%B5%E9%87%8F%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</title>
    <link href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/"/>
    <id>https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/</id>
    <published>2025-08-05T02:15:30.000Z</published>
    <updated>2026-02-07T15:32:07.249Z</updated>
    
    <content type="html"><![CDATA[<p>本篇是 Perfetto 系列文章的第八篇，主要深入介绍 Android 中的 Vsync 机制及其在 Perfetto 中的表现形式。文章将从 Perfetto 的角度来分析 Android 系统如何基于 Vsync 信号进行帧渲染和合成，涵盖 Vsync、Vsync-app、Vsync-sf、VsyncWorkDuration 等核心概念。</p><p>随着高刷新率屏幕的普及，理解 Vsync 机制变得更加重要。本文将以 120Hz 刷新率为主要叙事线，帮助开发者理解现代 Android 设备中 Vsync 的工作原理，以及如何在 Perfetto 中观察和分析 Vsync 相关的性能问题。</p><blockquote><p><strong>注：本文内容基于 Android 13~16 的公开实现与演进；文中代码以 AOSP main 的“签名对齐精简摘录”为主，少量位置使用 <code>...</code> 省略非主线逻辑，请以当前分支源码为准。</strong></p></blockquote><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#what-is-vsync">什么是 Vsync</a></li><li><a href="#principle">Android 中 Vsync 的基本工作原理</a></li><li><a href="#perfetto-observe">在 Perfetto 中观察 Vsync</a></li><li><a href="#app-frames">Android App 每一帧是如何基于 Vsync 工作的</a></li><li><a href="#refs">参考文档</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="what-is-vsync"></a></p><h2 id="什么是-Vsync"><a href="#什么是-Vsync" class="headerlink" title="什么是 Vsync"></a>什么是 Vsync</h2><p>Vsync（Vertical Synchronization，垂直同步）是 Android 图形系统的核心机制，它的存在是为了解决一个根本性的问题：如何让软件的渲染节奏与硬件的显示节奏保持同步。</p><p>在没有 Vsync 机制之前，常见问题是屏幕撕裂（Screen Tearing）。当显示器读取 framebuffer 的同时，GPU 写入了下一帧，就会在同一次刷新中出现上下两部分不一致的画面。</p><h3 id="Vsync-解决什么问题？"><a href="#Vsync-解决什么问题？" class="headerlink" title="Vsync 解决什么问题？"></a>Vsync 解决什么问题？</h3><p>Vsync 机制的核心思想非常简单：<strong>让所有的渲染工作都按照显示器的刷新节拍来进行</strong>。具体来说：</p><ol><li><strong>同步信号</strong>：显示器每次开始新的刷新周期时，都会发出一个 Vsync 信号。</li><li><strong>帧节拍与生产</strong>：应用侧在 Vsync 到来时由 Choreographer 驱动开始一帧的生产（Input&#x2F;Animation&#x2F;Traversal）；CPU 提交渲染命令后，GPU 异步流水执行。SurfaceFlinger 侧在 Vsync 到来时进行 Buffer 的合成操作。</li><li><strong>缓冲机制</strong>：使用双缓冲或三缓冲技术，确保显示器总是读取完整的帧数据。</li></ol><p>这样，帧的生产与显示以 Vsync 为节拍对齐。以 120Hz 为例，每 8.333ms 会有一个显示机会；应用需要在该窗口前把可合成的 Buffer 提交给 SurfaceFlinger。关键约束是 <code>queueBuffer</code>&#x2F;<code>acquire_fence</code>&#x2F;<code>present_fence</code> 的时序；若未赶上本周期，会顺延到下一个周期显示。</p><p><a id="principle"></a></p><h2 id="Android-中-Vsync-的基本工作原理"><a href="#Android-中-Vsync-的基本工作原理" class="headerlink" title="Android 中 Vsync 的基本工作原理"></a>Android 中 Vsync 的基本工作原理</h2><p>Android 系统的 Vsync 实现比基本概念复杂得多，需要考虑多个不同的渲染组件，以及它们之间的协调工作。</p><h3 id="Vsync-信号的分层架构"><a href="#Vsync-信号的分层架构" class="headerlink" title="Vsync 信号的分层架构"></a>Vsync 信号的分层架构</h3><p>在 Android 系统中，并不是只有一个简单的 Vsync 信号。实际上，系统维护着多个不同用途的 Vsync 信号：</p><p><strong>硬件 Vsync（HW Vsync）</strong>：<br>这是最底层的 Vsync 信号，由显示硬件（HWC，Hardware Composer）产生。它的频率严格对应显示器的刷新率，比如 60Hz 的显示器会每 16.67ms 产生一次 HW Vsync，120Hz 的显示器会每 8.333ms 产生一次。（硬件 Vsync 回调由 HWC&#x2F;SurfaceFlinger 管理，详见 <code>frameworks/native/services/surfaceflinger</code> 相关实现）</p><p>但是，HW Vsync 并不是一直开启的。由于频繁的硬件中断会消耗较多的电量，Android 系统采用了一种智能的策略：只有在需要精确同步的时候才开启 HW Vsync，大部分时间使用软件预测的方式生成 Vsync 信号。</p><p><strong>Vsync-app（应用 Vsync）</strong>：<br>这是专门用于驱动应用层渲染的 Vsync 信号。当应用需要进行 UI 更新时（比如用户触摸、动画运行、界面滚动等），应用会向系统申请接收 Vsync-app 信号。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/view/Choreographer.java</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleFrameLocked</span><span class="params">(<span class="type">long</span> now)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mFrameScheduled) &#123;</span><br><span class="line">        mFrameScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (USE_VSYNC) &#123;</span><br><span class="line">            <span class="comment">// 向系统申请下一个 Vsync 信号</span></span><br><span class="line">            mDisplayEventReceiver.scheduleVsync();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Vsync-app 是按需申请的。如果应用界面是静态的，没有任何动画或用户交互，那么应用不会申请 Vsync-app 信号，系统也就不会为这个应用生成 Vsync 事件。</p><p><strong>Vsync-sf（SurfaceFlinger Vsync）</strong>：<br>这是专门用于驱动 SurfaceFlinger 进行图层合成的 Vsync 信号。SurfaceFlinger 是 Android 系统中负责将所有应用的图层合成为最终画面的服务。</p><p><strong>Vsync-appSf（应用-SurfaceFlinger Vsync）</strong>：<br>Android 13 引入的新信号类型。为消除旧设计中 sf EventThread 既唤醒 SurfaceFlinger 又服务部分 Choreographer 客户端带来的时序歧义，系统将两类职责分离：<code>vsync-sf</code> 专注唤醒 SurfaceFlinger，<code>vsync-appSf</code> 面向需要与 SurfaceFlinger 同步的客户端。</p><p><a id="perfetto-observe"></a></p><h2 id="在-Perfetto-中观察-Vsync"><a href="#在-Perfetto-中观察-Vsync" class="headerlink" title="在 Perfetto 中观察 Vsync"></a>在 Perfetto 中观察 Vsync</h2><p>Perfetto trace 中包含多个与 Vsync 相关的 Track，理解这些 Track 的含义有助于分析性能问题。</p><p><strong>在 SurfaceFlinger 进程中</strong>：</p><ol><li><p><strong>vsync-app</strong><br>显示应用 Vsync 信号状态，数值在 0 和 1 之间变化。每次数值变化代表一个 Vsync 信号。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250811221826847.webp" alt="image-20250811221826847"></p></li><li><p>**vsync-sf **<br>显示 SurfaceFlinger Vsync 信号状态。无 Vsync Offset 时与 <code>vsync-app</code> 同步变化。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250811221902646.webp" alt="image-20250811221902646"></p></li><li><p><strong>vsync-appSf</strong><br>Android 13+ 新增，服务于需要与 SurfaceFlinger 同步的特殊 Choreographer 客户端。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250811222036489.webp" alt="image-20250811222036489"></p></li><li><p><strong>HW_VSYNC</strong><br>显示硬件 Vsync 开启状态。值为 1 表示开启，值为 0 表示关闭。为节省电量，硬件 Vsync 仅在需要精确同步时开启。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250811222159253.webp" alt="image-20250811222159253"></p></li></ol><p><strong>在应用进程中</strong>：</p><p><code>FrameDisplayEventReceiver.onVsync</code> Slice Track：<br>显示应用接收 Vsync 信号的时间点。该事件连接通过 Binder 建链、通过 <code>BitTube</code>&#x2F;Looper 通道分发事件，时间可能略晚于 SurfaceFlinger 中的 <code>vsync-app</code>。</p><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918220632473.webp" alt="image-20250918220632473"></p><p><code>UI Thread</code> Slice Track：<br>包含 <code>Choreographer#doFrame</code> 及相关的 Input、Animation、Traversal 等 Slice。每个 <code>doFrame</code> 对应一帧的处理工作。</p><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918220709655.webp" alt="image-20250918220709655"></p><p><code>RenderThread</code> Slice Track：<br>包含 <code>DrawFrame</code>、<code>syncAndDrawFrame</code>、<code>queueBuffer</code> 等 Slice，对应渲染线程工作。</p><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918220730872.webp" alt="image-20250918220730872"></p><p><a id="app-frames"></a></p><h2 id="Android-App-每一帧是如何基于-Vsync-工作的"><a href="#Android-App-每一帧是如何基于-Vsync-工作的" class="headerlink" title="Android App 每一帧是如何基于 Vsync 工作的"></a>Android App 每一帧是如何基于 Vsync 工作的</h2><p>Android 应用的每一帧基于 Vsync 机制完成从渲染到显示的完整过程涉及多个关键步骤。</p><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918221821265.webp" alt="image-20250918221821265"></p><h3 id="流程总览（按顺序）"><a href="#流程总览（按顺序）" class="headerlink" title="流程总览（按顺序）"></a>流程总览（按顺序）</h3><ol><li>触发重绘&#x2F;输入：<code>View.invalidate()</code>、动画、数据变化或输入事件触发 → <code>ViewRootImpl.scheduleTraversals()</code> → <code>Choreographer.postCallback(TRAVERSAL)</code></li><li>申请 Vsync：<code>Choreographer</code> 通过 <code>DisplayEventReceiver.scheduleVsync()</code> 申请下一次 Vsync（app 相位）</li><li>接收 Vsync：<code>DisplayEventReceiver.onVsync()</code> 收到 Vsync 后，向主线程消息队列投递异步消息</li><li>主线程帧处理：<code>Choreographer.doFrame()</code> 按顺序执行五类回调：<code>INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT</code></li><li>渲染提交：<code>RenderThread</code> 执行 <code>syncAndDrawFrame/DrawFrame</code>，CPU 记录 GPU 命令，<code>queueBuffer</code> 提交到 BufferQueue</li><li>合成显示：<code>SurfaceFlinger</code> 在 <code>vsync-sf</code> 到来时合成（GPU&#x2F;或HWC），生成 <code>present_fence</code>，输出到显示</li><li>帧完成度量：通过 <code>FrameTimeline</code>（PresentType&#x2F;JankType）与 <code>acquire/present_fence</code> 判定是否按期显示</li></ol><p>下面分别展开每一步的关键实现与 Perfetto 观测点。</p><h3 id="App-什么时候会申请-Vsync-信号"><a href="#App-什么时候会申请-Vsync-信号" class="headerlink" title="App 什么时候会申请 Vsync 信号"></a>App 什么时候会申请 Vsync 信号</h3><p>应用并不是时刻都在申请 Vsync 信号的。Vsync 信号是按需申请的，只有在以下情况下，应用才会向系统申请下一个 Vsync：</p><p><strong>触发申请 Vsync 的场景</strong>：</p><ol><li><strong>UI 更新需求</strong>：当 View 调用 <code>invalidate()</code> 时</li><li><strong>动画执行</strong>：ValueAnimator、ObjectAnimator 等动画开始时  </li><li><strong>用户交互</strong>：触摸事件、按键事件等需要 UI 响应时</li><li><strong>数据变化</strong>：RecyclerView 数据更新、TextView 文本改变等</li></ol><h3 id="App-申请-Vsync-的完整流程"><a href="#App-申请-Vsync-的完整流程" class="headerlink" title="App 申请 Vsync 的完整流程"></a>App 申请 Vsync 的完整流程</h3><p>当应用需要更新 UI 时，会通过以下流程申请 Vsync 信号：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. UI 组件请求重绘</span></span><br><span class="line"><span class="comment">// frameworks/base/core/java/android/view/View.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">invalidate</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 标记为需要重绘，但不立即执行</span></span><br><span class="line">    mPrivateFlags |= PFLAG_DIRTY;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (mParent != <span class="literal">null</span> &amp;&amp; mAttachInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 向父容器请求重绘</span></span><br><span class="line">        mParent.invalidateChild(<span class="built_in">this</span>, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. ViewRootImpl 调度遍历</span></span><br><span class="line"><span class="comment">// frameworks/base/core/java/android/view/ViewRootImpl.java</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 关键：向 Choreographer 注册回调，等待下一个 Vsync</span></span><br><span class="line">        mChoreographer.postCallback(</span><br><span class="line">            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Choreographer 申请 Vsync</span></span><br><span class="line"><span class="comment">// frameworks/base/core/java/android/view/Choreographer.java</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">postCallbackDelayedInternal</span><span class="params">(<span class="type">int</span> callbackType,</span></span><br><span class="line"><span class="params">        Object action, Object token, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">dueTime</span> <span class="operator">=</span> now + delayMillis;</span><br><span class="line">    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 重点：不只 TRAVERSAL，任意“到期回调”都可能触发下一帧调度</span></span><br><span class="line">    <span class="keyword">if</span> (dueTime &lt;= now) &#123;</span><br><span class="line">        scheduleFrameLocked(now);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleFrameLocked</span><span class="params">(<span class="type">long</span> now)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mFrameScheduled) &#123;</span><br><span class="line">        mFrameScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (isRunningOnLooperThreadLocked()) &#123;</span><br><span class="line">            scheduleVsyncLocked();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);</span><br><span class="line">            msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">            mHandler.sendMessageAtFrontOfQueue(msg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>TRAVERSAL</code> 仍然是最常见触发源，但从 AOSP main 实现看，并非“只有 TRAVERSAL 才申请 Vsync”。</p></blockquote><h3 id="主线程如何监听-Vsync-信号"><a href="#主线程如何监听-Vsync-信号" class="headerlink" title="主线程如何监听 Vsync 信号"></a>主线程如何监听 Vsync 信号</h3><p>应用主线程通过 <code>DisplayEventReceiver</code> 来监听 Vsync 信号。这个过程涉及几个关键步骤：</p><p><strong>1. 建立连接</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/view/Choreographer.java</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FrameDisplayEventReceiver</span> <span class="keyword">extends</span> <span class="title class_">DisplayEventReceiver</span></span><br><span class="line">        <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">FrameDisplayEventReceiver</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource, <span class="type">long</span> layerHandle)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(looper, vsyncSource, <span class="comment">/* eventRegistration */</span> <span class="number">0</span>, layerHandle);</span><br><span class="line">        <span class="comment">// 在构造时建立与 SurfaceFlinger 的连接</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2. 接收 Vsync 信号</strong>：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame,</span></span><br><span class="line"><span class="params">        VsyncEventData vsyncEventData)</span> &#123;</span><br><span class="line">    <span class="comment">// 接收到 Vsync 信号，但注意：这里并不直接执行 doFrame</span></span><br><span class="line">    mTimestampNanos = timestampNanos;</span><br><span class="line">    mFrame = frame;</span><br><span class="line">    mLastVsyncEventData.copyFrom(vsyncEventData);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 关键：将工作 post 到主线程的 MessageQueue 中</span></span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">    msg.setAsynchronous(<span class="literal">true</span>);  <span class="comment">// 设为异步消息，优先处理</span></span><br><span class="line">    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 这里才真正开始执行一帧的工作</span></span><br><span class="line">    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="几个遗留问题"><a href="#几个遗留问题" class="headerlink" title="几个遗留问题"></a>几个遗留问题</h3><p><strong>Q1：为什么不在 <code>onVsync()</code> 中直接执行 <code>doFrame()</code>？</strong><br><img src="/images/Android-Perfetto-08-Vsync/image-20250918221936675.webp" alt="image-20250918221936675"></p><ul><li>线程边界：在 <code>Choreographer</code> 场景下，<code>onVsync()</code> 回调运行在其绑定的 Looper（通常就是主线程）；通过消息队列再进入 <code>doFrame()</code>，可统一调度并保持帧处理时序一致</li><li>调度控制：通过 <code>sendMessageAtTime()</code> 精确对齐执行时刻</li><li>队列语义：进入主线程 MessageQueue，确保与其他高优先级任务协同</li></ul><p><strong>Q2：Vsync 消息来了但主线程在忙，会丢吗？</strong><br><img src="/images/Android-Perfetto-08-Vsync/image-20250918222045731.webp" alt="image-20250918222045731"></p><ul><li>不完全是“不会丢”。单次 <code>scheduleVsync()</code> 只请求一次事件；主线程长期繁忙时会出现“跳过多个硬件节拍、最终只处理较新的一帧”的现象。实际分析应结合 FrameTimeline 判断是否产生可见卡顿。</li><li>AOSP <code>DisplayEventDispatcher::processPendingEvents</code> 明确会用“后到达的 vsync 覆盖先到达的 vsync”（只保留最近一次用于分发）。</li></ul><p><strong>Q3：CPU&#x2F;GPU 是否必须在单个 Vsync 周期内完成？</strong>如果任何一个环节超过 1 个 vsync ，都会导致掉帧？</p><ul><li><p>现代 Android 系统采用多缓冲（通常是三缓冲）机制：</p><ul><li><p><strong>应用端</strong>：Front Buffer（显示中）+ Back Buffer（渲染中）+ 可能的第三个 Buffer</p></li><li><p><strong>SurfaceFlinger 端</strong>：也有类似的缓冲机制</p></li><li><p>这意味着即使应用的某一帧超过了 Vsync 周期，也不一定会立即掉帧。</p></li></ul></li><li><p>GPU 异步流水；关键是 <code>queueBuffer</code> 是否赶上 SF 合成窗口，多缓冲可掩盖单帧延迟但可能引入额外时延，可以看到下图里面，App 端的 BufferQueue 和 SurfaceFlinger 端的 Buffer 都是充足的，且有冗余，所以没有掉帧。</p></li><li><p>但是如果 App 在之前没有堆积 Buffer ，则还是会出现掉帧。</p></li></ul><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918222258536.webp" alt="image-20250918222258536"></p><p><strong>Q5：GPU 和 CPU 是怎么协同的？</strong>：</p><ul><li><p>GPU 渲染是异步的，这带来了额外的复杂性：</p><ul><li><strong>CPU 工作正常，GPU 成为瓶颈</strong>：即使应用主线程在 Vsync 周期内完成工作，GPU 渲染耗时过长仍会导致掉帧</li><li><strong>GPU Fence 机制</strong>：在 Buffer 被 SF latch 的阶段，关键同步点通常是 <code>acquire fence</code>（Buffer 何时可安全读取）；<code>present fence</code> 更偏向“该帧何时真正送显”的完成信号。根据系统 <code>Latch Unsignaled Buffers</code> 策略，SurfaceFlinger 在特定条件下可先推进流程，再在真正需要时等待 fence 信号，以此隐藏部分延迟。</li></ul><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918222626100.webp" alt="image-20250918222626100"></p></li></ul><p><strong>Q6：Vsync Phase（相位差）的真正作用是什么？</strong>：</p><ul><li><strong>提升跟手性</strong>：通过调整 sf vsync 的相位差，可以让应用从开始绘制到显示在屏幕上的时间从 3 个 Vsync 周期缩短到 2 个 Vsync 周期。这对于触摸响应等交互场景非常重要。</li><li><strong>解决应用绘制超时问题</strong>：当应用绘制超时时，合理的 sf 相位差可以为应用争取更多的处理时间，避免因为时序不当导致的掉帧。</li><li><code>VsyncWorkDuration</code> 更接近调度预算（workDuration&#x2F;readyDuration）的可视化，不等价于单一 appOffset 数值；分析时建议结合 <code>vsync-app/sf</code> 与 FrameTimeline 联动判断。</li><li>下图中显示的时间段就是我手上的手机配置的 app offset （13.3ms）</li></ul><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918222707300.webp" alt="image-20250918222707300"></p><h3 id="Vsync-Offset-x2F-WorkDuration-的技术实现"><a href="#Vsync-Offset-x2F-WorkDuration-的技术实现" class="headerlink" title="Vsync Offset &#x2F; WorkDuration 的技术实现"></a>Vsync Offset &#x2F; WorkDuration 的技术实现</h3><p>在当前 AOSP main 中，配置入口是 <code>VsyncConfiguration</code> 抽象接口，返回的是按场景组织的 <code>VsyncConfigSet</code>。实现上 <code>PhaseOffsets</code> 属于旧路径，<code>WorkDuration</code> 是新路径中更常见的实现之一：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/native/services/surfaceflinger/Scheduler/VsyncConfiguration.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">VsyncConfiguration</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">VsyncConfiguration</span>() = <span class="keyword">default</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> VsyncConfigSet <span class="title">getCurrentConfigs</span><span class="params">()</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> VsyncConfigSet <span class="title">getConfigsForRefreshRate</span><span class="params">(Fps fps)</span> <span class="type">const</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">setRefreshRateFps</span><span class="params">(Fps fps)</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">reset</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WorkDuration</span> : <span class="keyword">public</span> VsyncConfiguration &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">WorkDuration</span><span class="params">(Fps currentRefreshRate)</span></span>;</span><br><span class="line">    <span class="comment">// 内部根据 sf/app 的 workDuration 构造不同场景下的 offset 配置</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>关键概念</strong>：</p><ul><li><code>workDuration/readyDuration</code>：调度时的“工作预算”和“就绪提前量”，用于计算回调唤醒时刻</li><li><code>app/sf offset</code>：仍可作为常用分析口径，但本质是配置集合与调度模型共同作用的结果</li><li>常用口径里“app&#x2F;sf offset 差值”指两者相位差（通常看 <code>|sfOffset - appOffset|</code> 的绝对值，具体符号以设备实现与统计口径为准）</li></ul><h3 id="实际的优化效果"><a href="#实际的优化效果" class="headerlink" title="实际的优化效果"></a>实际的优化效果</h3><p>以 120Hz 设备为例，配置 3ms Offset 的效果：</p><p><strong>无 Offset（传统方式）</strong>：</p><ul><li>T0：应用和 SurfaceFlinger 同时接收 Vsync</li><li>T0+3ms：应用完成渲染</li><li>T0+8.333ms：下一个 Vsync，SurfaceFlinger 开始合成</li><li>T0+16.666ms：用户看到画面（总延迟 16.666ms）</li></ul><p><strong>有 Offset（优化方式）</strong>：</p><ul><li>T0+1ms：应用接收 Vsync-app，开始渲染</li><li>T0+3ms：应用完成渲染，提交 Buffer</li><li>T0+4ms：SurfaceFlinger 接收 Vsync-sf，立即开始合成</li><li>T0+6ms：SurfaceFlinger 完成合成</li><li>T0+8.333ms：用户看到画面（总延迟 8.333ms）</li></ul><p>通过合理配置 Offset，可以将延迟从 16.666ms 减少到 8.333ms，提升一倍的响应性能。</p><p><strong>实际的时间预算分配</strong>：</p><p>以 120Hz 设备为例（8.333ms 周期）：</p><ul><li><strong>理想情况</strong>：应用 4ms + SurfaceFlinger 2ms + 缓冲 2.333ms</li><li><strong>但实际可以接受</strong>：应用 6ms + SurfaceFlinger 3ms（如果有足够的 Buffer 缓冲）</li><li><strong>GPU 限制</strong>：在低端设备上，GPU 渲染可能需要 10-15ms，成为真正的瓶颈</li></ul><p><strong>掉帧的真正原因</strong>：</p><ol><li><strong>应用端超时 + Buffer 耗尽</strong>：连续多帧超时导致 BufferQueue 没有可用 Buffer</li><li><strong>GPU 渲染超时</strong>：即使 CPU 工作正常，GPU 渲染超时也会掉帧</li><li><strong>SurfaceFlinger 超时</strong>：系统级合成超时，影响所有应用</li><li><strong>系统资源竞争</strong>：CPU&#x2F;GPU&#x2F;内存等资源被其他进程占用</li></ol><h2 id="Vsync-信号的完整代码流程"><a href="#Vsync-信号的完整代码流程" class="headerlink" title="Vsync 信号的完整代码流程"></a>Vsync 信号的完整代码流程</h2><p>Vsync 信号从硬件传递到应用层的完整链路如下。</p><h3 id="按-AOSP-main-分支对齐的关键代码（精简摘录）"><a href="#按-AOSP-main-分支对齐的关键代码（精简摘录）" class="headerlink" title="按 AOSP main 分支对齐的关键代码（精简摘录）"></a>按 AOSP main 分支对齐的关键代码（精简摘录）</h3><p>下面片段都按当前 AOSP main 分支的方法签名整理，省略了与主线无关的分支与日志代码。</p><p><strong>1）Choreographer 申请下一次 Vsync（Java）</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/view/Choreographer.java</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleFrameLocked</span><span class="params">(<span class="type">long</span> now)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mFrameScheduled) &#123;</span><br><span class="line">        mFrameScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (USE_VSYNC) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isRunningOnLooperThreadLocked()) &#123;</span><br><span class="line">                scheduleVsyncLocked();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);</span><br><span class="line">                msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">                mHandler.sendMessageAtFrontOfQueue(msg);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleVsyncLocked</span><span class="params">()</span> &#123;</span><br><span class="line">    mDisplayEventReceiver.scheduleVsync();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2）Choreographer 接收 Vsync（Java）</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/view/Choreographer.java</span></span><br><span class="line"><span class="comment">// TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC</span></span><br><span class="line"><span class="comment">// for the internal display and scheduleVsync only allows requesting internal VSYNC.</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame,</span></span><br><span class="line"><span class="params">        VsyncEventData vsyncEventData)</span> &#123;</span><br><span class="line">    mTimestampNanos = timestampNanos;</span><br><span class="line">    mFrame = frame;</span><br><span class="line">    mLastVsyncEventData.copyFrom(vsyncEventData);</span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">    msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    mHavePendingVsync = <span class="literal">false</span>;</span><br><span class="line">    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3）JNI 层桥接：<code>DisplayEventDispatcher</code>（C++）</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NativeDisplayEventReceiver</span> : <span class="keyword">public</span> DisplayEventDispatcher &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">NativeDisplayEventReceiver</span>(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak,</span><br><span class="line">            <span class="type">const</span> sp&lt;MessageQueue&gt;&amp; messageQueue, jint vsyncSource,</span><br><span class="line">            jint eventRegistration, jlong layerHandle);</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">dispatchVsync</span><span class="params">(<span class="type">nsecs_t</span> timestamp, PhysicalDisplayId displayId, <span class="type">uint32_t</span> count,</span></span></span><br><span class="line"><span class="params"><span class="function">            VsyncEventData vsyncEventData)</span> <span class="keyword">override</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">dispatchHotplug</span><span class="params">(<span class="type">nsecs_t</span> timestamp, PhysicalDisplayId displayId, <span class="type">bool</span> connected)</span> <span class="keyword">override</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>4）Native 收发通道：<code>DisplayEventReceiver</code> + <code>BitTube</code>（C++）</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/native/libs/gui/DisplayEventReceiver.cpp</span></span><br><span class="line">DisplayEventReceiver::<span class="built_in">DisplayEventReceiver</span>(gui::ISurfaceComposer::VsyncSource vsyncSource,</span><br><span class="line">        EventRegistrationFlags eventRegistration, <span class="type">const</span> sp&lt;IBinder&gt;&amp; layerHandle) &#123;</span><br><span class="line">    sf-&gt;<span class="built_in">createDisplayEventConnection</span>(vsyncSource, ..., layerHandle, &amp;mEventConnection);</span><br><span class="line">    mDataChannel = std::<span class="built_in">make_unique</span>&lt;gui::BitTube&gt;();</span><br><span class="line">    mEventConnection-&gt;<span class="built_in">stealReceiveChannel</span>(mDataChannel.<span class="built_in">get</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">status_t</span> <span class="title">DisplayEventReceiver::requestNextVsync</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mEventConnection-&gt;<span class="built_in">requestNextVsync</span>();</span><br><span class="line">    <span class="keyword">return</span> NO_ERROR;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">ssize_t</span> <span class="title">DisplayEventReceiver::getEvents</span><span class="params">(Event* events, <span class="type">size_t</span> count)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> gui::BitTube::<span class="built_in">recvObjects</span>(mDataChannel.<span class="built_in">get</span>(), events, count);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>5）SurfaceFlinger 调度与分发（C++）</strong></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/native/services/surfaceflinger/Scheduler/VSyncDispatch.h</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> std::optional&lt;ScheduleResult&gt; <span class="title">schedule</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        CallbackToken token, ScheduleTiming scheduleTiming)</span> </span>= <span class="number">0</span>;</span><br></pre></td></tr></table></figure><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">EventThread::onVsync</span><span class="params">(<span class="type">nsecs_t</span> vsyncTime, <span class="type">nsecs_t</span> wakeupTime, <span class="type">nsecs_t</span> readyTime)</span> </span>&#123;</span><br><span class="line">    mPendingEvents.<span class="built_in">push_back</span>(<span class="built_in">makeVSync</span>(mVsyncSchedule-&gt;<span class="built_in">getPhysicalDisplayId</span>(), wakeupTime,</span><br><span class="line">            ++mVSyncState-&gt;count, vsyncTime, readyTime));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">EventThread::threadMain</span><span class="params">(std::unique_lock&lt;std::mutex&gt;&amp; lock)</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">const</span> <span class="keyword">auto</span> scheduleResult = mVsyncRegistration.<span class="built_in">schedule</span>(&#123;</span><br><span class="line">            .workDuration = mWorkDuration.<span class="built_in">get</span>().<span class="built_in">count</span>(),</span><br><span class="line">            .readyDuration = mReadyDuration.<span class="built_in">count</span>(),</span><br><span class="line">            .lastVsync = mLastVsyncCallbackTime.<span class="built_in">ns</span>(),</span><br><span class="line">            .committedVsyncOpt = mLastCommittedVsyncTime.<span class="built_in">ns</span>()&#125;);</span><br><span class="line">    <span class="built_in">LOG_ALWAYS_FATAL_IF</span>(!scheduleResult, <span class="string">&quot;Error scheduling callback&quot;</span>);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="关键时序点分析"><a href="#关键时序点分析" class="headerlink" title="关键时序点分析"></a>关键时序点分析</h3><p>通过上述代码流程，我们可以看到完整的时序链路：</p><ol><li><strong>HWC 产生硬件 Vsync</strong> → SurfaceFlinger Scheduler 获取硬件节拍</li><li><strong>Scheduler 计算唤醒窗口</strong> → <code>VSyncDispatch::schedule(...)</code></li><li><strong>EventThread 生成&#x2F;派发事件</strong> → 写入 <code>DisplayEventReceiver::Event</code>（通过 <code>BitTube</code>）</li><li><strong>App 侧 Native 收到事件</strong> → <code>DisplayEventDispatcher::dispatchVsync(...)</code></li><li><strong>Java <code>FrameDisplayEventReceiver</code> 回调</strong> → 异步消息切到 Looper 队列</li><li><strong><code>Choreographer#doFrame(...)</code> 执行</strong> → Input&#x2F;Animation&#x2F;Traversal&#x2F;Commit</li></ol><p>各环节的职责和优化点不同，理解完整流程有助于在 Perfetto 中分析 Vsync 相关性能问题。</p><h3 id="FrameTimeline"><a href="#FrameTimeline" class="headerlink" title="FrameTimeline"></a>FrameTimeline</h3><p>App 和 SurfaceFlinger 都有 FrameTimeline</p><p><img src="/images/Android-Perfetto-08-Vsync/image-20250918223035361.webp" alt="image-20250918223035361"></p><ul><li><strong>轨道</strong>：<code>Expected Timeline</code>、<code>Actual Timeline</code></li><li><strong>PresentType&#x2F;JankType</strong>：<ul><li>PresentType 指示本帧呈现方式（例如 On-time、Late），JankType 指示卡顿类型来源</li><li>常见 JankType：<code>AppDeadlineMissed</code>、<code>BufferStuffing</code>、<code>SfCpuDeadlineMissed</code>、<code>SfGpuDeadlineMissed</code> 等</li></ul></li><li><strong>操作步骤（Perfetto UI）</strong>：<ol><li>在应用进程选择目标 <code>Surface/Layer</code> 或使用 FrameToken 过滤</li><li>对齐 Expected 与 Actual，查看偏移与颜色编码</li><li>向上钻取：<code>Choreographer#doFrame</code>、<code>RenderThread</code>、<code>queueBuffer</code>、<code>acquire/present_fence</code></li></ol></li><li><strong>误判规避</strong>：<ul><li>仅凭 <code>doFrame</code> 时长判断掉帧不可靠；以 FrameTimeline 的 PresentType&#x2F;JankType 为准</li><li>多缓冲可能掩盖单帧超时，需要看连续帧与 Buffer 可用性</li></ul></li></ul><h3 id="刷新率-x2F-显示模式-x2F-VRR-对-Vsync-与-Offset-x2F-预测的影响"><a href="#刷新率-x2F-显示模式-x2F-VRR-对-Vsync-与-Offset-x2F-预测的影响" class="headerlink" title="刷新率&#x2F;显示模式&#x2F;VRR 对 Vsync 与 Offset&#x2F;预测的影响"></a>刷新率&#x2F;显示模式&#x2F;VRR 对 Vsync 与 Offset&#x2F;预测的影响</h3><ul><li><strong>模式切换</strong>：刷新率变更会重新配置 <code>VsyncConfiguration</code>，影响 app&#x2F;sf Offset 与预测模型；<ul><li>Perfetto：查 <code>display mode change</code> 事件与随后的 <code>vsync</code> 间隔变化</li></ul></li><li><strong>VRR（可变刷新率）</strong>：目标周期不恒定，软件预测更依赖 present_fence 反馈校准；<ul><li>Perfetto：观察 <code>vsync</code> 间隔分布与 <code>present_fence</code> 偏差</li></ul></li><li><strong>多显示&#x2F;外接显示</strong>：硬件层可按 <code>physicalDisplayId</code> 上报 vsync；但应用侧 Choreographer 通常仍以内屏&#x2F;pacesetter 时序为主（实现细节随版本演进）。分析时先确认你看的到底是 HWC&#x2F;SF 轨道，还是 app 轨道；<ul><li>版本差异：官方文档明确 Android 10 及以下“Per-display VSYNC 不支持”；Android 11+ 该限制在框架&#x2F;HWC 能力层面已移除，但应用侧 <code>Choreographer</code> 的请求路径在 main 分支仍有“internal display”相关注释，需结合目标系统分支实测判断</li><li>Perfetto：按显示 ID 过滤相关 Counter&#x2F;Slice</li></ul></li></ul><h3 id="Perfetto-实战-Checklist（建议按序查看）"><a href="#Perfetto-实战-Checklist（建议按序查看）" class="headerlink" title="Perfetto 实战 Checklist（建议按序查看）"></a>Perfetto 实战 Checklist（建议按序查看）</h3><ol><li><strong>Vsync 信号与周期</strong><ul><li><code>vsync-app / vsync-sf / vsync-appSf</code> 间隔是否稳定（60&#x2F;90&#x2F;120Hz 对应周期）</li><li>是否存在异常密集&#x2F;稀疏的 Vsync（预测抖动）<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223148748.webp" alt="image-20250918223148748"></li></ul></li><li><strong>Vsync 相位差配置</strong><ul><li><code>VsyncWorkDuration</code> 是否符合机型预期的 app&#x2F;sf Offset</li><li>app 与 sf 的先后是否匹配“先绘制后合成”的策略<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918222707300.webp" alt="image-20250918222707300"></li></ul></li><li><strong>FrameTimeline 判读</strong><ul><li>先看 <code>PresentType</code>，再看 <code>JankType</code>；确认是 app 还是 SF&#x2F;GPU 侧问题</li><li>选择目标 Surface&#x2F;FrameToken 定位具体帧<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223220718.webp" alt="image-20250918223220718"></li></ul></li><li><strong>应用主线程与渲染线程</strong><ul><li><code>Choreographer#doFrame</code> 各阶段耗时（Input&#x2F;Animation&#x2F;Traversal）</li><li><code>RenderThread</code> 的 <code>syncAndDrawFrame/DrawFrame</code> 耗时是否异常<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223340940.webp" alt="image-20250918223340940"></li></ul></li><li><strong>BufferQueue 与 Fence</strong><ul><li>生产者：RenderThread <code>queueBuffer</code> 之后，Buffer 进入可消费队列；但 SF 是否能立刻 latch 还要看 <code>acquire fence</code>。<code>present fence</code> 主要用于确认该帧实际送显完成时间。新版本在特定策略下可对 unsignaled buffer 先推进，再在需要时等待 fence。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918224205093.webp" alt="image-20250918224205093"></li><li>消费者 SF 与 BufferTX：SF 在每个合成节拍会尝试为目标 layer 取最新可用 Buffer。若某 layer 的 BufferTX 为 0，通常表示该 layer 暂无新 Buffer，SF 会沿用旧内容继续合成；对这个 App 来说表现为画面停滞&#x2F;卡顿，但不代表 SF 全局“停止合成”。<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223441117.webp" alt="image-20250918223441117"></li></ul></li><li><strong>合成策略与显示</strong><ul><li>SF 是否频繁走 ClientComposition；HWC validate&#x2F;present 是否异常</li><li>多显示&#x2F;模式切换&#x2F;VRR 时是否伴随明显预测偏差<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223517315.webp" alt="image-20250918223517315"></li></ul></li><li><strong>资源与其他干扰</strong><ul><li>CPU 竞争（大核占用）、GPU 忙、IO&#x2F;内存抖动（GC&#x2F;compaction）</li><li>其他前台应用&#x2F;系统服务是否占用关键资源<br><img src="/images/Android-Perfetto-08-Vsync/image-20250918223532482.webp" alt="image-20250918223532482"></li></ul></li></ol><p><a id="refs"></a></p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ol><li><a href="https://source.android.com/docs/core/graphics">Android Graphics Architecture</a></li><li><a href="https://source.android.com/docs/core/graphics/implement-vsync">VSYNC Implementation Guide</a></li><li><a href="https://source.android.com/docs/core/graphics/frame-pacing">Frame Pacing</a></li><li><a href="https://perfetto.dev/docs/">Perfetto Documentation</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://cloud.tencent.com/developer/article/1905184">Vsync offset 相关技术分析</a></li><li><a href="https://blog.csdn.net/learnframework/article/details/133964332">Android 13&#x2F;14高版本SurfaceFlinger出现VSYNC-app&#x2F;VSYNC-appSf&#x2F;VSYNC-sf剖析</a></li><li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/view/Choreographer.java">AOSP - Choreographer.java（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/jni/android_view_DisplayEventReceiver.cpp">AOSP - android_view_DisplayEventReceiver.cpp（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/gui/include/gui/DisplayEventDispatcher.h">AOSP - DisplayEventDispatcher.h（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/gui/DisplayEventReceiver.cpp">AOSP - DisplayEventReceiver.cpp（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/services/surfaceflinger/Scheduler/VSyncDispatch.h">AOSP - VSyncDispatch.h（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/services/surfaceflinger/Scheduler/EventThread.cpp">AOSP - EventThread.cpp（main）</a></li><li><a href="https://source.android.com/docs/core/display/multi_display/three-hardware-displays">Android Multi-display（官方文档）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/services/surfaceflinger/Scheduler/VsyncConfiguration.h">AOSP - VsyncConfiguration.h（main）</a></li><li><a href="https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/gui/DisplayEventDispatcher.cpp">AOSP - DisplayEventDispatcher.cpp（main）</a></li><li><a href="https://source.android.com/docs/core/graphics/unsignaled-buffer-latch">Unsignaled buffer latch（官方文档）</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本篇是 Perfetto 系列文章的第八篇，主要深入介绍 Android 中的 Vsync 机制及其在 Perfetto 中的表现形式。文章将从 Perfetto 的角度来分析 Android 系统如何基于 Vsync 信号进行帧渲染和合成，涵盖 Vsync、Vsync-app、Vsync-sf、VsyncWorkDuration 等核心概念。&lt;/p&gt;
&lt;p&gt;随着高刷新率屏幕的普及，理解 Vsync 机制变得更加重要。本文将以 120Hz 刷新率为主要叙事线，帮助开发者理解现代 Android 设备中 Vsync 的工作原理，以及如何在 Perfetto 中观察和分析 Vsync 相关的性能问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注：本文内容基于 Android 13~16 的公开实现与演进；文中代码以 AOSP main 的“签名对齐精简摘录”为主，少量位置使用 &lt;code&gt;...&lt;/code&gt; 省略非主线逻辑，请以当前分支源码为准。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</title>
    <link href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/"/>
    <id>https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/</id>
    <published>2025-08-02T02:11:45.000Z</published>
    <updated>2026-02-07T15:31:25.165Z</updated>
    
    <content type="html"><![CDATA[<p>本篇是 Perfetto 系列文章的第七篇，主要介绍 Android App 中的 MainThread 和 RenderThread，也就是大家熟悉的<strong>主线程</strong>和<strong>渲染线程</strong>。文章会从 Perfetto 的角度来看 MainThread 和 RenderThread 的工作流程，涉及卡顿、软件渲染、掉帧计算等相关知识。</p><p>随着 Google 正式推出 Perfetto 工具替代 Systrace，Perfetto 在性能分析领域已经成为主流选择。本文将结合 Perfetto 的具体 trace 信息，帮助读者理解 MainThread 和 RenderThread 的完整工作流程，让你在使用 Perfetto 分析性能问题时能够：</p><ul><li><strong>准确识别关键 trace tag</strong>：知道 UI Thread、RenderThread 等关键线程的作用</li><li><strong>理解帧渲染的完整流程</strong>：从 Vsync 信号到屏幕显示的每个步骤  </li><li><strong>定位性能瓶颈</strong>：通过 trace 信息快速找到卡顿和性能问题的根因<span id="more"></span></li></ul><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#render-flow">基于 Perfetto 的渲染流程分析</a></li><li><a href="#evolution">双线程渲染架构的演进</a></li><li><a href="#main-thread-create">主线程的创建过程</a></li><li><a href="#activitythread">ActivityThread 的功能</a></li><li><a href="#render-thread">渲染线程的创建和发展</a></li><li><a href="#performance">性能</a></li><li><a href="#frametimeline">Perfetto 独有的 FrameTimeline 功能</a></li><li><a href="#vsync">Perfetto 中 Vsync 信号</a></li><li><a href="#refs">参考</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容.</p><p>本文使用到的 Trace 文件我上传到了 Github ：<a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Perfetto/demo_app_aosp_scroll.perfetto-trace">https://github.com/Gracker/SystraceForBlog/tree/master/Android_Perfetto/demo_app_aosp_scroll.perfetto-trace</a> ，需要的可以自取。</p><blockquote><p><strong>注：本文内容基于 Android 16 的最新渲染架构</strong></p></blockquote><p><a id="render-flow"></a></p><h1 id="基于-Perfetto-的渲染流程分析"><a href="#基于-Perfetto-的渲染流程分析" class="headerlink" title="基于 Perfetto 的渲染流程分析"></a>基于 Perfetto 的渲染流程分析</h1><p>这里以滑动列表为例，我们通过 Perfetto 截取主线程和渲染线程<strong>一帧</strong>的工作流程(每一帧都会遵循这个流程，不过有的帧需要处理的事情多，有的帧需要处理的事情少)。在 Perfetto UI 中，重点观察 “UI Thread” 和 “RenderThread” 这两个线程的活动。</p><h2 id="帧的概念和基本参数"><a href="#帧的概念和基本参数" class="headerlink" title="帧的概念和基本参数"></a>帧的概念和基本参数</h2><p>在分析 Perfetto trace 之前，需要先了解帧（Frame）的基本概念。Android 系统按照固定的时间间隔刷新屏幕内容：</p><ul><li><strong>60Hz 设备</strong>：每 16.67ms 刷新一次，每秒 60 帧</li><li><strong>90Hz 设备</strong>：每 11.11ms 刷新一次，每秒 90 帧  </li><li><strong>120Hz 设备</strong>：每 8.33ms 刷新一次，每秒 120 帧</li></ul><p>在 Perfetto 中分析渲染性能时，需要重点关注以下两个线程：</p><ul><li><strong>UI Thread</strong>：应用主线程，处理用户输入、业务逻辑、布局计算</li><li><strong>RenderThread</strong>：渲染线程，执行 GPU 渲染命令，与 SurfaceFlinger 交互</li></ul><h2 id="主线程和渲染线程的工作流程"><a href="#主线程和渲染线程的工作流程" class="headerlink" title="主线程和渲染线程的工作流程"></a>主线程和渲染线程的工作流程</h2><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803165650716.webp" alt="image-20250803165650716"></p><p>通过上面的 Perfetto 截图，可以看到一帧完整的渲染流程。我们可以将 Perfetto 图想象成一条河流：主线程在上游处理逻辑，渲染线程在下游执行绘制。河流从左到右流动，每段代表一个步骤。</p><p><strong>重要说明</strong>：并非每一帧都会执行所有步骤。Input、Animation、Insets Animation 都是按需执行。Traversal（measure、layout、draw）同样是按需触发：只有 <code>requestLayout</code>、<code>invalidate</code>、窗口属性&#x2F;可见性变化等场景才会通过 <code>scheduleTraversals()</code> 投递 <code>CALLBACK_TRAVERSAL</code>。在连续滑动&#x2F;动画场景下它常常“看起来像每帧都在跑”。</p><p>通过以下描述，试着在脑中”播放”这个完整流程：</p><h3 id="1-主线程等待-Vsync-信号"><a href="#1-主线程等待-Vsync-信号" class="headerlink" title="1. 主线程等待 Vsync 信号"></a>1. 主线程等待 Vsync 信号</h3><ul><li><strong>Perfetto trace</strong>: 主线程处于 Sleep 状态（显示为空闲块）</li><li><strong>流程说明</strong>: 主线程等待垂直同步信号（Vsync）到来，这确保渲染与屏幕刷新率同步，避免画面撕裂</li></ul><h3 id="2-Vsync-app-信号传递过程"><a href="#2-Vsync-app-信号传递过程" class="headerlink" title="2. Vsync-app 信号传递过程"></a>2. Vsync-app 信号传递过程</h3><ul><li><strong>Perfetto trace</strong>: <code>vsync-app</code> 相关事件，SurfaceFlinger app 线程活动</li><li><strong>流程说明</strong>: 当硬件产生 Vsync 信号时，首先传递给 SurfaceFlinger。SurfaceFlinger 的 app 线程被唤醒，负责管理和分发 Vsync 信号给需要渲染的应用程序。这个中间层设计允许系统级的 Vsync 调度和优化</li></ul><p><strong>重要说明</strong>：</p><ul><li><strong>Vsync-app 是按需申请的</strong>：只有 App 主动请求时才会收到 vsync-app 信号，不申请就没有</li><li><strong>多 App 共享机制</strong>：同时可能有多个 App 申请 vsync-app 信号</li><li><strong>信号归属问题</strong>：SurfaceFlinger 中的 vsync-app 信号可能是其他 App 申请的，当前分析的 App 如果没有申请，就不会有帧输出，这是正常现象</li></ul><h3 id="3-SurfaceFlinger-唤醒-App-主线程"><a href="#3-SurfaceFlinger-唤醒-App-主线程" class="headerlink" title="3. SurfaceFlinger 唤醒 App 主线程"></a>3. SurfaceFlinger 唤醒 App 主线程</h3><ul><li><strong>Perfetto trace</strong>: <code>FrameDisplayEventReceiver.onVsync</code></li><li><strong>流程说明</strong>: SurfaceFlinger 通过 FrameDisplayEventReceiver 机制将 Vsync 信号发送给已注册的 App。App 的 Choreographer 接收到信号后开始启动一帧绘制流程</li></ul><h3 id="4-处理输入事件（Input）"><a href="#4-处理输入事件（Input）" class="headerlink" title="4. 处理输入事件（Input）"></a>4. 处理输入事件（Input）</h3><ul><li><strong>Perfetto trace</strong>: <code>Input</code> 块</li><li><strong>流程说明</strong>: 仅在有输入事件时才执行，主要处理触摸、滑动等用户交互</li><li><strong>触发条件</strong>: <ul><li><strong>有 Input 回调</strong>：手指按压屏幕并滑动时（如列表滑动、页面拖拽）</li><li><strong>无 Input 回调</strong>：手指抬起后的惯性滑动阶段、静止状态</li></ul></li><li><strong>注意</strong>: Input 回调是由前一帧的用户交互行为决定是否在当前帧执行</li></ul><h3 id="5-处理动画（Animation）"><a href="#5-处理动画（Animation）" class="headerlink" title="5. 处理动画（Animation）"></a>5. 处理动画（Animation）</h3><ul><li><strong>Perfetto trace</strong>: <code>Animation</code> 块  </li><li><strong>流程说明</strong>: 仅在有动画需要更新时才执行，更新动画状态和当前帧的动画值</li><li><strong>触发条件</strong>:<ul><li><strong>有 Animation 回调</strong>：惯性滑动阶段、属性动画运行时、列表 item 创建和内容变化、页面转场动画等</li><li><strong>无 Animation 回调</strong>：界面静止状态、纯 Input 交互阶段（无动画效果时）</li></ul></li><li><strong>注意</strong>: Animation 回调同样由前一帧 post 的回调决定当前帧是否执行</li></ul><h3 id="6-处理-Insets-动画"><a href="#6-处理-Insets-动画" class="headerlink" title="6. 处理 Insets 动画"></a>6. 处理 Insets 动画</h3><ul><li><strong>Perfetto trace</strong>: <code>Insets Animation</code> 块</li><li><strong>流程说明</strong>: 仅在有窗口插入变化时才执行，处理窗口边界动画</li><li><strong>触发条件</strong>:<ul><li><strong>有 Insets Animation 回调</strong>：键盘弹出&#x2F;收起、状态栏显示&#x2F;隐藏、导航栏变化等</li><li><strong>无 Insets Animation 回调</strong>：窗口边界稳定状态，大部分普通交互场景</li></ul></li></ul><h3 id="7-Traversal（测量、布局、绘制准备）"><a href="#7-Traversal（测量、布局、绘制准备）" class="headerlink" title="7. Traversal（测量、布局、绘制准备）"></a>7. Traversal（测量、布局、绘制准备）</h3><ul><li><strong>Perfetto trace</strong>: <code>performTraversals</code>, <code>measure</code>, <code>layout</code>, <code>draw</code></li><li><strong>流程说明</strong>: Android UI 渲染的三大核心流程，但并不是每个 Vsync 都完整执行一遍，是否执行取决于本帧是否有布局&#x2F;绘制请求。</li></ul><h4 id="7-1-Measure（测量阶段）"><a href="#7-1-Measure（测量阶段）" class="headerlink" title="7.1 Measure（测量阶段）"></a>7.1 Measure（测量阶段）</h4><ul><li><strong>作用</strong>: 确定每个 View 的尺寸大小</li><li><strong>过程</strong>: 从根 View 开始，递归测量所有子 View 的宽高</li><li><strong>关键概念</strong>: <ul><li><code>MeasureSpec</code>：封装了父容器对子 View 的尺寸要求（EXACTLY、AT_MOST、UNSPECIFIED）</li><li><code>onMeasure()</code>：每个 View 重写此方法来实现自己的测量逻辑</li></ul></li><li><strong>Perfetto 中的表现</strong>: <code>measure</code> 事件，耗时取决于 View 层级复杂度</li></ul><h4 id="7-2-Layout（布局阶段）"><a href="#7-2-Layout（布局阶段）" class="headerlink" title="7.2 Layout（布局阶段）"></a>7.2 Layout（布局阶段）</h4><ul><li><strong>作用</strong>: 确定每个 View 在父容器中的位置坐标</li><li><strong>过程</strong>: 基于 Measure 阶段的结果，为每个 View 分配实际的显示位置</li><li><strong>关键概念</strong>:<ul><li><code>layout(left, top, right, bottom)</code>：设置 View 的四个边界坐标</li><li><code>onLayout()</code>：ViewGroup 重写此方法来确定子 View 的位置</li></ul></li><li><strong>Perfetto 中的表现</strong>: <code>layout</code> 事件，通常比 measure 更快</li></ul><h4 id="7-3-Draw（绘制阶段）"><a href="#7-3-Draw（绘制阶段）" class="headerlink" title="7.3 Draw（绘制阶段）"></a>7.3 Draw（绘制阶段）</h4><ul><li><strong>作用</strong>: 将 View 的内容绘制到画布上</li><li><strong>现代实现</strong>: 不直接绘制像素，而是构建 DisplayList（绘制指令列表）</li><li><strong>关键流程</strong>:<ul><li><code>draw(Canvas)</code>：绘制 View 自身内容</li><li><code>onDraw(Canvas)</code>：子类重写实现具体绘制逻辑</li><li><code>dispatchDraw(Canvas)</code>：ViewGroup 用来绘制子 View</li></ul></li><li><strong>Perfetto 中的表现</strong>: <code>draw</code> 事件，在硬件加速下主要是构建 DisplayList</li></ul><h4 id="ViewRootImpl-performTraversals-核心代码"><a href="#ViewRootImpl-performTraversals-核心代码" class="headerlink" title="ViewRootImpl.performTraversals 核心代码"></a>ViewRootImpl.performTraversals 核心代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/core/java/android/view/ViewRootImpl.java</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</span><br><span class="line">        mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">performTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// ... 大量窗口/relayout/可见性/同步相关逻辑</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">layoutRequested</span> <span class="operator">=</span> mLayoutRequested &amp;&amp; (!mStopped || mReportNextDraw);</span><br><span class="line">    <span class="keyword">if</span> (layoutRequested) &#123;</span><br><span class="line">        <span class="comment">// 可能触发 measureHierarchy / performMeasure</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">didLayout</span> <span class="operator">=</span> layoutRequested &amp;&amp; (!mStopped || mReportNextDraw);</span><br><span class="line">    <span class="keyword">if</span> (didLayout) &#123;</span><br><span class="line">        performLayout(lp, mWidth, mHeight);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 满足条件才会进入 draw；若取消绘制会重新 scheduleTraversals()</span></span><br><span class="line">    performDraw(mActiveSurfaceSyncGroup);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注：最新 AOSP 中 <code>performTraversals</code> 的分支远比示例复杂，涉及 relayout、surface 变化、同步组、取消重绘、窗口可见性等；这里仅保留与 Measure&#x2F;Layout&#x2F;Draw 直接相关的主干逻辑。</p></blockquote><p><strong>三阶段的执行条件</strong>：</p><ul><li><strong>Measure</strong>: 在首次绘制、窗口&#x2F;Insets&#x2F;配置变化或布局请求触发时执行，不是每帧固定执行</li><li><strong>Layout</strong>: 当本帧存在 <code>layoutRequested</code> 且应用处于可绘制状态时执行  </li><li><strong>Draw</strong>: 满足绘制条件时执行；若被 <code>cancelDraw</code>&#x2F;<code>predraw</code> 等分支中断，会重新调度下一次 Traversal</li></ul><h3 id="8-同步-DisplayList-到渲染线程"><a href="#8-同步-DisplayList-到渲染线程" class="headerlink" title="8. 同步 DisplayList 到渲染线程"></a>8. 同步 DisplayList 到渲染线程</h3><ul><li><strong>Perfetto trace</strong>: syncAndDrawFrame，可见 “sync” 或 “syncAndDrawFrame” 事件（通常显示为主线程向渲染线程的数据传递点）</li><li><strong>流程说明</strong>: 主线程通过 <code>syncAndDrawFrame</code> 将本帧 RenderNode&#x2F;DisplayList 状态同步到 RenderThread。这里不是“纯异步 fire-and-forget”：UI 线程会短暂等待 RenderThread 完成关键同步点（<code>DrawFrameTask::postAndWait</code>），随后尽早解除阻塞；并不会一直等到屏幕真正显示该帧。</li></ul><h3 id="9-渲染线程获取-Buffer"><a href="#9-渲染线程获取-Buffer" class="headerlink" title="9. 渲染线程获取 Buffer"></a>9. 渲染线程获取 Buffer</h3><ul><li><strong>Perfetto trace</strong>: 常见可观察 <code>DequeueBufferDuration</code> &#x2F; <code>QueueBufferDuration</code> 相关信息（具体 tag 因版本和厂商实现不同）</li><li><strong>流程说明</strong>: 渲染线程在绘制提交阶段会通过 <code>ANativeWindow/RenderPipeline</code> 路径完成 buffer 申请与交换。是否等待、等待多久，直接影响这一帧是否赶上 deadline。</li></ul><h3 id="10-处理渲染指令并-flush-到-GPU"><a href="#10-处理渲染指令并-flush-到-GPU" class="headerlink" title="10. 处理渲染指令并 flush 到 GPU"></a>10. 处理渲染指令并 flush 到 GPU</h3><ul><li><strong>Perfetto trace</strong>: <code>drawing</code> 相关块</li><li><strong>流程说明</strong>: RenderThread（运行在 CPU 上）通过 <code>HardwareRenderer/CanvasContext</code> 处理 UI 线程同步过来的 RenderNode 树，生成 GPU 命令并提交。GPU 异步执行后产出 fence，用于后续的合成同步。</li></ul><h3 id="11-提交-Buffer（可能-unsignaled）"><a href="#11-提交-Buffer（可能-unsignaled）" class="headerlink" title="11. 提交 Buffer（可能 unsignaled）"></a>11. 提交 Buffer（可能 unsignaled）</h3><ul><li><strong>Perfetto trace</strong>: queueBuffer（可观察 acquireFence 状态）</li><li><strong>流程说明</strong>: 帧提交会经过 BufferQueue&#x2F;BLAST 机制进入 SurfaceFlinger，某些场景下会出现 unsignaled fence 相关行为（由系统策略决定是否允许提前 latch），目标是降低端到端延迟。</li></ul><h3 id="12-触发-Transaction-到-SurfaceFlinger"><a href="#12-触发-Transaction-到-SurfaceFlinger" class="headerlink" title="12. 触发 Transaction 到 SurfaceFlinger"></a>12. 触发 Transaction 到 SurfaceFlinger</h3><ul><li><strong>Perfetto trace</strong>:  TransactionQueue 或 BLAST transaction 事件 ，一般在 queueBuffer 之后，有些 Trace 没有这个 Tag</li><li><strong>流程说明</strong>: App 侧通过 BLAST&#x2F;SurfaceControl transaction 将 buffer 与层属性更新关联后提交到 SurfaceFlinger。SurfaceFlinger 再按 <code>LatchUnsignaledConfig</code> 等策略决定 latch 时机并完成合成显示。</li></ul><p><strong>在 Perfetto 中识别不同的渲染模式</strong>：</p><ul><li><strong>手指滑动时</strong>：每帧都有 <code>Input</code> → <code>Traversal</code> → <code>RenderThread</code> 的完整链路</li><li><strong>惯性滑动时</strong>：每帧都有 <code>Animation</code> → <code>Traversal</code> → <code>RenderThread</code>，没有 <code>Input</code></li><li><strong>静止状态时</strong>：偶尔出现 <code>Animation</code> →  <code>Traversal</code> → <code>RenderThread</code>，没有 <code>Input</code></li></ul><h2 id="软件绘制-vs-硬件加速"><a href="#软件绘制-vs-硬件加速" class="headerlink" title="软件绘制 vs 硬件加速"></a>软件绘制 vs 硬件加速</h2><p>虽然现在基本都使用硬件加速渲染，但了解两种渲染模式的区别仍然有助于理解 Perfetto trace：</p><table><thead><tr><th>方面</th><th>软件绘制</th><th>硬件加速</th></tr></thead><tbody><tr><td><strong>绘制线程</strong></td><td>主线程</td><td>RenderThread</td></tr><tr><td><strong>绘制引擎</strong></td><td>Skia (CPU)</td><td>OpenGL&#x2F;Vulkan (GPU)</td></tr><tr><td><strong>Perfetto 特征</strong></td><td>主线程有大块 <code>draw</code> 事件</td><td>主线程快速完成，RenderThread 处理绘制</td></tr><tr><td><strong>性能影响</strong></td><td>可能阻塞主线程</td><td>异步渲染，性能更好</td></tr></tbody></table><p>上面介绍的是基本的渲染流程，更详细的 Choreographer 原理可以参考 <a href="https://androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a>。</p><hr><p>接下来我们重点讲解主线程和渲染线程的深入内容：</p><ol><li>主线程的发展</li><li>主线程的创建</li><li>渲染线程的创建</li><li>主线程和渲染线程的分工</li></ol><p><a id="evolution"></a></p><h1 id="双线程渲染架构的演进"><a href="#双线程渲染架构的演进" class="headerlink" title="双线程渲染架构的演进"></a>双线程渲染架构的演进</h1><p>Android 的渲染系统经历了从单线程到双线程的重要演进过程。</p><h2 id="单线程时代（Android-4-4-之前）"><a href="#单线程时代（Android-4-4-之前）" class="headerlink" title="单线程时代（Android 4.4 之前）"></a>单线程时代（Android 4.4 之前）</h2><p>在早期的 Android 版本中，所有的 UI 相关工作都在主线程中执行：</p><ul><li>处理用户输入事件</li><li>执行 measure、layout、draw  </li><li>调用 OpenGL 进行实际绘制</li><li>与 SurfaceFlinger 交互</li></ul><p>这种设计的问题：</p><ol><li><strong>响应性差</strong>：主线程负载过重，容易出现 ANR</li><li><strong>性能瓶颈</strong>：CPU 和 GPU 无法并行工作</li><li><strong>帧率不稳定</strong>：复杂界面容易导致掉帧</li></ol><h2 id="双线程时代（Android-5-0-Lollipop-开始）"><a href="#双线程时代（Android-5-0-Lollipop-开始）" class="headerlink" title="双线程时代（Android 5.0 Lollipop 开始）"></a>双线程时代（Android 5.0 Lollipop 开始）</h2><p>Android 5.0 引入了 <strong>RenderThread</strong>，实现渲染工作的分离：</p><p><strong>主线程职责</strong>：</p><ul><li>处理用户输入和业务逻辑</li><li>执行 View 的 measure、layout、draw</li><li>构建 DisplayList（绘制指令列表）</li><li>与渲染线程同步数据</li></ul><p><strong>渲染线程职责</strong>：</p><ul><li>接收并处理 DisplayList</li><li>执行 OpenGL&#x2F;Vulkan 渲染命令</li><li>管理纹理和渲染资源</li><li>与 SurfaceFlinger 交互</li></ul><p>这种架构带来的优势：</p><ol><li><strong>并行处理</strong>：主线程可以在渲染线程工作时处理下一帧</li><li><strong>响应性提升</strong>：主线程不再被渲染阻塞</li><li><strong>性能优化</strong>：GPU 资源得到更好利用</li></ol><p><a id="main-thread-create"></a></p><h1 id="主线程的创建过程"><a href="#主线程的创建过程" class="headerlink" title="主线程的创建过程"></a>主线程的创建过程</h1><p>Android App 的进程是基于 Linux 的，其管理也是基于 Linux 的进程管理机制，所以其创建也是调用了 fork 函数</p><p>frameworks&#x2F;base&#x2F;core&#x2F;jni&#x2F;com_android_internal_os_Zygote.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">pid_t</span> pid = fork();</span><br></pre></td></tr></table></figure><p>Fork 出来的进程，我们这里可以把他看做主线程，但是这个线程还没有和 Android 进行连接，所以无法处理 Android App 的 Message ；由于 Android App 线程运行<strong>基于消息机制</strong> ，那么这个 Fork 出来的主线程需要和 Android 的 Message 消息绑定，才能处理 Android App 的各种 Message </p><p>这里就引入了 <strong>ActivityThread</strong> ，确切的说，ActivityThread 应该起名叫 ProcessThread 更贴切一些。ActivityThread 连接了 Fork 出来的进程和 App 的 Message ，他们的通力配合组成了我们熟知的 Android App 主线程。所以说 ActivityThread 其实并不是一个 Thread，而是他初始化了 Message 机制所需要的 MessageQueue、Looper、Handler ，而且其 Handler 负责处理大部分 Message 消息，所以我们习惯上觉得 ActivityThread 是主线程，其实他只是主线程的一个逻辑处理单元。</p><h2 id="ActivityThread-的创建"><a href="#ActivityThread-的创建" class="headerlink" title="ActivityThread 的创建"></a>ActivityThread 的创建</h2><p>App 进程 fork 出来之后，主路径是：<br><code>ZygoteConnection.handleChildProc</code> → <code>ZygoteInit.zygoteInit</code> → <code>RuntimeInit.applicationInit</code> → <code>ActivityThread.main</code></p><p>com&#x2F;android&#x2F;internal&#x2F;os&#x2F;ZygoteConnection.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Runnable <span class="title function_">handleChildProc</span><span class="params">(ZygoteArguments parsedArgs, FileDescriptor pipeFd,</span></span><br><span class="line"><span class="params">        <span class="type">boolean</span> isZygote)</span> &#123;</span><br><span class="line">    <span class="comment">// ... 省略前置逻辑</span></span><br><span class="line">    <span class="keyword">if</span> (!isZygote) &#123;</span><br><span class="line">        <span class="keyword">return</span> ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,</span><br><span class="line">                parsedArgs.mDisabledCompatChanges,</span><br><span class="line">                parsedArgs.mRemainingArgs, <span class="literal">null</span> <span class="comment">/* classLoader */</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> ZygoteInit.childZygoteInit(parsedArgs.mRemainingArgs);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于普通 App 进程，上面会走 <code>zygoteInit</code> 分支并最终进入 <code>ActivityThread.main</code>；<code>childZygoteInit</code> 是子 zygote 的分支，不是常规 App 主路径。</p><p>android&#x2F;app&#x2F;ActivityThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, <span class="string">&quot;ActivityThreadMain&quot;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1. 初始化 Looper、MessageQueue</span></span><br><span class="line">    Looper.prepareMainLooper();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 初始化 ActivityThread</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">startSeq</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (args != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> args.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; --i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (args[i] != <span class="literal">null</span> &amp;&amp; args[i].startsWith(PROC_START_SEQ_IDENT)) &#123;</span><br><span class="line">                startSeq = Long.parseLong(</span><br><span class="line">                        args[i].substring(PROC_START_SEQ_IDENT.length()));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">ActivityThread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ActivityThread</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 主要是调用 AMS.attachApplicationLocked，同步进程信息，做一些初始化工作</span></span><br><span class="line">    thread.attach(<span class="literal">false</span>, startSeq);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 4. 获取主线程的 Handler，这里是 H ，基本上 App 的 Message 都会在这个 Handler 里面进行处理 </span></span><br><span class="line">    <span class="keyword">if</span> (sMainThreadHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">        sMainThreadHandler = thread.getHandler();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 5. 初始化完成，Looper 开始工作</span></span><br><span class="line">    Looper.loop();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Main thread loop unexpectedly exited&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注释里面都很清楚，这里就不详细说了，main 函数处理完成之后，主线程就算是正式上线开始工作.</p><p><a id="activitythread"></a></p><h2 id="ActivityThread-的功能"><a href="#ActivityThread-的功能" class="headerlink" title="ActivityThread 的功能"></a>ActivityThread 的功能</h2><p>另外我们经常说的，Android 四大组件都是运行在主线程上的，其实这里也很好理解，看一下 ActivityThread 的 Handler 的 Message 就知道了</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">H</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123; <span class="comment">// 摘抄了部分，基于 Android 16 最新实现</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BIND_APPLICATION</span>        <span class="operator">=</span> <span class="number">110</span>; <span class="comment">// 应用启动</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CREATE_SERVICE</span>          <span class="operator">=</span> <span class="number">114</span>; <span class="comment">// 创建Service</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BIND_SERVICE</span>            <span class="operator">=</span> <span class="number">121</span>; <span class="comment">// 绑定Service  </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">RECEIVER</span>                <span class="operator">=</span> <span class="number">113</span>; <span class="comment">// 广播接收</span></span><br><span class="line">    <span class="comment">// ... 还有其他四大组件相关的消息类型</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，进程创建、Activity 启动、Service 的管理、Receiver 的管理、Provider 的管理这些都会在这里处理，然后进到具体的 handleXXX </p><p><a id="render-thread"></a></p><h1 id="渲染线程的创建和发展"><a href="#渲染线程的创建和发展" class="headerlink" title="渲染线程的创建和发展"></a>渲染线程的创建和发展</h1><p>主线程讲完了我们来讲渲染线程，渲染线程也就是 RenderThread ，最初的 Android 版本里面是没有渲染线程的，渲染工作都是在主线程完成，使用的也都是 CPU ，调用的是 libSkia 这个库，RenderThread 是在 Android Lollipop 中新加入的组件，负责承担一部分之前主线程的渲染工作，减轻主线程的负担</p><h2 id="软件绘制"><a href="#软件绘制" class="headerlink" title="软件绘制"></a>软件绘制</h2><p>我们一般提到的硬件加速，指的就是 GPU 加速，这里可以理解为用 RenderThread 调用 GPU 来进行渲染加速 。 硬件加速在目前的 Android 中是默认开启的， 所以如果我们什么都不设置，那么我们的进程默认都会有主线程和渲染线程(有可见的内容)。我们如果在 App 的 AndroidManifest 里面，在 Application 标签里面加一个 </p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>hardwareAccelerated=<span class="string">&quot;false&quot;</span></span><br></pre></td></tr></table></figure><p>我们就可以关闭硬件加速，系统检测到你这个 App 关闭了硬件加速，就不会初始化 RenderThread ，直接 cpu 调用 libSkia 来进行渲染。其 Trace 跟踪表现如下<strong>（资源比较老，用 Systrace 图示)</strong></p><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/15717420572997.jpg" alt="img"></p><p>与这篇文章开头开启硬件加速的 Perfetto 图对比，可以看到主线程由于要进行渲染工作，所以执行的时间变长了，也更容易出现卡顿，同时帧与帧之间的空闲间隔也变短了，使得其他 Message 的执行时间被压缩。在 Perfetto 中，这种差异通过线程活动的时间长度和密集程度可以清晰地观察到。</p><h2 id="硬件加速绘制"><a href="#硬件加速绘制" class="headerlink" title="硬件加速绘制"></a>硬件加速绘制</h2><p>正常情况下，硬件加速是开启的，主线程的 draw 主要是在构建&#x2F;更新 DisplayList（RenderNode 树），然后通过 <code>syncAndDrawFrame</code> 同步给 RenderThread。UI 线程会在同步关键路径上短暂等待，随后尽快返回继续处理主线程消息；RenderThread 再继续执行后续渲染与提交。</p><h2 id="渲染线程初始化"><a href="#渲染线程初始化" class="headerlink" title="渲染线程初始化"></a>渲染线程初始化</h2><p>渲染线程初始化在真正需要 draw 内容的时候，一般我们启动一个 Activity ，在第一个 draw 执行的时候，会去检测渲染线程是否初始化，如果没有则去进行初始化</p><p>android&#x2F;view&#x2F;ViewRootImpl.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 渲染线程初始化</span></span><br><span class="line">mAttachInfo.mThreadedRenderer.initializeIfNeeded(</span><br><span class="line">        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化 BlastBufferQueue - App 端缓冲区管理器</span></span><br><span class="line"><span class="keyword">if</span> (mBlastBufferQueue == <span class="literal">null</span>) &#123;</span><br><span class="line">    mBlastBufferQueue = <span class="keyword">new</span> <span class="title class_">BLASTBufferQueue</span>(mTag, mSurfaceControl,</span><br><span class="line">        mSurfaceSize.x, mSurfaceSize.y,</span><br><span class="line">        mWindowAttributes.format);</span><br><span class="line">    mBlastBufferQueue.update(mSurfaceControl, </span><br><span class="line">        mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里创建的 BlastBufferQueue 将在后续的渲染过程中发挥关键作用：</p><ul><li>为 RenderThread 提供高效的 Buffer 管理</li><li>支持批量 Transaction 提交，减少与 SurfaceFlinger 的交互开销</li><li>在 Perfetto 中可观察到 QueuedBuffer 指标的变化</li></ul><p>后续直接调用 draw </p><p>android&#x2F;view&#x2F;ThreadedRenderer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, <span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">draw</span><span class="params">(View view, AttachInfo attachInfo, DrawCallbacks callbacks)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Choreographer</span> <span class="variable">choreographer</span> <span class="operator">=</span> attachInfo.mViewRootImpl.mChoreographer;</span><br><span class="line">    choreographer.mFrameInfo.markDrawStart();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 RootDisplayList，构建 RenderNode 树</span></span><br><span class="line">    updateRootDisplayList(view, callbacks);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理动画 RenderNode</span></span><br><span class="line">    <span class="keyword">if</span> (attachInfo.mPendingAnimatingRenderNodes != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> attachInfo.mPendingAnimatingRenderNodes.size();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">            registerAnimatingRenderNode(</span><br><span class="line">                    attachInfo.mPendingAnimatingRenderNodes.get(i));</span><br><span class="line">        &#125;</span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes.clear();</span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 同步并绘制帧，这里会触发 RenderThread 工作</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">syncResult</span> <span class="operator">=</span> syncAndDrawFrame(choreographer.mFrameInfo);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 处理各种结果状态</span></span><br><span class="line">    <span class="keyword">if</span> ((syncResult &amp; SYNC_LOST_SURFACE_REWARD_IF_FOUND) != <span class="number">0</span>) &#123;</span><br><span class="line">        setEnabled(<span class="literal">false</span>);</span><br><span class="line">        attachInfo.mViewRootImpl.mSurface.release();</span><br><span class="line">        attachInfo.mViewRootImpl.invalidate();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ((syncResult &amp; SYNC_REDRAW_REQUESTED) != <span class="number">0</span>) &#123;</span><br><span class="line">        attachInfo.mViewRootImpl.invalidate();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的 draw 先更新 DisplayList，之后调用 <code>syncAndDrawFrame</code> 进入关键同步阶段，完成 <strong>UI Thread 到 RenderThread</strong> 的数据对齐。</p><h3 id="UI-Thread-与-RenderThread-的-DisplayList-同步机制"><a href="#UI-Thread-与-RenderThread-的-DisplayList-同步机制" class="headerlink" title="UI Thread 与 RenderThread 的 DisplayList 同步机制"></a>UI Thread 与 RenderThread 的 DisplayList 同步机制</h3><p>在 <code>syncAndDrawFrame</code> 这个关键函数中，发生了以下重要的同步操作：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/libs/hwui/renderthread/RenderProxy.cpp</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">RenderProxy::syncAndDrawFrame</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 1. 将 UI Thread 的 DisplayList 同步到 RenderThread</span></span><br><span class="line">    <span class="comment">// 这里会把主线程构建的 RenderNode 树传递给渲染线程</span></span><br><span class="line">    <span class="keyword">return</span> mDrawFrameTask.<span class="built_in">drawFrame</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>syncAndDrawFrame</code> 的底层并不是完全非阻塞。最新 AOSP 中 <code>DrawFrameTask::drawFrame()</code> 会执行 <code>postAndWait()</code>：先把任务投递到 RenderThread 队列，再在条件变量上等待；RenderThread 在合适的同步点会 <code>unblockUiThread()</code>。因此它是“<strong>有等待但尽量早释放 UI</strong>”的设计，而不是“UI 完全不等待”。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">DrawFrameTask::drawFrame</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mSyncResult = SyncResult::OK;</span><br><span class="line">    mSyncQueued = <span class="built_in">systemTime</span>(SYSTEM_TIME_MONOTONIC);</span><br><span class="line">    <span class="built_in">postAndWait</span>();   <span class="comment">// 关键：UI 线程在这里等待</span></span><br><span class="line">    <span class="keyword">return</span> mSyncResult;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">DrawFrameTask::postAndWait</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    AutoMutex _lock(mLock);</span><br><span class="line">    mRenderThread-&gt;<span class="built_in">queue</span>().<span class="built_in">post</span>([<span class="keyword">this</span>]() &#123; <span class="built_in">run</span>(); &#125;);</span><br><span class="line">    mSignal.<span class="built_in">wait</span>(mLock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体的同步过程包括：</p><ol><li><strong>RenderNode 树的传递</strong>：主线程在 draw 过程中构建的 RenderNode 树（包含 DisplayList）会被传递给 RenderThread</li><li><strong>属性同步</strong>：View 的变换矩阵、透明度、裁剪区域等属性会一并同步</li><li><strong>资源共享</strong>：纹理、Path、Paint 等绘制资源在两个线程之间建立共享机制</li><li><strong>渲染状态传递</strong>：当前帧需要的渲染状态信息传递给 RenderThread</li></ol><p>这个同步过程是 Android 硬件加速渲染的核心，它实现了 UI Thread 专注于逻辑处理，RenderThread 专注于渲染的分工模式。</p><p>渲染线程的核心实现在 libhwui 库里面，其代码位于 frameworks&#x2F;base&#x2F;libs&#x2F;hwui</p><h3 id="RenderThread-与-BlastBufferQueue-的交互流程"><a href="#RenderThread-与-BlastBufferQueue-的交互流程" class="headerlink" title="RenderThread 与 BlastBufferQueue 的交互流程"></a>RenderThread 与 BlastBufferQueue 的交互流程</h3><p>RenderThread 接收到同步的 DisplayList 后，开始真正的渲染工作，这个过程中会与 BlastBufferQueue 进行密切的交互：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/base/libs/hwui/renderthread/CanvasContext.cpp</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">CanvasContext::draw</span><span class="params">(<span class="type">bool</span> solelyTextureViewUpdates)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 1. 计算 dirty 区域并准备本帧</span></span><br><span class="line">    <span class="comment">// 2. 通过 mRenderPipeline-&gt;draw(...) 生成绘制命令</span></span><br><span class="line">    <span class="comment">// 3. 通过 mRenderPipeline-&gt;swapBuffers(...) 提交本帧</span></span><br><span class="line">    <span class="comment">// 4. 记录 dequeue/queue 时延到 FrameInfo (DequeueBufferDuration/QueueBufferDuration)</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>注：旧版本常见的 <code>dequeueBuffer/queueBuffer/flushTransaction</code> 直观流程仍可作为理解模型，但在最新主干里这些细节被收敛在 <code>RenderPipeline</code>&#x2F;<code>ANativeWindow</code> 路径中，<code>CanvasContext::draw()</code> 本身已不是旧代码形态。</p></blockquote><p><strong>BlastBufferQueue 的关键特性：</strong></p><ol><li><strong>App 端管理</strong>：不同于传统的 BufferQueue 由 SurfaceFlinger 创建，BlastBufferQueue 是由 App 端创建和管理</li><li><strong>减少同步等待</strong>：通过生产者-消费者模型，减少了 RenderThread 在 dequeueBuffer 时的等待时间</li><li><strong>高效的缓冲区轮转</strong>：支持更智能的缓冲区管理策略，特别适配高刷新率显示器</li><li><strong>异步提交</strong>：通过 transaction 机制异步地将完成的帧提交给 SurfaceFlinger</li><li><strong>支持 unsignaled buffer</strong>：配合 SurfaceFlinger 的 unsignaled latch 策略，允许在特定条件下减少端到端延迟</li></ol><p><strong>关于 Latching Unsignaled Buffers 的深入探讨</strong></p><p>现代 Android 系统对 <code>presentFence</code> 的处理有精细的控制，并非总是等待。这个机制被称为 **”Latching Unsignaled Buffers”**（捕获未就绪的缓冲区）。</p><ul><li><p><strong>传统模式</strong>: SurfaceFlinger 必须等待 App 的 <code>presentFence</code> 被 GPU signal 后，才能 “latch” (捕获) 这个 Buffer 进行合成。这保证了安全性，但增加了延迟。</p></li><li><p><strong>Latch Unsignaled 模式</strong>: 在此模式下，SurfaceFlinger 可以立即 latch 一个 GPU 尚未完成渲染的 Buffer（即 fence 未 signaled），并提前开始部分合成工作。当它需要真正使用这个 Buffer 的内容时，它才会在内部等待 <code>presentFence</code>。这通过流水线化进一步隐藏了 GPU 渲染的延迟，对降低游戏、视频等全屏应用的输入延迟至关重要。</p></li></ul><p><strong>控制开关与策略 (Android 13+)</strong>:</p><p>这个行为可以通过系统属性 <code>debug.sf.auto_latch_unsignaled</code> 进行全局调试，但更重要的是，它由一个名为 <code>LatchUnsignaledConfig</code> 的分层策略控制。一个典型的策略是 <code>AutoSingleLayer</code>：</p><ul><li>当屏幕上只有<strong>单个图层</strong>更新时（如全屏游戏或视频），系统会自动启用 Latch Unsignaled 模式，因为此时没有复杂的图层依赖，风险最低，收益最大。</li><li>当有多个图层更新时，系统会回退到更安全的传统等待模式，以避免潜在的视觉错误。</li></ul><p>因此，SurfaceFlinger 并非总是盲目等待 <code>presentFence</code>，而是根据精密的策略来决定是否“抢跑”，以在稳定性和极致性能之间取得平衡。</p><h2 id="主线程和渲染线程的分工"><a href="#主线程和渲染线程的分工" class="headerlink" title="主线程和渲染线程的分工"></a>主线程和渲染线程的分工</h2><p>主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ，更新 DisplayList ，但是不涉及与 SurfaceFlinger 直接打交道；渲染线程负责渲染相关的工作，包括与 BlastBufferQueue 的交互、GPU 渲染命令的执行，以及与 SurfaceFlinger 的最终交互。</p><p>当启动硬件加速后，在 Measure、Layout、Draw 的 Draw 这个环节，Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操作的记录，抽象为 RenderNode 类，这样间接的进行绘制操作的优点如下</p><ol><li>DisplayList 可以按需多次绘制而无须同业务逻辑交互</li><li>特定的绘制操作（如 translation、scale 等）可以作用于整个 DisplayList 而无须重新分发绘制操作</li><li>当知晓了所有绘制操作后，可以针对其进行优化：例如，所有的文本可以一起进行绘制一次</li><li>可以将对 DisplayList 的处理转移至另一个线程（也就是 RenderThread）</li><li>主线程在 sync 结束后可以处理其他的 Message，而不用等待 RenderThread 结束</li><li>通过 BlastBufferQueue 实现更高效的缓冲区管理，减少渲染延迟和主线程阻塞</li></ol><h3 id="BlastBufferQueue-的工作原理"><a href="#BlastBufferQueue-的工作原理" class="headerlink" title="BlastBufferQueue 的工作原理"></a>BlastBufferQueue 的工作原理</h3><p>BlastBufferQueue 是现代 Android 渲染架构中的关键组件，它改变了传统的缓冲区管理方式：</p><p><strong>传统 BufferQueue vs BlastBufferQueue：</strong></p><ol><li><p><strong>创建主体不同</strong>：</p><ul><li>传统 BufferQueue：由 SurfaceFlinger 创建和管理</li><li>BlastBufferQueue：由 App 端（ViewRootImpl）创建和管理</li></ul></li><li><p><strong>缓冲区获取机制</strong>：</p><ul><li>传统方式：RenderThread 需要通过 Binder 调用向 SurfaceFlinger 请求 Buffer，可能会因为没有可用 Buffer 而阻塞</li><li>BlastBufferQueue：App 端预先管理缓冲区池，RenderThread 可以更高效地获取 Buffer</li></ul></li><li><p><strong>提交机制</strong>：</p><ul><li>传统方式：通过 queueBuffer 直接提交给 SurfaceFlinger</li><li>BlastBufferQueue：通过 transaction 机制批量提交，减少 Binder 调用开销</li></ul></li></ol><p><strong>在 Perfetto 中观察 BlastBufferQueue：</strong></p><p>在 Perfetto 跟踪中，BlastBufferQueue 的状态通过以下关键指标显示：</p><h3 id="App-端的-QueuedBuffer-指标"><a href="#App-端的-QueuedBuffer-指标" class="headerlink" title="App 端的 QueuedBuffer 指标"></a>App 端的 QueuedBuffer 指标</h3><ul><li><strong>Perfetto 显示</strong>：<code>QueuedBuffer</code> 数值轨道</li><li><strong>AOSP 定义（BLASTBufferQueue）</strong>：<code>QueuedBuffer = mNumFrameAvailable + mNumAcquired - mPendingRelease.size()</code></li><li><strong>解释方式</strong>：它是一个“综合状态量”，反映可用帧、已获取帧、待 release 的平衡关系，不建议用固定常量偏移去做一刀切换算</li><li><strong>实战建议</strong>：重点看趋势和持续时间，而不是单个瞬时值</li></ul><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803170713946.webp" alt="image-20250803170713946"></p><h3 id="QueuedBuffer-数值变化时机"><a href="#QueuedBuffer-数值变化时机" class="headerlink" title="QueuedBuffer 数值变化时机"></a>QueuedBuffer 数值变化时机</h3><p><strong>QueuedBuffer +1 的时机</strong>：</p><ul><li><strong>常见触发</strong>：生产侧有新 buffer 到达 BLAST，并进入可处理状态</li><li><strong>Perfetto 表现</strong>：<code>QueuedBuffer</code> 轨道上升</li><li><strong>含义</strong>：App 端到 SF 端之间的“待处理帧压力”增加</li></ul><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803170852607.webp" alt="image-20250803170852607"></p><p><strong>QueuedBuffer -1 的时机</strong>：</p><ul><li><strong>触发条件</strong>：收到 SurfaceFlinger 的 <code>releaseBufferCallback</code></li><li><strong>Perfetto 表现</strong>：可观察到 <code>releaseBuffer</code> 相关事件</li><li><strong>含义</strong>：某个 buffer 完成消费或被处理后释放，队列压力下降</li></ul><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803171008400.webp" alt="image-20250803171008400"></p><h3 id="SurfaceFlinger-端的-BufferTX-指标"><a href="#SurfaceFlinger-端的-BufferTX-指标" class="headerlink" title="SurfaceFlinger 端的 BufferTX 指标"></a>SurfaceFlinger 端的 BufferTX 指标</h3><ul><li><strong>Perfetto 显示</strong>：SurfaceFlinger 进程中的 <code>BufferTX</code> 数值轨道</li><li><strong>AOSP 定义（Layer）</strong>：该值对应每层 <code>mPendingBuffers</code> 的跟踪；buffer 到达 server 侧会增加，buffer 被 latch 或 drop 会减少</li><li><strong>触发条件</strong>：与 transaction 和 buffer 生命周期共同相关，不建议简单等价为“收到 transaction 就 +1”</li><li><strong>注意</strong>：它不是通用“固定最大 3”的指标，受图层类型、生产消费节奏和系统策略影响</li></ul><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803171228146.webp" alt="image-20250803171228146"></p><h3 id="App-端和-SF-端的协作流程"><a href="#App-端和-SF-端的协作流程" class="headerlink" title="App 端和 SF 端的协作流程"></a>App 端和 SF 端的协作流程</h3><ol><li><strong>App 端</strong>：RenderThread 提交新帧后，<code>QueuedBuffer</code> 常见上升</li><li><strong>跨进程</strong>：BLAST&#x2F;SurfaceControl transaction 关联 frameNumber 并进入 SF 侧处理</li><li><strong>SF 端</strong>：<code>BufferTX</code> 随 pending buffer 变化（到达增、latch&#x2F;drop 减）</li><li><strong>回流</strong>：<code>releaseBufferCallback</code> 到 App 端后，<code>QueuedBuffer</code> 下降</li></ol><h3 id="关键性能观察点"><a href="#关键性能观察点" class="headerlink" title="关键性能观察点"></a>关键性能观察点</h3><p>在分析性能时，重点关注：</p><ul><li><strong>App 端 QueuedBuffer 趋势</strong>：连续上升且长时间不回落，通常表示生产&#x2F;消费节奏失衡；结合主线程 <code>performTraversals</code> 与 RenderThread <code>DrawFrames</code> 判断瓶颈在 App 还是 SF&#x2F;GPU 侧</li><li><strong>SurfaceFlinger 端 BufferTX 趋势</strong>：长期偏高常见于消费侧压力大；长期偏低且 App 又频繁 miss deadline，往往是生产侧供给不足</li></ul><p><a id="performance"></a></p><h1 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h1><p>如果主线程需要处理所有任务，则执行耗时较长的操作（例如，网络访问或数据库查询）将会阻塞整个界面线程。一旦被阻塞，线程将无法分派任何事件，包括绘图事件。主线程执行超时通常会带来两个问题</p><ol><li><strong>卡顿</strong>：如果主线程 + 渲染线程每一帧的执行都超过 8.33ms(120fps 的情况下)，那么就可能会出现掉帧（说可能是因为有的情况下其实不会掉帧，因为有 app duration 、buffer 堆积等情况）。</li><li><strong>卡死</strong>：如果界面线程被阻塞超过几秒钟时间（根据组件不同 , 这里的阈值也不同），用户会看到 “<a href="http://developer.android.google.cn/guide/practices/responsiveness.html">应用无响应</a>“ (ANR) 对话框(部分厂商屏蔽了这个弹框,会直接 Crash 到桌面)</li></ol><p>对于用户来说，这两个情况都是用户不愿意看到的，所以对于 App 开发者来说，两个问题是发版本之前必须要解决的，ANR 这个由于有详细的调用栈，所以相对来说比较好定位；但是间歇性卡顿这个，可能就需要使用工具来进行分析了：<strong>Perfetto + Trace View （Android Studio 已经集成）</strong>，所以理解主线程和渲染线程的关系和他们的工作原理是非常重要的，这也是本系列的一个初衷。</p><p><a id="frametimeline"></a></p><h2 id="Perfetto-独有的-FrameTimeline-功能"><a href="#Perfetto-独有的-FrameTimeline-功能" class="headerlink" title="Perfetto 独有的 FrameTimeline 功能"></a>Perfetto 独有的 FrameTimeline 功能</h2><p>Perfetto 相比 Systrace 的一个重要优势是提供了 <strong>FrameTimeline</strong> 功能，可以一眼就可以看到卡顿的地方。</p><blockquote><p><strong>注意</strong>: FrameTimeline 需要 Android 12(S) 或更高版本支持</p></blockquote><h3 id="FrameTimeline-的核心概念"><a href="#FrameTimeline-的核心概念" class="headerlink" title="FrameTimeline 的核心概念"></a>FrameTimeline 的核心概念</h3><p>根据 <a href="https://perfetto.dev/docs/data-sources/frametimeline">Perfetto 官方文档</a>，当帧在屏幕上的实际呈现时间与调度器预期的呈现时间不匹配时，就会产生卡顿。FrameTimeline 为每个有帧在屏幕上显示的应用添加了两个新的轨道：</p><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803172616453.webp" alt="image-20250803172616453"></p><h4 id="1-Expected-Timeline（预期时间线）"><a href="#1-Expected-Timeline（预期时间线）" class="headerlink" title="1. Expected Timeline（预期时间线）"></a>1. Expected Timeline（预期时间线）</h4><ul><li><strong>作用</strong>: 显示系统分配给应用的渲染时间窗口</li><li><strong>开始时间</strong>: Choreographer 回调被调度运行的时间</li><li><strong>含义</strong>: 为了避免系统卡顿，应用需要在这个时间范围内完成工作</li></ul><h4 id="2-Actual-Timeline（实际时间线）"><a href="#2-Actual-Timeline（实际时间线）" class="headerlink" title="2. Actual Timeline（实际时间线）"></a>2. Actual Timeline（实际时间线）</h4><ul><li><strong>作用</strong>: 显示应用完成帧的实际时间（包括 GPU 工作）</li><li><strong>开始时间</strong>: <code>Choreographer#doFrame</code> 或 <code>AChoreographer_vsyncCallback</code> 开始运行的时间</li><li><strong>结束时间</strong>: <code>max(GPU 时间, Post 时间)</code>，其中 Post 时间是帧被提交到 SurfaceFlinger 的时间</li></ul><p>当你点击 Actual Timeline 上的一个 追踪的时候，会显示这一帧具体的被消费的时间（可以看延时）。</p><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803172911195.webp" alt="image-20250803172911195"></p><h3 id="颜色编码系统"><a href="#颜色编码系统" class="headerlink" title="颜色编码系统"></a>颜色编码系统</h3><p>FrameTimeline 使用直观的颜色来标识不同的帧状态：</p><table><thead><tr><th>颜色</th><th>含义</th><th>说明</th></tr></thead><tbody><tr><td><strong>绿色</strong></td><td>正常帧</td><td>没有观察到卡顿，理想状态</td></tr><tr><td><strong>浅绿色</strong></td><td>高延迟状态</td><td>帧率稳定但帧呈现延迟，导致输入延迟增加</td></tr><tr><td><strong>红色</strong></td><td>卡顿帧</td><td>当前进程导致的卡顿</td></tr><tr><td><strong>黄色</strong></td><td>应用无责任卡顿</td><td>帧出现卡顿但应用不是原因，SurfaceFlinger 导致的卡顿</td></tr><tr><td><strong>蓝色</strong></td><td>丢帧</td><td>SurfaceFlinger 丢弃了该帧，选择了更新的帧</td></tr></tbody></table><p>点击不同颜色的 ActualTimeline 可以在信息栏看到下面的描述，告诉你卡顿的原因：<br><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803173304026.webp" alt="image-20250803173304026"></p><h3 id="卡顿类型分析"><a href="#卡顿类型分析" class="headerlink" title="卡顿类型分析"></a>卡顿类型分析</h3><p>FrameTimeline 可以识别多种卡顿类型：</p><h4 id="应用端卡顿："><a href="#应用端卡顿：" class="headerlink" title="应用端卡顿："></a>应用端卡顿：</h4><ul><li><strong>AppDeadlineMissed</strong>: 应用运行时间超过预期</li><li><strong>BufferStuffing</strong>: 应用在前一帧呈现前就发送新帧，导致 Buffer 队列堆积</li></ul><h4 id="SurfaceFlinger-卡顿："><a href="#SurfaceFlinger-卡顿：" class="headerlink" title="SurfaceFlinger 卡顿："></a>SurfaceFlinger 卡顿：</h4><ul><li><strong>SurfaceFlingerCpuDeadlineMissed</strong>: SurfaceFlinger 主线程超时</li><li><strong>SurfaceFlingerGpuDeadlineMissed</strong>: GPU 合成时间超时</li><li><strong>DisplayHAL</strong>: HAL 层呈现延迟</li><li><strong>PredictionError</strong>: 调度器预测偏差</li></ul><h3 id="配置-FrameTimeline"><a href="#配置-FrameTimeline" class="headerlink" title="配置 FrameTimeline"></a>配置 FrameTimeline</h3><p>在 Perfetto 配置中启用 FrameTimeline：</p><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">data_sources</span> &#123;</span><br><span class="line">    <span class="keyword">config</span> &#123;</span><br><span class="line">        name<span class="operator">:</span> <span class="string">&quot;android.surfaceflinger.frametimeline&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a id="vsync"></a></p><h2 id="Perfetto-中-Vsync-信号"><a href="#Perfetto-中-Vsync-信号" class="headerlink" title="Perfetto 中 Vsync 信号"></a>Perfetto 中 Vsync 信号</h2><p>在 Perfetto 中，<strong>Vsync 信号使用 Counter 类型来显示</strong>，这与很多人的直觉认知不同：</p><ul><li><strong>0 → 1 的变化</strong>：表示一个 Vsync 信号</li><li><strong>1 → 0 的变化</strong>：同样表示一个 Vsync 信号</li><li><strong>错误理解</strong>：很多人误以为只有变成 1 才是 Vsync 信号</li></ul><h3 id="正确的-Vsync-信号识别"><a href="#正确的-Vsync-信号识别" class="headerlink" title="正确的 Vsync 信号识别"></a>正确的 Vsync 信号识别</h3><p>下图中 1 、2、3、4 的时间点都是 Vsync 信号到达</p><p><img src="/../images/Android-Perfetto-07-MainThread-And-RenderThread/image-20250803173809421.webp" alt="image-20250803173809421"></p><p><strong>关键要点</strong>：</p><ol><li><strong>每次数值变化都是一个 Vsync</strong>：无论是 0→1 还是 1→0</li><li><strong>信号频率</strong>：120Hz 设备上约每 8.33ms 会有一次变化（实际可能因系统调度略有差异，这里指的是连续出帧场景）</li><li><strong>多 App 场景</strong>：Counter 可能因为其他 App 的申请而保持活跃状态</li></ol><h3 id="分析技巧"><a href="#分析技巧" class="headerlink" title="分析技巧"></a>分析技巧</h3><p><strong>判断 App 是否接收到 Vsync</strong>：</p><ul><li><strong>正确方法</strong>：查看 App 进程中是否有对应的 <code>FrameDisplayEventReceiver.onVsync</code> 事件</li><li><strong>错误方法</strong>：仅凭 SurfaceFlinger 中的 <code>vsync-app</code> counter 变化来判断</li></ul><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://juejin.im/post/5a9e01c3f265da239d48ce32">https://juejin.im/post/5a9e01c3f265da239d48ce32</a></li><li><a href="http://www.cocoachina.com/articles/35302">http://www.cocoachina.com/articles/35302</a></li><li><a href="https://juejin.im/post/5b7767fef265da43803bdc65">https://juejin.im/post/5b7767fef265da43803bdc65</a></li><li><a href="http://gityuan.com/2019/06/15/flutter_ui_draw/">http://gityuan.com/2019/06/15/flutter_ui_draw&#x2F;</a></li><li><a href="https://developer.android.google.cn/guide/components/processes-and-threads">https://developer.android.google.cn/guide/components/processes-and-threads</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>本文涉及到的 Perfetto 跟踪文件也上传了，各位下载后可以在 Perfetto UI (<a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a>) 中打开分析</p><p><a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Perfetto/demo_app_aosp_scroll.perfetto-trace">点此链接下载文章所涉及到的 Perfetto 跟踪文件</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://www.androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本篇是 Perfetto 系列文章的第七篇，主要介绍 Android App 中的 MainThread 和 RenderThread，也就是大家熟悉的&lt;strong&gt;主线程&lt;/strong&gt;和&lt;strong&gt;渲染线程&lt;/strong&gt;。文章会从 Perfetto 的角度来看 MainThread 和 RenderThread 的工作流程，涉及卡顿、软件渲染、掉帧计算等相关知识。&lt;/p&gt;
&lt;p&gt;随着 Google 正式推出 Perfetto 工具替代 Systrace，Perfetto 在性能分析领域已经成为主流选择。本文将结合 Perfetto 的具体 trace 信息，帮助读者理解 MainThread 和 RenderThread 的完整工作流程，让你在使用 Perfetto 分析性能问题时能够：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;准确识别关键 trace tag&lt;/strong&gt;：知道 UI Thread、RenderThread 等关键线程的作用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解帧渲染的完整流程&lt;/strong&gt;：从 Vsync 信号到屏幕显示的每个步骤  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定位性能瓶颈&lt;/strong&gt;：通过 trace 信息快速找到卡顿和性能问题的根因</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="BlastBufferQueue" scheme="https://androidperformance.com/tags/BlastBufferQueue/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</title>
    <link href="https://androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/"/>
    <id>https://androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/</id>
    <published>2025-04-26T06:13:38.000Z</published>
    <updated>2026-02-07T05:17:47.834Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Android Perfetto 系列的第六篇，主要介绍 Android 设备上 120Hz 刷新率的相关知识。如今，120Hz 已成为 Android 旗舰手机的标配，本文将讨论高刷新率带来的优势和挑战，以及从系统角度解析 120Hz 的工作原理。</p><p>在过去的几年中，移动设备的屏幕刷新率经历了从 60Hz 到 90Hz，再到现在普遍的 120Hz 的演进过程。这种提升不仅带来了更流畅的视觉体验，也对系统架构和应用开发提出了新的要求。通过 Perfetto 工具，我们可以更直观地理解高刷新率设备上帧渲染的过程和性能表现。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#concepts">基本概念</a></li><li><a href="#impl">系统实现与工作原理</a></li><li><a href="#pros-cons">120Hz 的优势与挑战</a></li><li><a href="#thoughts">思考与展望</a></li><li><a href="#conclusion">结论</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="concepts"></a></p><h1 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h1><h2 id="什么是屏幕刷新率？"><a href="#什么是屏幕刷新率？" class="headerlink" title="什么是屏幕刷新率？"></a>什么是屏幕刷新率？</h2><p>屏幕刷新率是一个<strong>硬件</strong>概念，指的是屏幕每秒钟刷新显示内容的次数，单位是赫兹（Hz）。</p><ul><li>60Hz 屏幕：每秒刷新 60 次，每次刷新间隔约 16.67ms</li><li>90Hz 屏幕：每秒刷新 90 次，每次刷新间隔约 11.11ms</li><li>120Hz 屏幕：每秒刷新 120 次，每次刷新间隔约 8.33ms</li></ul><p>屏幕刷新率决定了显示设备能够展示的最高帧率，但屏幕只负责按固定频率显示内容，具体显示什么内容由软件系统决定。</p><h2 id="什么是-FPS？"><a href="#什么是-FPS？" class="headerlink" title="什么是 FPS？"></a>什么是 FPS？</h2><p>FPS（Frames Per Second）是一个<strong>软件</strong>概念，指的是系统每秒生成多少帧内容提供给屏幕显示。</p><ul><li>60FPS：系统每秒生成 60 帧内容，每帧有约 16.67ms 的处理时间</li><li>90FPS：系统每秒生成 90 帧内容，每帧有约 11.11ms 的处理时间</li><li>120FPS：系统每秒生成 120 帧内容，每帧有约 8.33ms 的处理时间</li></ul><p>为了获得最佳的视觉体验，FPS 应该与屏幕刷新率匹配。如果 FPS 低于刷新率，会出现掉帧；如果 FPS 高于刷新率，多余的帧会被丢弃，造成资源浪费。</p><p>为了获得最佳的视觉体验，理想情况下 FPS 应该与屏幕刷新率匹配，但实际体验与内容类型和用户感知紧密相关：</p><ol><li><p><strong>内容类型差异</strong>：</p><ul><li><strong>视频内容</strong>：电影（24fps）或视频（30fps）即使在 120Hz 屏幕上也能看起来流畅，这是因为视频内容包含自然运动模糊，且符合观看者对该媒介的预期</li><li><strong>交互式界面</strong>：而滑动列表、动画等交互场景对帧率要求更高，从 120fps 降到 110fps 都可能被用户感知为卡顿</li></ul></li><li><p><strong>帧率稳定性</strong>：稳定的低帧率（如稳定的 60fps）通常比不稳定的高帧率（如在 90-120fps 之间波动）体验更好</p></li><li><p><strong>系统行为</strong>：</p><ul><li>当 FPS 低于刷新率时，显示系统会复用帧或插入黑帧</li><li>当 FPS 高于刷新率时，多余的帧会被丢弃，造成计算资源浪费</li></ul></li></ol><p>不同应用场景有不同的流畅度标准，开发者需要根据应用类型选择合适的优化策略。</p><h2 id="什么是-Vsync？"><a href="#什么是-Vsync？" class="headerlink" title="什么是 Vsync？"></a>什么是 Vsync？</h2><p>Vsync（垂直同步）是将软件帧率与屏幕刷新率同步的机制，目的是避免画面撕裂现象。Android 系统中，Vsync 信号被用来触发应用渲染新一帧的时机，确保渲染过程与屏幕刷新周期保持一致。</p><h2 id="为什么-120Hz-成为新标准？"><a href="#为什么-120Hz-成为新标准？" class="headerlink" title="为什么 120Hz 成为新标准？"></a>为什么 120Hz 成为新标准？</h2><p>市场从 60Hz 到 90Hz，再到 120Hz 的演进有着明确的技术和用户体验驱动因素：</p><ol><li><p><strong>更高的流畅度</strong>：120Hz 比 60Hz 提供了两倍的视觉信息，使滑动、动画等交互感觉更加流畅自然。</p></li><li><p><strong>减少延迟</strong>：输入事件到显示结果的延迟从 60Hz 的 16.67ms 减少到 120Hz 的 8.33ms，让用户操作反馈更及时。</p></li><li><p><strong>硬件支持成熟</strong>：现代移动处理器（如高通骁龙 8 系列、联发科天玑系列）已经有足够性能支持 120Hz 的稳定运行。</p></li><li><p><strong>电池技术进步</strong>：更高效的电池和电源管理技术缓解了高刷新率带来的功耗压力。</p></li><li><p><strong>可变刷新率技术</strong>：LTPO 等自适应刷新率技术允许设备在不同场景下智能切换刷新率，平衡流畅度和功耗。</p></li></ol><p>如今，120Hz 不仅是 Android 旗舰机型的标配，连 iOS 设备（iPhone 13 Pro 及以上）也已支持 120Hz 的 ProMotion 技术，标志着高刷新率已成为高端移动设备的基本特性。</p><p><a id="impl"></a></p><h1 id="系统实现与工作原理"><a href="#系统实现与工作原理" class="headerlink" title="系统实现与工作原理"></a>系统实现与工作原理</h1><h2 id="Perfetto-视角下的-120Hz-渲染流程"><a href="#Perfetto-视角下的-120Hz-渲染流程" class="headerlink" title="Perfetto 视角下的 120Hz 渲染流程"></a>Perfetto 视角下的 120Hz 渲染流程</h2><p>在 120Hz 刷新率下，Android 系统的渲染流程没有本质变化，主要区别是每一帧的时间预算从 16.67ms 缩短到了 8.33ms ( <strong>当然这里没有讨论 App duration，如果 App Duration 配置大于 8.33ms，那么 App 的 UI + Render 在  App duration 区间完成都是可以的，注意 每个机器的 App Duration 配置不一样</strong>）。下图展示了 120Hz 环境下应用渲染的 Perfetto 追踪图：</p><p><img src="/images/Android-Perfetto-06-Why-120Hz/image-20250426144755156.webp" alt="120Hz 下 App 的渲染流程"></p><p>在 120Hz 设备上，我们可以看到：</p><ol><li><strong>Vsync 间隔</strong>：VSYNC 信号每 8.33ms 触发一次</li><li><strong>帧处理流程</strong>：每一帧的处理依然遵循 Input → Animation → Traversal 的顺序</li><li><strong>时间压缩</strong>：所有处理步骤必须在更短的时间内完成，对系统和应用性能要求更高</li></ol><p>上图中出现了两个 Buffer 相关的 Trace，这里做一个简单的说明：</p><ol><li><strong>QueuedBuffer</strong>：（例如：QueuedBuffer - VRI[ImproveSnsTimelineUI]#748BLAST#748）<ul><li>这个 Trace Tag 是在 <strong>App 进程</strong>中打印的</li><li>表示应用完成一帧渲染后，将渲染好的 Buffer 放入队列准备提交给 SurfaceFlinger</li><li>在使用 BlastBufferQueue 的系统中，这个时刻标志着 RenderThread 完成渲染并准备将结果传输到系统服务</li></ul></li><li><strong>BufferTX</strong>：（例如：BufferTX - com.tencent.mm&#x2F;com.tencent.mm.plugin.sns.ui.improve.ImproveSnsTimelineUI#47974）<ul><li>这个 Trace Tag 是在 <strong>SurfaceFlinger 进程</strong>中打印的</li><li>表示 SurfaceFlinger 接收到应用传来的 Buffer 并开始处理的时刻</li><li>TX 代表 “Transfer&#x2F;Transmission”，即缓冲区的传输过程</li></ul></li></ol><p><strong>正确的流程应该是：</strong></p><ol><li>App 的 RenderThread 调用 <code>queueBuffer</code>，此时 App 认为自己已交出一个 Buffer，于是 <code>QueuedBuffer</code> <strong>+1</strong>。</li><li>该 Buffer 被传输给 SF，SF 接收（latch&#x2F;acquire）后，<code>BufferTX</code> <strong>+1</strong>。</li><li>SF 在未来的某个 Vsync 周期，使用这个 Buffer 完成合成并上屏。</li><li>当 SF 不再需要这个 Buffer 时（例如，它已经被新的帧替换，或者已稳定显示了足够长的时间），SF 会释放（release）这个 Buffer。</li><li>SF 释放 Buffer 后，<code>BufferTX</code> <strong>-1</strong>。同时，App 会收到 Buffer 已被释放的回调，此时 App 端的 <code>QueuedBuffer</code> <strong>才会 -1</strong>，表示这个 Buffer 已成功返回缓冲池，可被再次使用。</li></ol><p>Perfetto 同时也提供了 Buffer 追踪的功能，点击 App 上面的 Actual Timeline （这部分知识可以看 <a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/#Vsync-App-%E6%B2%A1%E9%82%A3%E4%B9%88%E7%9B%B4%E8%A7%82">Actual Timeline 介绍</a>），就可以看到这个 Buffer 从生产到消费的全过程。</p><p><img src="/images/Android-Perfetto-06-Why-120Hz/image-20250426150441133.webp" alt="image-20250426150441133"></p><h2 id="支撑-120Hz-的系统架构优化"><a href="#支撑-120Hz-的系统架构优化" class="headerlink" title="支撑 120Hz 的系统架构优化"></a>支撑 120Hz 的系统架构优化</h2><p>要流畅地支持 120Hz 高刷新率，Android 系统架构做了不少<strong>调整和改进</strong>。这些变化涉及多个组件，也包括了对整个渲染管线的<strong>重要优化</strong>。下面我们来详细看看几个关键的技术点：</p><h3 id="自适应刷新率技术"><a href="#自适应刷新率技术" class="headerlink" title="自适应刷新率技术"></a>自适应刷新率技术</h3><p>现代 Android 设备采用多层次刷新率管理策略：</p><ul><li><p><strong>硬件层支持</strong>：LTPO（低温多晶氧化物）显示技术允许屏幕在 1Hz 到 120Hz 范围内精确调节刷新率，而非固定档位切换</p></li><li><p><strong>内容感知算法</strong>：系统通过分析屏幕内容类型自动调整刷新率：</p><ul><li>静态内容（阅读、图片浏览）：降至 10-30Hz</li><li>视频播放：匹配视频源帧率（通常 24-60Hz）</li><li>滚动和交互：提升至 90-120Hz</li><li>游戏：根据游戏引擎输出帧率动态调整</li></ul></li><li><p><strong>API 支持</strong>：Android 提供 <code>Surface.setFrameRate()</code> API，允许应用明确指定其首选帧率，系统会尽可能满足这一请求</p></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 应用可以指定首选帧率和刷新率行为</span></span><br><span class="line">surface.setFrameRate(<span class="number">60.0f</span>, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);</span><br></pre></td></tr></table></figure><p><a id="pros-cons"></a></p><h1 id="120Hz-的优势与挑战"><a href="#120Hz-的优势与挑战" class="headerlink" title="120Hz 的优势与挑战"></a>120Hz 的优势与挑战</h1><h2 id="120Hz-带来的体验提升"><a href="#120Hz-带来的体验提升" class="headerlink" title="120Hz 带来的体验提升"></a>120Hz 带来的体验提升</h2><p>我拿到第一台120Hz手机的时候，最直观的感受就是：</p><ol><li><p><strong>一切操作都变得更流畅</strong>：从桌面滑动、应用切换到刷微博，画面更新更频繁，内容跟随手指移动也更加精准。尤其在快速滑动朋友圈或微博feed流时，文字依然清晰可辨，而不是一片模糊。</p></li><li><p><strong>游戏体验大幅提升</strong>：玩《王者荣耀》或《和平精英》这类竞技游戏时，画面流畅度提升让我的操作精准度也跟着提高。在激烈对抗中，能提前8ms看到敌人动作，虽然时间很短，但确实能带来优势。</p></li><li><p><strong>眼睛疲劳感减轻</strong>：这点可能是个人感受，但长时间盯着120Hz屏幕确实比60Hz舒适，特别是阅读和滑动内容时，眼睛追踪内容的负担减轻了。</p></li><li><p><strong>触控体验更精准</strong>：120Hz不仅是显示更新快，触控采样率通常也会提高，让操作响应更及时，无论是打字还是精细控制都更准确。</p></li></ol><h2 id="120Hz-面临的实际问题"><a href="#120Hz-面临的实际问题" class="headerlink" title="120Hz 面临的实际问题"></a>120Hz 面临的实际问题</h2><p>当然，高刷屏幕也带来了一系列技术挑战：</p><ol><li><p><strong>功耗问题</strong>：实测中，同一台手机在120Hz模式下比60Hz大约多耗电15-20%。对于本就紧张的手机续航来说，这是不小的压力。</p></li><li><p><strong>开发门槛提高</strong>：原来在60Hz环境下勉强能跑的应用，到了120Hz可能就会显得卡顿。每帧只有8.33ms的处理时间，对开发者的代码效率提出了更高要求。</p></li><li><p><strong>发热增加</strong>：长时间运行高帧率游戏，手机发热明显比60Hz更严重，这不仅影响体验，还可能导致性能降频。</p></li><li><p><strong>应用适配并不完善</strong>：很多应用并未针对高刷做优化，即使在120Hz屏幕上，实际输出帧率可能还是60fps，浪费了屏幕潜力。</p></li></ol><p><a id="thoughts"></a></p><h1 id="思考与展望"><a href="#思考与展望" class="headerlink" title="思考与展望"></a>思考与展望</h1><h2 id="按需调整：从-ProMotion-看刷新率的智能管理"><a href="#按需调整：从-ProMotion-看刷新率的智能管理" class="headerlink" title="按需调整：从 ProMotion 看刷新率的智能管理"></a>按需调整：从 ProMotion 看刷新率的智能管理</h2><p>随着 120Hz 高刷新率逐渐成为旗舰手机的标配，一个值得思考的问题是：我们真的需要在所有场景下都保持 120Hz 的刷新率吗？</p><p>苹果的 ProMotion 技术实际上给出了一个更为合理的答案：只有在真正需要高感知度的动画场景下才激活高刷新率，而在其他场景则可以适当降低刷新率以节省电量。</p><p><img src="/images/Android-Perfetto-06-Why-120Hz/image-20250426152428866.webp" alt="iOS Promotion 的动画建议帧率"></p><p>从上图可以看到，苹果在iOS开发文档中为不同类型的动画场景提供了非常精细的ProMotion帧率推荐配置：</p><ol><li><p><strong>高影响力动画（High-impact animations）</strong>：</p><ul><li>适用场景：全屏转场（如照片应用中点击缩略图展开）、第一人称游戏、Sheet弹出展示等</li><li>推荐帧率：80-120Hz，首选120Hz（CAFrameRateRange(minimum:80, maximum:120, preferred:120)）</li><li>使用建议：谨慎使用，仅在关键交互场景应用，以减少电量消耗</li></ul></li><li><p><strong>透明度&#x2F;颜色过渡和微小移动</strong>：</p><ul><li>适用场景：开关状态变化、进度指示器旋转、背景模糊效果等</li><li>推荐帧率：使用系统默认帧率范围（CAFrameRateRange.default）</li><li>使用建议：这类动画不需要过高帧率，视觉效果差异不大</li></ul></li><li><p><strong>低速小动画</strong>：</p><ul><li>适用场景：时钟指针移动、缓慢进度条等</li><li>推荐帧率：根据动画速度，可选择8-15Hz、15-24Hz或30-48Hz不等</li><li>使用建议：低帧率在这些场景下视觉效果已足够好，同时可显著节省电量</li></ul></li><li><p><strong>其他所有情况</strong>：</p><ul><li>推荐使用系统默认帧率</li></ul></li></ol><p>这种精细化的帧率管理策略，不仅让系统能够在用户体验和电池寿命之间取得最佳平衡，也为开发者提供了明确的指导。相比于简单粗暴地全局使用120Hz，这种有针对性的帧率调整方案显然更加科学和高效。</p><h2 id="电量与体验的权衡"><a href="#电量与体验的权衡" class="headerlink" title="电量与体验的权衡"></a>电量与体验的权衡</h2><p>测试表明，将刷新率从 120Hz 降至 60Hz 可节省约 10-15% 的电量。智能地控制刷新率，能在保持良好用户体验的同时，显著延长电池续航时间。</p><p>120Hz 的主要价值在于提升交互流畅度和响应速度，而非始终保持高刷新率。更智能的做法是根据实际需求动态调整：<strong>在用户感知敏感的场景使用高刷新率，在用户感知不敏感的场景降低刷新率</strong>。</p><h2 id="开发者的适配策略"><a href="#开发者的适配策略" class="headerlink" title="开发者的适配策略"></a>开发者的适配策略</h2><p>应用开发者应当意识到，并非所有内容都需要以最高帧率渲染。通过 Android 提供的 API（如 Surface.setFrameRate()），可以为不同内容类型指定合适的帧率，配合系统的自适应刷新率机制，共同达到最佳的性能与电量平衡。</p><p>总之，未来高刷新率技术的发展方向应该是更加智能、更加精细的自适应调节，而非简单地追求更高的数字。真正的技术进步是在用户无感知的情况下，在体验和能效之间找到最佳平衡点。</p><p><a id="conclusion"></a></p><h1 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h1><p>回顾过去几年高刷屏幕的发展，我认为120Hz确实是手机交互体验的一次重要跃升。虽然它带来了功耗和开发复杂性等挑战，但好处是显而易见的：更流畅的体验、更低的输入延迟、更自然的动画效果。</p><p>对于开发者而言，Perfetto这类工具让我们能够看清120Hz下的性能问题，有的放矢地进行优化。虽然从16ms减少到8ms的预算听起来很紧张，但事实上主流处理器已经有足够能力应对这个挑战。只要合理规划UI复杂度、避免主线程阻塞，流畅的120fps体验是完全可以实现的。</p><p>从趋势来看，我不认为手机屏幕刷新率会无限攀升。120Hz可能会在相当长的时间内成为标准，而未来的焦点将更多放在如何智能化地调整刷新率，在不同场景下找到体验和功耗的最佳平衡点。毕竟，我们追求的不是数字上的高，而是实际体验的好。</p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Android Perfetto 系列的第六篇，主要介绍 Android 设备上 120Hz 刷新率的相关知识。如今，120Hz 已成为 Android 旗舰手机的标配，本文将讨论高刷新率带来的优势和挑战，以及从系统角度解析 120Hz 的工作原理。&lt;/p&gt;
&lt;p&gt;在过去的几年中，移动设备的屏幕刷新率经历了从 60Hz 到 90Hz，再到现在普遍的 120Hz 的演进过程。这种提升不仅带来了更流畅的视觉体验，也对系统架构和应用开发提出了新的要求。通过 Perfetto 工具，我们可以更直观地理解高刷新率设备上帧渲染的过程和性能表现。&lt;/p&gt;</summary>
    
    
    
    <category term="iOS" scheme="https://androidperformance.com/categories/iOS/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="BlastBufferQueue" scheme="https://androidperformance.com/tags/BlastBufferQueue/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</title>
    <link href="https://androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/"/>
    <id>https://androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/</id>
    <published>2025-03-26T10:55:11.000Z</published>
    <updated>2026-02-07T15:28:41.203Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍了 App 开发者不经常接触到但在 Android Framework 渲染链路中非常重要的一个类 Choreographer，包括 Choreographer 的引入背景、简介、部分源码解析、与 MessageQueue 的交互、在 APM 中的应用，以及手机厂商基于 Choreographer 的一些优化思路。</p><p>Choreographer 的引入主要是配合 Vsync，为上层应用的渲染提供稳定的 Message 处理时机。当 Vsync 信号到来时，系统通过对 Vsync 信号周期的调整，控制每一帧绘制操作的时机。目前主流手机的屏幕刷新率已达到 120Hz，即每 8.3ms 刷新一次，系统为配合屏幕刷新频率，相应调整 Vsync 周期。每个 Vsync 周期到来时，Vsync 信号唤醒 Choreographer 执行应用的绘制操作，这正是引入 Choreographer 的主要作用。了解 Choreographer 还可以帮助应用开发者深入理解每一帧的运行原理，同时加深对 <strong>Message</strong>、<strong>Handler</strong>、<strong>Looper</strong>、<strong>MessageQueue</strong>、<strong>Input</strong>、<strong>Animation</strong>、<strong>Measure</strong>、<strong>Layout</strong>、<strong>Draw</strong> 等核心组件的认识。许多 <strong>APM</strong>（应用性能监控）工具也利用了 <strong>Choreographer</strong>（通过 FrameCallback）、<strong>FrameMetrics&#x2F;gfxinfo framestats</strong>（底层依赖 FrameInfo）、<strong>MessageQueue</strong>（通过 IdleHandler）和 <strong>Looper</strong>（通过自定义 MessageLogging）这些组合机制进行性能监测。深入理解这些机制后，开发者可以更有针对性地进行性能优化，形成系统化的优化思路。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列目录</a></li><li><a href="#main-thread-essence">主线程运行机制的本质</a></li><li><a href="#choreographer-intro">Choreographer 简介</a></li><li><a href="#source">源码解析</a></li><li><a href="#messagequeue">MessageQueue 与 Choreographer</a></li><li><a href="#apm">APM 与 Choreographer</a></li><li><a href="#vendor">厂商优化</a></li><li><a href="#refs">参考资料</a></li><li><a href="#zhihu">本文知乎地址</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>本文是 Perfetto 系列文章的第五篇，主要是对 Perfetto 中的 Choreographer 进行简单介绍</p><p>本系列的<strong>目的</strong>是通过 Perfetto 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Perfetto 这个图形化的角度，你可以理解的更深入一些。</p><p><a id="series"></a></p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li>本文的 Systrace 版本：<a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="main-thread-essence"></a></p><h1 id="主线程运行机制的本质"><a href="#主线程运行机制的本质" class="headerlink" title="主线程运行机制的本质"></a>主线程运行机制的本质</h1><p>在讲 Choreographer 之前，我们先理一下 Android 主线程运行的本质，其实就是 Message 的处理过程，我们的各种操作，包括每一帧的渲染操作 ，都是通过 Message 的形式发给主线程的 MessageQueue ，MessageQueue 处理完消息继续等下一个消息，如下图所示</p><p><strong>MethodTrace 图示</strong></p><p><img src="/images/15717420275540.jpg"></p><p><strong>Perfetto 图示</strong></p><p><img src="/images/Android-Perfetto-05-Chorergrapher/image-20250331235337987.webp" alt="image-20250331235337987"></p><h2 id="演进"><a href="#演进" class="headerlink" title="演进"></a>演进</h2><p>引入 Vsync 之前的 Android 版本，渲染一帧相关的 Message ，中间是没有间隔的，上一帧绘制完，下一帧的 Message 紧接着就开始被处理。这样的问题就是，帧率不稳定，可能高也可能低，不稳定，如下图</p><p><strong>MethodTrace 图示</strong></p><p><img src="/images/15717420453069.jpg"></p><p><strong>Trace 图示（资源比较老，用 Systrace 图示)</strong></p><p><img src="/images/15717420572997.jpg"></p><p>可以看到这时候的瓶颈是在 dequeueBuffer, 因为屏幕是有刷新周期的, FB 消耗 Front Buffer 的速度是一定的, 所以 SF 消耗 App Buffer 的速度也是一定的, 所以 App 会卡在 dequeueBuffer 这里,这就会导致 App Buffer 获取不稳定, 很容易就会出现卡顿掉帧的情况.</p><p>对于用户来说，稳定的帧率才是好的体验，比如你玩王者荣耀，相比 fps 在 60 和 40 之间频繁变化，用户感觉更好的是稳定在 50 fps 的情况.</p><p>所以 Android 的演进中，最初引入了 <strong>Vsync + TripleBuffer + Choreographer</strong> 的机制，后来从 Android R (Android 11) 开始引入并持续完善 <strong>BlastBufferQueue</strong>，共同构成了现代 Android 稳定帧率输出机制，让软件层和硬件层可以以共同的频率一起工作。</p><h2 id="引入-Choreographer"><a href="#引入-Choreographer" class="headerlink" title="引入 Choreographer"></a>引入 Choreographer</h2><p>Choreographer 的引入主要是配合 Vsync，为上层应用的渲染提供稳定的 Message 处理时机。当 Vsync 信号到来时，系统通过对 Vsync 信号周期的调整，控制每一帧绘制操作的时机。目前主流手机的屏幕刷新率已达到 120Hz，即每 8.3ms 刷新一次，系统为配合屏幕刷新频率，相应调整 Vsync 周期。每个 Vsync 周期到来时，Vsync 信号唤醒 Choreographer 执行应用的绘制操作，如果每个 Vsync 周期应用都能渲染完成，那么应用的 fps 就是120，给用户的感觉就是非常流畅，这就是引入 Choreographer 的主要作用</p><p><img src="/images/15722752299458.jpg"></p><p>当然目前主流旗舰手机的刷新率已达到120Hz，Vsync周期已缩短至8.3ms，上图中的操作要在更短的时间内完成，对性能的要求也越来越高，具体可以看<a href="https://www.androidperformance.com/2019/05/15/90hz-on-android/">新的流畅体验，90Hz 漫谈</a> 这篇文章(文章虽然讨论90Hz，但同样的原理适用于120Hz)</p><p><a id="choreographer-intro"></a></p><h1 id="Choreographer-简介"><a href="#Choreographer-简介" class="headerlink" title="Choreographer 简介"></a>Choreographer 简介</h1><p>Choreographer 扮演 Android 渲染链路中承上启下的角色</p><ol><li><strong>承上</strong>：负责接收和处理 App 的各种更新消息和回调，等到 Vsync 到来的时候统一处理。比如集中处理 Input（主要是 Input 事件的处理）、Animation（动画相关）、Traversal（包括 measure、layout、draw 等操作），判断卡顿掉帧情况，记录 CallBack 耗时等</li><li><strong>启下</strong>：负责请求和接收 Vsync 信号。接收 Vsync 事件回调（通过 FrameDisplayEventReceiver.onVsync）；请求 Vsync（FrameDisplayEventReceiver.scheduleVsync）</li></ol><p>从上面可以看出，Choreographer 在 Android 渲染管线中扮演关键的协调者角色，其重要性在于通过 <strong>Choreographer + SurfaceFlinger + Vsync + BlastBufferQueue</strong> 这一套完整的渲染机制，确保 Android 应用能够以稳定的帧率运行（60 fps、90 fps 或 120 fps），有效减少帧率波动带来的视觉不适感。</p><p>了解 Choreographer 还可以帮助应用开发者深入理解每一帧的运行原理，同时加深对 <strong>Message</strong>、<strong>Handler</strong>、<strong>Looper</strong>、<strong>MessageQueue</strong>、<strong>Input</strong>、<strong>Animation</strong>、<strong>Measure</strong>、<strong>Layout</strong>、<strong>Draw</strong> 等核心组件的认识。许多 <strong>APM</strong>（应用性能监控）工具也利用了 <strong>Choreographer</strong>（通过 FrameCallback）、<strong>FrameMetrics&#x2F;gfxinfo framestats</strong>（底层依赖 FrameInfo）、<strong>MessageQueue</strong>（通过 IdleHandler）和 <strong>Looper</strong>（通过自定义 MessageLogging）这些组合机制进行性能监测。深入理解这些机制后，开发者可以更有针对性地进行性能优化，形成系统化的优化思路。</p><p>另外，虽然图表是解释流程的有效方式，但本文将更多依赖 Perfetto 和 MethodTrace 工具的可视化输出。Perfetto 以时间线方式（从左到右）展示整个系统的运行状况，涵盖 CPU、SurfaceFlinger、SystemServer 和应用进程等关键组件的活动。使用 <strong>Perfetto</strong> 和 <strong>MethodTrace</strong> 可以直观展示关键执行流程，当您熟悉系统代码后，Perfetto 的输出能够直接映射到设备的实际运行状态。因此，本文除了引用少量网络图表外，主要依靠 Perfetto 来展示分析结果。</p><h2 id="从-Perfetto-的角度来看-Choreographer-的工作流程"><a href="#从-Perfetto-的角度来看-Choreographer-的工作流程" class="headerlink" title="从 Perfetto 的角度来看 Choreographer 的工作流程"></a>从 Perfetto 的角度来看 Choreographer 的工作流程</h2><p>下图以滑动设置界面为例子，我们先看一下从上到下设置界面的一个完整的预览图，可以看到 Perfetto 中从左到右，每一个绿色的帧都表示一帧，表示最终我们可以手机上看到的画面</p><ol><li>图中每一个 VSYNC-app 的值的区间是一个 Vsync 的时间，对应当前设备的刷新率，如 60Hz 时为 16.6ms，120Hz时为8.3ms，上升沿或者下降沿就是 Vsync 到达的时间</li><li>每一帧处理的流程：接收到 Vsync 信号回调-&gt; UI Thread –&gt; RenderThread –&gt; SurfaceFlinger</li><li>UI Thread 和 RenderThread 就可以完成 App 一帧的渲染，在 Android 11 及以上版本中，会通过 BlastBufferQueue 路径把渲染完的 Buffer 提交给 SurfaceFlinger 去合成，然后我们就可以在屏幕上看到这一帧</li><li>可以看到 Settings 滑动的每一帧耗时都很短（Ui Thread 耗时 + RenderThread 耗时），但是由于 Vsync 的存在，每一帧都会等到 Vsync 才会去做处理</li></ol><p><img src="/images/Android-Perfetto-05-Chorergrapher/image-20250401001138400.webp" alt="image-20250401001138400"></p><p>有了上面这个整体的概念，我们将 UI Thread 的每一帧放大来看，看看 Choreographer 的位置以及 Choreographer 是怎么组织每一帧的</p><p><img src="/images/Android-Perfetto-05-Chorergrapher/image-20250401002047386.webp" alt="image-20250401002047386"></p><h2 id="Choreographer-的工作流程"><a href="#Choreographer-的工作流程" class="headerlink" title="Choreographer 的工作流程"></a>Choreographer 的工作流程</h2><ol><li>Choreographer 初始化</li><li>初始化 FrameHandler，绑定 Looper</li><li>初始化 FrameDisplayEventReceiver，与 SurfaceFlinger 建立通信用于接收和请求 Vsync</li><li>初始化 CallBackQueues</li><li>SurfaceFlinger 的 appEventThread 唤醒发送 Vsync，Choreographer 回调 FrameDisplayEventReceiver.onVsync，进入 Choreographer 的主处理函数 doFrame</li><li>Choreographer.doFrame 计算掉帧逻辑</li><li>Choreographer.doFrame 处理 Choreographer 的第一个 callback：input </li><li>Choreographer.doFrame 处理 Choreographer 的第二个 callback：animation </li><li>Choreographer.doFrame 处理 Choreographer 的第三个 callback：insets animation </li><li>Choreographer.doFrame 处理 Choreographer 的第四个 callback：traversal </li><li>traversal-draw 中 UIThread 与 RenderThread 同步数据</li><li>Choreographer.doFrame 处理 Choreographer 的第五个 callback：commit</li><li>RenderThread 处理绘制命令</li><li>在 Android 11 及以上版本中，RenderThread 通过 BlastBufferQueue 向 SurfaceFlinger 提交绘制内容（不同版本细节持续演进）</li><li>BlastBufferQueue 由 App 端创建和管理</li><li>通过生产者 (BBQ_BufferQueue_Producer) 和消费者 (BufferQueue_Consumer) 模型工作</li><li>UI 线程等待 RenderThread 的概率与时长通常会降低，可以更早地准备下一帧（仍取决于具体同步点）</li></ol><p><strong>第一步初始化完成后，后续就会在步骤 2-10 之间循环</strong></p><p>同时也附上这一帧所对应的 MethodTrace（这里预览一下即可，下面会有详细的大图）</p><p><img src="/images/15717420948412.jpg"></p><h2 id="Choreographer-与-RenderThread-及-BlastBufferQueue-的交互"><a href="#Choreographer-与-RenderThread-及-BlastBufferQueue-的交互" class="headerlink" title="Choreographer 与 RenderThread 及 BlastBufferQueue 的交互"></a>Choreographer 与 RenderThread 及 BlastBufferQueue 的交互</h2><p>从 Android R (Android 11) 开始，RenderThread 与 SurfaceFlinger 之间的交互发生了重要变化，其核心是 BlastBufferQueue 的引入与后续演进。下面我们来看看这一机制是如何工作的。</p><h3 id="BlastBufferQueue-工作原理"><a href="#BlastBufferQueue-工作原理" class="headerlink" title="BlastBufferQueue 工作原理"></a>BlastBufferQueue 工作原理</h3><p>BlastBufferQueue 是一个专为 UI 渲染优化的 BufferQueue 变体，它替代了传统由 SurfaceFlinger 创建的 BufferQueue，转为由 App 端创建和管理，用于 App 与 SurfaceFlinger 之间的缓冲区管理。</p><ol><li><p><strong>更高效的缓冲区管理</strong><br>在传统的 BufferQueue 中，App 需要通过 dequeueBuffer 获取一个可用的缓冲区，然后渲染内容，最后通过 queueBuffer 将缓冲区提交给 SurfaceFlinger。这个过程中，如果没有可用的缓冲区，App 需要等待，这会导致阻塞。</p><p>BlastBufferQueue 通过更智能的缓冲区管理，减少了这种等待，特别是在高刷新率设备上，效果更明显。</p></li><li><p><strong>RenderThread 与 UI 线程的解耦</strong><br>在较老实现中，UI 线程与 Buffer 提交路径耦合更紧，主线程更容易在关键同步点发生等待。引入 BlastBufferQueue 并持续优化后，UI 线程通常可以更早完成本帧 Java 侧工作并进入下一帧准备，从而降低主线程阻塞概率（是否阻塞仍取决于具体同步点与场景）。</p></li><li><p><strong>创建机制</strong><br>BlastBufferQueue 在 ViewRootImpl 的 relayoutWindow 过程中创建：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建 BBQ 的示例代码</span></span><br><span class="line"><span class="keyword">if</span> (mBlastBufferQueue == <span class="literal">null</span>) &#123;</span><br><span class="line">    mBlastBufferQueue = <span class="keyword">new</span> <span class="title class_">BLASTBufferQueue</span>(mTag, mSurfaceControl,</span><br><span class="line">        mSurfaceSize.x, mSurfaceSize.y,</span><br><span class="line">        mWindowAttributes.format);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p><strong>与 Choreographer 的配合</strong><br>Choreographer 仍然是协调这一切的核心。当 Vsync 信号到来时，Choreographer 会触发 doFrame，执行各种回调，其中 CALLBACK_TRAVERSAL 会触发 ViewRootImpl 的 performTraversals()，最终走到 draw 流程。</p><p>在 draw 流程中，通过 BlastBufferQueue，RenderThread 可以更独立地工作，而 UI 线程可以更早地返回处理其他任务。</p></li></ol><p>下面我们就从源码的角度，来看一下具体的实现</p><p><a id="source"></a></p><h1 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h1><p>下面从源码的角度来简单看一下，源码只摘抄了部分重要的逻辑，其他的逻辑则被剔除，另外 Native 部分与 SurfaceFlinger 交互的部分也没有列入，不是本文的重点，有兴趣的可以自己去跟一下。下面的源码基于 AOSP mainline（<code>frameworks/base</code> HEAD，核对日期：2026-02）实现，不同 Android release 的细节可能会有差异。</p><h2 id="Choreographer-的初始化"><a href="#Choreographer-的初始化" class="headerlink" title="Choreographer 的初始化"></a>Choreographer 的初始化</h2><h3 id="Choreographer-的单例初始化"><a href="#Choreographer-的单例初始化" class="headerlink" title="Choreographer 的单例初始化"></a>Choreographer 的单例初始化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Thread local storage for the choreographer.</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Choreographer&gt; sThreadInstance =</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Choreographer&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Choreographer <span class="title function_">initialValue</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 获取当前线程的 Looper</span></span><br><span class="line">        <span class="type">Looper</span> <span class="variable">looper</span> <span class="operator">=</span> Looper.myLooper();</span><br><span class="line">        <span class="keyword">if</span> (looper == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;The current thread must have a looper!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 构造 Choreographer 对象，使用 VSYNC_SOURCE_APP 作为Vsync源</span></span><br><span class="line">        <span class="type">Choreographer</span> <span class="variable">choreographer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Choreographer</span>(looper, VSYNC_SOURCE_APP);</span><br><span class="line">        <span class="keyword">if</span> (looper == Looper.getMainLooper()) &#123;</span><br><span class="line">            mMainInstance = choreographer;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> choreographer;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Choreographer-的构造函数"><a href="#Choreographer-的构造函数" class="headerlink" title="Choreographer 的构造函数"></a>Choreographer 的构造函数</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Choreographer</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    mLooper = looper;</span><br><span class="line">    <span class="comment">// 1. 初始化 FrameHandler</span></span><br><span class="line">    mHandler = <span class="keyword">new</span> <span class="title class_">FrameHandler</span>(looper);</span><br><span class="line">    <span class="comment">// 2. 初始化 DisplayEventReceiver</span></span><br><span class="line">    mDisplayEventReceiver = USE_VSYNC</span><br><span class="line">            ? <span class="keyword">new</span> <span class="title class_">FrameDisplayEventReceiver</span>(looper, vsyncSource)</span><br><span class="line">            : <span class="literal">null</span>;</span><br><span class="line">    mLastFrameTimeNanos = Long.MIN_VALUE;</span><br><span class="line">    mFrameIntervalNanos = (<span class="type">long</span>)(<span class="number">1000000000</span> / getRefreshRate());</span><br><span class="line">    <span class="comment">//3. 初始化 CallbacksQueues</span></span><br><span class="line">    mCallbackQueues = <span class="keyword">new</span> <span class="title class_">CallbackQueue</span>[CALLBACK_LAST + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= CALLBACK_LAST; i++) &#123;</span><br><span class="line">        mCallbackQueues[i] = <span class="keyword">new</span> <span class="title class_">CallbackQueue</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="FrameHandler"><a href="#FrameHandler" class="headerlink" title="FrameHandler"></a>FrameHandler</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FrameHandler</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (msg.what) &#123;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_FRAME:<span class="comment">//开始渲染下一帧的操作</span></span><br><span class="line">                doFrame(System.nanoTime(), <span class="number">0</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_SCHEDULE_VSYNC:<span class="comment">//请求 Vsync </span></span><br><span class="line">                doScheduleVsync();</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_SCHEDULE_CALLBACK:<span class="comment">//处理 Callback</span></span><br><span class="line">                doScheduleCallback(msg.arg1);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Choreographer-初始化链"><a href="#Choreographer-初始化链" class="headerlink" title="Choreographer 初始化链"></a>Choreographer 初始化链</h3><p>在 Activity 启动过程，执行完 onResume 后，会调用 Activity.makeVisible()，然后再调用到 addView()， 层层调用会进入如下方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">ActivityThread.handleResumeActivity(IBinder, <span class="type">boolean</span>, <span class="type">boolean</span>, String) (android.app) </span><br><span class="line">--&gt;WindowManagerImpl.addView(View, LayoutParams) (android.view) </span><br><span class="line">  --&gt;WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) </span><br><span class="line">    --&gt;ViewRootImpl.ViewRootImpl(Context, Display) (android.view) </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ViewRootImpl</span><span class="params">(Context context, Display display)</span> &#123;</span><br><span class="line">        ......</span><br><span class="line">        mChoreographer = Choreographer.getInstance();</span><br><span class="line">        ......</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h2 id="FrameDisplayEventReceiver-简介"><a href="#FrameDisplayEventReceiver-简介" class="headerlink" title="FrameDisplayEventReceiver 简介"></a>FrameDisplayEventReceiver 简介</h2><p>Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类，所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver ， 有三个比较重要的方法</p><ol><li>onVsync – Vsync 信号回调</li><li>run – 执行 doFrame</li><li>scheduleVsync  – 请求 Vsync 信号</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FrameDisplayEventReceiver</span> <span class="keyword">extends</span> <span class="title class_">DisplayEventReceiver</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame, VsyncEventData eventData)</span> &#123;</span><br><span class="line">        ......</span><br><span class="line">        mTimestampNanos = timestampNanos;</span><br><span class="line">        mFrame = frame;</span><br><span class="line">        <span class="comment">// VsyncEventData 在 Android 13/14 已存在，较新版本继续扩展了可用字段</span></span><br><span class="line">        mVsyncEventData = eventData;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">        msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        mHavePendingVsync = <span class="literal">false</span>;</span><br><span class="line">        doFrame(mTimestampNanos, mFrame, mVsyncEventData);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scheduleVsync</span><span class="params">()</span> &#123;</span><br><span class="line">        ......  </span><br><span class="line">        nativeScheduleVsync(mReceiverPtr);</span><br><span class="line">        ......</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Choreographer-中-Vsync-的注册"><a href="#Choreographer-中-Vsync-的注册" class="headerlink" title="Choreographer 中 Vsync 的注册"></a>Choreographer 中 Vsync 的注册</h2><p>从下面的函数调用栈可以看到，Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调，通知 UIThread 进行数据处理。</p><p>那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢？答案是 FrameDisplayEventReceiver 的初始化的时候，最终通过监听文件句柄的形式，其对应的初始化流程如下</p><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Choreographer</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    mLooper = looper;</span><br><span class="line">    mDisplayEventReceiver = USE_VSYNC</span><br><span class="line">            ? <span class="keyword">new</span> <span class="title class_">FrameDisplayEventReceiver</span>(looper, vsyncSource)</span><br><span class="line">            : <span class="literal">null</span>;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">FrameDisplayEventReceiver</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(looper, vsyncSource);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;DisplayEventReceiver.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">DisplayEventReceiver</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mMessageQueue = looper.getQueue();</span><br><span class="line">    mReceiverPtr = nativeInit(<span class="keyword">new</span> <span class="title class_">WeakReference</span>&lt;DisplayEventReceiver&gt;(<span class="built_in">this</span>), mMessageQueue,</span><br><span class="line">            vsyncSource);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>简单来说，FrameDisplayEventReceiver 的初始化过程中，通过 BitTube (本质是一个 socket pair)，来传递和请求 Vsync 事件，当 SurfaceFlinger 收到 Vsync 事件之后，通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher，DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后，回调 Choreographer.FrameDisplayEventReceiver.onVsync，触发开始一帧的绘制，如下图</p><p><img src="/images/15717421215251.jpg"></p><h3 id="DisplayEventReceiver-与-SurfaceFlinger-的通信细节"><a href="#DisplayEventReceiver-与-SurfaceFlinger-的通信细节" class="headerlink" title="DisplayEventReceiver 与 SurfaceFlinger 的通信细节"></a>DisplayEventReceiver 与 SurfaceFlinger 的通信细节</h3><p>在 Android 系统中，DisplayEventReceiver 通过 JNI 调用 nativeInit 方法来建立与 SurfaceFlinger 服务的通信通道。这个过程涉及多个关键步骤：</p><ol><li><p><strong>创建 NativeDisplayEventReceiver 对象</strong>：在 Java 层调用 nativeInit 后，JNI 创建一个 NativeDisplayEventReceiver 实例，用于接收 Vsync 信号。</p></li><li><p><strong>获取 IDisplayEventConnection</strong>：通过 ISurfaceComposer 接口获取 IDisplayEventConnection，这是一个 Binder 接口，用于与 SurfaceFlinger 服务通信。</p></li><li><p><strong>建立 BitTube 连接</strong>：BitTube 是一个基于 socket pair 的通信机制，专为高频、小数据量的跨进程通信设计。它在 App 进程和 SurfaceFlinger 进程之间创建一个高效的通信通道。</p></li><li><p><strong>文件描述符监听</strong>：通过 Looper 监听 BitTube 的文件描述符，当有 Vsync 信号到来时，Looper 会通知 DisplayEventDispatcher 处理事件。</p></li></ol><p>整个通信流程如下：</p><figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">App 进程                                    SurfaceFlinger 进程</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- 创建 DisplayEventReceiver --------------&gt;</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">&lt;- 返回 IDisplayEventConnection (Binder) ----</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- 创建 BitTube --------------------------&gt;</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">&lt;- 文件描述符交换 --------------------------</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- 注册文件描述符到 Looper ----------------</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- 请求 Vsync (requestNextVsync) ----------&gt;</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">&lt;- 发送 Vsync 事件数据 ---------------------</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- Looper 通知 -&gt; handleEvent -------------</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br><span class="line">  |<span class="string">-- 回调 Java 层 onVsync -------------------</span>|</span><br><span class="line">  |<span class="string">                                             </span>|</span><br></pre></td></tr></table></figure><p>这种设计的优势在于避免了使用 Binder 传递高频的 Vsync 事件数据，通过直接的 socket 通信提高了性能和实时性，这对于保证流畅的 UI 渲染至关重要。同时，由于 BitTube 使用了文件描述符，可以无缝集成到 Android 的 Looper 机制中，使得整个系统能够以事件驱动的方式工作。</p><h2 id="Choreographer-处理一帧的逻辑"><a href="#Choreographer-处理一帧的逻辑" class="headerlink" title="Choreographer 处理一帧的逻辑"></a>Choreographer 处理一帧的逻辑</h2><p>Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中，从下图可以看到，FrameDisplayEventReceiver.onVsync post 了自己，其 run 方法直接调用了 doFrame 开始一帧的逻辑处理</p><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame, VsyncEventData eventData)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mTimestampNanos = timestampNanos;</span><br><span class="line">    mFrame = frame;</span><br><span class="line">    mVsyncEventData = eventData;</span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">    msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    mHavePendingVsync = <span class="literal">false</span>;</span><br><span class="line">    doFrame(mTimestampNanos, mFrame, mVsyncEventData);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>doFrame 函数主要做下面几件事</p><ol><li>计算掉帧逻辑</li><li>记录帧绘制信息</li><li>执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT</li></ol><h3 id="记录帧绘制信息"><a href="#记录帧绘制信息" class="headerlink" title="记录帧绘制信息"></a>记录帧绘制信息</h3><p>Choreographer 中 FrameInfo 来负责记录帧的绘制信息，doFrame 执行的时候，会把每一个关键节点的绘制时间记录下来，我们使用 dumpsys gfxinfo 就可以看到。当然 Choreographer 只是记录了一部分，剩余的部分在 hwui 那边来记录。</p><p>从 FrameInfo 这些标志就可以看出记录的内容，后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> FrameInfoFlags &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FRAME_TIMELINE_VSYNC_ID</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The intended vsync time, unadjusted by jitter</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INTENDED_VSYNC</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Jitter-adjusted vsync time, this is what was used as input into the</span></span><br><span class="line">    <span class="comment">// animation &amp; drawing system</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">VSYNC</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The id of the input event that caused the current frame</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INPUT_EVENT_ID</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When input event handling started</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">HANDLE_INPUT_START</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When animation evaluations started</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">ANIMATION_START</span> <span class="operator">=</span> <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When ViewRootImpl#performTraversals() started</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PERFORM_TRAVERSALS_START</span> <span class="operator">=</span> <span class="number">7</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When View:draw() started</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DRAW_START</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When the frame needs to be ready by</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FRAME_DEADLINE</span> <span class="operator">=</span> <span class="number">9</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// When frame actually started.</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FRAME_START_TIME</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Interval between two consecutive frames</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FRAME_INTERVAL</span> <span class="operator">=</span> <span class="number">11</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos, <span class="type">int</span> frame, VsyncEventData eventData)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INPUT Callbacks </span></span><br><span class="line">    mFrameInfo.markInputHandlingStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_ANIMATION Callbacks</span></span><br><span class="line">    mFrameInfo.markAnimationsStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INSETS_ANIMATION Callbacks</span></span><br><span class="line">    <span class="comment">// 处理 CALLBACK_TRAVERSAL Callbacks</span></span><br><span class="line">    mFrameInfo.markPerformTraversalsStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_COMMIT Callbacks</span></span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="执行-Callbacks"><a href="#执行-Callbacks" class="headerlink" title="执行 Callbacks"></a>执行 Callbacks</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos, <span class="type">int</span> frame, VsyncEventData eventData)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INPUT Callbacks </span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_ANIMATION Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INSETS_ANIMATION Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_TRAVERSAL Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_COMMIT Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Input 回调调用栈</strong></p><p><strong>input callback</strong> 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable</p><p>android&#x2F;view&#x2F;ViewRootImpl.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ConsumeBatchedInputRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">doConsumeBatchedInput</span><span class="params">(<span class="type">long</span> frameTimeNanos)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mConsumeBatchedInputScheduled) &#123;</span><br><span class="line">        mConsumeBatchedInputScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">if</span> (mInputEventReceiver != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)</span><br><span class="line">                    &amp;&amp; frameTimeNanos != -<span class="number">1</span>) &#123;</span><br><span class="line">                scheduleConsumeBatchedInput();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        doProcessInputEvents();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Input 时间经过处理，最终会传给 DecorView 的 dispatchTouchEvent，这就到了我们熟悉的 Input 事件分发</p><p><img src="/images/15717421837064.jpg"></p><p><strong>Animation 回调调用栈</strong></p><p>一般我们接触的多的是调用 View.postOnAnimation 的时候，会使用到 CALLBACK_ANIMATION </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postOnAnimation</span><span class="params">(Runnable action)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AttachInfo</span> <span class="variable">attachInfo</span> <span class="operator">=</span> mAttachInfo;</span><br><span class="line">    <span class="keyword">if</span> (attachInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">        attachInfo.mViewRootImpl.mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_ANIMATION, action, <span class="literal">null</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// Postpone the runnable until we know</span></span><br><span class="line">        <span class="comment">// on which thread it needs to run.</span></span><br><span class="line">        getRunQueue().post(action);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那么一般是什么时候回调用到 View.postOnAnimation 呢，我截取了一张图，大家可以自己去看一下，接触最多的应该是 startScroll，Fling 这种操作</p><p><img src="/images/15717421963577.jpg"></p><p>其调用栈根据其 post 的内容，下面是松手之后的 fling 动画。</p><p><img src="/images/15717422041938.jpg"></p><p>另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postFrameCallbackDelayed</span><span class="params">(FrameCallback callback, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (callback == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;callback must not be null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    postCallbackDelayedInternal(CALLBACK_ANIMATION,</span><br><span class="line">            callback, FRAME_CALLBACK_TOKEN, delayMillis);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Traversal 调用栈</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//为了提高优先级，先 postSyncBarrier</span></span><br><span class="line">        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</span><br><span class="line">        mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">TraversalRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 真正开始执行 measure、layout、draw</span></span><br><span class="line">        doTraversal();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">doTraversal</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 这里把 SyncBarrier remove</span></span><br><span class="line">mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);</span><br><span class="line">        <span class="comment">// 真正开始</span></span><br><span class="line">        performTraversals();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">performTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="comment">// measure 操作</span></span><br><span class="line">      <span class="keyword">if</span> (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) &#123;</span><br><span class="line">            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// layout 操作</span></span><br><span class="line">      <span class="keyword">if</span> (didLayout) &#123;</span><br><span class="line">          performLayout(lp, mWidth, mHeight);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// draw 操作</span></span><br><span class="line">      <span class="keyword">if</span> (!cancelDraw &amp;&amp; !newSurface) &#123;</span><br><span class="line">          performDraw();</span><br><span class="line">      &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>doTraversal 的 TraceView 示例</strong></p><p><img src="/images/15717422180571.jpg"></p><h2 id="下一帧的-Vsync-请求"><a href="#下一帧的-Vsync-请求" class="headerlink" title="下一帧的 Vsync 请求"></a>下一帧的 Vsync 请求</h2><p>由于动画、滑动、Fling 这些操作的存在，我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑，在连续的操作，比如动画、滑动、Fling 这些情况下，每一帧的 doFrame 的时候，都会根据情况触发下一个 Vsync 的申请，这样我们就可以获得连续的 Vsync 信号。</p><p>看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求)<br><img src="/images/15724225347501.jpg"><br>我们比较熟悉的 invalidate 和 requestLayout 都会触发 Vsync 信号请求</p><p>我们下面以 Animation 为例，看看 Animation 是如何驱动下一个 Vsync ，来持续更新画面的</p><h3 id="ObjectAnimator-动画驱动逻辑"><a href="#ObjectAnimator-动画驱动逻辑" class="headerlink" title="ObjectAnimator 动画驱动逻辑"></a>ObjectAnimator 动画驱动逻辑</h3><p>android&#x2F;animation&#x2F;ObjectAnimator.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;animation&#x2F;ValueAnimator.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">boolean</span> playBackwards)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    addAnimationCallback(<span class="number">0</span>); <span class="comment">// 动画 start 的时候添加 Animation Callback </span></span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">addAnimationCallback</span><span class="params">(<span class="type">long</span> delay)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    getAnimationHandler().addAnimationFrameCallback(<span class="built_in">this</span>, delay);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;animation&#x2F;AnimationHandler.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addAnimationFrameCallback</span><span class="params">(<span class="keyword">final</span> AnimationFrameCallback callback, <span class="type">long</span> delay)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mAnimationCallbacks.size() == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// post FrameCallback</span></span><br><span class="line">        getProvider().postFrameCallback(mFrameCallback);</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里的 mFrameCallback 回调 doFrame，里面 post了自己</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Choreographer.<span class="type">FrameCallback</span> <span class="variable">mFrameCallback</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Choreographer</span>.FrameCallback() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos)</span> &#123;</span><br><span class="line">        doAnimationFrame(getProvider().getFrameTime());</span><br><span class="line">        <span class="keyword">if</span> (mAnimationCallbacks.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// post 自己</span></span><br><span class="line">            getProvider().postFrameCallback(<span class="built_in">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ，这里就会触发 Choreographer 的 Vsync 请求逻辑</p><p>android&#x2F;animation&#x2F;AnimationHandler.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postFrameCallback</span><span class="params">(Choreographer.FrameCallback callback)</span> &#123;</span><br><span class="line">    mChoreographer.postFrameCallback(callback);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">postCallbackDelayedInternal</span><span class="params">(<span class="type">int</span> callbackType,</span></span><br><span class="line"><span class="params">        Object action, Object token, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">dueTime</span> <span class="operator">=</span> now + delayMillis;</span><br><span class="line">        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (dueTime &lt;= now) &#123;</span><br><span class="line">            <span class="comment">// 请求 Vsync scheduleFrameLocked -&gt;scheduleVsyncLocked-&gt; mDisplayEventReceiver.scheduleVsync -&gt;nativeScheduleVsync</span></span><br><span class="line">            scheduleFrameLocked(now);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);</span><br><span class="line">            msg.arg1 = callbackType;</span><br><span class="line">            msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">            mHandler.sendMessageAtTime(msg, dueTime);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上面的 Animation.start 设置，利用了 Choreographer.FrameCallback 接口，每一帧都去请求下一个 Vsync<br><strong>动画过程中一帧的 TraceView 示例</strong></p><p><img src="/images/15717422327935.jpg"></p><h2 id="源码小结"><a href="#源码小结" class="headerlink" title="源码小结"></a>源码小结</h2><ol><li><p><strong>Choreographer</strong> 采用线程单例模式设计，与Looper强耦合。每个线程只能拥有一个Choreographer实例，且必须绑定一个有效的Looper对象，因为其内部Handler依赖Looper进行消息分发。在应用中，通常绑定主线程的Looper以确保UI操作的线程安全性。</p></li><li><p><strong>DisplayEventReceiver</strong> 是一个抽象基类，其JNI实现创建IDisplayEventConnection对象作为Vsync信号的监听器。通过此机制，SurfaceFlinger的AppEventThread发出的Vsync中断信号能够被精确传递到Choreographer实例。当Vsync信号到达时，系统回调DisplayEventReceiver的onVsync方法，触发渲染流程。</p></li><li><p><strong>DisplayEventReceiver</strong> 提供scheduleVsync方法用于请求Vsync信号。应用程序需要更新UI时，先通过此方法申请下一个Vsync中断，然后在onVsync回调中执行实际的绘制逻辑，确保渲染与屏幕刷新同步。</p></li><li><p><strong>Choreographer</strong> 定义了FrameCallback接口，其doFrame方法在每次Vsync到来时被调用。这一设计对Android动画系统具有重要意义，使动画能够与屏幕刷新率精确同步，相比早期自行计时的实现，提供了更加流畅、省电的动画体验。</p></li><li><p><strong>Choreographer</strong> 核心功能是接收Vsync信号并触发通过postCallback注册的回调函数。框架定义了五种类型的回调，按照执行优先级排序：</p><ol><li><strong>CALLBACK_INPUT</strong>：处理输入事件，如触摸、按键等交互</li><li><strong>CALLBACK_ANIMATION</strong>：处理各类动画计算与更新</li><li><strong>CALLBACK_INSETS_ANIMATION</strong>：处理系统插入动画，如软键盘、状态栏动画等</li><li><strong>CALLBACK_TRAVERSAL</strong>：处理视图树的测量、布局与绘制</li><li><strong>CALLBACK_COMMIT</strong>：执行 post-draw 收尾工作，并在必要时修正 frame time（用于更准确地反映延迟与抖动）</li></ol></li><li><p><strong>ListView</strong> 和 <strong>RecyclerView</strong> 的Item复用机制(ViewHolder模式)在框架层面上的具体实现会涉及到CALLBACK_INPUT和CALLBACK_ANIMATION阶段。在滑动或快速滚动时，Item的初始化、测量与绘制可能在Input回调中触发(如直接响应触摸事件)，也可能在Animation回调中执行(如惯性滑动或自动滚动)。RecyclerView通过更高效的复用机制和预取(Prefetch)策略，能够在这两个阶段更智能地准备ViewHolder，减少主线程阻塞，尤其在高刷新率设备上表现更为出色。</p></li><li><p><strong>CALLBACK_INPUT</strong> 和 <strong>CALLBACK_ANIMATION</strong> 在执行过程中会修改View的各种属性(如位置、透明度、变换矩阵等)，因此必须先于CALLBACK_TRAVERSAL执行，以确保所有状态更新都能在当前帧的测量、布局与绘制过程中被正确应用。这种严格的执行顺序保证了Android UI渲染的一致性和可预测性。</p></li></ol><p><a id="apm"></a></p><h1 id="APM-与-Choreographer"><a href="#APM-与-Choreographer" class="headerlink" title="APM 与 Choreographer"></a>APM 与 Choreographer</h1><p>由于 Choreographer 的位置，许多性能监控的手段都是利用 Choreographer 来做的。需要注意的是：<code>FrameInfo</code> 本身是 Framework 内部结构（<code>@hide</code>），应用侧通常通过 <code>FrameCallback</code>、<code>FrameMetrics</code>、<code>dumpsys gfxinfo framestats</code>、Perfetto 等手段间接获取帧信息。常用的方法如下：</p><ol><li>利用 FrameCallback 的 doFrame 回调</li><li>利用 FrameMetrics API</li><li>利用 framestats（底层依赖 FrameInfo）：<code>adb shell dumpsys gfxinfo &lt;packagename&gt; framestats</code></li><li>利用 SurfaceFlinger latency：<code>adb shell dumpsys SurfaceFlinger --latency</code></li><li>利用 SurfaceFlinger PageFlip 机制：<code>adb service call SurfaceFlinger 1013</code>（需要系统权限）</li><li>Choreographer 自身的掉帧计算逻辑</li><li>BlockCanary 基于 Looper 的性能监控</li><li>Perfetto 工具的强大监控能力<ol><li>Perfetto 已是 Android 主线 tracing 基础设施（Android 10+ 已明显替代早期 systrace.py 工作流）</li><li>Perfetto可以捕获更详细的系统性能数据，包括Choreographer的工作细节</li><li>使用Perfetto UI可以可视化分析帧渲染过程</li></ol></li></ol><h2 id="利用-Perfetto-进行高级监控"><a href="#利用-Perfetto-进行高级监控" class="headerlink" title="利用 Perfetto 进行高级监控"></a>利用 Perfetto 进行高级监控</h2><p>Perfetto 是 Android 新一代的系统跟踪工具，目前已是 Android 主线系统跟踪方案。它提供了比 Systrace 更强大的功能：</p><ol><li><p><strong>更全面的性能数据采集</strong><br>Perfetto 可以同时收集 CPU、内存、图形渲染、系统服务等多维度的数据</p></li><li><p><strong>更低的开销</strong><br>采用高效的跟踪引擎，对系统性能影响更小</p></li><li><p><strong>更好的 Choreographer 追踪</strong><br>可以详细跟踪 Choreographer 的工作过程，包括:</p><ul><li>Vsync 信号的接收和处理</li><li>doFrame 方法的执行细节</li><li>各类回调的执行时间</li><li>UI 线程和 RenderThread 的协作过程</li></ul></li><li><p><strong>追踪 BlastBufferQueue 相关链路</strong><br>能够跟踪 BlastBufferQueue 的工作过程，帮助开发者理解缓冲区管理机制</p></li></ol><h3 id="使用-Perfetto-跟踪-Choreographer"><a href="#使用-Perfetto-跟踪-Choreographer" class="headerlink" title="使用 Perfetto 跟踪 Choreographer"></a>使用 Perfetto 跟踪 Choreographer</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 开始记录 Perfetto trace（示例：15 秒，常用类别）</span></span><br><span class="line">adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace -t 15s \</span><br><span class="line">    <span class="built_in">sched</span> freq idle am wm gfx view binder_driver hal</span><br><span class="line"></span><br><span class="line"><span class="comment"># 完成后获取 trace 文件</span></span><br><span class="line">adb pull /data/misc/perfetto-traces/trace.perfetto-trace</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在 Perfetto UI 中分析(https://ui.perfetto.dev/)</span></span><br></pre></td></tr></table></figure><p>在 Perfetto UI 中，可以找到名为 “Choreographer#doFrame” 的事件，它展示了每一帧的处理时间和细节。还可以查看 UI 线程和 RenderThread 之间的协作关系，以及与 SurfaceFlinger 的交互。</p><h2 id="利用-framestats（底层依赖-FrameInfo）进行监控"><a href="#利用-framestats（底层依赖-FrameInfo）进行监控" class="headerlink" title="利用 framestats（底层依赖 FrameInfo）进行监控"></a>利用 framestats（底层依赖 FrameInfo）进行监控</h2><p>adb shell dumpsys gfxinfo <packagename> framestats</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Window: StatusBar</span><br><span class="line">Stats since: 17990256398ns</span><br><span class="line">Total frames rendered: 1562</span><br><span class="line">Janky frames: 361 (23.11%)</span><br><span class="line">50th percentile: 6ms</span><br><span class="line">90th percentile: 23ms</span><br><span class="line">95th percentile: 36ms</span><br><span class="line">99th percentile: 101ms</span><br><span class="line">Number Missed Vsync: 33</span><br><span class="line">Number High input latency: 683</span><br><span class="line">Number Slow UI thread: 273</span><br><span class="line">Number Slow bitmap uploads: 8</span><br><span class="line">Number Slow issue draw commands: 18</span><br><span class="line">Number Frame deadline missed: 287</span><br><span class="line">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 </span><br><span class="line"></span><br><span class="line">---PROFILEDATA---</span><br><span class="line">Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,</span><br><span class="line">0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,</span><br><span class="line">0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,</span><br><span class="line">0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,</span><br><span class="line">0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,</span><br></pre></td></tr></table></figure><h2 id="利用-SurfaceFlinger-进行监控"><a href="#利用-SurfaceFlinger-进行监控" class="headerlink" title="利用 SurfaceFlinger 进行监控"></a>利用 SurfaceFlinger 进行监控</h2><p>命令解释：</p><ol><li>数据的单位是纳秒，时间是以开机时间为起始点</li><li>每一次的命令都会得到128行的帧相关的数据</li></ol><p>数据：</p><ol><li>第一行数据，表示刷新的时间间隔refresh_period</li><li>第1列：这一部分的数据表示应用程序绘制图像的时间点</li><li>第2列：在SF(软件)将帧提交给H&#x2F;W(硬件)绘制之前的垂直同步时间，也就是每帧绘制完提交到硬件的时间戳，该列就是垂直同步的时间戳</li><li>第3列：在SF将帧提交给H&#x2F;W的时间点，算是H&#x2F;W接受完SF发来数据的时间点，绘制完成的时间点。</li></ol><p><strong>掉帧 jank 计算</strong></p><p>每一行都可以通过下面的公式得到一个值，该值是一个标准，我们称为jankflag，如果当前行的jankflag与上一行的jankflag发生改变，那么就叫掉帧</p><p>ceil((C - A) &#x2F; refresh-period)</p><h2 id="利用-SurfaceFlinger-PageFlip-机制进行监控"><a href="#利用-SurfaceFlinger-PageFlip-机制进行监控" class="headerlink" title="利用 SurfaceFlinger PageFlip 机制进行监控"></a>利用 SurfaceFlinger PageFlip 机制进行监控</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Parcel</span> <span class="variable">data</span> <span class="operator">=</span> Parcel.obtain();</span><br><span class="line"><span class="type">Parcel</span> <span class="variable">reply</span> <span class="operator">=</span> Parcel.obtain();</span><br><span class="line">    data.writeInterfaceToken(<span class="string">&quot;android.ui.ISurfaceComposer&quot;</span>);</span><br><span class="line">mFlinger.transact(<span class="number">1013</span>, data, reply, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">pageFlipCount</span> <span class="operator">=</span> reply.readInt();</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">frames</span> <span class="operator">=</span> pageFlipCount - mLastPageFlipCount;</span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">duration</span> <span class="operator">=</span> now - mLastUpdateTime;</span><br><span class="line">mFps = (<span class="type">float</span>) (frames * <span class="number">1e9</span> / duration);</span><br><span class="line">mLastPageFlipCount = pageFlipCount;</span><br><span class="line">mLastUpdateTime = now;</span><br><span class="line">reply.recycle();</span><br><span class="line">data.recycle();</span><br></pre></td></tr></table></figure><h2 id="Choreographer-自身的掉帧计算逻辑"><a href="#Choreographer-自身的掉帧计算逻辑" class="headerlink" title="Choreographer 自身的掉帧计算逻辑"></a>Choreographer 自身的掉帧计算逻辑</h2><p>SKIPPED_FRAME_WARNING_LIMIT 默认为30 , 由 debug.choreographer.skipwarning 这个属性控制</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (jitterNanos &gt;= mFrameIntervalNanos) &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">skippedFrames</span> <span class="operator">=</span> jitterNanos / mFrameIntervalNanos;</span><br><span class="line">    <span class="keyword">if</span> (skippedFrames &gt;= SKIPPED_FRAME_WARNING_LIMIT) &#123;</span><br><span class="line">        Log.i(TAG, <span class="string">&quot;Skipped &quot;</span> + skippedFrames + <span class="string">&quot; frames!  &quot;</span></span><br><span class="line">                + <span class="string">&quot;The application may be doing too much work on its main thread.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="BlockCanary"><a href="#BlockCanary" class="headerlink" title="BlockCanary"></a>BlockCanary</h2><p>Blockcanary 做性能监控使用的是 Looper 的消息机制，通过对 MessageQueue 中每一个 Message 的前后进行记录，打到监控性能的目的</p><p>android&#x2F;os&#x2F;Looper.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">        <span class="type">Printer</span> <span class="variable">logging</span> <span class="operator">=</span> me.mLogging;</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt; Dispatching to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> +</span><br><span class="line">                    msg.callback + <span class="string">&quot;: &quot;</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line">        msg.target.dispatchMessage(msg);</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&lt;&lt;&lt;&lt;&lt; Finished to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> + msg.callback);</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a id="messagequeue"></a></p><h1 id="MessageQueue-与-Choreographer"><a href="#MessageQueue-与-Choreographer" class="headerlink" title="MessageQueue 与 Choreographer"></a>MessageQueue 与 Choreographer</h1><p>在 Android 消息机制中，异步消息具有特殊的处理优先级。系统可以通过 enqueueBarrier 方法向消息队列插入一个屏障（Barrier），使得该屏障之后的所有同步消息暂时无法被执行，直到调用 removeBarrier 方法移除屏障。而被标记为异步的消息则不受屏障影响，可以正常处理。</p><p>消息默认为同步类型，只有通过 Message 的 setAsynchronous 方法（该方法为隐藏 API）才能将消息设置为异步。在初始化 Handler 时，可以通过特定参数指定该 Handler 发送的所有消息均为异步类型，此时 Handler 的 enqueueMessage 方法会自动调用 Message 的 setAsynchronous 方法。</p><p>异步消息的核心价值在于能够绕过消息屏障继续执行，如果没有设置屏障，异步消息与同步消息的处理方式完全相同。通过 removeSyncBarrier 方法可以移除之前设置的屏障。</p><h2 id="SyncBarrier-在-Choreographer-中使用的一个示例"><a href="#SyncBarrier-在-Choreographer-中使用的一个示例" class="headerlink" title="SyncBarrier 在 Choreographer 中使用的一个示例"></a>SyncBarrier 在 Choreographer 中使用的一个示例</h2><p>scheduleTraversals 的时候 postSyncBarrier</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//为了提高优先级，先 postSyncBarrier</span></span><br><span class="line">        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</span><br><span class="line">        mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>doTraversal 的时候 removeSyncBarrier</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doTraversal</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 这里把 SyncBarrier remove</span></span><br><span class="line">mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);</span><br><span class="line">        <span class="comment">// 真正开始</span></span><br><span class="line">        performTraversals();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Choreographer post Message 的时候，会把这些消息设为 Asynchronous ，这样 Choreographer 中的这些 Message 的优先级就会比较高，</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);</span><br><span class="line">msg.arg1 = callbackType;</span><br><span class="line">msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">mHandler.sendMessageAtTime(msg, dueTime);</span><br></pre></td></tr></table></figure><p><a id="vendor"></a></p><h1 id="厂商优化"><a href="#厂商优化" class="headerlink" title="厂商优化"></a>厂商优化</h1><p>系统厂商由于可以直接修改源码，也利用这方面的便利，做一些功能和优化，不过由于保密的问题，代码就不直接放上来了，我可以大概说一下思路，感兴趣的可以私下讨论</p><h2 id="移动事件优化"><a href="#移动事件优化" class="headerlink" title="移动事件优化"></a>移动事件优化</h2><p>Choreographer 本身是没有 input 消息的， 不过修改源码之后，input 消息可以直接给到 Choreographer 这里， 有了这些 Input 消息，Choreographer 就可以做一些事情，比如说提前响应，不去等 Vsync</p><h2 id="后台动画优化"><a href="#后台动画优化" class="headerlink" title="后台动画优化"></a>后台动画优化</h2><p>当 Android 应用退到后台时，如果未被系统终止，其仍可能继续执行各类操作。在某些情况下，应用会持续调用 Choreographer 中的 Animation Callback，即使这些动画对用户不可见，这些 Callback 的执行完全无意义，却会对 CPU 资源造成较高的占用。</p><p>因此，系统厂商在 Choreographer 中会针对这种情况做优化，通过一系列策略限制不符合条件的后台应用继续执行无意义的动画回调，有效降低系统资源占用。</p><p><img src="/images/15717422623134.jpg"></p><h2 id="帧绘制优化"><a href="#帧绘制优化" class="headerlink" title="帧绘制优化"></a>帧绘制优化</h2><p>和移动事件优化一样，由于有了 Input 事件的信息，在某些场景下我们可以通知 SurfaceFlinger 不用去等待 Vsync 直接做合成操作</p><h2 id="应用启动优化"><a href="#应用启动优化" class="headerlink" title="应用启动优化"></a>应用启动优化</h2><p>我们前面说，主线程的所有操作都是给予 Message 的 ，如果某个操作，非重要的 Message 被排列到了队列后面，那么对这个操作产生影响；而通过重新排列 MessageQueue，在应用启动的时候，把启动相关的重要的启动 Message 放到队列前面，来起到加快启动速度的作用</p><h2 id="animation-callback-前置"><a href="#animation-callback-前置" class="headerlink" title="animation callback 前置"></a>animation callback 前置</h2><p>在上一帧的主线程做完之后，距离下一个 vsync 其实是有一定时间的空闲的，这段时间其实可以用来准备下一帧。那么我们就可以把下一帧的 animation callback 提前到这里来做，这样可以有效利用 cpu 的空闲时间，减少掉帧</p><h2 id="插帧"><a href="#插帧" class="headerlink" title="插帧"></a>插帧</h2><p>跟 animation callback 前置一样，在上一帧的主线程做完之后，距离下一个 vsync 其实是有一定时间的空闲的，这段时间其实可以用来准备下一帧。只不过这里我们直接生成新的一帧：即一个 Vsync 里面，执行两次 doFrame 。插帧相当于是提前准备好后面几帧的数据，这样在遇到真正耗时的帧的时候，不会出现卡顿。</p><p>插帧实现主要是在滑动场景，用在实现了 OverScroller 的滑动组件上，比如 ListView，RecyclerVIew 这些。因为如果用了 OverScroller，我们就会在 touch up 的时候， 就知道了滑动的时间和距离，就可以在这中间做手脚（插帧），相当于在一个 Vsync 内画好了后面好几个 VSync 里面的内容。</p><h2 id="高帧率优化"><a href="#高帧率优化" class="headerlink" title="高帧率优化"></a>高帧率优化</h2><p>现代 Android 设备上的高刷新率（120 Hz）将 Vsync 间隔从 16.6 ms 缩短至 8.3 ms，这带来了巨大的性能和功耗挑战。如何在一帧内完成渲染的必要操作，是手机厂商必须要思考和优化的地方：</p><ol><li>超级 App 的性能表现以及优化</li><li>游戏高帧率合作</li><li>120 fps、90 fps 和 60 fps 相互切换的逻辑</li></ol><p><a id="refs"></a></p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://www.jianshu.com/p/304f56f5d486">https://www.jianshu.com/p/304f56f5d486</a></li><li><a href="http://gityuan.com/2017/02/25/choreographer/">http://gityuan.com/2017/02/25/choreographer/</a></li><li><a href="https://developer.android.com/reference/android/view/Choreographer">https://developer.android.com/reference/android/view/Choreographer</a></li><li><a href="https://www.jishuwen.com/d/2Vcc">https://www.jishuwen.com/d/2Vcc</a></li><li><a href="https://juejin.im/entry/5c8772eee51d456cda2e8099">https://juejin.im/entry/5c8772eee51d456cda2e8099</a></li><li><a href="https://time.geekbang.org/column/intro/142">Android 开发高手课</a></li><li><a href="https://perfetto.dev/">Perfetto - System profiling, app tracing, and trace analysis</a></li><li><a href="https://developer.android.com/studio/profile/perfetto-ui">使用 Perfetto 分析 UI 性能</a></li><li><a href="https://source.android.com/docs/core/graphics/arch-bq-gralloc">BufferQueue and Gralloc</a></li><li><a href="https://developer.android.com/about/versions/15/features#graphics">Android 15 图形渲染优化</a></li></ol><p><a id="zhihu"></a></p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/87954949">知乎 - Android 基于 Choreographer 的渲染机制详解 - Perfetto 版</a><br><a href="https://juejin.im/post/5daedc65e51d457834735c65">掘金 - Android 基于 Choreographer 的渲染机制详解 - Perfetto 版</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android 性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍了 App 开发者不经常接触到但在 Android Framework 渲染链路中非常重要的一个类 Choreographer，包括 Choreographer 的引入背景、简介、部分源码解析、与 MessageQueue 的交互、在 APM 中的应用，以及手机厂商基于 Choreographer 的一些优化思路。&lt;/p&gt;
&lt;p&gt;Choreographer 的引入主要是配合 Vsync，为上层应用的渲染提供稳定的 Message 处理时机。当 Vsync 信号到来时，系统通过对 Vsync 信号周期的调整，控制每一帧绘制操作的时机。目前主流手机的屏幕刷新率已达到 120Hz，即每 8.3ms 刷新一次，系统为配合屏幕刷新频率，相应调整 Vsync 周期。每个 Vsync 周期到来时，Vsync 信号唤醒 Choreographer 执行应用的绘制操作，这正是引入 Choreographer 的主要作用。了解 Choreographer 还可以帮助应用开发者深入理解每一帧的运行原理，同时加深对 &lt;strong&gt;Message&lt;/strong&gt;、&lt;strong&gt;Handler&lt;/strong&gt;、&lt;strong&gt;Looper&lt;/strong&gt;、&lt;strong&gt;MessageQueue&lt;/strong&gt;、&lt;strong&gt;Input&lt;/strong&gt;、&lt;strong&gt;Animation&lt;/strong&gt;、&lt;strong&gt;Measure&lt;/strong&gt;、&lt;strong&gt;Layout&lt;/strong&gt;、&lt;strong&gt;Draw&lt;/strong&gt; 等核心组件的认识。许多 &lt;strong&gt;APM&lt;/strong&gt;（应用性能监控）工具也利用了 &lt;strong&gt;Choreographer&lt;/strong&gt;（通过 FrameCallback）、&lt;strong&gt;FrameMetrics&amp;#x2F;gfxinfo framestats&lt;/strong&gt;（底层依赖 FrameInfo）、&lt;strong&gt;MessageQueue&lt;/strong&gt;（通过 IdleHandler）和 &lt;strong&gt;Looper&lt;/strong&gt;（通过自定义 MessageLogging）这些组合机制进行性能监测。深入理解这些机制后，开发者可以更有针对性地进行性能优化，形成系统化的优化思路。&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</title>
    <link href="https://androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/"/>
    <id>https://androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/</id>
    <published>2025-02-08T09:53:03.000Z</published>
    <updated>2026-02-07T05:17:47.832Z</updated>
    
    <content type="html"><![CDATA[<p>本篇是 Perfetto 系列文章的第四篇，如何使用 trace_processor_shell 在本地打开超过 2G 的大文件。在实际的问题分析过程中，我们经常会碰到非常大的 Trace 文件（大于 2GB），直接扔进 ui.perfetto.dev 是没法打开的，这是因为浏览器内存的限制。这时候我们就需要使用官方提供的 trace_processor_shell 工具来本地打开大文件。</p><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列目录</a></li><li><a href="#dl">0. trace_processor_shell 工具下载</a></li><li><a href="#open-big">1. 使用 trace_processor_shell 打开 Trace 大文件</a></li><li><a href="#compare">2. 命令行启动 vs 直接打开 UI 的区别</a></li><li><a href="#mac">3. Mac 权限问题</a></li><li><a href="#refs">参考文档</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><p><a id="series"></a></p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="dl"></a></p><h1 id="0-trace-processor-shell-工具下载"><a href="#0-trace-processor-shell-工具下载" class="headerlink" title="0. trace_processor_shell 工具下载"></a>0. trace_processor_shell 工具下载</h1><p>官方下载地址：<a href="https://github.com/google/perfetto/releases">https://github.com/google/perfetto/releases</a> ，找到最新的 release 版本，选择自己的平台下载即可：</p><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/08561ad6-fbe2-4d43-af51-b0c82bead328.webp"></p><p>下载之后里面就会有 trace_processor_shell 工具(以 Mac 平台为例)</p><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/5a1880b4-88bd-4e76-82a2-08a9f83e05f0.webp"></p><p>trace_processor_shell 是 Perfetto 开源项目的核心工具之一，提供高性能的本地 Trace 解析服务。通过 –httpd 参数启动 HTTP 服务器后，它允许：</p><ul><li>本地原生加速：绕过浏览器 WASM 的性能限制，直接调用 C++ 实现的解析引擎。基于 Rust&#x2F;C++ 混合实现的解析引擎，优化了内存布局和并行处理，支持流式解析超大型 trace 文件。</li><li>交互式分析：与 Perfetto UI 深度集成，支持动态查询和可视化。</li><li>离线调试：无需上传 trace 到云端，保护隐私并支持内网环境。</li></ul><p><strong>其他的参数</strong></p><table><thead><tr><th>参数</th><th>作用</th><th>示例值</th></tr></thead><tbody><tr><td><code>--http-port</code></td><td>指定监听端口</td><td><code>--httpd :8080</code></td></tr><tr><td><code>--preload</code></td><td>预加载常用数据表</td><td><code>--preload sched</code></td></tr><tr><td><code>--num-threads</code></td><td>设置解析线程数（默认 CPU 核数）</td><td><code>--num-threads 8</code></td></tr></tbody></table><p><a id="open-big"></a></p><h1 id="1-使用-trace-processor-shell-打开-Trace-大文件"><a href="#1-使用-trace-processor-shell-打开-Trace-大文件" class="headerlink" title="1. 使用 trace_processor_shell 打开 Trace 大文件"></a>1. 使用 trace_processor_shell 打开 Trace 大文件</h1><p>.&#x2F;trace_processor_shell –httpd ..&#x2F;jank-航旅纵横-火车票-上下滑动超级卡顿.perfetto-trace</p><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/a12d4ea1-3db3-4dc6-a262-2bc116bada2e.webp"></p><p>这时候在网页端打开 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev</a> ，会有下面的弹框</p><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/69ef4b6e-2751-4c52-97d4-c465eefed07f.webp"></p><p>弹框选项功能详解如下</p><h3 id="1-YES-use-loaded-trace"><a href="#1-YES-use-loaded-trace" class="headerlink" title="1. YES, use loaded trace"></a>1. <strong><code>YES, use loaded trace</code></strong></h3><ul><li><strong>功能</strong>：直接复用当前 Trace Processor 已加载的 trace 文件状态（即命令行中指定的 <code>../jank-航旅纵横-火车票-上下滑动超级卡顿.perfetto-trace</code>）。</li><li><strong>适用场景</strong>：<br>若你已通过 <code>trace_processor_shell --httpd</code> 加载了 trace 文件，且希望 UI 直接使用当前进程的解析状态（包括已执行的 SQL 查询、过滤条件等），选择此选项。</li><li><strong>优势</strong>：<br>避免重复解析文件，节省时间和内存。</li></ul><h3 id="2-YES-but-reset-state"><a href="#2-YES-but-reset-state" class="headerlink" title="2. YES, but reset state"></a>2. <strong><code>YES, but reset state</code></strong></h3><ul><li><strong>功能</strong>：强制重置 Trace Processor 状态，重新加载当前 trace 文件（或加载新文件）。</li><li><strong>适用场景</strong>：<ul><li>需要清除当前 Trace Processor 的所有状态（如临时查询结果、过滤器等），重新开始分析。</li><li>想通过同一端口加载另一个 trace 文件（需先停止当前进程或更换端口）。</li></ul></li><li><strong>等效操作</strong>：<br>等同于关闭当前 <code>trace_processor_shell</code> 进程后重新执行命令。</li></ul><h3 id="3-NO-Use-builtin-WASM"><a href="#3-NO-Use-builtin-WASM" class="headerlink" title="3. NO, Use builtin WASM"></a>3. <strong><code>NO, Use builtin WASM</code></strong></h3><ul><li><strong>功能</strong>：完全绕过本地 Trace Processor 服务，改用浏览器内置的 WebAssembly (WASM) 引擎解析 trace 文件。</li><li><strong>适用场景</strong>：<ul><li>本地 Trace Processor 服务不可用或存在兼容性问题。</li><li>需要支持分享链接、下载修改后的 trace 文件等 WASM 模式专属功能。</li></ul></li><li><strong>代价</strong>：<br>大文件（如 &gt;100MB）解析速度显著下降，且可能因浏览器内存限制崩溃。</li></ul><hr><p>如果选择 YES, use loaded trace , 打开 Trace 后，下面这几个功能是不可用的</p><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/437aea7f-3a0e-4832-992a-8590affca72f.webp"></p><p><a id="compare"></a></p><h1 id="2-命令行启动-vs-直接打开-UI-的区别"><a href="#2-命令行启动-vs-直接打开-UI-的区别" class="headerlink" title="2. 命令行启动 vs 直接打开 UI 的区别"></a>2. 命令行启动 vs 直接打开 UI 的区别</h1><h2 id="通过命令行启动-trace-processor-shell-httpd"><a href="#通过命令行启动-trace-processor-shell-httpd" class="headerlink" title="通过命令行启动 (trace_processor_shell --httpd)"></a><strong>通过命令行启动 (<code>trace_processor_shell --httpd</code>)</strong></h2><ul><li><strong>核心机制</strong>：<br>本地启动一个高性能的 C++ Trace Processor 服务（监听 <code>127.0.0.1:9001</code>），提供原生加速的 trace 解析能力。</li><li><strong>优势</strong>：<ul><li><strong>性能</strong>：原生代码解析速度远超 WASM，尤其适合大型 trace 文件（如 &gt;100MB）。</li><li><strong>功能扩展性</strong>：支持 SQL 查询、自定义指标计算等高级功能。</li><li><strong>状态保持</strong>：Trace Processor 的解析状态（如 SQL 临时表）可跨页面会话保留。</li></ul></li><li><strong>限制</strong>：<ul><li>无法直接通过 UI 分享 trace 文件链接或下载修改后的文件。</li><li>同一时间仅允许一个浏览器标签页使用加速服务。</li></ul></li></ul><h2 id="直接打开-UI-网页-ui-perfetto-dev"><a href="#直接打开-UI-网页-ui-perfetto-dev" class="headerlink" title="直接打开 UI 网页 (ui.perfetto.dev)"></a><strong>直接打开 UI 网页 (<code>ui.perfetto.dev</code>)</strong></h2><ul><li><strong>核心机制</strong>：<br>完全依赖浏览器内置的 WebAssembly 引擎解析 trace 文件，无本地服务参与。</li><li><strong>优势</strong>：<ul><li><strong>便捷性</strong>：无需安装或启动本地工具，适合快速查看小型 trace。</li><li><strong>功能完整性</strong>：支持分享链接、下载修改后的 trace 文件等协作功能。</li></ul></li><li><strong>劣势</strong>：<ul><li><strong>性能瓶颈</strong>：WASM 解析速度慢，大文件可能导致浏览器卡顿或崩溃。</li><li><strong>功能限制</strong>：不支持部分高级 SQL 查询和自定义分析功能。</li></ul></li></ul><hr><h2 id="总结建议"><a href="#总结建议" class="headerlink" title="总结建议"></a>总结建议</h2><ul><li><strong>优先命令行启动</strong>：处理大型 trace 或需要复杂分析时，使用 <code>trace_processor_shell --httpd</code> 提升性能。</li><li><strong>临时轻量分析</strong>：直接上传到 <code>ui.perfetto.dev</code> 更方便，但需注意文件大小限制。</li></ul><p><a id="mac"></a></p><h1 id="3-Mac-权限问题"><a href="#3-Mac-权限问题" class="headerlink" title="3. Mac 权限问题"></a>3. Mac 权限问题</h1><p>Mac 上直接运行 .&#x2F;trace_processor_shell –httpd 会报下面的错误<br><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/d07f0af0-33c2-4353-9bac-aa55ae2b239a.webp"></p><p>需要在设置-隐私与安全，点击 Allow 才可以继续运行<br><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/d7ac3779-ad32-4884-a70f-ea46ff39cf1b.webp"></p><p><a id="refs"></a></p><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ol><li><a href="https://github.com/google/perfetto">Perfetto Github 库</a></li><li><a href="https://perfetto.dev/docs/">Perfetto 官方文档</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本篇是 Perfetto 系列文章的第四篇，如何使用 trace_processor_shell 在本地打开超过 2G 的大文件。在实际的问题分析过程中，我们经常会碰到非常大的 Trace 文件（大于 2GB），直接扔进 ui.perfetto.dev 是没法打开的，这是因为浏览器内存的限制。这时候我们就需要使用官方提供的 trace_processor_shell 工具来本地打开大文件。&lt;/p&gt;
&lt;p&gt;随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android ANR 系列 3 ：ANR 案例分享</title>
    <link href="https://androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/"/>
    <id>https://androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/</id>
    <published>2025-02-08T08:29:30.000Z</published>
    <updated>2026-02-07T05:17:47.822Z</updated>
    
    <content type="html"><![CDATA[<p>本文为 Android App ANR 系列的第三篇，主要分享几个 ANR 的案例，系列文章目录如下</p><ol><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/">Android App ANR 系列 1 ：理解 Android ANR 设计思想</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/">Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/">Android App ANR 系列 3 ：ANR 案例分享</a></li></ol><span id="more"></span><blockquote><p>ANR(Application Not Responding)，应用程序无响应，简单一个定义，却涵盖了很多 Android 系统的设计思想</p><p>首先，ANR 属于应用程序的范畴。这不同于 SNR(System Not Respoding)，SNR 反映的问题是系统进程(system_server)失去了响应能力，而 ANR 明确将问题圈定在应用程序。SNR 由 Watchdog 机制保证，具体可以查阅 Watchdog 机制以及问题分析; ANR 由消息处理机制保证，Android 在系统层实现了一套精密的机制来发现 ANR，核心原理是消息调度和超时处理</p><p>其次，ANR 机制主体实现在系统层。所有与 ANR 相关的消息，都会经过系统进程(system_server)调度，然后派发到应用进程完成对消息的实际处理，同时，系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当，超时限制就起作用了，它收集一些系统状态，譬如 CPU&#x2F;IO 使用情况、进程函数调用栈，并且报告用户有进程无响应了(ANR 对话框，部分 Rom 不显示 ANR 对话框，而是直接闪退到主界面)</p><p>然后，ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制，要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入)， 如果处理超时，则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作，譬如密集 CPU 运算、大量 IO、复杂界面布局等，都会降低应用程序的响应能力</p><p>最后，部分 ANR 问题是很难分析的。有时候由于系统底层的一些影响，导致消息调度失败，出现问题的场景又难以复现。 这类 ANR 问题往往需要花费大量的时间去了解系统的一些行为，超出了 ANR 机制本身的范畴。有一些 ANR 问题很难调查清楚，因为整个系统不稳定的因素很多，例如 Linux Kernel 本身的 Bug 引起的内存碎片过多、硬件损坏等。这类比较底层的原因引起的 ANR 问题往往无从查起，并且这根本不是应用程序的问题，浪费了应用开发人员很多时间，如果你从事过整个系统的开发和维护工作的话会深有体会。所以我不能保证了解了本章的所有内容后能够解决一切 ANR 问题，如果出现了很疑难的 ANR 问题，我建议最好去和做 Framework、驱动和内核的朋友聊聊，或者，如果问题只是个十万分之一的偶然现象，不影响程序的正常运行，我倒是建议不去理它</p><p>– From <a href="https://duanqz.github.io/2015-10-12-ANR-Analysis">duanqz</a></p></blockquote><h1 id="ANR-常见原因"><a href="#ANR-常见原因" class="headerlink" title="ANR 常见原因"></a>ANR 常见原因</h1><p>对于 ANR 的原因，通常要做到 ：大胆假设，小心求证 。发现异常的地方提取之后，先假设是这里的问题导致的，然后以这个假设为出发点，看前后的 Log 看看是否能支持自己的假设，如果不能，那么换一个点.</p><h2 id="问题出在当前进程"><a href="#问题出在当前进程" class="headerlink" title="问题出在当前进程"></a>问题出在当前进程</h2><ol><li>死锁</li><li>主线程调用 thread 的 join()方法、sleep()方法、wait()方法或者等待线程锁的时候</li><li>主线程阻塞在 nSyncDraw</li><li>主线程耗时操作，如复杂的 layout，庞大的 for 循环，IO 等</li><li>主线程被子线程同步锁 block</li><li>主线程等待子线程超时</li><li>主线程 Activity 生命周期函数执行超时</li><li>主线程 Service 生命周期函数执行超时</li><li>主线程 Broadcast.onReceive 函数执行超时（即使调用了 goAsync ）</li><li>渲染线程耗时</li><li>耗时的网络访问</li><li>大量的数据读写</li><li>数据库操作</li><li>硬件操作（比如 Camera)</li><li>service binder 的数量达到上限</li><li>其它线程终止或崩溃导致主线程一直等待</li><li>Dump 内存操作</li><li>大量 SharedPerference 同时读写</li></ol><h2 id="问题出在远端进程或者系统"><a href="#问题出在远端进程或者系统" class="headerlink" title="问题出在远端进程或者系统"></a>问题出在远端进程或者系统</h2><ol><li>与 SystemServer 进行 Binder 通信，SystemServer 执行耗时<ol><li>方法本身执行耗时导致超时</li><li>SystemServer Binder 锁竞争太多，导致等锁超时</li></ol></li><li>等待其他进程返回超时，比如从其他进程的 ContentProvider 中获取数据超时</li><li>Window 错乱导致 Input 超时</li><li>ContentProvider 对应的进程频繁崩溃，也会杀掉当前进程</li><li>整机低内存</li><li>整机 CPU 占用高</li><li>整机 IO 使用率高</li><li>SurfaceFlinger 超时</li><li>系统冻结功能出现 Bug</li><li>System Server 中 WatchDog 出现 ANR</li><li>整机触发温控限制频率</li></ol><h1 id="ANR-案例分享"><a href="#ANR-案例分享" class="headerlink" title="ANR 案例分享"></a>ANR 案例分享</h1><h2 id="ANR-案例：头条-死锁"><a href="#ANR-案例：头条-死锁" class="headerlink" title="ANR 案例：头条 - 死锁"></a>ANR 案例：头条 - 死锁</h2><p>主要操作是频繁的从侧边栏拉出今日头条进行分屏操作，多次操作之后，应用发生了 ANR，这个会导致手机短暂的在分屏栏中的应用界面黑屏并卡死。不过 4-5 秒之后又正常。</p><p>应用发生 ANR 的原因是自身主线程被阻塞导致。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span>prio=5tid=1Blocked</span><br><span class="line">| group=<span class="string">&quot;main&quot;</span>sCount=1dsCount=0obj=0x74f9bbe8self=<span class="number">0xe7084400</span></span><br><span class="line">| sysTid=28210nice=0cgrp=defaultsched=<span class="number">0</span>/0handle=<span class="number">0xe9dcd534</span></span><br><span class="line">| state=S schedstat=(<span class="number">13454428309928953492</span>) utm=121stm=13core=3HZ=<span class="number">100</span></span><br><span class="line">| stack=<span class="number">0xff3b6000</span>-0xff3b8000stackSize=8MB</span><br><span class="line">| held mutexes=</span><br><span class="line">at com.ss.android.common.applog.LogReaper.insertCrashLog(SourceFile:<span class="number">98</span>)</span><br><span class="line">- waiting to lock &lt;<span class="number">0x0d3fbd00</span>&gt; (a com.ss.android.common.applog.LogReaper) held by thread34</span><br><span class="line">at com.ss.android.common.applog.AppLog.uncaughtException(SourceFile:<span class="number">1408</span></span><br><span class="line">at u.aly.n.uncaughtException(SourceFile:<span class="number">34</span>)</span><br><span class="line">at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:<span class="number">1068</span>)</span><br><span class="line">at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:<span class="number">1063</span>)</span><br></pre></td></tr></table></figure><p>关键信息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">waiting to lock &lt;<span class="number">0x0d3fbd00</span>&gt; (a com.ss.android.common.applog.LogReaper) held by thread34</span><br></pre></td></tr></table></figure><p>因此，在下面的 ANR 日志总，查找 tid&#x3D;&#x3D;34</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;LogReaper&quot;</span>prio=5tid=34TimedWaiting</span><br><span class="line">| group=<span class="string">&quot;main&quot;</span>sCount=1dsCount=0obj=0x12fcaba0self=<span class="number">0xcb226e00</span></span><br><span class="line">| sysTid=28274nice=0cgrp=defaultsched=<span class="number">0</span>/0handle=<span class="number">0xc9f9b920</span></span><br><span class="line">| state=S schedstat=(<span class="number">77341565269270880</span>) utm=7stm=0core=5HZ=<span class="number">100</span></span><br><span class="line">| stack=<span class="number">0xc9e99000</span>-0xc9e9b000stackSize=1038KB</span><br><span class="line">| held mutexes=</span><br><span class="line">at java.lang.Object.wait!(Native method)</span><br><span class="line">- waiting on &lt;<span class="number">0x00fc7065</span>&gt; (a java.util.concurrent.atomic.AtomicInteger)</span><br><span class="line">at java.lang.Object.wait(Object.java:<span class="number">407</span>)</span><br><span class="line">at com.ss.android.action.b.d.a(SourceFile:<span class="number">216</span>)</span><br><span class="line">at com.ss.android.newmedia.b.onLogSessionBatchEvent(SourceFile:<span class="number">468</span>)</span><br><span class="line">at com.ss.android.common.applog.DBHelper.batchSession(SourceFile:<span class="number">616</span>)</span><br><span class="line">- locked &lt;<span class="number">0x0d4ff1c4</span>&gt; (a com.ss.android.common.applog.DBHelper)</span><br><span class="line">at com.ss.android.common.applog.LogReaper.switchSession(SourceFile:<span class="number">175</span>)</span><br><span class="line">at com.ss.android.common.applog.LogReaper.switchSession(SourceFile:<span class="number">153</span>)</span><br><span class="line">at com.ss.android.common.applog.LogReaper.processItem(SourceFile:<span class="number">122</span>)</span><br><span class="line">- locked &lt;<span class="number">0x0d3fbd00</span>&gt; (a com.ss.android.common.applog.LogReaper)</span><br><span class="line">at com.ss.android.common.applog.LogReaper.run(SourceFile:<span class="number">632</span>)</span><br></pre></td></tr></table></figure><p>关键信息：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">waiting on &lt;<span class="number">0x00fc7065</span>&gt; (a java.util.concurrent.atomic.AtomicInteger)</span><br></pre></td></tr></table></figure><p>应用内代码阻塞引起的</p><h2 id="ANR-案例：冻结导致-ANR"><a href="#ANR-案例：冻结导致-ANR" class="headerlink" title="ANR 案例：冻结导致 ANR"></a>ANR 案例：冻结导致 ANR</h2><p>搜索 am_anr 查看 anr 发生的时间</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">05</span>-<span class="number">0100</span>:<span class="number">51</span>:<span class="number">39.594</span> <span class="number">1449</span> 5234I am_anr  : [<span class="number">0</span>,<span class="number">2169</span>,com.xxx.weather2,<span class="number">820526660</span>,Input dispatching timed <span class="title function_">out</span> <span class="params">(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: <span class="number">0.</span>  Wait queue length: <span class="number">1.</span>)</span>]</span><br></pre></td></tr></table></figure><p>从 ANR 描述可以看到，是当前的 Window 还在处理上一个 Input 事件超时，导致新的事件没有被及时处理，所以发生了 ANR</p><p>搜索 ANR in 查看当时的 cpu 信息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">05</span>-<span class="number">0100</span>:<span class="number">51</span>:<span class="number">53.974</span> <span class="number">1449</span> 5234E ActivityManager: ANR in com.xxx.weather2 (com.xxx.weather2/com.xxx.weather.xxxMainActivity)</span><br><span class="line"><span class="number">05</span>-<span class="number">0100</span>:<span class="number">51</span>:<span class="number">53.974</span> <span class="number">1449</span> 5234E ActivityManager: PID:<span class="number">2169</span></span><br><span class="line"><span class="number">05</span>-<span class="number">0100</span>:<span class="number">51</span>:<span class="number">53.974</span> <span class="number">1449</span> 5234E ActivityManager: Reason:Input dispatching timed <span class="title function_">out</span> <span class="params">(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: <span class="number">0.</span>  Wait queue length: <span class="number">1.</span>)</span></span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager: Parent: com.xxx.weather2/com.xxx.weather.xxxMainActivity</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager: Load: <span class="number">29.89</span> / <span class="number">31.82</span> / <span class="number">32.27</span></span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager: CPU usage from 4583ms to 12043ms <span class="title function_">later</span> <span class="params">(<span class="number">2020</span>-<span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">44.177</span> to <span class="number">2020</span>-<span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">51.637</span>)</span>:</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager:   <span class="number">18</span>% <span class="number">1449</span>/system_server: <span class="number">9.1</span>% user + <span class="number">9.3</span>% kernel / faults: <span class="number">7819</span> minor <span class="number">1</span> major</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager:   <span class="number">10</span>% <span class="number">720</span>/surfaceflinger: <span class="number">6.2</span>% user + <span class="number">4.4</span>% kernel / faults: <span class="number">734</span> minor <span class="number">26</span> major</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">53.974</span>  <span class="number">1449</span>  <span class="number">5234</span> E ActivityManager:   <span class="number">10</span>% <span class="number">651</span>/android.hardware.audio@<span class="number">5.0</span>-service-mediatek: <span class="number">9.5</span>% user + <span class="number">0.8</span>% kernel / faults: <span class="number">1</span> minor <span class="number">4</span> major</span><br></pre></td></tr></table></figure><p>可以看到当时的 cpu 并不繁忙，所以不是系统负载原因导致的 ANR，那么后续分析就要从 Log 中抽取对应的信息，来分析超时的 input 事件发生前后系统在做什么</p><p>分析过程如下</p><ol><li>找到 ANR 发生时候的具体时间：00:51:39.594</li><li>从 5s 前的 Log 开始看，找到有用的信息：00:51:34.478</li><li>发现这个时间点并没有什么异常，只是应用没有响应 key_back_down，导致 5s 后报了 anr</li><li>从 ANR 之后的 Log 中发现，xxxHansManager : unfreeze uid: 10182 package: com.xxx.weather2 reason: Signal 这句 Log，说明 com.xxx.weather2 之前是冻结状态，这里进行了解冻，那么可以大胆猜想，是不是 com.xxx.weather2 被冻结之后，无法响应事件导致的</li><li>搜索冻结 Log，可以看到在 00:51:04.000 的时候，系统把 com.xxx.weather2 冻结了 xxxHansManager : freeze uid: 10182 package: com.xxx.weather2 reason: LcdOff</li><li>这个需要让系统看是否是冻结的逻辑问题</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">由于 LcdOff， 所以启动冻，weather2 被冻结 ：com.xxx.weather2 reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">03.994</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10245</span> <span class="keyword">package</span>: com.tencent.mm reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">03.994</span>  <span class="number">1449</span>  <span class="number">2187</span> D xxxListManagerImpl: com.tencent.mm in autoStart list!</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">03.998</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10246</span> <span class="keyword">package</span>: com.xunmeng.pinduoduo reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">03.998</span>  <span class="number">1449</span>  <span class="number">2187</span> D xxxListManagerImpl: com.xunmeng.pinduoduo in autoStart list!</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.000</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10182</span> <span class="keyword">package</span>: com.xxx.weather2 reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.002</span>   <span class="number">645</span> <span class="number">30914</span> I netd    : firewallSetUidRule(<span class="number">4</span>, <span class="number">10182</span>, <span class="number">2</span>) &lt;<span class="number">0.</span>09ms&gt;</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.005</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10187</span> <span class="keyword">package</span>: com.heytap.yoli reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.005</span>  <span class="number">1449</span>  <span class="number">2187</span> D xxxListManagerImpl: com.heytap.yoli in autoStart list!</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.005</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : isHansImportantCase uid: <span class="number">10196</span> pkg: cn.kuwo.player reason: audiofocus</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.007</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10234</span> <span class="keyword">package</span>: com.tencent.mobileqq reason: LcdOff</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.008</span>  <span class="number">1449</span>  <span class="number">2187</span> D xxxListManagerImpl: com.tencent.mobileqq in autoStart list!</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.014</span>  <span class="number">1449</span>  <span class="number">2187</span> I xxxHansManager : freeze uid: <span class="number">10235</span> <span class="keyword">package</span>: com.tencent.qqlive reason: LcdOff</span><br><span class="line"></span><br><span class="line">weather2 接收到 KEYCODE_BACK 的 ACTION_DOWN</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.471</span>  <span class="number">1449</span>  <span class="number">9842</span> D xxxPhoneWindowManager: interceptKeyBeforeQueueing:KeyEvent &#123; action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x8</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38812125</span>, downTime=<span class="number">38812125</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.471</span>  <span class="number">1449</span>  <span class="number">9842</span> I sysui_multi_action: [<span class="number">757</span>,<span class="number">803</span>,<span class="number">799</span>,key_back_down,<span class="number">802</span>,<span class="number">1</span>]</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.477</span>  <span class="number">1449</span>  <span class="number">1620</span> D xxxPhoneWindowManager: interceptKeyBeforeDispatching key: win=Window&#123;1b36ae8 u0 com.xxx.weather2/com.xxx.weather.xxxMainActivity&#125;  event = KeyEvent &#123; action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x8</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38812125</span>, downTime=<span class="number">38812125</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.477</span>  <span class="number">1449</span>  <span class="number">1620</span> D xxxPhoneWindowManager: interceptKeyBeforeDispatching <span class="type">newEvent</span> <span class="variable">keyCode</span> <span class="operator">=</span> <span class="number">4</span></span><br><span class="line"></span><br><span class="line">weather2 接收到 KEYCODE_BACK 的 ACTION_UP</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">34.478</span>  <span class="number">1449</span>  <span class="number">1620</span> D xxxPhoneWindowManager: interceptKeyBeforeDispatching key: win=Window&#123;1b36ae8 u0 com.xxx.weather2/com.xxx.weather.xxxMainActivity&#125;  event = KeyEvent &#123; action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x8</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38842126</span>, downTime=<span class="number">38842126</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">34.478</span>  <span class="number">1449</span>  <span class="number">1620</span> D xxxPhoneWindowManager: interceptKeyBeforeDispatching <span class="type">newEvent</span> <span class="variable">keyCode</span> <span class="operator">=</span> <span class="number">4</span></span><br><span class="line"></span><br><span class="line">KEYCODE_BACK 的 ACTION_UP 5s 没有响应，触发 ANR</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">39.484</span>  <span class="number">1449</span>  <span class="number">1620</span> 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: <span class="number">0.</span>  Wait queue length: <span class="number">1.</span></span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">39.484</span>  <span class="number">2256</span>  <span class="number">5518</span> I QUALITY-TOTAL: exp: anr</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">39.594</span>  <span class="number">1449</span>  <span class="number">5234</span> I am_anr  : [<span class="number">0</span>,<span class="number">2169</span>,com.xxx.weather2,<span class="number">820526660</span>,Input dispatching timed <span class="title function_">out</span> <span class="params">(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: <span class="number">0.</span>  Wait queue length: <span class="number">1.</span>)</span>]</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">39.593</span>  <span class="number">1449</span>  <span class="number">1620</span> D ActivityManager:  ANR post Runnable <span class="keyword">for</span> ProcessRecord&#123;ae0c833 <span class="number">2169</span>:com.xxx.weather2/u0a182&#125; to deal with anr happend at <span class="number">38847248</span>@#@<span class="number">2169</span></span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">39.593</span>  <span class="number">1449</span>  <span class="number">1620</span> D ActivityManager:  ANR <span class="title function_">caller</span><span class="params">(<span class="number">2</span>)</span> = com.android.server.am.ActivityManagerService$LocalService.inputDispatchingTimedOut:<span class="number">19872</span> com.android.server.wm.ActivityRecord.keyDispatchingTimedOut:<span class="number">2641</span> com.android.server.wm.AppWindowToken.keyDispatchingTimedOut:<span class="number">2007</span> com.android.server.wm.InputManagerCallback.notifyANR:<span class="number">111</span> com.android.server.input.InputManagerService.notifyANR:<span class="number">1822</span> &lt;bottom of call stack&gt; &lt;bottom of call stack&gt; &lt;bottom of call stack&gt;</span><br><span class="line"></span><br><span class="line">系统发 Signal <span class="number">3</span> 给 weather2（SIGNAL_QUIT = <span class="number">3</span>，给到 Signal Catcher 线程用于输出 Trace）</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.868</span>  <span class="number">2169</span>  <span class="number">2202</span> I oloros.weather: Thread[<span class="number">7</span>,tid=<span class="number">2202</span>,WaitingInMainSignalCatcherLoop,Thread*=<span class="number">0xd9878400</span>,peer=<span class="number">0x138c0250</span>,<span class="string">&quot;Signal Catcher&quot;</span>]: reacting to signal</span><br><span class="line"></span><br><span class="line">系统检测到 Signal 给到的 weather2， 所以给 com.xxx.weather2 解冻</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">1449</span>  <span class="number">9811</span> I xxxHansManager : unfreeze uid: <span class="number">10182</span> <span class="keyword">package</span>: com.xxx.weather2 reason: Signal</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">2169</span>  <span class="number">2202</span> I oloros.weather:</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">2169</span>  <span class="number">2169</span> W ViewRootImpl[xxxMainActivity]: Dropping event due to no window focus: KeyEvent &#123; action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x8</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38812125</span>, downTime=<span class="number">38812125</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;, hasFocus:<span class="literal">true</span>, mStopped:<span class="literal">true</span>, mPausedForTransition:<span class="literal">false</span></span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">2169</span>  <span class="number">2169</span> W ViewRootImpl[xxxMainActivity]: Cancelling event due to no window focus: KeyEvent &#123; action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x28</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38847249</span>, downTime=<span class="number">38812125</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;, hasFocus:<span class="literal">true</span>, mStopped:<span class="literal">true</span>, mPausedForTransition:<span class="literal">false</span></span><br><span class="line"></span><br><span class="line">可以看到这里 weather2 卡了 <span class="number">36391.</span>9ms，也就是从  <span class="number">00</span>:<span class="number">51</span>:<span class="number">04.471</span>（KEYCODE_BACK 发出） 到 <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>（当前时间） 这段时间</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">1449</span>  <span class="number">1620</span> I InputDispatcher: Window <span class="string">&#x27;1b36ae8 com.xxx.weather2/com.xxx.weather.xxxMainActivity (server)&#x27;</span> spent <span class="number">36391.</span>9ms processing the last input event: KeyEvent</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.869</span>  <span class="number">2169</span>  <span class="number">2169</span> V ViewRootImpl[xxxMainActivity]: Sending input event to IME: KeyEvent &#123; action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=<span class="number">0</span>, metaState=<span class="number">0</span>, flags=<span class="number">0x28</span>, repeatCount=<span class="number">0</span>, eventTime=<span class="number">38847249</span>, downTime=<span class="number">38812125</span>, deviceId=-<span class="number">1</span>, source=<span class="number">0x101</span>, displayId=<span class="number">0</span> &#125;</span><br><span class="line"><span class="number">05</span>-<span class="number">01</span> <span class="number">00</span>:<span class="number">51</span>:<span class="number">40.870</span>  <span class="number">2169</span>  <span class="number">2169</span> I Choreographer: Skipped <span class="number">2212</span> frames!  The application may be doing too much work on its main thread.</span><br></pre></td></tr></table></figure><h2 id="ANR-案例：Broadcast-超时导致-ANR"><a href="#ANR-案例：Broadcast-超时导致-ANR" class="headerlink" title="ANR 案例：Broadcast 超时导致 ANR"></a>ANR 案例：Broadcast 超时导致 ANR</h2><p>这个案例 Log 缺失，具体就是用户在进行迁移，BroadcastReceiver 的 onReceive 中会起一个线程处理，最终报的是 BroadcastReceiver ANR</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">04</span>-<span class="number">0717</span>:<span class="number">03</span>:<span class="number">17.174435</span> <span class="number">1448</span> 1476V WindowManager: Lookingforfocus: Window&#123;efa2a4f u0 正在升级数据库，数据量较大，请耐心等待&#125;, flags=<span class="number">25296898</span>, canReceive=true1</span><br><span class="line"><span class="number">04</span>-<span class="number">0717</span>:<span class="number">03</span>:<span class="number">17.174459</span> <span class="number">1448</span> 1476V WindowManager: findFocusedWindow: Foundnewfocus @ Window&#123;efa2a4f u0 正在升级数据库，数据量较大，请耐心等待&#125;<span class="number">2</span></span><br></pre></td></tr></table></figure><p>分析如下：</p><ol><li>日历后台广播 ANR 的原因是 onReceive 执行超时，之前没有看出来是因为 onReceive 里面耗时操作是用的 new Thread 的操作，以为在子线程里面做耗时操作，就不会影响后面广播的执行</li><li>后面发现 onReceive 里面调用了 PendingResult result &#x3D; goAsync(); 这句话会在有序广播接收器执行的时候，可以在子线程执行耗时操作，而不会影响 receiver 的生命周期.这个方法非常简单，返回 mPendingResult 并将其设置为 null。</li><li>如果我们在 onReceive 方法中调用该方法，这意味着广播处理流程被打断了，当 onReceive 方法执行完毕，由于 mPendingResult 为 null，因此并不会马上回调 AMS.finishReceiver 方法。而且由于 goAsync 返回了 PendingResult，因此我们可以任意时刻、任意线程去调用 PendingResult.finish 去回调 AMS。相当于将一个同步回调变成了异步回调。而在这异步回调过程中，我们可以新起线程进行一些耗时的 IO 操作等等。简单来说，goAsync 提供了一种机制，让我们可以在异步线程中处理广播消息，以防止主线程被阻塞。<a href="https://juejin.im/post/5c15fc10e51d454ad55ef9fa">https://juejin.im/post/5c15fc10e51d454ad55ef9fa</a></li><li>这个案例中，虽然在 onReceive 中使用了线程去处理耗时任务，但是由于调用了 goAsync，所以还是会计算超时时间，如果在规定的时间内没有完成，就算是在子线程，也会触发 BroadcastReceiver 的 ANR</li></ol><p>可以看 goAsync 这个方法的官方注释：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This can be called by an application in &#123;<span class="doctag">@link</span> #onReceive&#125; to allow</span></span><br><span class="line"><span class="comment"> * it to keep the broadcast active after returning from that function.</span></span><br><span class="line"><span class="comment"> * This does &lt;em&gt;not&lt;/em&gt; change the expectation of being relatively</span></span><br><span class="line"><span class="comment"> * responsive to the broadcast, but does allow</span></span><br><span class="line"><span class="comment"> * the implementation to move work related to it over to another thread</span></span><br><span class="line"><span class="comment"> * to avoid glitching the main UI thread due to disk IO.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;As a general rule, broadcast receivers are allowed to run for up to 10 seconds</span></span><br><span class="line"><span class="comment"> * before they system will consider them non-responsive and ANR the app.  Since these usually</span></span><br><span class="line"><span class="comment"> * execute on the app&#x27;s main thread, they are already bound by the ~5 second time limit</span></span><br><span class="line"><span class="comment"> * of various operations that can happen there (not to mention just avoiding UI jank), so</span></span><br><span class="line"><span class="comment"> * the receive limit is generally not of concern.  However, once you use &#123;<span class="doctag">@code</span> goAsync&#125;, though</span></span><br><span class="line"><span class="comment"> * able to be off the main thread, the broadcast execution limit still applies, and that</span></span><br><span class="line"><span class="comment"> * includes the time spent between calling this method and ultimately</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@link</span> PendingResult#finish() PendingResult.finish()&#125;.&lt;/p&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;If you are taking advantage of this method to have more time to execute, it is useful</span></span><br><span class="line"><span class="comment"> * to know that the available time can be longer in certain situations.  In particular, if</span></span><br><span class="line"><span class="comment"> * the broadcast you are receiving is not a foreground broadcast (that is, the sender has not</span></span><br><span class="line"><span class="comment"> * used &#123;<span class="doctag">@link</span> Intent#FLAG_RECEIVER_FOREGROUND&#125;), then more time is allowed for the receivers</span></span><br><span class="line"><span class="comment"> * to run, allowing them to execute for 30 seconds or even a bit more.  This is something that</span></span><br><span class="line"><span class="comment"> * receivers should rarely take advantage of (long work should be punted to another system</span></span><br><span class="line"><span class="comment"> * facility such as &#123;<span class="doctag">@link</span> android.app.job.JobScheduler&#125;, &#123;<span class="doctag">@link</span> android.app.Service&#125;, or</span></span><br><span class="line"><span class="comment"> * see especially &#123;<span class="doctag">@link</span> android.support.v4.app.JobIntentService&#125;), but can be useful in</span></span><br><span class="line"><span class="comment"> * certain rare cases where it is necessary to do some work as soon as the broadcast is</span></span><br><span class="line"><span class="comment"> * delivered.  Keep in mind that the work you do here will block further broadcasts until</span></span><br><span class="line"><span class="comment"> * it completes, so taking advantage of this at all excessively can be counter-productive</span></span><br><span class="line"><span class="comment"> * and cause later events to be received more slowly.&lt;/p&gt;</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> Returns a &#123;<span class="doctag">@link</span> PendingResult&#125; representing the result of</span></span><br><span class="line"><span class="comment"> * the active broadcast.  The BroadcastRecord itself is no longer active;</span></span><br><span class="line"><span class="comment"> * all data and other interaction must go through &#123;<span class="doctag">@link</span> PendingResult&#125;</span></span><br><span class="line"><span class="comment"> * APIs.  The &#123;<span class="doctag">@link</span> PendingResult#finish PendingResult.finish()&#125; method</span></span><br><span class="line"><span class="comment"> * must be called once processing of the broadcast is done.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> PendingResult <span class="title function_">goAsync</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">PendingResult</span> <span class="variable">res</span> <span class="operator">=</span> mPendingResult;</span><br><span class="line">    mPendingResult =<span class="literal">null</span>;</span><br><span class="line">   returnres;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其用法如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override1</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceive</span><span class="params">(<span class="keyword">final</span> Context context, <span class="keyword">final</span> Intent intent)</span>&#123;</span><br><span class="line">   <span class="type">finalPendingResult</span> <span class="variable">result</span> <span class="operator">=</span> goAsync();</span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">worker</span> <span class="operator">=</span>newRunnable() &#123;</span><br><span class="line">       <span class="meta">@Override</span></span><br><span class="line">       <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span>&#123;</span><br><span class="line">            onReceiveAsync(context, intent);</span><br><span class="line">            result.finish();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    mAsyncHandler.post(worker);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ANR-案例：Launcher-Input-ANR"><a href="#ANR-案例：Launcher-Input-ANR" class="headerlink" title="ANR 案例：Launcher - Input ANR"></a>ANR 案例：Launcher - Input ANR</h2><p>搜索 am_anr 找到 ANR 发生的时间点</p><ol><li>属于 input dispatch anr</li><li>属于应用处理 input 事件超时（而不是 no focus window）</li></ol><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">19</span>:<span class="number">44</span>:<span class="number">56.815</span>  <span class="number">2515</span> <span class="number">25056</span> I am_anr  : [<span class="number">0</span>,<span class="number">3365</span>,com.xxx.launcher,<span class="number">819478085</span>,<span class="function">Input dispatching timed <span class="title">out</span> <span class="params">(Waiting to send key event because the focused window has <span class="keyword">not</span> finished processing all of the input events that were previously delivered to it.  Outbound queue length: <span class="number">0.</span>  Wait queue length: <span class="number">9.</span>)</span>]</span></span><br></pre></td></tr></table></figure><p>搜索 ANR in 找到 cpu 使用情况 1.从 Load : 0.02 &#x2F; 0.01 &#x2F; 0.0 来看，整机的负载并不高，大概率是逻辑导致的 ANR.<br>2.logd 和 SurfaceFlinger 的 cpu 使用略高，可以当做怀疑的对象重点观察.</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span><span class="number">-0119</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: ANR in com.xxx.<span class="built_in">launcher</span> (com.xxx.launcher/.Launcher)</span><br><span class="line"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: PID:<span class="number">3365</span></span><br><span class="line"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: Reason: <span class="function">Input dispatching timed <span class="title">out</span> <span class="params">(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:<span class="number">0.</span>  Wait queue length:<span class="number">9.</span>)</span></span></span><br><span class="line"><span class="function"> 19:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: Parent: com.xxx.launcher/.Launcher</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: Load:<span class="number">0.02</span>/<span class="number">0.01</span>/<span class="number">0.0</span></span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager: CPU usage from0ms to10936ms later (<span class="number">2020</span>- <span class="number">19</span>:<span class="number">44</span>:<span class="number">56.751</span>to2020- <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.688</span>):</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">97</span>%<span class="number">546</span>/logd:<span class="number">97</span>% user +<span class="number">0.2</span>% kernel / faults:<span class="number">12</span>minor</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">25</span>%<span class="number">956</span>/surfaceflinger:<span class="number">11</span>% user +<span class="number">13</span>% kernel / faults:<span class="number">151</span>minor17major</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">1.4</span>%<span class="number">1664</span>/media.codec:<span class="number">0.9</span>% user +<span class="number">0.5</span>% kernel / faults:<span class="number">28729</span>minor4major</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">7.5</span>%<span class="number">2515</span>/system_server:<span class="number">2.9</span>% user +<span class="number">4.6</span>% kernel / faults:<span class="number">1818</span>minor8major</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">3.2</span>%<span class="number">3365</span>/com.xxx.launcher:<span class="number">2.1</span>% user +<span class="number">1</span>% kernel / faults:<span class="number">3535</span>minor29major</span></span><br><span class="line"><span class="function"> <span class="number">19</span>:<span class="number">45</span>:<span class="number">07.753</span> <span class="number">251525056</span>E ActivityManager:   <span class="number">2</span>%<span class="number">3620</span>/com.xxx.persist.system:<span class="number">1.2</span>% user +<span class="number">0.7</span>% kernel / faults:<span class="number">870</span>minor</span></span><br></pre></td></tr></table></figure><p>分析 trace.txt 查看是否有有用的信息，首先看 Launcher 的主线程堆栈</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span>prio=<span class="number">5</span>tid=<span class="number">1</span>Native</span><br><span class="line">  | group=<span class="string">&quot;main&quot;</span>sCount=<span class="number">1</span>dsCount=<span class="number">0f</span>lags=<span class="number">1</span>obj=<span class="number">0x72a50cd0</span>self=<span class="number">0x6f7ae10800</span></span><br><span class="line">  | sysTid=<span class="number">3365</span>nice=<span class="number">-10</span>cgrp=defaultsched=<span class="number">0</span>/<span class="number">0</span>handle=<span class="number">0x700123bed8</span></span><br><span class="line">  | state=S schedstat=(<span class="number">37451337214473599138010494263</span>) utm=<span class="number">26967</span>stm=<span class="number">10484</span>core=<span class="number">0</span>HZ=<span class="number">100</span></span><br><span class="line">  |stack=<span class="number">0x7fc0270000</span><span class="number">-0x7fc0272000</span>stackSize=<span class="number">8192</span>KB</span><br><span class="line">  | held mutexes=</span><br><span class="line">  kernel: (couldn<span class="number">&#x27;</span>t read /proc/self/task/<span class="number">3365</span>/stack)</span><br><span class="line">  native: #<span class="number">00</span>pc0000000000071a8c  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (syscall+<span class="number">28</span>)</span><br><span class="line">  native: #<span class="number">01</span>pc0000000000075710  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__futex_wait_ex(voidvolatile*,<span class="type">bool</span>,<span class="type">int</span>,<span class="type">bool</span>, timespecconst*)+<span class="number">140</span>)</span><br><span class="line">  native: #<span class="number">02</span>pc00000000000d9744  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (pthread_cond_wait+<span class="number">60</span>)</span><br><span class="line">  native: #<span class="number">03</span>pc00000000002bf5e8  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::DrawFrameTask::<span class="built_in">postAndWait</span>()+<span class="number">168</span>)</span><br><span class="line">  native: #<span class="number">04</span>pc00000000002bf510  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::DrawFrameTask::<span class="built_in">drawFrame</span>()+<span class="number">44</span>)<span class="number">12</span></span><br><span class="line">  at android.graphics.HardwareRenderer.<span class="built_in">nSyncAndDrawFrame</span>(Native method)</span><br><span class="line">  at android.graphics.HardwareRenderer.<span class="built_in">syncAndDrawFrame</span>(HardwareRenderer.java:<span class="number">422</span>)</span><br><span class="line">  at android.view.ThreadedRenderer.<span class="built_in">draw</span>(ThreadedRenderer.java:<span class="number">671</span>)</span><br><span class="line">  at android.view.ViewRootImpl.<span class="built_in">draw</span>(ViewRootImpl.java:<span class="number">3983</span>)</span><br><span class="line">  - locked &lt;<span class="number">0x030694da</span>&gt; (a java.lang.Object)</span><br><span class="line">  at android.view.ViewRootImpl.<span class="built_in">performDraw</span>(ViewRootImpl.java:<span class="number">3782</span>)</span><br><span class="line">  at android.view.ViewRootImpl.<span class="built_in">performTraversals</span>(ViewRootImpl.java:<span class="number">3085</span>)</span><br><span class="line">  at android.view.ViewRootImpl.<span class="built_in">doTraversal</span>(ViewRootImpl.java:<span class="number">1994</span>)</span><br><span class="line">  at android.view.ViewRootImpl$TraversalRunnable.<span class="built_in">run</span>(ViewRootImpl.java:<span class="number">8201</span>)</span><br><span class="line">  at android.view.Choreographer$CallbackRecord.<span class="built_in">run</span>(Choreographer.java:<span class="number">1085</span>)</span><br><span class="line">  at android.view.Choreographer.<span class="built_in">doCallbacks</span>(Choreographer.java:<span class="number">908</span>)</span><br><span class="line">  at android.view.Choreographer.<span class="built_in">doFrame</span>(Choreographer.java:<span class="number">835</span>)</span><br><span class="line">  at android.view.Choreographer$FrameHandler.<span class="built_in">handleMessage</span>(Choreographer.java:<span class="number">1013</span>)</span><br><span class="line">  at android.os.Handler.<span class="built_in">dispatchMessage</span>(Handler.java:<span class="number">107</span>)</span><br><span class="line">  at android.os.Looper.<span class="built_in">loop</span>(Looper.java:<span class="number">238</span>)</span><br><span class="line">  at android.app.ActivityThread.<span class="built_in">main</span>(ActivityThread.java:<span class="number">7798</span>)</span><br><span class="line">  at java.lang.reflect.Method.<span class="built_in">invoke</span>(Native method)</span><br><span class="line">  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.<span class="built_in">run</span>(RuntimeInit.java:<span class="number">492</span>)</span><br><span class="line">  at com.android.internal.os.ZygoteInit.<span class="built_in">main</span>(ZygoteInit.java:<span class="number">995</span>)</span><br></pre></td></tr></table></figure><p>可以看到这里主线程阻塞在了等待 RenderThread 返回，那么继续查看 RenderThread 的堆栈</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;RenderThread&quot;</span>daemon prio=<span class="number">7</span>tid=<span class="number">25</span>Native</span><br><span class="line">  | group=<span class="string">&quot;main&quot;</span>sCount=<span class="number">1</span>dsCount=<span class="number">0f</span>lags=<span class="number">1</span>obj=<span class="number">0x13100b00</span>self=<span class="number">0x6f0c698c00</span></span><br><span class="line">  | sysTid=<span class="number">3616</span>nice=<span class="number">-10</span>cgrp=defaultsched=<span class="number">0</span>/<span class="number">0</span>handle=<span class="number">0x6f072e0d50</span></span><br><span class="line">  | state=S schedstat=(<span class="number">35808915205059629728352628704</span>) utm=<span class="number">29226</span>stm=<span class="number">6582</span>core=<span class="number">3</span>HZ=<span class="number">100</span></span><br><span class="line">  |stack=<span class="number">0x6f071ea000</span><span class="number">-0x6f071ec000</span>stackSize=<span class="number">991</span>KB</span><br><span class="line">  | held mutexes=</span><br><span class="line">  kernel: (couldn<span class="number">&#x27;</span>t read /proc/self/task/<span class="number">3616</span>/stack)</span><br><span class="line">  native: #<span class="number">00</span>pc00000000000c49f4  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__ioctl+<span class="number">4</span>)</span><br><span class="line">  native: #<span class="number">01</span>pc000000000007d518  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (ioctl+<span class="number">132</span>)</span><br><span class="line">  native: #<span class="number">02</span>pc0000000000059e58  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">talkWithDriver</span>(<span class="type">bool</span>)+<span class="number">244</span>)</span><br><span class="line">  native: #<span class="number">03</span>pc000000000005ad94  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">waitForResponse</span>(android::Parcel*,<span class="type">int</span>*)+<span class="number">60</span></span><br><span class="line">  native: #<span class="number">04</span>pc000000000005ab38  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">transact</span>(<span class="type">int</span>,unsignedint, android::Parcelconst&amp;, android::Parcel*,unsignedint)+<span class="number">180</span>)</span><br><span class="line">  native: #<span class="number">05</span>pc000000000004f000  /system/lib64/libbinder.<span class="built_in">so</span> (android::BpBinder::<span class="built_in">transact</span>(unsignedint, android::Parcelconst&amp;, android::Parcel*,unsignedint)+<span class="number">228</span>)</span><br><span class="line">  native: #<span class="number">06</span>pc000000000007fef8  /system/lib64/libgui.<span class="built_in">so</span> (android::BpGraphicBufferProducer::<span class="built_in">dequeueBuffer</span>(<span class="type">int</span>*, android::sp&lt;android::Fence&gt;*,unsignedint,unsignedint,<span class="type">int</span>,unsignedlong,unsignedlong*, android::FrameEventHistoryDelta*)+<span class="number">224</span>)</span><br><span class="line">  native: #<span class="number">07</span>pc00000000000b9880  /system/lib64/libgui.<span class="built_in">so</span> (android::Surface::<span class="built_in">dequeueBuffer</span>(ANativeWindowBuffer**,<span class="type">int</span>*)+<span class="number">392</span>)</span><br><span class="line">  native: #<span class="number">08</span>pc0000000000380540  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::ReliableSurface::<span class="built_in">hook_dequeueBuffer</span>(ANativeWindow*, ANativeWindowBuffer**,<span class="type">int</span>*)+<span class="number">104</span>)</span><br><span class="line">  native: #<span class="number">09</span>pc000000000000989c  /vendor/lib64/egl/eglSubDriverAndroid.<span class="built_in">so</span> (???)</span><br><span class="line">  native: #<span class="number">10</span>pc0000000000009388  /vendor/lib64/egl/eglSubDriverAndroid.<span class="built_in">so</span> (???)</span><br><span class="line">  native: #<span class="number">11</span>pc000000000022cd2c  /vendor/lib64/egl/libGLESv2_adreno.<span class="built_in">so</span> (???)</span><br><span class="line">  native: #<span class="number">12</span>pc0000000000212b8c  /vendor/lib64/egl/libGLESv2_adreno.<span class="built_in">so</span> (???)</span><br><span class="line">  native: #<span class="number">13</span>pc0000000000020838  /system/lib64/libEGL.<span class="built_in">so</span> (android::<span class="built_in">eglQuerySurfaceImpl</span>(<span class="type">void</span>*,<span class="type">void</span>*,<span class="type">int</span>,<span class="type">int</span>*)+<span class="number">248</span>)</span><br><span class="line">  native: #<span class="number">14</span>pc00000000002c6794  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::EglManager::<span class="built_in">beginFrame</span>(<span class="type">void</span>*)+<span class="number">224</span>)</span><br><span class="line">  native: #<span class="number">15</span>pc00000000002d414c  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::CanvasContext::<span class="built_in">draw</span>()+<span class="number">236</span>)</span><br><span class="line">  native: #<span class="number">16</span>pc00000000002d34e8  /system/lib64/libhwui.<span class="built_in">so</span> (_ZNSt3__110__function6__funcIZN7android10uirenderer12renderthread13DrawFrameTask11postAndWaitEvE3$_0NS_9allocatorIS6_EEFvvEEclEv$c303f2d2360db58ed70a2d0ac7ed911b+<span class="number">380</span>)</span><br><span class="line">  native: #<span class="number">17</span>pc00000000002de044  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::WorkQueue::<span class="built_in">process</span>()+<span class="number">228</span>)</span><br><span class="line">  native: #<span class="number">18</span>pc00000000002ddd24  /system/lib64/libhwui.<span class="built_in">so</span> (android::uirenderer::renderthread::RenderThread::<span class="built_in">threadLoop</span>()+<span class="number">576</span>)</span><br><span class="line">  native: #<span class="number">19</span>pc0000000000013654  /system/lib64/libutils.<span class="built_in">so</span> (android::Thread::_threadLoop(<span class="type">void</span>*)+<span class="number">328</span>)</span><br><span class="line">  native: #<span class="number">20</span>pc00000000000da200  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__pthread_start(<span class="type">void</span>*)+<span class="number">36</span>)</span><br><span class="line">  native: #<span class="number">21</span>pc00000000000769d4  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__start_thread+<span class="number">64</span>)</span><br></pre></td></tr></table></figure><p>可以看到 RenderThread 是卡在了 binder 通信上，调用的方法是 dequeueBuffer，在等待 dequeueBuffer 返回结果。正常情况下主线程是不等待 dequeueBuffer 的，在 nSyncAndDrawFrame 返回后就继续执行了，这里卡住是因为在等 RenderThread 的上一个 draw 任务完成，也就是说上一个 draw 任务卡在了 dequeueBuffer，导致这一帧的主线程卡住导致超时</p><p>对帧渲染流程熟悉的话，应该知道 dequeueBuffer 的对端是 SurfaceFlinger，这时候需要查看 SurfaceFlinger 是否有问题（这里如果有 binder info 就可以直接看到 renderthread 在跟 SurfaceFlinger 的哪个线程进行通信，binder info 中可以看到 binder 通信的细节，比如这个案例我们应该可以看到(由于没有 dump 出来 binder info，所以下面的只是一个例子)</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 956 是 SurfaceFlinger，这里是 SurfaceFlinger 的 incoming binder 信息</span></span><br><span class="line">proc956：</span><br><span class="line">incoming transaction28350131:<span class="number">0000000000000000f</span>rom3365:<span class="number">3616</span>to956:<span class="number">1357</span>code2flags10pri0:<span class="number">110</span>r1 node28346208size112:<span class="number">0</span>data0000000000000000</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3365 是 Launcher，3365:3616 =&gt; 956:1356 意思是从 Launcher(3365)的渲染线程(3616)到 SurfaceFlinger(956) 的 Binder:956_4(1357)</span></span><br><span class="line">proc3365：</span><br><span class="line">outgoing transaction28350131:<span class="number">0000000000000000f</span>rom3365:<span class="number">3616</span>to956:<span class="number">1357</span>code2flags10pri0:<span class="number">110</span>r1</span><br></pre></td></tr></table></figure><p>这里我们搜 dequeueBuffer，来看 RenderThread 是在与 SurfaceFlinger 的那个 Bidner 线程通信，发现是 Binder:956_4</p><p>对应的 sysTid&#x3D;1357</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;Binder:956_4&quot;</span>sysTid=<span class="number">1357</span></span><br><span class="line">    #<span class="number">00</span>pc0000000000071a8c  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (syscall+<span class="number">28</span>) (BuildId:<span class="number">340</span>eb42dd36c6541aec54c306eb4d0ee)</span><br><span class="line">    #<span class="number">01</span>pc0000000000075710  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__futex_wait_ex(voidvolatile*,<span class="type">bool</span>,<span class="type">int</span>,<span class="type">bool</span>, timespecconst*)+<span class="number">140</span>) (BuildId:<span class="number">340</span>eb42dd36c6541aec54c306eb4d0ee)</span><br><span class="line">    #<span class="number">02</span>pc00000000000d97e8  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (pthread_cond_timedwait+<span class="number">120</span>) (BuildId:<span class="number">340</span>eb42dd36c6541aec54c306eb4d0ee)</span><br><span class="line">    #<span class="number">03</span>pc0000000000072df0  /system/lib64/libc++.<span class="built_in">so</span> (_ZNSt3__118condition_variable15__do_timed_waitERNS_11unique_lockINS_5mutexEEENS_6chrono10time_pointINS5_12system_clockENS5_8durationIxNS_5ratioILl1ELl1000000000EEEEEEE+<span class="number">108</span>) (BuildId:<span class="number">5</span>aad1075509f6e517eb78db32da8fbf6)</span><br><span class="line">    #<span class="number">04</span>pc000000000006f9d8  /system/lib64/libgui.<span class="built_in">so</span> (android::BufferQueueProducer::<span class="built_in">waitForFreeSlotThenRelock</span>(android::BufferQueueProducer::FreeSlotCaller, std::__1::unique_lock&lt;std::__1::mutex&gt;&amp;,<span class="type">int</span>*)<span class="type">const</span>+<span class="number">788</span>) (BuildId: e017958f19275814d1f2d55ce8b10bfa)</span><br><span class="line">    #<span class="number">05</span>pc000000000006fd8c  /system/lib64/libgui.<span class="built_in">so</span> (android::BufferQueueProducer::<span class="built_in">dequeueBuffer</span>(<span class="type">int</span>*, android::sp&lt;android::Fence&gt;*,unsignedint,unsignedint,<span class="type">int</span>,unsignedlong,unsignedlong*, android::FrameEventHistoryDelta*)+<span class="number">776</span>) (BuildId: e017958f19275814d1f2d55ce8b10bfa)</span><br><span class="line">    #<span class="number">06</span>pc000000000007f0a8  /system/lib64/libgui.<span class="built_in">so</span> (android::BnGraphicBufferProducer::<span class="built_in">onTransact</span>(unsignedint, android::Parcelconst&amp;, android::Parcel*,unsignedint)+<span class="number">3580</span>) (BuildId: e017958f19275814d1f2d55ce8b10bfa)</span><br><span class="line">    #<span class="number">07</span>pc000000000004d67c  /system/lib64/libbinder.<span class="built_in">so</span> (android::BBinder::<span class="built_in">transact</span>(unsignedint, android::Parcelconst&amp;, android::Parcel*,unsignedint)+<span class="number">136</span>) (BuildId: bb7ca5d12323a310f26473506cf070ed)</span><br><span class="line">    #<span class="number">08</span>pc000000000005a55c  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">executeCommand</span>(<span class="type">int</span>)+<span class="number">1008</span>) (BuildId: bb7ca5d12323a310f26473506cf070ed)</span><br><span class="line">    #<span class="number">09</span>pc000000000005a0b8  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">getAndExecuteCommand</span>()+<span class="number">156</span>) (BuildId: bb7ca5d12323a310f26473506cf070ed)</span><br><span class="line">    #<span class="number">10</span>pc000000000005a898  /system/lib64/libbinder.<span class="built_in">so</span> (android::IPCThreadState::<span class="built_in">joinThreadPool</span>(<span class="type">bool</span>)+<span class="number">220</span>) (BuildId: bb7ca5d12323a310f26473506cf070ed)</span><br><span class="line">    #<span class="number">11</span>pc000000000008093c  /system/lib64/libbinder.<span class="built_in">so</span> (android::PoolThread::<span class="built_in">threadLoop</span>()+<span class="number">24</span>) (BuildId: bb7ca5d12323a310f26473506cf070ed)</span><br><span class="line">    #<span class="number">12</span>pc0000000000013654  /system/lib64/libutils.<span class="built_in">so</span> (android::Thread::_threadLoop(<span class="type">void</span>*)+<span class="number">328</span>) (BuildId:<span class="number">594f</span>10db565bb0b9cf0223c7a1990ce5)</span><br><span class="line">    #<span class="number">13</span>pc00000000000da200  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__pthread_start(<span class="type">void</span>*)+<span class="number">36</span>) (BuildId:<span class="number">340</span>eb42dd36c6541aec54c306eb4d0ee)</span><br><span class="line">    #<span class="number">14</span>pc00000000000769d4  /apex/com.android.runtime/lib64/bionic/libc.<span class="built_in">so</span> (__start_thread+<span class="number">64</span>) (BuildId:<span class="number">340</span>eb42dd36c6541aec54c306eb4d0ee)</span><br></pre></td></tr></table></figure><p>可以看到这里是卡在了 waitForFreeSlotThenRelock，其代码逻辑在 frameworks&#x2F;native&#x2F;libs&#x2F;gui&#x2F;BufferQueueProducer.cpp 中, 感兴趣的可以自己看一下</p><p>这里卡在 waitForFreeSlotThenRelock 的意思就是，没有足够的 Buffer，因为每个 Layer 对应的 Buffer 的个数是一定的(2 个或者 3 个或者 4 个，一般是 3 个或者 4 个)，如果 4 个 Buffer 都在使用过程中，那么应用调用 dequeueBuffer 去申请 free 的 Buffer 是不会成功的，需要进行等待，这里就是在等待 free 的 buffer 超时，导致了应用的 ANR</p><p>Log 里面的 SurfaceFlinger、hwui 的部分 Log 也频繁打印来提示问题点</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SurfaceFlinger 的 Log 提示可用 buffer 数量不足</span></span><br><span class="line">  <span class="number">19</span>:<span class="number">44</span>:<span class="number">42.371</span>   <span class="number">956</span>   <span class="number">956</span> E BufferQueueConsumer: [com.xxx.launcher/com.xxx.launcher.Launcher#<span class="number">0</span>] acquireBuffer: max acquired buffer count reached: <span class="number">2</span> (max <span class="number">1</span>)</span><br><span class="line">  <span class="number">19</span>:<span class="number">44</span>:<span class="number">42.371</span>   <span class="number">956</span>   <span class="number">956</span> E BufferLayerConsumer: [com.xxx.launcher/com.xxx.launcher.Launcher#<span class="number">0</span>] updateTexImage: acquire failed: <span class="function">Function <span class="keyword">not</span> <span class="title">implemented</span> <span class="params">(<span class="number">-38</span>)</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"><span class="comment">// 提示是 dequeueBuffer 失败</span></span></span><br><span class="line"><span class="function">  19:<span class="number">45</span>:<span class="number">45.127</span>  <span class="number">3365</span>  <span class="number">3616</span> W OpenGLRenderer: dequeueBuffer failed, error =</span> <span class="number">-110</span>; switching to fallback</span><br><span class="line"></span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.138</span>  <span class="number">3365</span>  <span class="number">3616</span> I OpenGLRenderer: Davey! duration=<span class="number">4015</span>ms; Flags=<span class="number">0</span>, IntendedVsync=<span class="number">21148991192817</span>, Vsync=<span class="number">21148991192817</span>, OldestInputEvent=<span class="number">9223372036854775807</span>, NewestInputEvent=<span class="number">0</span>, HandleInputStart=<span class="number">21148991206202</span>, AnimationStart=<span class="number">21148991249587</span>, PerformTraversalsStart=<span class="number">21148991252765</span>, DrawStart=<span class="number">21148991798858</span>, SyncQueued=<span class="number">21148991974796</span>, SyncStart=<span class="number">21152137782451</span>, IssueDrawCommandsStart=<span class="number">21152138724534</span>, SwapBuffers=<span class="number">21156150646824</span>, FrameCompleted=<span class="number">21156152280366</span>, DequeueBufferDuration=<span class="number">0</span>, QueueBufferDuration=<span class="number">0</span>,</span><br><span class="line"></span><br><span class="line"><span class="comment">// MessageQueue 的信息标识是哪个 Message block</span></span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.142</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : &gt;&gt;&gt; msg<span class="number">&#x27;</span>s executing time is too <span class="type">long</span></span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.142</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Blocked msg = &#123; when=<span class="number">-4</span>s2ms what=<span class="number">0</span> target=android.view.Choreographer$FrameHandler callback=android.view.Choreographer$FrameDisplayEventReceiver &#125; , cost  = <span class="number">4002</span> ms</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.142</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : &gt;&gt;&gt;Current msg List is:</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.142</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">1</span>&gt;  = &#123; when=<span class="number">-4</span>s5ms what=<span class="number">13</span> target=android.view.ViewRootImpl$ViewRootHandler &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.142</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">2</span>&gt;  = &#123; when=<span class="number">-4</span>s1ms what=<span class="number">0</span> target=android.view.ViewRootImpl$ViewRootHandler callback=com.android.launcher3.util.ViewOnDrawExecutor &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">3</span>&gt;  = &#123; when=<span class="number">-4</span>s1ms what=<span class="number">0</span> target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.Editor$<span class="number">1</span> &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">4</span>&gt;  = &#123; when=<span class="number">-3</span>s23ms what=<span class="number">0</span> target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.ViewFlipper$<span class="number">2</span> &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">5</span>&gt;  = &#123; when=<span class="number">-1</span>s502ms what=<span class="number">6</span> target=android.view.inputmethod.InputMethodManager$H arg1=<span class="number">74627</span> obj=android.view.inputmethod.InputMethodManager$PendingEvent@<span class="number">561b</span>ec9 &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">6</span>&gt;  = &#123; when=+<span class="number">14</span>s859ms what=<span class="number">0</span> target=android.os.Handler callback=xxxUIEngineProguard.i.b$b &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : Current msg &lt;<span class="number">7</span>&gt;  = &#123; when=+<span class="number">24</span>d20h30m18s414ms what=<span class="number">0</span> target=android.view.ViewRootImpl$ViewRootHandler callback=android.widget.ViewFlipper$<span class="number">2</span> &#125;</span><br><span class="line">  <span class="number">19</span>:<span class="number">45</span>:<span class="number">45.143</span>  <span class="number">3365</span>  <span class="number">3365</span> E ANR_LOG : &gt;&gt;&gt;CURRENT MSG DUMP OVER&lt;&lt;&lt;</span><br></pre></td></tr></table></figure><p>这种问题一般是 SurfaceFlinger 的问题，可以转给对应的负责人继续分析</p><h1 id="异常-Log-案例"><a href="#异常-Log-案例" class="headerlink" title="异常 Log 案例"></a>异常 Log 案例</h1><h2 id="主线程进行数据库读写"><a href="#主线程进行数据库读写" class="headerlink" title="主线程进行数据库读写"></a>主线程进行数据库读写</h2><figure class="highlight c#"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span> prio=<span class="number">5</span> tid=<span class="number">1</span> Native</span><br><span class="line">held mutexes=</span><br><span class="line">kernel: (couldn<span class="string">&#x27;t read /proc/self/task/11003/stack)</span></span><br><span class="line"><span class="string">native: #00 pc 000492a4 /system/lib/libc.so (nanosleep+12)</span></span><br><span class="line"><span class="string">native: #01 pc 0002dc21 /system/lib/libc.so (usleep+52)</span></span><br><span class="line"><span class="string">native: #02 pc 00009cab /system/lib/libsqlite.so (???)</span></span><br><span class="line"><span class="string">native: #03 pc 00011119 /system/lib/libsqlite.so (???)</span></span><br><span class="line"><span class="string">native: #04 pc 00016455 /system/lib/libsqlite.so (???)</span></span><br><span class="line"><span class="string">native: #16 pc 0000fa29 /system/lib/libsqlite.so (???)</span></span><br><span class="line"><span class="string">native: #17 pc 0000fad7 /system/lib/libsqlite.so (sqlite3_prepare16_v2+14)</span></span><br><span class="line"><span class="string">native: #18 pc 0007f671 /system/lib/libandroid_runtime.so (???)</span></span><br><span class="line"><span class="string">native: #19 pc 002b4721 /system/framework/arm/boot-framework.oat (Java_android_database_sqlite_SQLiteConnection_nativePrepareStatement__JLjava_lang_String_2+116)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:294)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:215)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808)</span></span><br><span class="line"><span class="string">locked &lt;0x0db193bf&gt; (a java.lang.Object)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696)</span></span><br><span class="line"><span class="string">at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:690)</span></span><br><span class="line"><span class="string">at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)</span></span><br><span class="line"><span class="string">at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)</span></span><br><span class="line"><span class="string">locked &lt;0x045a4a8c&gt; (a com.xxxx.video.common.data.DataBaseHelper)</span></span><br><span class="line"><span class="string">at com.xxxx.video.common.data.DataBaseORM.&lt;init&gt;(DataBaseORM.java:46)</span></span><br><span class="line"><span class="string">at com.xxxx.video.common.data.DataBaseORM.getInstance(DataBaseORM.java:53)</span></span><br><span class="line"><span class="string">locked &lt;0x017095d5&gt; (a java.lang.Class&lt;com.xxxx.video.common.data.DataBaseORM&gt;)</span></span><br></pre></td></tr></table></figure><h2 id="Binder-数据量过大"><a href="#Binder-数据量过大" class="headerlink" title="Binder 数据量过大"></a>Binder 数据量过大</h2><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">07</span><span class="number">-2104</span>:<span class="number">43</span>:<span class="number">21.573</span> <span class="number">1000</span> <span class="number">148812756</span>E Binder  : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$<span class="number">1</span>@<span class="number">770</span><span class="function">c74f <span class="title">calling1size388568</span><span class="params">(data:<span class="number">1</span>,<span class="number">32</span>,<span class="number">7274595</span>)</span></span></span><br><span class="line"><span class="function">07-2104:<span class="number">43</span>:<span class="number">21.573</span> <span class="number">1000</span> <span class="number">148812756</span>E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$<span class="number">1</span>@<span class="number">770</span>c74f calling1size388568(data:<span class="number">1</span>,<span class="number">32</span>,<span class="number">7274595</span>)</span></span><br><span class="line"><span class="function"><span class="number">07</span><span class="number">-2104</span>:<span class="number">43</span>:<span class="number">21.607</span> <span class="number">1000</span> <span class="number">1488</span> <span class="number">2951</span>E Binder  : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$<span class="number">1</span>@<span class="number">770</span>c74f calling1size211848(data:<span class="number">1</span>,<span class="number">23</span>,<span class="number">7274595</span>)</span></span><br><span class="line"><span class="function"><span class="number">07</span><span class="number">-2104</span>:<span class="number">43</span>:<span class="number">21.607</span> <span class="number">1000</span> <span class="number">1488</span> <span class="number">2951</span>E Binder  : android.util.Log$TerribleFailure: Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$<span class="number">1</span>@<span class="number">770</span>c74f calling1size211848(data:<span class="number">1</span>,<span class="number">23</span>,<span class="number">7274595</span>)</span></span><br><span class="line"><span class="function"><span class="number">07</span><span class="number">-2104</span>:<span class="number">43</span>:<span class="number">21.662</span> <span class="number">1000</span> <span class="number">1488</span> <span class="number">6258</span>E Binder  : Unreasonably large binder replybuffer: on android.content.pm.BaseParceledListSlice$<span class="number">1</span>@<span class="number">770</span>c74f calling1size259300(data:<span class="number">1</span>,<span class="number">33</span>,<span class="number">7274595</span>)</span></span><br></pre></td></tr></table></figure><h2 id="Binder-通信失败"><a href="#Binder-通信失败" class="headerlink" title="Binder 通信失败"></a>Binder 通信失败</h2><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">07</span><span class="number">-2106</span>:<span class="number">04</span>:<span class="number">35.580</span>&lt;<span class="number">6</span>&gt;[<span class="number">32837.690321</span>] binder:<span class="number">1698</span>:<span class="number">2362</span>transaction failed29189/<span class="number">-3</span>,size100<span class="number">-0l</span>ine3042</span><br><span class="line"><span class="number">07</span><span class="number">-2106</span>:<span class="number">04</span>:<span class="number">35.594</span>&lt;<span class="number">6</span>&gt;[<span class="number">32837.704042</span>] binder:<span class="number">1765</span>:<span class="number">4071</span>transaction failed29189/<span class="number">-3</span>,size76<span class="number">-0l</span>ine3042</span><br><span class="line"><span class="number">07</span><span class="number">-2106</span>:<span class="number">04</span>:<span class="number">35.899</span>&lt;<span class="number">6</span>&gt;[<span class="number">32838.009132</span>] binder:<span class="number">1765</span>:<span class="number">4067</span>transaction failed29189/<span class="number">-3</span>,size224<span class="number">-8l</span>ine3042</span><br><span class="line"><span class="number">07</span><span class="number">-2106</span>:<span class="number">04</span>:<span class="number">36.018</span>&lt;<span class="number">6</span>&gt;[<span class="number">32838.128903</span>] binder:<span class="number">1765</span>:<span class="number">2397</span>transaction failed29189/<span class="number">-22</span>,size348<span class="number">-0l</span>ine2916</span><br></pre></td></tr></table></figure><h1 id="ANR-相关资料分享"><a href="#ANR-相关资料分享" class="headerlink" title="ANR 相关资料分享"></a>ANR 相关资料分享</h1><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487203&idx=1&sn=182584b69910c843ae95f60e74127249&chksm=e9d0c501dea74c178e16f95a2ffc5007c5dbca89a02d56895ed9b05883cf0562da689ac6146b&token=2044639920&lang=zh_CN&scene=21#wechat_redirect">西瓜视频稳定性治理体系建设一：Tailor 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s/RF3m9_v5bYTYbwY-d1RloQ">西瓜视频稳定性治理体系建设二：Raphael 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489902&idx=1&sn=bfdf9f48dc6dc973722b5dcab9cd5882&chksm=e9d0d28cdea75b9ad255eb5de227240d2e6f0e9d66e562d3f49cf69f8ed4127c9954ef21bb6d&scene=178&cur_album_id=1833937688379310087#rd">西瓜视频稳定性治理体系建设三：Sliver 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489949&idx=1&sn=01948c047c0ce203956a3cf81dd20e83&chksm=e9d0d27fdea75b697e70a665b4c6912a8081649700766cf007a7b75d420a57089fe06d2e85b0&scene=178&cur_album_id=1833937688379310087#rd">西瓜卡顿 &amp; ANR 优化治理及监控体系建设</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488116&idx=1&sn=fdf80fa52c57a3360ad1999da2a9656b&chksm=e9d0d996dea750807aadc62d7ed442948ad197607afb9409dd5a296b16fb3d5243f9224b5763&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 设计原理及影响因素</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488182&idx=1&sn=6337f1b51d487057b162064c3e24c439&chksm=e9d0d954dea75042193ed09f30eb8ba0acd93870227c5d33b33361b739a03562afb685df9215&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 监控工具与分析思路</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488243&idx=1&sn=1f948e0ef616c6dfe54513a2a94357be&chksm=e9d0d911dea75007f36b3701b51842b9fa40969fe8175c2cb4aecf96793504602c574945d636&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列分享 - 实例剖析集锦</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488314&idx=1&sn=559e52288ae2730a580fcd550f22d895&chksm=e9d0d8d8dea751ceecb715d472796f0c678a9358abf91eb279cdb0576329595e87531e221438&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - Barrier 导致主线程假死</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488558&idx=1&sn=27dda3c3630116d37ab56a8c7bdf1382&chksm=e9d0dfccdea756daed46b340fb8021b57ea8cc300e58bdb59f0305f8290704984308a089bf2d&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待</a></li><li><a href="https://mp.weixin.qq.com/s/40T6ITvJNWR8F42530k4DA">Android ANR|原理解析及常见案例</a></li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="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#1-%E6%A6%82%E8%A7%88</a></li><li><a href="https://duanqz.github.io/2015-10-12-ANR-Analysis">https://duanqz.github.io/2015-10-12-ANR-Analysis</a></li><li><a href="http://gityuan.com/2016/12/02/app-not-response/">http://gityuan.com/2016/12/02/app-not-response/</a></li><li><a href="http://gityuan.com/2017/01/01/input-anr/">http://gityuan.com/2017/01/01/input-anr/</a></li><li><a href="https://xiaozhuanlan.com/topic/5097486132">https://xiaozhuanlan.com/topic/5097486132</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文为 Android App ANR 系列的第三篇，主要分享几个 ANR 的案例，系列文章目录如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/&quot;&gt;Android App ANR 系列 1 ：理解 Android ANR 设计思想&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/&quot;&gt;Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/&quot;&gt;Android App ANR 系列 3 ：ANR 案例分享&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android ANR 系列 2 ：ANR 分析套路和关键 Log 介绍</title>
    <link href="https://androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/"/>
    <id>https://androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/</id>
    <published>2025-02-08T08:29:09.000Z</published>
    <updated>2026-02-07T05:17:47.821Z</updated>
    
    <content type="html"><![CDATA[<p>本文为 Android App ANR 系列的第二篇，主要分享 ANR 分析套路和关键 Log 介绍，系列文章目录如下</p><ol><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/">Android App ANR 系列 1 ：理解 Android ANR 设计思想</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/">Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/">Android App ANR 系列 3 ：ANR 案例分享</a></li></ol><span id="more"></span><blockquote><p>ANR(Application Not Responding)，应用程序无响应，简单一个定义，却涵盖了很多 Android 系统的设计思想</p><p>首先，ANR 属于应用程序的范畴。这不同于 SNR(System Not Respoding)，SNR 反映的问题是系统进程(system_server)失去了响应能力，而 ANR 明确将问题圈定在应用程序。SNR 由 Watchdog 机制保证，具体可以查阅 Watchdog 机制以及问题分析; ANR 由消息处理机制保证，Android 在系统层实现了一套精密的机制来发现 ANR，核心原理是消息调度和超时处理</p><p>其次，ANR 机制主体实现在系统层。所有与 ANR 相关的消息，都会经过系统进程(system_server)调度，然后派发到应用进程完成对消息的实际处理，同时，系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当，超时限制就起作用了，它收集一些系统状态，譬如 CPU&#x2F;IO 使用情况、进程函数调用栈，并且报告用户有进程无响应了(ANR 对话框，部分 Rom 不显示 ANR 对话框，而是直接闪退到主界面)</p><p>然后，ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制，要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入)， 如果处理超时，则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作，譬如密集 CPU 运算、大量 IO、复杂界面布局等，都会降低应用程序的响应能力</p><p>最后，部分 ANR 问题是很难分析的。有时候由于系统底层的一些影响，导致消息调度失败，出现问题的场景又难以复现。 这类 ANR 问题往往需要花费大量的时间去了解系统的一些行为，超出了 ANR 机制本身的范畴。有一些 ANR 问题很难调查清楚，因为整个系统不稳定的因素很多，例如 Linux Kernel 本身的 Bug 引起的内存碎片过多、硬件损坏等。这类比较底层的原因引起的 ANR 问题往往无从查起，并且这根本不是应用程序的问题，浪费了应用开发人员很多时间，如果你从事过整个系统的开发和维护工作的话会深有体会。所以我不能保证了解了本章的所有内容后能够解决一切 ANR 问题，如果出现了很疑难的 ANR 问题，我建议最好去和做 Framework、驱动和内核的朋友聊聊，或者，如果问题只是个十万分之一的偶然现象，不影响程序的正常运行，我倒是建议不去理它</p><p>– From <a href="https://duanqz.github.io/2015-10-12-ANR-Analysis">duanqz</a></p></blockquote><h1 id="ANR-分析套路"><a href="#ANR-分析套路" class="headerlink" title="ANR 分析套路"></a>ANR 分析套路</h1><p>ANR 问题主要有两种原因：<strong>应用自身的问题</strong> 和 <strong>系统异常导致的问题</strong>。在分析 ANR 问题的时候，最主要的就是要确定是哪个原因导致的（当然也有一些中间地带，比如代码写的不好，在正常情况下不会暴露，在系统出问题的时候很快就暴露出来）。</p><h1 id="ANR-问题一般的分析步骤如下："><a href="#ANR-问题一般的分析步骤如下：" class="headerlink" title="ANR 问题一般的分析步骤如下："></a>ANR 问题一般的分析步骤如下：</h1><ol><li>EventLog 看具体的 ANR 时间（搜索 am_anr），看看是否跟 ANR log 能够对上，以确定 ANR Log 是否有效，如果 ANR Log 有效，分析 ANR Log，提取有用信息：pid、tid、死锁等,遇到 ANR 问题，摆在我们面前的 trace 是不是第一案发现场，如果 ANR 发生的输出的信息很多，当时的 CPU 和 I&#x2F;O 资源比较紧张，那么这段日志输出的时间点可能会延迟 10 秒到 20 秒都有可能，所以我们有时候需要提高警惕。不过正常情况下，EventLog 中的 am_anr 的输出时间是最早的，也是最接近 ANR 时间的。</li><li>看 MainLog(Android Log) 或者 SystemLog 查看 ANR 详细信息(搜索 ANR in)，提取有效的信息。<ol><li>发生 ANR 的时间</li><li>打印 ANR 日志的进程</li><li>发生 ANR 的进程</li><li>发生 ANR 的原因</li><li>CPU 负载</li><li>Memory 负载</li><li>CPU 使用统计时间段</li><li>各进程的 CPU 使用率<ol><li>总的 CPU 使用率</li><li>缺页次数 fault<ol><li>xxx minor 表示高速缓存中的缺页次数，可以理解为进程在做内存访问</li><li>xxx major 表示内存的缺页次数，可以理解为进程在做 IO 操作</li></ol></li></ol></li><li>CPU 使用汇总</li></ol></li><li>配合 MainLog(Android Log) 和 EventLog 把 CPU 开始和结束的时间点内的所有有用信息提取出来到一个文件中.<ol><li>收集关键操作，比如解锁、安装应用、亮灭屏、应用启动等</li><li>收集异常和系统关键 Log<ol><li>系统变慢 ：比如 Slow operation、Slow dispatch、Slow delivery、dvm_lock_sample</li><li>进程变化 ：am_kill、am_proc_died、lowmemorykiller、ANR、应用启动关系等</li><li>系统信息 ：cpu info、meminfo、binder info（是否满了） 、iowait (是否过高)</li></ol></li><li>收集 ANR 进程的所有关键线程的运行情况、线程优先级等</li><li>根据第四步提取出来的关键信息文件，进一步理出系统当时的情况、状态（（推荐 vscode 或者 notepad ++ ，有 线索就全局搜索）），比如<ol><li>是处于低内存频繁杀进程？</li><li>重启第一次解锁系统繁忙</li><li>还是短时间内多个应用启动系统繁忙</li><li>还是应用自己的逻辑等待？</li></ol></li></ol></li><li>不行就加 Log 复现.</li></ol><h2 id="区分是应用的问题还是系统的问题"><a href="#区分是应用的问题还是系统的问题" class="headerlink" title="区分是应用的问题还是系统的问题"></a>区分是应用的问题还是系统的问题</h2><h3 id="首先应该分析是否是应用的问题"><a href="#首先应该分析是否是应用的问题" class="headerlink" title="首先应该分析是否是应用的问题"></a>首先应该分析是否是应用的问题</h3><p>分析应用的问题的关键是需要理清当时用户的操作是什么，应用在用户这个操作的过程中扮演了什么角色，然后再进行进一步的分析</p><ol><li>分析应用是否关键组件中的生命周期中有耗时操作，可能平时没有暴露出来，一旦系统负载上来，就会暴露问题（建议在关键生命周期函数中加上对应的 Log，方便 Debug）。</li><li>分析是否出现极端情况，导致应用的逻辑耗时，比如大量的数据处理或者导入，同时运行线程过多等（看应用的 cpu \ io 使用情况）。</li><li>分析是否存在死锁。</li><li>分析是否是在等待 binder 返回。</li><li>分析 Trace 文件中 MainThread 和 RenderThread 是否存在异常。</li><li>分析 Trace 文件中 MainThread 跟 WorkerThread 是否存在等待关系。</li></ol><h3 id="分析系统的状态"><a href="#分析系统的状态" class="headerlink" title="分析系统的状态"></a>分析系统的状态</h3><ol><li>查看 CPU 使用情况(cpu 使用率和 cpu 负载)，看看 SystemServer、lowmemorykiller、HeapTaskDeamon、Audio、SurfaceFlinger 这些系统相关的进程或者线程是否占用高</li><li>查看是否存在大量 IO 的情况，看 io 负载<ol><li>faults: 118172 minor(高速缓存的缺页次数)。</li><li>major(内存的缺页次数)。</li></ol></li><li>查看系统是否是低内存<ol><li>看 dumpsys meminfo 的结果，看看是否处于低内存。</li><li>kernel log 是否有频繁的 lowmemorykiller。</li><li>event log 是否有频繁的应用被系统低内存策略杀掉。</li><li>kswapd0 。</li></ol></li><li>应用是否被冻结：应用处于 D 状态，发生 ANR，如果最后的操作是 refriger，那么是应用被冻结了，正常情况下是功耗优化引起的，可以找一下前后是否有 xxxHansManager : unfreeze 这样的 Log；或者在 Systrace 中的 Kernel Callstack 显示 ：{kernel callsite when blocked:: “__refrigerator+0xe4&#x2F;0x198”}。</li><li>查看是否存在系统异常，比如自研功能导致系统繁忙，没有及时响应应用 Binder 之类的，这种需要分析 Log 中 SystemServer 的日志输出，查看是否有异常的 Log 输出。</li></ol><h2 id="继续分析：应用可以解决还是系统可以解决"><a href="#继续分析：应用可以解决还是系统可以解决" class="headerlink" title="继续分析：应用可以解决还是系统可以解决"></a>继续分析：应用可以解决还是系统可以解决</h2><ol><li>ANR 问题如果转给系统，大概率是无解的。</li><li>如果应用的代码是正常的，也没有极端的使用场景和数据处理，纯粹是由于系统或者其他的 App 造成的，那么可以转给系统处理。</li><li>如果应用代码本身有一定的问题，在非极端场景或者非系统异常的不会暴露出来，那么需要应用这边想一想规避方案。</li></ol><h1 id="ANR-Log-分析"><a href="#ANR-Log-分析" class="headerlink" title="ANR Log 分析"></a>ANR Log 分析</h1><p>Log 可由测试使用 Log 工具抓取，可由线上平台获取，也可以自己抓取：</p><figure class="highlight elm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">adb</span> bugre<span class="keyword">port</span> log.zip1</span><br></pre></td></tr></table></figure><p>ANR Trace Log 所包含的内容如下</p><h2 id="线程详细数据"><a href="#线程详细数据" class="headerlink" title="线程详细数据"></a>线程详细数据</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span> prio=<span class="number">5</span> tid=<span class="number">1</span> Native</span><br><span class="line">  | group=<span class="string">&quot;main&quot;</span> sCount=<span class="number">1</span> dsCount=<span class="number">0</span> flags=<span class="number">1</span> obj=<span class="number">0x72c8bbf8</span> self=<span class="number">0xb400007b0ec10800</span></span><br><span class="line">  | sysTid=<span class="number">5991</span> nice=-<span class="number">10</span> cgrp=<span class="keyword">default</span> sched=<span class="number">0</span>/<span class="number">0</span> handle=<span class="number">0x7b95f61500</span></span><br><span class="line">  | state=S schedstat=( <span class="number">807053249</span> <span class="number">267562324</span> <span class="number">1494</span> ) utm=<span class="number">63</span> stm=<span class="number">17</span> core=<span class="number">3</span> HZ=<span class="number">100</span></span><br><span class="line">  | stack=<span class="number">0x7fcccd9000</span>-<span class="number">0x7fcccdb000</span> stackSize=8192KB</span><br><span class="line">  | held mutexes=</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">00</span> pc 00000000000c6418  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+<span class="number">8</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">01</span> pc 0000000000019a9c  /system/lib64/libutils.so (android::Looper::pollInner(<span class="type">int</span>)+<span class="number">184</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">02</span> pc 000000000001997c  /system/lib64/libutils.so (android::Looper::pollOnce(<span class="type">int</span>, <span class="type">int</span>*, <span class="type">int</span>*, <span class="keyword">void</span>**)+<span class="number">112</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">03</span> pc <span class="number">0000000000114310</span>  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, <span class="type">long</span>, <span class="type">int</span>)+<span class="number">44</span>)</span><br><span class="line">  at android.os.MessageQueue.nativePollOnce(Native method)</span><br><span class="line">  at android.os.MessageQueue.next(MessageQueue.java:<span class="number">339</span>)</span><br><span class="line">  at android.os.Looper.loop(Looper.java:<span class="number">198</span>)</span><br><span class="line">  at android.app.ActivityThread.main(ActivityThread.java:<span class="number">8142</span>)</span><br><span class="line">  at java.lang.reflect.Method.invoke(Native method)</span><br><span class="line">  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="number">592</span>)</span><br><span class="line">  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="number">1006</span>)</span><br></pre></td></tr></table></figure><p>上面 Trace 中 部分字段的含义</p><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/894ce64d-1586-459b-92cd-45f5999bd666.webp"></p><h2 id="线程堆栈"><a href="#线程堆栈" class="headerlink" title="线程堆栈"></a><strong>线程堆栈</strong></h2><p>下面是该线程对应的调用堆栈</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">native</span>: #<span class="number">00</span> pc 00000000000c6418 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+<span class="number">8</span>)</span><br><span class="line"><span class="keyword">native</span>: #<span class="number">01</span> pc 0000000000019a9c /system/lib64/libutils.so (android::Looper::pollInner(<span class="type">int</span>)+<span class="number">184</span>)</span><br><span class="line"><span class="keyword">native</span>: #<span class="number">02</span> pc 000000000001997c /system/lib64/libutils.so (android::Looper::pollOnce(<span class="type">int</span>, <span class="type">int</span>*, <span class="type">int</span>*, <span class="keyword">void</span>**)+<span class="number">112</span>)</span><br><span class="line"><span class="keyword">native</span>: #<span class="number">03</span> pc <span class="number">0000000000114310</span> /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, <span class="type">long</span>, <span class="type">int</span>)+<span class="number">44</span>)</span><br><span class="line">at android.os.MessageQueue.nativePollOnce(Native method)</span><br><span class="line">at android.os.MessageQueue.next(MessageQueue.java:<span class="number">339</span>)</span><br><span class="line">at android.os.Looper.loop(Looper.java:<span class="number">198</span>)</span><br><span class="line">at android.app.ActivityThread.main(ActivityThread.java:<span class="number">8142</span>)</span><br><span class="line">at java.lang.reflect.Method.invoke(Native method)</span><br><span class="line">at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="number">592</span>)</span><br><span class="line">at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="number">1006</span>)</span><br></pre></td></tr></table></figure><p>分为 Java 和 Native，一般来说，nativePollOnce 是在等待 Message，这是正常的</p><h2 id="线程状态"><a href="#线程状态" class="headerlink" title="线程状态"></a>线程状态</h2><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/dde07d81-cefd-4478-9704-c81b900bb54d.webp"></p><p>Thread.java 中的状态和 Thread.cpp 中的状态是有对应关系的。可以看到前者更加概括，也比较容易理解，面向 Java 的使用者；而后者更详细，面向虚拟机内部的环境。traces.txt 中显示的线程状态都是 Thread.cpp 中定义的,完整的对应关系如下</p><p>art&#x2F;runtime&#x2F;thread_state.h</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// State stored in our C++ class Thread.</span></span><br><span class="line"><span class="comment">// When we refer to &quot;a suspended state&quot;, or when function names mention &quot;ToSuspended&quot; or</span></span><br><span class="line"><span class="comment">// &quot;FromSuspended&quot;, we mean any state other than kRunnable, i.e. any state in which the thread is</span></span><br><span class="line"><span class="comment">// guaranteed not to access the Java heap. The kSuspended state is merely one of these.</span></span><br><span class="line"><span class="keyword">enum</span> <span class="title class_">ThreadState</span> &#123;</span><br><span class="line">  <span class="comment">//                                   Java</span></span><br><span class="line">  <span class="comment">//                                   Thread.State   JDWP state</span></span><br><span class="line">  kTerminated = <span class="number">66</span>,                 <span class="comment">// TERMINATED     TS_ZOMBIE    Thread.run has returned, but Thread* still around</span></span><br><span class="line">  kRunnable,                        <span class="comment">// RUNNABLE       TS_RUNNING   runnable</span></span><br><span class="line">  kTimedWaiting,                    <span class="comment">// TIMED_WAITING  TS_WAIT      in Object.wait() with a timeout</span></span><br><span class="line">  kSleeping,                        <span class="comment">// TIMED_WAITING  TS_SLEEPING  in Thread.sleep()</span></span><br><span class="line">  kBlocked,                         <span class="comment">// BLOCKED        TS_MONITOR   blocked on a monitor</span></span><br><span class="line">  kWaiting,                         <span class="comment">// WAITING        TS_WAIT      in Object.wait()</span></span><br><span class="line">  kWaitingForLockInflation,         <span class="comment">// WAITING        TS_WAIT      blocked inflating a thin-lock</span></span><br><span class="line">  kWaitingForTaskProcessor,         <span class="comment">// WAITING        TS_WAIT      blocked waiting for taskProcessor</span></span><br><span class="line">  kWaitingForGcToComplete,          <span class="comment">// WAITING        TS_WAIT      blocked waiting for GC</span></span><br><span class="line">  kWaitingForCheckPointsToRun,      <span class="comment">// WAITING        TS_WAIT      GC waiting for checkpoints to run</span></span><br><span class="line">  kWaitingPerformingGc,             <span class="comment">// WAITING        TS_WAIT      performing GC</span></span><br><span class="line">  kWaitingForDebuggerSend,          <span class="comment">// WAITING        TS_WAIT      blocked waiting for events to be sent</span></span><br><span class="line">  kWaitingForDebuggerToAttach,      <span class="comment">// WAITING        TS_WAIT      blocked waiting for debugger to attach</span></span><br><span class="line">  kWaitingInMainDebuggerLoop,       <span class="comment">// WAITING        TS_WAIT      blocking/reading/processing debugger events</span></span><br><span class="line">  kWaitingForDebuggerSuspension,    <span class="comment">// WAITING        TS_WAIT      waiting for debugger suspend all</span></span><br><span class="line">  kWaitingForJniOnLoad,             <span class="comment">// WAITING        TS_WAIT      waiting for execution of dlopen and JNI on load code</span></span><br><span class="line">  kWaitingForSignalCatcherOutput,   <span class="comment">// WAITING        TS_WAIT      waiting for signal catcher IO to complete</span></span><br><span class="line">  kWaitingInMainSignalCatcherLoop,  <span class="comment">// WAITING        TS_WAIT      blocking/reading/processing signals</span></span><br><span class="line">  kWaitingForDeoptimization,        <span class="comment">// WAITING        TS_WAIT      waiting for deoptimization suspend all</span></span><br><span class="line">  kWaitingForMethodTracingStart,    <span class="comment">// WAITING        TS_WAIT      waiting for method tracing to start</span></span><br><span class="line">  kWaitingForVisitObjects,          <span class="comment">// WAITING        TS_WAIT      waiting for visiting objects</span></span><br><span class="line">  kWaitingForGetObjectsAllocated,   <span class="comment">// WAITING        TS_WAIT      waiting for getting the number of allocated objects</span></span><br><span class="line">  kWaitingWeakGcRootRead,           <span class="comment">// WAITING        TS_WAIT      waiting on the GC to read a weak root</span></span><br><span class="line">  kWaitingForGcThreadFlip,          <span class="comment">// WAITING        TS_WAIT      waiting on the GC thread flip (CC collector) to finish</span></span><br><span class="line">  kNativeForAbort,                  <span class="comment">// WAITING        TS_WAIT      checking other threads are not run on abort.</span></span><br><span class="line">  kStarting,                        <span class="comment">// NEW            TS_WAIT      native thread started, not yet ready to run managed code</span></span><br><span class="line">  kNative,                          <span class="comment">// RUNNABLE       TS_RUNNING   running in a JNI native method</span></span><br><span class="line">  kSuspended,                       <span class="comment">// RUNNABLE       TS_RUNNING   suspended by GC or debugger</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>Trace 文件中，线程名的最后一行标识的就是当前线程的状态</p><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/4878f3bb-ac76-4804-928f-894ab90cfb1b.webp"></p><h3 id="正常主线程-Trace"><a href="#正常主线程-Trace" class="headerlink" title="正常主线程 Trace"></a>正常主线程 Trace</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span> prio=<span class="number">5</span> tid=<span class="number">1</span> Native</span><br><span class="line">  | group=<span class="string">&quot;main&quot;</span> sCount=<span class="number">1</span> dsCount=<span class="number">0</span> flags=<span class="number">1</span> obj=<span class="number">0x72c8bbf8</span> self=<span class="number">0xb400007b0ec10800</span></span><br><span class="line">  | sysTid=<span class="number">5991</span> nice=-<span class="number">10</span> cgrp=<span class="keyword">default</span> sched=<span class="number">0</span>/<span class="number">0</span> handle=<span class="number">0x7b95f61500</span></span><br><span class="line">  | state=S schedstat=( <span class="number">807053249</span> <span class="number">267562324</span> <span class="number">1494</span> ) utm=<span class="number">63</span> stm=<span class="number">17</span> core=<span class="number">3</span> HZ=<span class="number">100</span></span><br><span class="line">  | stack=<span class="number">0x7fcccd9000</span>-<span class="number">0x7fcccdb000</span> stackSize=8192KB</span><br><span class="line">  | held mutexes=</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">00</span> pc 00000000000c6418  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+<span class="number">8</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">01</span> pc 0000000000019a9c  /system/lib64/libutils.so (android::Looper::pollInner(<span class="type">int</span>)+<span class="number">184</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">02</span> pc 000000000001997c  /system/lib64/libutils.so (android::Looper::pollOnce(<span class="type">int</span>, <span class="type">int</span>*, <span class="type">int</span>*, <span class="keyword">void</span>**)+<span class="number">112</span>)</span><br><span class="line">  <span class="keyword">native</span>: #<span class="number">03</span> pc <span class="number">0000000000114310</span>  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, <span class="type">long</span>, <span class="type">int</span>)+<span class="number">44</span>)</span><br><span class="line">  at android.os.MessageQueue.nativePollOnce(Native method)</span><br><span class="line">  at android.os.MessageQueue.next(MessageQueue.java:<span class="number">339</span>)</span><br><span class="line">  at android.os.Looper.loop(Looper.java:<span class="number">198</span>)</span><br><span class="line">  at android.app.ActivityThread.main(ActivityThread.java:<span class="number">8142</span>)</span><br><span class="line">  at java.lang.reflect.Method.invoke(Native method)</span><br><span class="line">  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="number">592</span>)</span><br><span class="line">  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="number">1006</span>)</span><br></pre></td></tr></table></figure><h3 id="异常主线程-Trace"><a href="#异常主线程-Trace" class="headerlink" title="异常主线程 Trace"></a>异常主线程 Trace</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span> prio=<span class="number">5</span> tid=<span class="number">1</span> Blocked</span><br><span class="line">  | group=<span class="string">&quot;main&quot;</span> sCount=<span class="number">1</span> dsCount=<span class="number">0</span> flags=<span class="number">1</span> obj=<span class="number">0x70f65400</span> self=<span class="number">0xe28dae00</span></span><br><span class="line">  | sysTid=<span class="number">22002</span> nice=-<span class="number">10</span> cgrp=<span class="keyword">default</span> sched=<span class="number">0</span>/<span class="number">0</span> handle=<span class="number">0xe9674474</span></span><br><span class="line">  | state=S schedstat=( <span class="number">1943159901</span> <span class="number">290647362</span> <span class="number">1661</span> ) utm=<span class="number">159</span> stm=<span class="number">34</span> core=<span class="number">7</span> HZ=<span class="number">100</span></span><br><span class="line">  | stack=<span class="number">0xff041000</span>-<span class="number">0xff043000</span> stackSize=8192KB</span><br><span class="line">  | held mutexes=</span><br><span class="line">  at com.facebook.cache.disk.DiskStorageCache.e(DiskStorageCache.java:<span class="number">3</span>)</span><br><span class="line">  - waiting to lock &lt;<span class="number">0x0e57c91f</span>&gt; (a java.lang.Object) held by thread <span class="number">89</span></span><br><span class="line">  at com.xxx.community.util.imageloader.FrescoImageLoader.a(FrescoImageLoader.java:<span class="number">18</span>)</span><br><span class="line">  at com.xxx.community.util.imageloader.FrescoImageLoader$<span class="number">2</span>$<span class="number">1.</span>run(FrescoImageLoader.java:<span class="number">2</span>)</span><br><span class="line">  at android.os.Handler.handleCallback(Handler.java:<span class="number">938</span>)</span><br><span class="line">  at android.os.Handler.dispatchMessage(Handler.java:<span class="number">99</span>)</span><br><span class="line">  at android.os.Looper.loop(Looper.java:<span class="number">254</span>)</span><br><span class="line">  at android.app.ActivityThread.main(ActivityThread.java:<span class="number">8142</span>)</span><br><span class="line">  at java.lang.reflect.Method.invoke(Native method)</span><br><span class="line">  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:<span class="number">592</span>)</span><br><span class="line">  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:<span class="number">1006</span>)</span><br></pre></td></tr></table></figure><h2 id="CPU-使用率分析"><a href="#CPU-使用率分析" class="headerlink" title="CPU 使用率分析"></a>CPU 使用率分析</h2><p>搜索 ANR in 关键字可以看到 ANR 前一段时间内的 CPU 使用情况，其解析如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager: ANR in com.xxx.launcher (com.xxx.launcher/.Launcher)(进程名)</span><br><span class="line">ActivityManager: PID: <span class="number">5991</span>(进程 pid)</span><br><span class="line">ActivityManager: Reason: Input dispatching timed <span class="title function_">out</span> <span class="params">(6a6083a com.xxx.launcher/com.xxx.launcher.Launcher (server)</span> is not responding. Waited 5001ms <span class="keyword">for</span> <span class="title function_">FocusEvent</span><span class="params">(hasFocus=<span class="literal">true</span>)</span>)(原因)</span><br><span class="line">ActivityManager: Parent: com.xxx.launcher/.Launcher</span><br><span class="line">ActivityManager: Load: <span class="number">15.29</span> / <span class="number">5.19</span> / <span class="number">1.87</span>(Load 表明是 <span class="number">1</span> 分钟,<span class="number">5</span> 分钟,<span class="number">15</span> 分钟 CPU 的负载)</span><br><span class="line">ActivityManager: ----- Output from /proc/pressure/memory -----(内存压力)</span><br><span class="line">ActivityManager: somavg10=<span class="number">1.35</span> avg60=<span class="number">0.31</span> avg300=<span class="number">0.06</span> total=<span class="number">346727</span></span><br><span class="line">ActivityManager: full avg10=<span class="number">0.00</span> avg60=<span class="number">0.00</span> avg300=<span class="number">0.00</span> total=<span class="number">34803</span></span><br><span class="line">ActivityManager: ----- End output from /proc/pressure/memory -----</span><br><span class="line"></span><br><span class="line"><span class="comment">// 13s 内的 cpu 使用情况</span></span><br><span class="line">ActivityManager: CPU usage from 0ms to 13135ms <span class="title function_">later</span> <span class="params">(<span class="number">2020</span>-09-09 <span class="number">02</span>:09:<span class="number">54.942</span> to <span class="number">2020</span>-09-09 <span class="number">02</span>:<span class="number">10</span>:<span class="number">08.077</span>)</span>:</span><br><span class="line">ActivityManager:   <span class="number">191</span>%(CPU 的使用率) <span class="number">1948</span>/system_server: <span class="number">72</span>%(用户态的使用率) user + <span class="number">119</span>%(内核态的使用率) kernel / faults: <span class="number">78816</span> minor <span class="number">9</span> major</span><br><span class="line">ActivityManager:     <span class="number">10</span>% <span class="number">2218</span>/android.bg: <span class="number">3.6</span>% user + <span class="number">6.6</span>% kernel</span><br><span class="line">ActivityManager:   <span class="number">30</span>% <span class="number">5991</span>/com.xxx.launcher: <span class="number">23</span>% user + <span class="number">6.4</span>% kernel / faults: <span class="number">118172</span> minor(高速缓存的缺页次数) <span class="number">2</span> major(内存的缺页次数)</span><br><span class="line">ActivityManager:     <span class="number">16</span>% <span class="number">6174</span>/launcher-loader: <span class="number">13</span>% user + <span class="number">2.8</span>% kernel</span><br><span class="line">ActivityManager:     <span class="number">3.9</span>% <span class="number">5991</span>/m.xxx.launcher: <span class="number">3.1</span>% user + <span class="number">0.8</span>% kernel</span><br><span class="line">ActivityManager:   <span class="number">20</span>% <span class="number">6549</span>/com.xxx.xxx: <span class="number">16</span>% user + <span class="number">3.7</span>% kernel / faults: <span class="number">3541</span> minor</span><br><span class="line">ActivityManager:     <span class="number">10</span>% <span class="number">6889</span>/DBCacheManager: <span class="number">8.7</span>% user + <span class="number">1.2</span>% kernel</span><br><span class="line">ActivityManager:     <span class="number">9.4</span>% <span class="number">6942</span>/DefaultDispatch: <span class="number">7.1</span>% user + <span class="number">2.2</span>% kernel</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1s 内的 各个进程各个线程的 cpu 使用情况</span></span><br><span class="line">ActivityManager: CPU usage from 246ms to 1271ms <span class="title function_">later</span> <span class="params">(<span class="number">2020</span>-09-09 <span class="number">02</span>:09:<span class="number">55.188</span> to <span class="number">2020</span>-09-09 <span class="number">02</span>:09:<span class="number">56.213</span>)</span>:</span><br><span class="line">ActivityManager:   <span class="number">290</span>% <span class="number">1948</span>/system_server: <span class="number">114</span>% user + <span class="number">176</span>% kernel / faults: <span class="number">9353</span> minor</span><br><span class="line">ActivityManager:     <span class="number">32</span>% <span class="number">5159</span>/LockSettingsSer: <span class="number">29</span>% user + <span class="number">2.9</span>% kernel</span><br><span class="line">ActivityManager:     <span class="number">25</span>% <span class="number">8661</span>/AnrConsumer: <span class="number">8.8</span>% user + <span class="number">16</span>% kernel</span><br><span class="line">ActivityManager:   <span class="number">44</span>% <span class="number">5991</span>/com.xxx.launcher: <span class="number">37</span>% user + <span class="number">7.4</span>% kernel / faults: <span class="number">5756</span> minor</span><br><span class="line">ActivityManager:     <span class="number">16</span>% <span class="number">6174</span>/launcher-loader: <span class="number">13</span>% user + <span class="number">3.7</span>% kernel</span><br><span class="line">ActivityManager:     <span class="number">14</span>% <span class="number">5991</span>/m.xxx.launcher: <span class="number">14</span>% user + <span class="number">0</span>% kernel</span><br><span class="line">ActivityManager:   <span class="number">37</span>% <span class="number">6549</span>/com.xxx.xxx: <span class="number">28</span>% user + <span class="number">9.3</span>% kernel / faults: <span class="number">153</span> minor</span><br><span class="line">ActivityManager:     <span class="number">37</span>% <span class="number">6942</span>/DefaultDispatch: <span class="number">28</span>% user + <span class="number">9.3</span>% kernel</span><br><span class="line">ActivityManager:   <span class="number">20</span>% <span class="number">5962</span>/com.android.phone: <span class="number">14</span>% user + <span class="number">5.5</span>% kernel / faults: <span class="number">1345</span> minor</span><br><span class="line">ActivityManager:     <span class="number">11</span>% <span class="number">5962</span>/m.android.phone: <span class="number">7.4</span>% user + <span class="number">3.7</span>% kernel</span><br></pre></td></tr></table></figure><h2 id="CPU-负载"><a href="#CPU-负载" class="headerlink" title="CPU 负载"></a>CPU 负载</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager: Load: <span class="number">15.29</span> / <span class="number">5.19</span> / <span class="number">1.871</span></span><br></pre></td></tr></table></figure><p>Load 后面的三个数字的意思分别是 1 分钟、5 分钟、15 分钟内系统的平均负荷。当 CPU 完全空闲的时候，平均负荷为 0；当 CPU 工作量饱和的时候，平均负荷为 1，通过 Load 可以判断系统负荷是否过重</p><p>有一个形象的比喻：个&#x3D; CPU 想象成一座大桥，桥上只有一根车道，所有车辆都必须从这根车道上通过，系统负荷为 0，意味着大桥上一辆车也没有，系统负荷为 0.5，意味着大桥一半的路段有车，系统负荷为 1.0，意味着大桥的所有路段都有车，也就是说大桥已经”满”了，系统负荷为 2.0，意味着车辆太多了，大桥已经被占满了 (100%），后面等着上桥的车辆还有一倍。大桥的通行能力，就是 CPU 的最大工作量；桥梁上的车辆，就是一个个等待 CPU 处理的进程（process）</p><p>经验法则是这样的</p><ol><li>1.当系统负荷持续大于 0.7，你必须开始调查了，问题出在哪里，防止情况恶化</li><li>2.当系统负荷持续大于 1.0，你必须动手寻找解决办法，把这个值降下来</li><li>3.当系统负荷达到 5.0，就表明你的系统有很严重的问题</li></ol><p>现在的手机是多核 CPU 架构，八核的多的是，意味着 Cpu 处理的能力就乘以了８，每个核运行的时间可以从下面的文件中得到，&#x2F;sys&#x2F;devices&#x2F;system&#x2F;cpu&#x2F;cpu%d&#x2F;cpufreq&#x2F;stats&#x2F;time_in_state 中读取的，%d 代表是 CPU 的核。文件中记录了 CPU 从开机到读取文件时，在各个频率下的运行时间，单位：10 ms</p><h2 id="Memory-负载"><a href="#Memory-负载" class="headerlink" title="Memory 负载"></a>Memory 负载</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager: ----- Output from /proc/pressure/memory -----(内存压力)</span><br><span class="line">ActivityManager: somavg10=<span class="number">1.35</span> avg60=<span class="number">0.31</span> avg300=<span class="number">0.06</span> total=<span class="number">346727</span></span><br><span class="line">ActivityManager: full avg10=<span class="number">0.00</span> avg60=<span class="number">0.00</span> avg300=<span class="number">0.00</span> total=<span class="number">34803</span></span><br><span class="line">ActivityManager: ----- End output from /proc/pressure/memory -----</span><br></pre></td></tr></table></figure><p>Memory 负载是从 &#x2F;proc&#x2F;pressure&#x2F;memory 中获取的</p><p>avg10、avg60、avg300 分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间，以毫秒为单位</p><p>some 这一行，代表至少有一个任务在某个资源上阻塞的时间占比，full 这一行，代表所有的非 idle 任务同时被阻塞的时间占比，这期间 cpu 被完全浪费，会带来严重的性能问题。我们以 IO 的 some 和 full 来举例说明，假设在 60 秒的时间段内，系统有两个 task，在 60 秒的周期内的运行情况如下图所示：</p><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/ce7a3897-9d73-43e1-8b55-d7fb11727489.webp"></p><p>红色阴影部分表示任务由于等待 IO 资源而进入阻塞状态。Task A 和 Task B 同时阻塞的部分为 full，占比 16.66%；至少有一个任务阻塞（仅 Task B 阻塞的部分也计算入内）的部分为 some，占比 50%。</p><p>some 和 full 都是在某一时间段内阻塞时间占比的总和，阻塞时间不一定连续，如下图所示：</p><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/a62e6bd8-8672-4cb7-8815-ff740d0eaec9.webp"></p><p>具体内容可以参考内核工匠的文章： <a href="https://blog.csdn.net/feelabclihu/article/details/105534140">https://blog.csdn.net/feelabclihu/article/details/105534140</a></p><h2 id="IO-负载"><a href="#IO-负载" class="headerlink" title="IO 负载"></a>IO 负载</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager:   <span class="number">30</span>% <span class="number">5991</span>/com.xxx.launcher: <span class="number">23</span>% user + <span class="number">6.4</span>% kernel / faults: <span class="number">118172</span> minor(高速缓存的缺页次数) <span class="number">2</span> major(内存的缺页次数)</span><br></pre></td></tr></table></figure><ol><li>Minor，高速缓存的缺页次数，指的是 Minor Page Fault(次要页错误，简称 MnPF)，磁盘数据被加载到内存后，内核再次读取时，会发出一个 MnPF 信息。 一个文件第一次被读写时会有很多的 MPF，被缓存到内存后再次访问 MPF 就会很少，MnPF 反而变多，这是内核为减少效率低下的磁盘 I&#x2F;O 操作采用的缓存技术的结果可以理解为进程在做内存访问</li><li>Major，内存的缺页次数，指 Major Page Fault(主要页错误，简称 MPF)，内核在读取数据时会先后查找 CPU 的高速缓存和物理内存，如果找不到会发出一个 MPF 信息，请求将数据加载到内存可以理解为进程在做 IO 操作</li></ol><p>如果有大量的 major，那么说明当时 IO 操作负载比较高</p><h2 id="进程负载"><a href="#进程负载" class="headerlink" title="进程负载"></a>进程负载</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager:   <span class="number">30</span>% <span class="number">5991</span>/com.xxx.launcher: <span class="number">23</span>% user + <span class="number">6.4</span>% kernel / faults: <span class="number">118172</span> minor(高速缓存的缺页次数) <span class="number">2</span> major(内存的缺页次数)</span><br></pre></td></tr></table></figure><ol><li>1.23% user：用户态的 cpu 占用</li><li>2.6.4% kernel：内核态的 cpu 占用</li></ol><h2 id="CPU-异常进程"><a href="#CPU-异常进程" class="headerlink" title="CPU 异常进程"></a>CPU 异常进程</h2><ol><li>SystemServer cpu 占用偏高，系统整体运行会缓慢</li><li>kswapd0 cpu 占用率偏高，系统整体运行会缓慢，从而引起各种 ANR。把问题转给”内存优化”，请他们进行优化</li><li>logd 　 CPU 占用率偏高，也会引起系统卡顿和 ANR，因为各个进程输出 LOG 的操作被阻塞从而执行的极为缓慢。</li><li>Vold 占用 CPU 过高，会引起系统卡顿和 ANR，请负责存储的同学先调查</li><li>应用自身 CPU 占用率较高，高概率应用自身问题</li><li>应用处于 D 状态，发生 ANR，如果最后的操作是 refriger，那么是应用被冻结了，正常情况下是功耗优化引起的，可以找一下前后是否有 xxxHansManager : unfreeze 这样的 Log；或者在 Systrace 中的 Kernel Callstack 显示 ：{kernel callsite when blocked:: “__refrigerator+0xe4&#x2F;0x198”}</li><li>CPU 使用率前面的 “+”。部分进程的 CPU 使用率前面有 “+” 号，譬如 cat 和 zygote64，表示在上一次 CPU 统计的时间片段内，还没有这些进程，而这一次 CPU 统计的时间片段内，运行了这些进程。 类似的还有 “-” 号，表示两次 CPU 统计时间片段时，这些进程消亡了</li></ol><h1 id="系统关键-Log-介绍"><a href="#系统关键-Log-介绍" class="headerlink" title="系统关键 Log 介绍"></a>系统关键 Log 介绍</h1><h2 id="应用冻结"><a href="#应用冻结" class="headerlink" title="应用冻结"></a>应用冻结</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xxxHansManager : freeze uid: <span class="number">10245</span> <span class="keyword">package</span>: com.tencent.mm reason: LcdOff</span><br></pre></td></tr></table></figure><p>如果冻结逻辑有 Bug，也会导致应用产生 ANR，这一行 Log 比较简单，主要是被冻结的进程信息和被冻结的原因</p><h2 id="ActivityManager-Slow-operation"><a href="#ActivityManager-Slow-operation" class="headerlink" title="ActivityManager : Slow operation"></a>ActivityManager : Slow operation</h2><p>AMS 在执行关键任务的时候，如果任务耗时超过 50 ms，则会打印对应的 Log</p><p>frameworks&#x2F;base&#x2F;services&#x2F;core&#x2F;java&#x2F;com&#x2F;android&#x2F;server&#x2F;am&#x2F;ActivityManagerService.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">checkTime</span><span class="params">(<span class="type">long</span> startTime, String where)</span> &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">    <span class="keyword">if</span> ((now-startTime) &gt; <span class="number">50</span>) &#123;</span><br><span class="line">        <span class="comment">// If we are taking more than 50ms, log about it.</span></span><br><span class="line">        Slog.w(TAG, <span class="string">&quot;Slow operation: &quot;</span> + (now-startTime) + <span class="string">&quot;ms so far, now at &quot;</span> + where);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对应的 Log 如下，如果系统中频繁打印这种 Log，说明系统目前处于一个比较卡的状态，分析的时候就得考虑到系统的因素</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ActivityManager: Slow operation: 138ms so far, now at startProcess: done updating battery stats</span><br><span class="line">ActivityManager: Slow operation: 138ms so far, now at startProcess: building log message</span><br><span class="line">ActivityManager: Slow operation: 138ms so far, now at startProcess: starting to update pids map</span><br></pre></td></tr></table></figure><h2 id="Looper-Slow-dispatch"><a href="#Looper-Slow-dispatch" class="headerlink" title="Looper : Slow dispatch"></a>Looper : Slow dispatch</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Looper  : Slow dispatch took 418ms main h=android.app.ActivityThread$H c=android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA<span class="meta">@e68bdc4</span> m=<span class="number">0</span></span><br></pre></td></tr></table></figure><h2 id="Looper-Slow-delivery"><a href="#Looper-Slow-delivery" class="headerlink" title="Looper : Slow delivery"></a>Looper : Slow delivery</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Looper  : Slow delivery took 361ms android.ui h=com.android.server.am.ActivityManagerService$UiHandler c=<span class="literal">null</span> m=<span class="number">53</span></span><br></pre></td></tr></table></figure><h2 id="Looper：Slow-Looper"><a href="#Looper：Slow-Looper" class="headerlink" title="Looper：Slow Looper"></a>Looper：Slow Looper</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">W/Looper: Slow Looper main: Activity com.androidperformance.memoryfix/.MainActivity is 439ms <span class="title function_">late</span> <span class="params">(wall=0ms running=0ms ClientTransaction&#123; callbacks=[android.app.servertransaction.TopResumedActivityChangeItem] &#125;)</span> because of <span class="number">3</span> msg, msg <span class="number">2</span> took 268ms (seq=<span class="number">2</span> running=207ms runnable=15ms late=1ms h=android.app.ActivityThread$H w=<span class="number">110</span>), msg <span class="number">3</span> took 171ms (seq=<span class="number">3</span> running=140ms runnable=5ms io=1ms late=268ms h=android.app.ActivityThread$H w=<span class="number">159</span>)</span><br></pre></td></tr></table></figure><h2 id="onTrimMemory"><a href="#onTrimMemory" class="headerlink" title="onTrimMemory"></a>onTrimMemory</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ClockTag AlarmClockApplication: onTrimMemory: <span class="number">80</span></span><br></pre></td></tr></table></figure><h2 id="dvm-lock-sample"><a href="#dvm-lock-sample" class="headerlink" title="dvm_lock_sample"></a>dvm_lock_sample</h2><p>当某个线程等待 lock 的时间 blocked 超过阈值（比如：500ms）,则输出当前的持锁状态.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dvm_lock_sample：[system_server,<span class="number">1</span>,Binder_9,<span class="number">1500</span>,ActivityManagerService.java,<span class="number">6403</span>,-,<span class="number">1448</span>,<span class="number">0</span>]</span><br></pre></td></tr></table></figure><ol><li>system_server: Binder_9 执行到 ActivityManagerService.java 的 6403 行代码,一直在等待 AMS 锁</li><li>“-“代表持锁的是同一个文件，即该锁被同一文件的 1448 行代码所持有, 从而导致 Binder_9 线程被阻塞 1500ms.</li></ol><h2 id="binder-sample"><a href="#binder-sample" class="headerlink" title="binder_sample"></a>binder_sample</h2><p>binder_sample： 监控每个进程的主线程的 binder transaction 的耗时情况, 当超过阈值（比如：500ms）时,则输出相应的目标调用信息.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">6628</span>  <span class="number">6628</span> I binder_sample: [android.view.accessibility.IAccessibilityManager,<span class="number">6</span>,<span class="number">2010</span>,com.xxx.community,<span class="number">100</span>]</span><br></pre></td></tr></table></figure><ol><li>进程是 6628，主线程 6628</li><li>执行 android.view.accessibility.IAccessibilityManager 接口</li><li>所对应方法 code &#x3D; 6 ( 即 TRANSACTION_addAccessibilityInteractionConnection ）</li><li>所花费时间为 2010 ms</li><li>该 block 所在 package 为 com.xxx.community</li><li>最后一个参数是 sample 比例</li></ol><p>查找对应的接口函数，比如上面例子里面 IAccessibilityManager 中 Code &#x3D; 6 是对应的哪个函数，可以在 cs.android.com 里面 搜索 FIRST_CALL_TRANSACTION ,点击调用，然后查看 out 目录里面的对应的 IAccessibilityManager 文件（一直往下滑，直到可以搜索到 IAccessibilityManager ）</p><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/53e8ee44-b1f7-4729-83e8-4ccb4f784a6a.webp"></p><p>其中对应的 static final int TRANSACTION_addAccessibilityInteractionConnection &#x3D; (android.os.IBinder.FIRST_CALL_TRANSACTION + 5)</p><h2 id="Long-monitor-contention"><a href="#Long-monitor-contention" class="headerlink" title="Long monitor contention"></a>Long monitor contention</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">16809</span> <span class="number">14216</span> W system_server: Long monitor contention with owner <span class="title function_">InputDispatcher</span> <span class="params">(<span class="number">17039</span>)</span> at android.content.res.Configuration com.android.server.wm.ActivityTaskManagerService.getGlobalConfigurationForPid(<span class="type">int</span>)(ActivityTaskManagerService.java:<span class="number">1066</span>) waiters=<span class="number">0</span> in <span class="type">boolean</span> com.android.server.wm.WindowProcessController.hasActivities() <span class="keyword">for</span> 141ms</span><br></pre></td></tr></table></figure><p>art&#x2F;runtime&#x2F;monitor.cc</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">std::string Monitor::PrettyContentionInfo(const std::string&amp; owner_name,</span><br><span class="line">                                          pid_t owner_tid,</span><br><span class="line">                                          ArtMethod* owners_method,</span><br><span class="line">                                          uint32_t owners_dex_pc,</span><br><span class="line">                                          size_t num_waiters) &#123;</span><br><span class="line">  Locks::mutator_lock_-&gt;AssertSharedHeld(Thread::Current());</span><br><span class="line">  const <span class="type">char</span>* owners_filename;</span><br><span class="line">  <span class="type">int32_t</span> <span class="variable">owners_line_number</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">if</span> (owners_method != nullptr) &#123;</span><br><span class="line">    TranslateLocation(owners_method, owners_dex_pc, &amp;owners_filename, &amp;owners_line_number);</span><br><span class="line">  &#125;</span><br><span class="line">  std::ostringstream oss;</span><br><span class="line">  oss &lt;&lt; <span class="string">&quot;monitor contention with owner &quot;</span> &lt;&lt; owner_name &lt;&lt; <span class="string">&quot; (&quot;</span> &lt;&lt; owner_tid &lt;&lt; <span class="string">&quot;)&quot;</span>;</span><br><span class="line">  <span class="keyword">if</span> (owners_method != nullptr) &#123;</span><br><span class="line">    oss &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; owners_method-&gt;PrettyMethod();</span><br><span class="line">    oss &lt;&lt; <span class="string">&quot;(&quot;</span> &lt;&lt; owners_filename &lt;&lt; <span class="string">&quot;:&quot;</span> &lt;&lt; owners_line_number &lt;&lt; <span class="string">&quot;)&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  oss &lt;&lt; <span class="string">&quot; waiters=&quot;</span> &lt;&lt; num_waiters;</span><br><span class="line">  <span class="keyword">return</span> oss.str();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Binder-Thread"><a href="#Binder-Thread" class="headerlink" title="Binder Thread"></a>Binder Thread</h2><p>当 system_server 等进程的线程池使用完, 无空闲线程时, 则 binder 通信都处于饥饿状态, 则饥饿状态超过一定阈值则输出信息.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">E IPCThreadState: binder thread <span class="title function_">pool</span> <span class="params">(<span class="number">31</span> threads)</span> starved <span class="keyword">for</span> <span class="number">120</span> ms</span><br></pre></td></tr></table></figure><h2 id="am-kill"><a href="#am-kill" class="headerlink" title="am_kill"></a>am_kill</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">am_kill : [<span class="number">0</span>,<span class="number">18555</span>,com.sina.weibo.image,<span class="number">945</span>,remove task]<span class="number">1</span></span><br></pre></td></tr></table></figure><p>上面这句话里面，remove task 指的是这个应用被杀的原因</p><p>下面是各种被杀的情况所对应的 Reason，有时候我们需要看应用被杀的原因，来判断系统是否正常或者用户的操作步骤：</p><h3 id="force-stop"><a href="#force-stop" class="headerlink" title="force-stop"></a>force-stop</h3><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/7cfb4102-f137-426b-84f0-a6a06c58fd47.webp"></p><h3 id="异常杀进程"><a href="#异常杀进程" class="headerlink" title="异常杀进程"></a>异常杀进程</h3><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/e8f54015-0bf1-494c-8262-9a56e797d04d.webp"></p><h3 id="主动杀进程"><a href="#主动杀进程" class="headerlink" title="主动杀进程"></a>主动杀进程</h3><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/8d7d8da3-0171-4be5-921c-79eb33e276d7.webp"></p><h3 id="调度杀进程"><a href="#调度杀进程" class="headerlink" title="调度杀进程"></a>调度杀进程</h3><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/4e5b3f4e-4668-4d92-bb7f-83b90fd0de18.webp"></p><h3 id="其他杀进程"><a href="#其他杀进程" class="headerlink" title="其他杀进程"></a>其他杀进程</h3><p><img src="/../images/Android-ANR-02-How-to-analysis-ANR/933942df-43e8-4b74-8e12-768198a17667.webp"></p><p>以上介绍的所有杀进程都是调用 ProcessRecord.kill()方法, 必然会输出相应的 EventLog.那么还有哪些场景的杀进程不会输出 log 呢:</p><ol><li>Process.killProcess(int pid) &#x2F;&#x2F;可杀任何指定进程,或者直接发 signal</li><li>adb shell kill -9 pid &#x2F;&#x2F;可杀任何指定的进程</li><li>直接 lmk 杀进程</li></ol><p>也就是说进程被杀而无 log 输出,那么可能是通过直接调用 kill 或者发信号, 再或许是 lmk 所杀.</p><h2 id="am-proc-died"><a href="#am-proc-died" class="headerlink" title="am_proc_died"></a>am_proc_died</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">am_proc_died: [<span class="number">0</span>,<span class="number">13210</span>,com.xxx.gallery3d,<span class="number">935</span>,<span class="number">19</span>]</span><br></pre></td></tr></table></figure><h2 id="am-anr"><a href="#am-anr" class="headerlink" title="am_anr"></a>am_anr</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">am_anr : [<span class="number">0</span>,<span class="number">8769</span>,com.android.updater,<span class="number">952680005</span>,Broadcast of Intent &#123; act=android.intent.action.BOOT_COMPLETED flg=<span class="number">0x9000010</span> cmp=com.android.updater/.BootCompletedReceiver (has extras) &#125;]</span><br></pre></td></tr></table></figure><ol><li>进程 pid：8769</li><li>进程名：com.android.updater</li><li>发生 ANR 的类型是：BroadcastTimeout 的具体类或者原因：{ act&#x3D;android.intent.action.BOOT_COMPLETED flg&#x3D;0x9000010 cmp&#x3D;com.android.updater&#x2F;.BootCompletedReceiver (has extras) }</li></ol><h2 id="Lowmemorykiller"><a href="#Lowmemorykiller" class="headerlink" title="Lowmemorykiller"></a>Lowmemorykiller</h2><p>lowmemorykiller 的 log 在 kernel log 中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lowmemorykiller: Kill <span class="string">&#x27;com.heytap.themestore&#x27;</span> (<span class="number">15545</span>), uid <span class="number">10136</span>, oom_adj <span class="number">995</span> to free 105068kB</span><br><span class="line">lowmemorykiller: Reclaimed 105068kB at oom_adj <span class="number">606</span></span><br></pre></td></tr></table></figure><p>通过 lowmemorykiller 机制杀应用：</p><ol><li>com.heytap.themestore 进程名</li><li>15545：PID</li><li>10136：UID</li><li>995：oom_adj</li><li>105068kB： 释放的内存</li><li>606:min_adj</li></ol><h2 id="Blocked-msg"><a href="#Blocked-msg" class="headerlink" title="Blocked msg"></a>Blocked msg</h2><p>内部 加的 Log，当 Message 执行时间超过 1.5s 的时候，就会打印下面的 Log，从下面的 Log 可以看到：</p><ol><li>执行超时的 Message，和这个 Message 执行耗时</li><li>被这个 Message 阻塞的 Message</li></ol><p>可以从这个 Log 里面观察是自己的 Message 执行阻塞了别的 Message 还是自己的 Message 被其他的 Message 阻塞</p><figure class="highlight roboconf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">E ANR_LOG : &gt;&gt;&gt; msg&#x27;s executing time is too long</span><br><span class="line">E ANR_LOG : Blocked msg = &#123; <span class="attribute">when=-32s683ms what=110 target=android.app.ActivityThread$H obj=AppBindData&#123;appInfo=ApplicationInfo&#123;bd8d51e com.android.contacts&#125;&#125; &#125; , cost  = 32436 ms</span></span><br><span class="line"><span class="attribute">E ANR_LOG</span> : &gt;&gt;&gt;Current msg List is:</span><br><span class="line">E ANR_LOG : Current msg &lt;1&gt;  = &#123; when=-32s672ms what=140 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H arg1=5 &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;2&gt;  = &#123; when=-32s671ms what=114 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=CreateServiceData&#123;token=android<span class="variable">.os</span><span class="variable">.BinderProxy</span>@f7611ff className=com<span class="variable">.android</span><span class="variable">.contacts</span><span class="variable">.xxxAppServicesManagerClient</span> packageName=com<span class="variable">.android</span><span class="variable">.contacts</span> intent=null&#125; &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;3&gt;  = &#123; when=-32s671ms what=121 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=BindServiceData&#123;token=android<span class="variable">.os</span><span class="variable">.BinderProxy</span>@f7611ff intent=Intent &#123; cmp=com<span class="variable">.android</span><span class="variable">.contacts</span>/<span class="variable">.xxxAppServicesManagerClient</span> &#125;&#125; &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;4&gt;  = &#123; when=-31s658ms what=1 target=android<span class="variable">.os</span><span class="variable">.AsyncTask</span>$InternalHandler obj=android<span class="variable">.os</span><span class="variable">.AsyncTask</span>$AsyncTaskResult@75e517c &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;5&gt;  = &#123; when=-29s750ms what=140 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H arg1=10 &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;6&gt;  = &#123; when=-29s103ms what=118 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=&#123;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&#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;7&gt;  = &#123; when=-28s370ms what=118 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=&#123;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&#125; &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;8&gt;  = &#123; when=-27s821ms what=122 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=BindServiceData&#123;token=android<span class="variable">.os</span><span class="variable">.BinderProxy</span>@f7611ff intent=Intent &#123; cmp=com<span class="variable">.android</span><span class="variable">.contacts</span>/<span class="variable">.xxxAppServicesManagerClient</span> &#125;&#125; &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;9&gt;  = &#123; when=-27s821ms what=116 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=android<span class="variable">.os</span><span class="variable">.BinderProxy</span>@f7611ff &#125;</span><br><span class="line">E ANR_LOG : Current msg &lt;10&gt;  = &#123; when=-27s654ms what=114 target=android<span class="variable">.app</span><span class="variable">.ActivityThread</span>$H obj=CreateServiceData&#123;token=android<span class="variable">.os</span><span class="variable">.BinderProxy</span>@e23cf1b className=com<span class="variable">.android</span><span class="variable">.contacts</span><span class="variable">.xxxAppServicesManagerClient</span> packageName=com<span class="variable">.android</span><span class="variable">.contacts</span> intent=null&#125; &#125;</span><br><span class="line">E ANR_LOG : &gt;&gt;&gt;CURRENT MSG DUMP OVER&lt;&lt;&lt;</span><br></pre></td></tr></table></figure><h1 id="通用场景-Log-介绍"><a href="#通用场景-Log-介绍" class="headerlink" title="通用场景 Log 介绍"></a>通用场景 Log 介绍</h1><h2 id="灭屏-Log"><a href="#灭屏-Log" class="headerlink" title="灭屏 Log"></a>灭屏 Log</h2><p>关键是 power_screen_broadcast_done、screen_toggled 和 power_screen_state</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1810</span>  <span class="number">2470</span> I intercept_power: [ACTION_DOWN,<span class="number">1</span>,<span class="number">0</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">2470</span> I intercept_power: [ACTION_UP,<span class="number">0</span>,<span class="number">0</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">2470</span> I power_sleep_requested: <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_state: [<span class="number">0</span>,<span class="number">2</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">1254</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I screen_toggled: <span class="number">0</span> <span class="comment">// 0 代表灭屏，1 代表亮屏</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_broadcast_send: <span class="number">1</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">1825</span> I wm_set_keyguard_shown: [<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,setKeyguardShown] <span class="comment">// (keyguardShowing|1),(aodShowing|1),(keyguardGoingAway|1),(Reason|3)</span></span><br><span class="line"></span><br><span class="line"><span class="number">2768</span>  <span class="number">2768</span> I sysui_status_bar_state: [<span class="number">1</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_broadcast_done: [<span class="number">0</span>,<span class="number">611</span>,<span class="number">1</span>] <span class="comment">// 0 代表灭屏，611 是灭屏时间</span></span><br></pre></td></tr></table></figure><h2 id="亮屏-Log"><a href="#亮屏-Log" class="headerlink" title="亮屏 Log"></a>亮屏 Log</h2><p>关键是 power_screen_broadcast_done、screen_toggled 和 power_screen_state</p><p>其中<br>power_screen_state</p><ol><li>1.offOrOn|1|5</li><li>2.becauseOfUser|1|5</li><li>3.totalTouchDownTime|2|3</li><li>4.touchCycles|1|1</li><li>5.latency|1|3</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1810</span>  <span class="number">2470</span> I intercept_power: [ACTION_DOWN,<span class="number">1</span>,<span class="number">0</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">2470</span> I intercept_power: [ACTION_UP,<span class="number">1</span>,<span class="number">0</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I screen_toggled: <span class="number">1</span> <span class="comment">// 1 代表亮屏</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_broadcast_send: <span class="number">1</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_state: [<span class="number">1</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">0</span>,<span class="number">319</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">1810</span> I power_screen_broadcast_done: [<span class="number">1</span>,<span class="number">633</span>,<span class="number">1</span>] <span class="comment">// 1 代表亮屏，633 是亮屏时间</span></span><br></pre></td></tr></table></figure><h2 id="解锁完整-Log"><a href="#解锁完整-Log" class="headerlink" title="解锁完整 Log"></a>解锁完整 Log</h2><p>wm_set_keyguard_shown 的值</p><ol><li>(keyguardShowing|1) ：Keyguard 是否在显示</li><li>(aodShowing|1) ：aodShowing 是否在显示</li><li>(keyguardGoingAway|1) ：Keyguard 消失</li><li>(Reason|3) ：原因</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> <span class="number">1810</span>  <span class="number">5711</span> I wm_set_keyguard_shown: [<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>,keyguardGoingAway]</span><br><span class="line"><span class="comment">// 桌面 Resume</span></span><br><span class="line"> <span class="number">1810</span>  <span class="number">5711</span> I wm_set_resumed_activity: [<span class="number">0</span>,com.xxx.launcher/.Launcher,resumeTopActivityInnerLocked]</span><br><span class="line"> <span class="number">1810</span>  <span class="number">5711</span> I wm_resume_activity: [<span class="number">0</span>,<span class="number">93093883</span>,<span class="number">63</span>,com.xxx.launcher/.Launcher]</span><br><span class="line"><span class="number">20615</span> <span class="number">20615</span> I wm_on_restart_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,performRestartActivity]</span><br><span class="line"><span class="number">20615</span> <span class="number">20615</span> I wm_on_start_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,handleStartActivity]</span><br><span class="line"><span class="number">20615</span> <span class="number">20615</span> I wm_on_resume_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,RESUME_ACTIVITY]</span><br><span class="line"><span class="number">20615</span> <span class="number">20615</span> I wm_on_top_resumed_gained_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,topWhenResuming]</span><br><span class="line"></span><br><span class="line"> <span class="number">1810</span>  <span class="number">5711</span> I wm_set_keyguard_shown: [<span class="number">0</span>,<span class="number">0</span>,<span class="number">1</span>,setKeyguardShown] <span class="comment">// keyguard 不显示，aod 不显示，keyguardGoingAway，原因是调用了 setKeyguardShown 显示桌面</span></span><br></pre></td></tr></table></figure><h2 id="通知栏下拉完整-Log"><a href="#通知栏下拉完整-Log" class="headerlink" title="通知栏下拉完整 Log"></a>通知栏下拉完整 Log</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2768</span>  <span class="number">2768</span> I sysui_multi_action: [<span class="number">757</span>,<span class="number">803</span>,<span class="number">799</span>,panel_open,<span class="number">802</span>,<span class="number">1</span>]</span><br><span class="line"><span class="number">2768</span>  <span class="number">2768</span> I sysui_multi_action: [<span class="number">757</span>,<span class="number">1328</span>,<span class="number">758</span>,<span class="number">4</span>,<span class="number">1326</span>,<span class="number">29</span>,<span class="number">1327</span>,<span class="number">0</span>,<span class="number">1329</span>,<span class="number">0</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3004</span> I notification_visibility: [-<span class="number">1</span>|android|<span class="number">55</span>|<span class="literal">null</span>|<span class="number">1000</span>,<span class="number">1</span>,<span class="number">457483196</span>,<span class="number">457483196</span>,<span class="number">0</span>,<span class="number">4</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3004</span> I notification_visibility: [<span class="number">0</span>|com.android.systemui|<span class="number">10005</span>|<span class="literal">null</span>|<span class="number">10132</span>,<span class="number">1</span>,<span class="number">97168545</span>,<span class="number">97168545</span>,<span class="number">0</span>,<span class="number">2</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3004</span> I notification_visibility: [<span class="number">0</span>|com.android.systemui|<span class="number">2147483647</span>|ranker_group|<span class="number">10132</span>|ranker_group,<span class="number">1</span>,<span class="number">97168236</span>,<span class="number">97168236</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3004</span> I notification_visibility: [-<span class="number">1</span>|com.android.systemui|<span class="number">10006</span>|<span class="literal">null</span>|<span class="number">10132</span>,<span class="number">1</span>,<span class="number">82586063</span>,<span class="number">82586063</span>,<span class="number">0</span>,<span class="number">0</span>]</span><br></pre></td></tr></table></figure><h2 id="通知栏折叠完整-Log"><a href="#通知栏折叠完整-Log" class="headerlink" title="通知栏折叠完整 Log"></a>通知栏折叠完整 Log</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2768</span>  <span class="number">2768</span> I sysui_multi_action: [<span class="number">757</span>,<span class="number">111</span>,<span class="number">758</span>,<span class="number">2</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I notification_visibility: [-<span class="number">1</span>|android|<span class="number">55</span>|<span class="literal">null</span>|<span class="number">1000</span>,<span class="number">0</span>,<span class="number">457546516</span>,<span class="number">457546516</span>,<span class="number">0</span>,<span class="number">4</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I notification_visibility: [<span class="number">0</span>|com.android.systemui|<span class="number">10005</span>|<span class="literal">null</span>|<span class="number">10132</span>,<span class="number">0</span>,<span class="number">97231865</span>,<span class="number">97231865</span>,<span class="number">0</span>,<span class="number">2</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I notification_visibility: [<span class="number">0</span>|com.android.systemui|<span class="number">2147483647</span>|ranker_group|<span class="number">10132</span>|ranker_group,<span class="number">0</span>,<span class="number">97231557</span>,<span class="number">97231557</span>,<span class="number">0</span>,<span class="number">1</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I notification_visibility: [-<span class="number">1</span>|com.android.systemui|<span class="number">10006</span>|<span class="literal">null</span>|<span class="number">10132</span>,<span class="number">0</span>,<span class="number">82649384</span>,<span class="number">82649384</span>,<span class="number">0</span>,<span class="number">0</span>]</span><br></pre></td></tr></table></figure><h2 id="应用启动完整-Log"><a href="#应用启动完整-Log" class="headerlink" title="应用启动完整 Log"></a>应用启动完整 Log</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建 Task</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I wm_task_created: [<span class="number">100</span>,-<span class="number">1</span>]</span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I wm_stack_created: <span class="number">100</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I wm_create_task: [<span class="number">0</span>,<span class="number">100</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建 Activity</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I wm_create_activity: [<span class="number">0</span>,<span class="number">231348670</span>,<span class="number">100</span>,com.androidperformance.memoryfix/.MainActivity,android.intent.action.MAIN,NULL,NULL,<span class="number">270532608</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 桌面走 pause 流程</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">5473</span> I wm_pause_activity: [<span class="number">0</span>,<span class="number">93093883</span>,com.xxx.launcher/.Launcher,userLeaving=<span class="literal">true</span>]</span><br><span class="line"><span class="number">0615</span> <span class="number">20615</span> I wm_on_top_resumed_lost_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,topStateChangedWhenResumed]</span><br><span class="line"><span class="number">0615</span> <span class="number">20615</span> I wm_on_paused_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,performPause]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3720</span> I wm_add_to_stopping: [<span class="number">0</span>,<span class="number">93093883</span>,com.xxx.launcher/.Launcher,makeInvisible]</span><br><span class="line"></span><br><span class="line"><span class="comment">//启动 Activity</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">2045</span> I am_proc_start: [<span class="number">0</span>,<span class="number">18803</span>,<span class="number">10263</span>,com.androidperformance.memoryfix,pre-top-activity,&#123;com.androidperformance.memoryfix/com.androidperformance.memoryfix.MainActivity&#125;]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3428</span> I am_proc_bound: [<span class="number">0</span>,<span class="number">18803</span>,com.androidperformance.memoryfix]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3428</span> I wm_restart_activity: [<span class="number">0</span>,<span class="number">231348670</span>,<span class="number">100</span>,com.androidperformance.memoryfix/.MainActivity]</span><br><span class="line"><span class="number">1810</span>  <span class="number">3428</span> I wm_set_resumed_activity: [<span class="number">0</span>,com.androidperformance.memoryfix/.MainActivity,minimalResumeActivityLocked]</span><br><span class="line"><span class="number">8803</span> <span class="number">18803</span> I wm_on_create_called: [<span class="number">231348670</span>,com.androidperformance.memoryfix.MainActivity,performCreate]</span><br><span class="line"><span class="number">8803</span> <span class="number">18803</span> I wm_on_start_called: [<span class="number">231348670</span>,com.androidperformance.memoryfix.MainActivity,handleStartActivity]</span><br><span class="line"><span class="number">8803</span> <span class="number">18803</span> I wm_on_resume_called: [<span class="number">231348670</span>,com.androidperformance.memoryfix.MainActivity,RESUME_ACTIVITY]</span><br><span class="line"><span class="number">8803</span> <span class="number">18803</span> I wm_on_top_resumed_gained_called: [<span class="number">231348670</span>,com.androidperformance.memoryfix.MainActivity,topStateChangedWhenResumed]</span><br><span class="line"><span class="number">1810</span>  <span class="number">2034</span> I wm_activity_launch_time: [<span class="number">0</span>,<span class="number">231348670</span>,com.androidperformance.memoryfix/.MainActivity,<span class="number">471</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment">// 桌面走 stop 流程</span></span><br><span class="line"><span class="number">1810</span>  <span class="number">1978</span> I wm_stop_activity: [<span class="number">0</span>,<span class="number">93093883</span>,com.xxx.launcher/.Launcher]</span><br><span class="line"><span class="number">0615</span> <span class="number">20615</span> I wm_on_stop_called: [<span class="number">93093883</span>,com.xxx.launcher.Launcher,STOP_ACTIVITY_ITEM]</span><br></pre></td></tr></table></figure><h2 id="Window-Focus-相关的流程"><a href="#Window-Focus-相关的流程" class="headerlink" title="Window Focus 相关的流程"></a>Window Focus 相关的流程</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 从桌面启动 App，focus 变化 ：Launcher =&gt; null =&gt; App</span></span><br><span class="line">WindowManager: Changing focus from Window&#123;b0416d7 u0 com.xxx.launcher/com.xxx.launcher.Launcher&#125; to <span class="literal">null</span>,diplayid=<span class="number">0</span></span><br><span class="line">WindowManager: Changing focus from <span class="literal">null</span> to Window&#123;10f5145 u0 com.android.settings/com.android.settings.Settings&#125;,diplayid=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 从 App 返回桌面，focus 变化 ：App =&gt; null =&gt; Launcher</span></span><br><span class="line">WindowManager: Changing focus from Window&#123;10f5145 u0 com.android.settings/com.android.settings.Settings&#125; to <span class="literal">null</span>,diplayid=<span class="number">0</span></span><br><span class="line">WindowManager: Changing focus from <span class="literal">null</span> to Window&#123;b0416d7 u0 com.xxx.launcher/com.xxx.launcher.Launcher&#125;,diplayid=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 从 App 界面进入锁屏：focus 变化 ：App =&gt; null =&gt; 锁屏</span></span><br><span class="line">WindowManager: Changing focus from Window&#123;10f5145 u0 com.android.settings/com.android.settings.Settings&#125; to <span class="literal">null</span>,diplayid=<span class="number">0</span></span><br><span class="line">WindowManager: Changing focus from <span class="literal">null</span> to Window&#123;82e5f30 u0 NotificationShade&#125;,diplayid=<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 从锁屏界面解锁进入 App，focus 变化 ：锁屏 =&gt; App</span></span><br><span class="line">WindowManager: Changing focus from Window&#123;82e5f30 u0 NotificationShade&#125; to Window&#123;10f5145 u0 com.android.settings/com.android.settings.Settings&#125;,diplayid=<span class="number">0</span></span><br></pre></td></tr></table></figure><h1 id="ANR-相关资料分享"><a href="#ANR-相关资料分享" class="headerlink" title="ANR 相关资料分享"></a>ANR 相关资料分享</h1><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487203&idx=1&sn=182584b69910c843ae95f60e74127249&chksm=e9d0c501dea74c178e16f95a2ffc5007c5dbca89a02d56895ed9b05883cf0562da689ac6146b&token=2044639920&lang=zh_CN&scene=21#wechat_redirect">西瓜视频稳定性治理体系建设一：Tailor 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s/RF3m9_v5bYTYbwY-d1RloQ">西瓜视频稳定性治理体系建设二：Raphael 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489902&idx=1&sn=bfdf9f48dc6dc973722b5dcab9cd5882&chksm=e9d0d28cdea75b9ad255eb5de227240d2e6f0e9d66e562d3f49cf69f8ed4127c9954ef21bb6d&scene=178&cur_album_id=1833937688379310087#rd">西瓜视频稳定性治理体系建设三：Sliver 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489949&idx=1&sn=01948c047c0ce203956a3cf81dd20e83&chksm=e9d0d27fdea75b697e70a665b4c6912a8081649700766cf007a7b75d420a57089fe06d2e85b0&scene=178&cur_album_id=1833937688379310087#rd">西瓜卡顿 &amp; ANR 优化治理及监控体系建设</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488116&idx=1&sn=fdf80fa52c57a3360ad1999da2a9656b&chksm=e9d0d996dea750807aadc62d7ed442948ad197607afb9409dd5a296b16fb3d5243f9224b5763&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 设计原理及影响因素</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488182&idx=1&sn=6337f1b51d487057b162064c3e24c439&chksm=e9d0d954dea75042193ed09f30eb8ba0acd93870227c5d33b33361b739a03562afb685df9215&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 监控工具与分析思路</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488243&idx=1&sn=1f948e0ef616c6dfe54513a2a94357be&chksm=e9d0d911dea75007f36b3701b51842b9fa40969fe8175c2cb4aecf96793504602c574945d636&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列分享 - 实例剖析集锦</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488314&idx=1&sn=559e52288ae2730a580fcd550f22d895&chksm=e9d0d8d8dea751ceecb715d472796f0c678a9358abf91eb279cdb0576329595e87531e221438&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - Barrier 导致主线程假死</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488558&idx=1&sn=27dda3c3630116d37ab56a8c7bdf1382&chksm=e9d0dfccdea756daed46b340fb8021b57ea8cc300e58bdb59f0305f8290704984308a089bf2d&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待</a></li><li><a href="https://mp.weixin.qq.com/s/40T6ITvJNWR8F42530k4DA">Android ANR|原理解析及常见案例</a></li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="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#1-%E6%A6%82%E8%A7%88</a></li><li><a href="https://duanqz.github.io/2015-10-12-ANR-Analysis">https://duanqz.github.io/2015-10-12-ANR-Analysis</a></li><li><a href="http://gityuan.com/2016/12/02/app-not-response/">http://gityuan.com/2016/12/02/app-not-response/</a></li><li><a href="http://gityuan.com/2017/01/01/input-anr/">http://gityuan.com/2017/01/01/input-anr/</a></li><li><a href="https://xiaozhuanlan.com/topic/5097486132">https://xiaozhuanlan.com/topic/5097486132</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文为 Android App ANR 系列的第二篇，主要分享 ANR 分析套路和关键 Log 介绍，系列文章目录如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/&quot;&gt;Android App ANR 系列 1 ：理解 Android ANR 设计思想&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/&quot;&gt;Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/&quot;&gt;Android App ANR 系列 3 ：ANR 案例分享&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android ANR 系列 1 ：理解 Android ANR 设计思想</title>
    <link href="https://androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/"/>
    <id>https://androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/</id>
    <published>2025-02-08T08:28:17.000Z</published>
    <updated>2026-02-07T05:17:47.820Z</updated>
    
    <content type="html"><![CDATA[<p>本文为 Android App ANR 系列的第一篇，主要是从系统的角度来剖析 Android ANR 的设计思想，系列文章目录如下</p><ol><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/">Android App ANR 系列 1 ：理解 Android ANR 设计思想</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/">Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/">Android App ANR 系列 3 ：ANR 案例分享</a></li></ol><span id="more"></span><h1 id="一、ANR-的普遍性与复杂性"><a href="#一、ANR-的普遍性与复杂性" class="headerlink" title="一、ANR 的普遍性与复杂性"></a>一、ANR 的普遍性与复杂性</h1><p>在 Android 生态系统中，应用无响应（<code>ANR</code>，Application Not Responding）不仅是开发者面临的常见挑战，更是系统设计哲学的核心体现。虽然 <code>ANR</code> 常被简化为“主线程耗时操作”的代名词，但这种表面化的理解远远不足以揭示问题的本质。实际上，<code>ANR</code> 的根本原因在于 Android 多进程架构、事件分发和资源调度机制之间复杂的协同作用，其实质是系统层面对应用行为实施严格约束与监控的综合体现。</p><p>Android 明确将 <code>ANR</code> 定性为应用层问题，这与 <code>SNR</code>（System Not Responding）形成了鲜明对比。<code>SNR</code> 指的是系统进程（如 <code>system_server</code>）失去响应能力，通常依靠 Watchdog 机制通过监控关键系统线程状态来实现；而 <code>ANR</code> 则依托于消息调度机制，系统进程利用精心设计的超时模型追踪应用主线程的响应能力。这种区别反映了系统针对不同层级问题所采取的治理策略：</p><ul><li><strong>SNR</strong> 侧重于确保系统核心服务的生存，采用主动轮询的监控方式；</li><li><strong>ANR</strong> 则聚焦于应用进程的实时响应，通过事件驱动的异步检测机制来进行判断。</li></ul><p>从系统架构的角度来看，<code>ANR</code> 机制主要实现于系统层（即 <code>system_server</code> 进程中），其核心在于构建跨进程的事件监控体系。当应用进程通过 <code>Binder</code> 向系统服务发起操作请求（如启动 <code>Activity</code>、处理广播等）时，系统会同步启动超时计时器；而对于输入事件这类异步操作，<code>InputDispatcher</code> 会通过 <code>socket</code> 与窗口建立事件通道，并在事件派发后启动超时检测。这种分层监控设计充分体现了 Android 针对不同任务类型所采用的差异化处理策略。</p><p><code>ANR</code> 机制的深层意义在于平衡开放性与系统可控性。作为一个开放平台，Android 允许应用自由申请硬件资源（如 <code>CPU</code>、<code>IO</code>、<code>内存</code> 等），但必须通过严格的规则防止单个应用的异常行为蔓延至整个系统。当检测到超时事件时，系统会启动多维度熔断机制：首先，通过强制终止问题进程来释放关键系统资源（例如防止其占用 <code>Binder</code> 线程池或文件描述符），从而避免级联故障；同时，系统会冻结进程状态，并采集 <code>CPU</code> 使用率、线程堆栈、内存快照等关键信息，将这些数据写入 <code>/data/anr/traces.txt</code>，为后续问题分析保留现场。更巧妙的是，系统还通过用户可见的弹窗将最终操作权交还给用户，从而既避免了自动化处理可能带来的误判风险，又保持了人机交互的连续性。这种“故障隔离—现场保护—用户决策”相结合的设计，充分展现了 Android 在技术严谨性与用户体验友好性之间的平衡智慧。</p><hr><h1 id="二、ANR-的核心设计哲学"><a href="#二、ANR-的核心设计哲学" class="headerlink" title="二、ANR 的核心设计哲学"></a>二、ANR 的核心设计哲学</h1><h2 id="ANR-的本质：系统级监控与强制干预"><a href="#ANR-的本质：系统级监控与强制干预" class="headerlink" title="ANR 的本质：系统级监控与强制干预"></a>ANR 的本质：系统级监控与强制干预</h2><p>ANR 机制构成了 Android 系统架构中一套深层次的稳定性防御体系，其核心在于通过 <strong>跨层协同监控</strong> 与 <strong>异步决策隔离</strong> 构建一个独立于应用状态的全局安全网。这种设计远非简单的超时检测，而是深深植根于 Linux 进程沙箱机制和 Android 组件架构的有机结合中。系统进程（如 <code>system_server</code>）通过 <code>ActivityManagerService</code> (AMS) 与 <code>InputManagerService</code> (IMS) 这两大核心模块，分别对组件生命周期和输入事件流实施全方位监控。正因这种分层架构，监控逻辑得以与业务逻辑解耦，即使目标应用的主线程完全阻塞，系统依然可以依靠独立线程进行超时裁决，从根本上避免了“监控者反被监控对象拖垮”的风险。</p><p>在实现层面，ANR 充分体现了<strong>事件驱动型系统设计</strong>的精髓。例如，在组件类 ANR 的场景中，当 AMS 通过 <code>Binder</code> 向应用进程派发跨进程任务时，系统会同步启动一个倒计时器（例如针对 <code>Service</code> 启动的 20 秒阈值），这一“埋炸弹”机制实质上将异步任务转化为带有超时约束的同步契约。应用进程在完成任务后必须通过 <code>Binder</code> 回调主动“拆弹”，否则系统将介入收集现场信息（如主线程堆栈）并触发用户交互。整个过程由系统进程主导，应用仅作为事件响应方存在，从而确保监控的绝对权威性。</p><p>任务派发依赖 <code>Binder</code> 的同步调用来确保原子性，同时 AMS 通过专门的 <code>Handler</code> 将超时检测消息推入消息队列，从而在规定时间内监控任务的执行情况。这种设计既保证了跨进程通信期间任务的完整性，也能在超时后迅速触发熔断处理。</p><hr><h2 id="组件类-ANR：异步任务的全局防护逻辑"><a href="#组件类-ANR：异步任务的全局防护逻辑" class="headerlink" title="组件类 ANR：异步任务的全局防护逻辑"></a>组件类 ANR：异步任务的全局防护逻辑</h2><p>组件类 ANR 的监控逻辑围绕 <code>ActivityManagerService</code> (AMS) 展开，其本质是通过 <strong>任务派发–回调–熔断</strong> 的三阶段模型，实现对异步任务生命周期的全链路追踪。当系统通过 <code>Binder</code> 跨进程通信向应用派发任务（例如启动 <code>Service</code>）时，AMS 会同步启动超时检测机制：利用 <code>MainHandler</code> 发送延迟消息实现精确计时。以 <code>Service</code> 启动为例，当 AMS 调用 <code>IApplicationThread</code> 的 <code>scheduleCreateService()</code> 后，会启动对应的超时监控（默认 20 秒）。如果应用在规定时间内未通过 <code>serviceDoneExecuting()</code> 回调通知 AMS，则触发 ANR 判定。</p><p>开发者需要特别注意 <strong>跨进程回调的时序陷阱</strong>：即使异步任务在子线程完成，但若主线程因消息队列堵塞（例如过度调用 <code>runOnUiThread</code>）导致 <code>Binder</code> 回调延迟，系统依然会判定为 ANR。</p><p><strong>Android 14+</strong> 引入的 <code>ProcessStateRecord</code> 对进程状态进行了更细粒度的划分，不仅详细记录了主线程消息处理的状态，还实时监控后台任务和挂起状态，从而降低了误判率，并为开发者提供了更丰富的调试信息。</p><p>这一设计的关键在于 <strong>同步事务与异步熔断的解耦</strong>。任务派发依靠 <code>Binder</code> 的同步调用确保原子性，而超时检测则通过 <code>Handler</code> 消息机制异步执行，避免阻塞系统主线程。</p><p>当 ANR 触发时，系统会执行多维度的熔断策略：</p><ol><li><p><strong>现场采集</strong><br>系统会收集主线程堆栈信息、CPU 使用情况、进程状态等关键数据，并将这些数据写入 <code>/data/anr/traces.txt</code> 文件。同时，系统还会利用 <code>ProcessCpuTracker</code> 记录详细的 CPU 使用统计，为后续问题分析提供依据。</p></li><li><p><strong>资源隔离</strong><br>系统通过 <code>ProcessRecord</code> 的调度优先级调整机制确保熔断决策的实时性，从而保证即使在系统负载较高的情况下，ANR 处理流程也能得到及时执行。</p></li><li><p><strong>诊断数据收集</strong><br>系统提供 <code>ApplicationExitInfo</code> API，允许开发者查询历史 ANR 记录，包括发生时间、进程状态、异常堆栈等详细信息，这些数据对于问题复现和根因分析极为重要。</p></li></ol><p>值得注意的是，<strong>Android 15</strong> 对后台服务施加了更严格的约束：前台服务必须在 3 秒内完成初始化并调用 <code>startForeground()</code>，否则系统将直接触发 ANR。具体而言，系统通过内部属性（例如 <code>persist.sys.fgs_timeout</code>）以及 API 参数（如 AMS 内部控制前台服务启动超时的参数）来管理这一超时机制。开发者可参照最新的 API 文档了解这些变更，从而在设计服务时确保满足严格的响应时限要求。</p><p>系统还提供了多种工具来支持 ANR 问题的诊断和分析：</p><ul><li><strong>系统日志收集</strong>：开发者可通过 <code>adb</code> 命令获取 ANR 堆栈信息和系统报告，其中包含了问题发生时的详细系统状态。</li><li><strong>性能分析工具</strong>：Android Studio 的 CPU Profiler 能够实时监控应用性能，帮助开发者发现潜在的性能问题。</li><li><strong>系统级分析</strong>：Perfetto 提供了强大的系统级性能分析能力，帮助开发者理解复杂的性能问题。</li></ul><p>通过这种多层次的监控和防护机制，Android 系统确保了应用响应性能的可靠性，并为开发者提供了完整的问题诊断工具链。开发者需深入理解这些机制，在应用设计中充分考虑性能因素，遵循系统生命周期契约，合理管理主线程负载，确保关键回调的及时响应。</p><p>这种设计哲学体现了 Android 平台对应用质量的严格要求：通过明确的超时限制和完善的监控机制，推动开发者构建更可靠、响应更及时的应用。同时，丰富的诊断工具也为开发者提供了必要的支持，帮助他们在遇到问题时能快速定位并解决问题。</p><hr><h2 id="Input-类-ANR：输入事件分发的动态熔断体系"><a href="#Input-类-ANR：输入事件分发的动态熔断体系" class="headerlink" title="Input 类 ANR：输入事件分发的动态熔断体系"></a>Input 类 ANR：输入事件分发的动态熔断体系</h2><p>输入类 ANR 的监控机制更为复杂，其核心挑战在于如何在 <strong>高实时性要求</strong> 与 <strong>资源高效利用</strong> 之间取得平衡。从硬件事件产生到应用主线程处理，输入系统通过 <code>EventHub</code>、<code>InputReader</code> 和 <code>InputDispatcher</code> 三大组件协同构建了一条高效且可控的事件分发链路。</p><ul><li><p><strong>事件读取层</strong> (<code>EventHub</code>)<br><code>EventHub</code> 利用 Linux 的 <code>epoll</code> 机制监听 <code>/dev/input</code> 设备节点，支持多设备并发监听，并通过事件驱动模型（而非轮询）实现零空闲 CPU 消耗。当硬件中断触发时，系统通过 inotify 接收原始输入数据，并将其封装为 <code>RawEvent</code>。</p></li><li><p><strong>事件预处理</strong> (<code>InputReader</code>)<br><code>InputReader</code> 通过特定的 <code>InputMapper</code> 对原始数据进行设备相关的预处理（如触摸校准），并将其转换为标准的输入事件（如 <code>MotionEvent</code> 或 <code>KeyEvent</code>）。同时，根据设备类型和配置进行必要的事件过滤，确保数据质量。</p></li><li><p><strong>事件分发层</strong> (<code>InputDispatcher</code>)<br><code>InputDispatcher</code> 的核心职责是确定当前焦点窗口，并通过基于 Unix Domain Socket 的 <code>InputChannel</code> 将事件推送至应用进程。它采用 multiplexing 机制高效管理多个 <code>InputChannel</code>，并依赖 <code>WindowManagerService</code> 获取最新窗口焦点信息，确保事件准确送达目标窗口。</p></li></ul><p>输入 ANR 机制依赖于对事件状态的持续追踪与超时判定，其核心在于 <strong>队列状态管理</strong> 与 <strong>跨线程协作模型</strong> 的设计：</p><ul><li><code>inboundQueue</code>：存储从 <code>InputReader</code> 接收的待分发事件</li><li><code>outboundQueues</code>：为每个连接维护的输出队列，同时通过 <code>waitQueue</code> 跟踪已分发但未收到完成响应的事件</li><li><code>waitQueue</code>：记录已经分发出去但尚未收到应用端处理确认的事件。</li></ul><p>当事件被分发后，系统通过 <code>MonitoredTimeout</code> 机制跟踪其处理状态。默认超时时间为 5000 毫秒（可通过系统属性调整），超时检测采用事件驱动模式，在新事件到达、应用回调完成或周期性心跳检查时触发。一旦检测到超时，系统会通过 <code>WindowManagerService</code> 通知 <code>ActivityManagerService</code>，并收集包括 <code>InputDispatcher</code> 状态及应用进程信息在内的诊断数据，随后可能触发 ANR 弹窗及进程重启流程。</p><p>整个输入系统采用了优化的线程模型设计：</p><ul><li><code>InputReaderThread</code> 专注于事件读取与预处理</li><li><code>InputDispatcherThread</code> 负责事件分发与超时监控</li></ul><p>两者通过无锁队列实现高效的线程间通信，使得即使某个应用进程的主线程阻塞，系统层面的输入处理依然能够正常运行，从而有效防止问题扩散。</p><p>对于开发者而言，应特别关注主线程的响应性，避免在输入事件处理回调中执行耗时操作。同时，理解输入系统的分层设计有助于在性能优化时从整体角度提高事件处理链路的效率。</p><hr><h2 id="No-Focused-Window-类-ANR"><a href="#No-Focused-Window-类-ANR" class="headerlink" title="No Focused Window 类 ANR"></a>No Focused Window 类 ANR</h2><p>No Focused Window ANR 是输入系统中另一类重要的无响应场景，其本质在于窗口焦点状态异常，导致输入事件无法正确分发。与常规输入超时不同，这类 ANR 反映的是 WindowManager 子系统与输入系统之间的协同问题。</p><p>在 <code>WindowManagerService</code> (WMS) 的设计中，窗口焦点管理是一个独立而复杂的子系统。当用户界面发生变化（如 Activity 切换或对话框弹出）时，系统会触发一系列窗口事务操作：首先对旧窗口执行 <code>relayoutWindow</code> 以解除焦点标记，然后为新窗口执行 <code>addWindow</code> 并授予焦点。这些状态变化会通过 <code>WindowManagerPolicy</code> 实时同步至 <code>InputDispatcher</code>，确保输入事件能够路由到当前的焦点窗口。</p><p>焦点的获得与丢失由多种系统行为触发。例如：</p><ul><li><strong>焦点获得</strong>：新 Activity 启动完成并显示第一帧、<code>Dialog</code> 或 <code>PopupWindow</code> 弹出、分屏模式下触碰窗口区域、从后台任务切换器中恢复应用、解锁后前台应用恢复。</li><li><strong>焦点丢失</strong>：Activity 被全屏 Activity 覆盖、用户按下 <code>Home</code> 键、系统弹出权限请求等关键级别 <code>Dialog</code>、应用进入后台、设备锁屏等情况。</li></ul><p>No Focused Window ANR 往往与窗口生命周期管理异常有关。最常见的情况是在 Activity 切换过程中，由于目标 Activity 的 <code>handleResumeActivity</code> 执行延迟，系统在一定时间内无法确定合法的焦点窗口。与输入超时 ANR 不同，输入超时是目标窗口存在但未及时处理事件，而 No Focused Window ANR 则是系统无法找到合适的事件接收者。基于这一区别，系统对这两类情况采取不同的防护策略：对于输入超时，系统会在默认 5 秒后触发 ANR；而对于无焦点窗口情况，如果连续多次事件分发找不到目标窗口，系统会更快地启动 ANR 流程。</p><p>从应用开发角度来看，影响焦点切换的代码路径较为有限，主要涉及 Activity 生命周期回调、窗口添加&#x2F;移除以及输入事件处理等环节。即使这些环节出现问题（如主线程阻塞），通常也会触发常规 ANR，而非 No Focused Window ANR。因此，在遇到此类问题时，更应关注系统整体资源使用状态、 <code>system_server</code> 进程的 CPU 负载以及系统服务间 <code>Binder</code> 调用延迟等系统级指标，而不是单纯聚焦于某个应用的代码优化。这也是为什么 No Focused Window ANR 常被视为系统性能问题而非应用质量问题的根本原因。</p><hr><h2 id="系统设计的统一性原则"><a href="#系统设计的统一性原则" class="headerlink" title="系统设计的统一性原则"></a>系统设计的统一性原则</h2><p>无论是组件类 ANR 还是 Input 类 ANR，其监控机制均遵循以下核心原则：</p><ol><li><strong>状态可追踪性</strong><br>通过队列（如 <code>waitQueue</code>）和定时器（例如 AMS 中的 <code>Handler</code>）精确追踪任务进度，确保系统始终掌握应用行为的最新状态。</li><li><strong>故障隔离性</strong><br>在超时后迅速终止问题进程，防止局部故障扩散成系统级雪崩。</li><li><strong>用户控制权兜底</strong><br>通过弹窗提示与进程终止机制，确保用户始终拥有最终的操作权，即使应用内部已完全失控。</li><li><strong>开发者约束性</strong><br>强制要求主线程保持轻量与异步设计，促使应用架构更贴合系统设计哲学。</li></ol><p>从架构角度来看，ANR 机制是 Android 系统对 <strong>开放生态可控性</strong> 的最终回答——它既允许开发者自由创新，又通过刚性规则划定行为边界。这种平衡不仅体现在技术实现上，也深刻影响了整个 Android 应用的性能优化文化。</p><h1 id="ANR-问题的全局解析与主动防御"><a href="#ANR-问题的全局解析与主动防御" class="headerlink" title="ANR 问题的全局解析与主动防御"></a>ANR 问题的全局解析与主动防御</h1><p>ANR 问题的复杂性要求我们在分析框架中同时具备 <strong>技术纵深感</strong> 与 <strong>系统全局观</strong>，并通过递进逻辑将碎片化的现象转化为一个可演进的认知体系。这种方法不仅仅是简单的目录分层，而是利用多维视角的交叉验证，建立从微观代码缺陷到宏观系统约束的完整映射关系。</p><hr><h2 id="从现象到根源：逐层解剖-ANR-问题"><a href="#从现象到根源：逐层解剖-ANR-问题" class="headerlink" title="从现象到根源：逐层解剖 ANR 问题"></a>从现象到根源：逐层解剖 ANR 问题</h2><p>构建纵向分析路径遵循 <strong>“现象 → 机制 → 支撑 → 资源”</strong> 的链条式逻辑，其目标在于厘清从用户看到的 ANR 弹窗到硬件资源问题之间的完整链条：</p><ol><li><p><strong>机制表象（ANR 弹窗）</strong><br>作为用户可见的最外层现象，ANR 弹窗实际上是系统对故障的最终裁决——它并不揭示具体根因，而只是展示结果。开发者往往仅停留在查看堆栈日志、寻找主线程阻塞点的层面，但这就如同只观察火山喷发而忽略了地壳运动的根本驱动因素。</p></li><li><p><strong>系统实现（AMS&#x2F;InputDispatcher）</strong><br>深入系统服务层，ANR 弹窗背后隐藏着 AMS 的 <code>appNotResponding</code> 触发流程。AMS 通过 <code>Binder</code> 事务状态机追踪组件的生命周期，而 <code>InputDispatcher</code> 则利用 <code>socket</code> 事件流监控输入响应。此层分析揭示了 <strong>超时判定逻辑的差异性</strong>：AMS 采用同步阻塞式检测（例如 <code>BroadcastQueue</code> 的超时计算），而 <code>InputDispatcher</code> 则利用基于 <code>epoll</code> 的异步非阻塞模型实现事件循环监控。</p></li><li><p><strong>底层支撑（Binder&#x2F;调度器）</strong><br>系统服务的高效运行依赖于 Linux 内核的核心机制。<code>Binder</code> 驱动通过内存映射实现跨进程通信，其线程池调度策略（例如 <code>BINDER_MAX_POOL_THREADS</code> 阈值限制）直接影响事务处理能力；而系统的公平调度机制则通过动态分配 CPU 时间片决定主线程是否能够及时获得执行资源。此层的关键在于解析资源分配公平性与实时性之间的矛盾——例如，为了保障多任务的公平性，系统可能允许后台进程的 CPU 密集型任务抢占前台应用的响应时间。</p></li><li><p><strong>硬件资源（CPU&#x2F;IO&#x2F;Memory）</strong><br>最终，所有软件行为都受限于物理硬件。CPU 的乱序执行可能导致锁竞争问题的随机性；磁盘 I&#x2F;O 延迟会放大主线程在 <code>SharedPreferences</code> 写入时的阻塞；内存带宽争抢则可能使 <code>RenderThread</code> 无法及时获取纹理数据。此层要求建立 <strong>硬件指标与软件行为的关联模型</strong>，例如利用 <code>perf</code> 工具分析 CPU 缓存命中率与 ANR 触发频率的相关性。</p></li></ol><p>这种纵向深入并非线性递进，而是一个 <strong>循环验证</strong> 的过程：当硬件层分析发现内存带宽瓶颈时，需要回溯到 <code>Binder</code> 驱动层，检查是否因频繁跨进程通信引发内存拷贝风暴，最终在系统服务层进行数据传输机制的优化。</p><hr><h2 id="从被动应对到主动防御：ANR-治理的三步走"><a href="#从被动应对到主动防御：ANR-治理的三步走" class="headerlink" title="从被动应对到主动防御：ANR 治理的三步走"></a>从被动应对到主动防御：ANR 治理的三步走</h2><p>方法论的演进路径——<strong>“诊断 → 追踪 → 预测 → 设计”</strong>，反映了技术认知成熟度的跃迁，具体步骤包括：</p><ol><li><p><strong>堆栈分析</strong><br>传统 ANR 分析依赖于 <code>traces.txt</code> 中的线程堆栈，这本质上是故障发生时的静态快照。当问题由偶发竞争条件（如 <code>Binder</code> 线程池瞬时饱和）引起时，堆栈可能显示为正常的 <code>NativePollOnce</code> 状态，而无法揭示真实的资源争抢过程。此时，需要引入 <strong>多时间点堆栈对比技术</strong>，通过比较 ANR 前后 5 秒内的堆栈变化，识别线程状态迁移模式。</p></li><li><p><strong>动态追踪</strong><br>利用 <code>systrace</code> 和 <code>perfetto</code> 等工具提供的毫秒级事件追踪能力，可以监控主线程 <code>Looper</code> 的事件处理周期，量化 <code>dispatchMessage</code> 的执行耗时；同时结合 <code>Binder</code> 驱动中的 <code>binder_transaction</code> 事件，可以绘制跨进程调用的热力图。动态追踪的核心价值在于 <strong>揭示隐藏的时间相关性</strong>，例如发现输入事件延迟往往紧随 <code>SharedPreferences</code> 磁盘写入操作出现。</p></li><li><p><strong>机器学习预测</strong><br>当 ANR 的根因涉及多个子系统交互（如 CPU 调度、内存回收和 I&#x2F;O 负载的耦合效应）时，传统方法难以处理高维数据。通过收集线程状态、<code>Binder</code> 交互数据以及 CPU 争夺情况等 20 多项指标，利用机器学习算法建立分析模型，可以自动识别 ANR 类型（例如主线程阻塞、IPC 死锁或资源竞争）。Google 已在 Android Vitals 中应用类似技术，实现了 ANR 根因的云端聚合分析。</p></li><li><p><strong>架构预防性设计</strong><br>终极目标是从代码设计阶段就内化系统约束，例如：</p><ul><li><strong>通信拓扑约束</strong>：限制跨进程调用层级，避免 <code>A → B → C</code> 的链式调用，改用事件总线广播模式。</li><li><strong>资源预算管理</strong>：为每个业务模块分配 <code>Binder</code> 事务配额，超出阈值时自动降级。</li><li><strong>异步边界强化</strong>：利用 <code>HandlerThread</code> 和 <code>Executor</code> 严格隔离同步与异步操作，防止线程模型出现混乱。</li></ul></li></ol><p>这种从被动应对到主动防御的方法论进化路径，不仅为系统从根源上预防 ANR 提供了有效策略，也为开发者提供了丰富的诊断工具和优化思路。</p><h1 id="ANR-相关资料分享"><a href="#ANR-相关资料分享" class="headerlink" title="ANR 相关资料分享"></a>ANR 相关资料分享</h1><ol><li><a href="https://juejin.cn/post/6864555867023343623">反思｜Android 输入系统 &amp; ANR机制的设计与实现</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487203&idx=1&sn=182584b69910c843ae95f60e74127249&chksm=e9d0c501dea74c178e16f95a2ffc5007c5dbca89a02d56895ed9b05883cf0562da689ac6146b&token=2044639920&lang=zh_CN&scene=21#wechat_redirect">西瓜视频稳定性治理体系建设一：Tailor 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s/RF3m9_v5bYTYbwY-d1RloQ">西瓜视频稳定性治理体系建设二：Raphael 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489902&idx=1&sn=bfdf9f48dc6dc973722b5dcab9cd5882&chksm=e9d0d28cdea75b9ad255eb5de227240d2e6f0e9d66e562d3f49cf69f8ed4127c9954ef21bb6d&scene=178&cur_album_id=1833937688379310087#rd">西瓜视频稳定性治理体系建设三：Sliver 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489949&idx=1&sn=01948c047c0ce203956a3cf81dd20e83&chksm=e9d0d27fdea75b697e70a665b4c6912a8081649700766cf007a7b75d420a57089fe06d2e85b0&scene=178&cur_album_id=1833937688379310087#rd">西瓜卡顿 &amp; ANR 优化治理及监控体系建设</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488116&idx=1&sn=fdf80fa52c57a3360ad1999da2a9656b&chksm=e9d0d996dea750807aadc62d7ed442948ad197607afb9409dd5a296b16fb3d5243f9224b5763&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 设计原理及影响因素</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488182&idx=1&sn=6337f1b51d487057b162064c3e24c439&chksm=e9d0d954dea75042193ed09f30eb8ba0acd93870227c5d33b33361b739a03562afb685df9215&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 监控工具与分析思路</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488243&idx=1&sn=1f948e0ef616c6dfe54513a2a94357be&chksm=e9d0d911dea75007f36b3701b51842b9fa40969fe8175c2cb4aecf96793504602c574945d636&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列分享 - 实例剖析集锦</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488314&idx=1&sn=559e52288ae2730a580fcd550f22d895&chksm=e9d0d8d8dea751ceecb715d472796f0c678a9358abf91eb279cdb0576329595e87531e221438&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - Barrier 导致主线程假死</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488558&idx=1&sn=27dda3c3630116d37ab56a8c7bdf1382&chksm=e9d0dfccdea756daed46b340fb8021b57ea8cc300e58bdb59f0305f8290704984308a089bf2d&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待</a></li><li><a href="https://mp.weixin.qq.com/s/40T6ITvJNWR8F42530k4DA">Android ANR|原理解析及常见案例</a></li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="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#1-%E6%A6%82%E8%A7%88</a></li><li><a href="https://duanqz.github.io/2015-10-12-ANR-Analysis">https://duanqz.github.io/2015-10-12-ANR-Analysis</a></li><li><a href="http://gityuan.com/2016/12/02/app-not-response/">http://gityuan.com/2016/12/02/app-not-response/</a></li><li><a href="http://gityuan.com/2017/01/01/input-anr/">http://gityuan.com/2017/01/01/input-anr/</a></li><li><a href="https://xiaozhuanlan.com/topic/5097486132">https://xiaozhuanlan.com/topic/5097486132</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文为 Android App ANR 系列的第一篇，主要是从系统的角度来剖析 Android ANR 的设计思想，系列文章目录如下&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/&quot;&gt;Android App ANR 系列 1 ：理解 Android ANR 设计思想&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/&quot;&gt;Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/&quot;&gt;Android App ANR 系列 3 ：ANR 案例分享&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 3：熟悉 Perfetto View</title>
    <link href="https://androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/"/>
    <id>https://androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/</id>
    <published>2024-05-20T23:30:23.000Z</published>
    <updated>2026-02-07T05:17:47.832Z</updated>
    
    <content type="html"><![CDATA[<p>本篇是 Perfetto 系列文章的第三篇，前两篇介绍了 Perfetto 是什么以及 Perfetto Trace 怎么抓，本篇主要是在网页端打开 Perfetto Trace 之后，面对复杂的 Perfetto 信息该怎么看。</p><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列目录</a></li><li><a href="#view">Perfetto View 界面</a></li><li><a href="#trace">Perfetto Trace 界面</a></li><li><a href="#ops">基本操作</a></li><li><a href="#tips">Perfetto 使用技巧</a></li><li><a href="#summary">总结</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><p><a id="series"></a></p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="view"></a></p><h1 id="Perfetto-View-界面"><a href="#Perfetto-View-界面" class="headerlink" title="Perfetto View 界面"></a>Perfetto View 界面</h1><p>抓到 Perfetto Trace 之后，一般是在 <a href="https://ui.perfetto.dev/">ui.perfetto.dev</a> 中打开（如果用官方提供的脚本，则会在抓去结束后自动在这个网站上打开，想看看怎么实现的话可以去看看脚本的源码）。打开后界面如下：</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/2dceb8bd-7825-42e0-8a6b-4c241684f60e.webp" alt="Perfetto View 界面"></p><p>可以通过 Open trace file 或者直接把 Perfetto Trace 拖到白色区域来打开 Trace。</p><p><a id="trace"></a></p><h1 id="Perfetto-Trace-界面"><a href="#Perfetto-Trace-界面" class="headerlink" title="Perfetto Trace 界面"></a>Perfetto Trace 界面</h1><p>打开 Perfetto Trace 之后界面如下：</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/d8549610-92e4-41c1-a3ee-4a2c9258e223.webp" alt="Trace 操作区"></p><p>大致上 Perfetto Trace 界面可以分为四个区域：</p><ol><li><strong>最右边的操作区</strong>：这里最主要的是 Current Trace 这一栏下面的那几个会经常用到。<ol><li>Show timeline ：显示当前 Trace，切到了别的界面之后，再点这个就会回到 Trace View 界面</li><li>Query：写 SQL 查询语句和查看执行结果的地方</li><li>Metrics：官方默认帮你写好的一些分析结果，可以选择直接打开</li><li>Info and stats ：当前 Trace 和手机 App 的一些信息</li></ol></li><li><strong>上方的信息和操作区域</strong>：最主要就是看时间。</li><li><strong>中间的 Trace 内容区</strong>：操作最多的区域，Trace 内容都在这部分，最上面的几部分是从功能的角度来划成一个区域的，比如 CPU 区（可以查看当前 Task 跑在哪个核心上，频率是多少，跑了多长时间、被谁唤醒）、Ftrace event 区等；下面的就是以 App Process 为单位展示的（包括 App 的各种线程、Input 事件、Binder Call、Memory、Buffer 等信息）。</li><li><strong>最下方的信息区</strong>：这个区域主要是展示各种信息、我们选中了某个 Task 段之后，这里就会展示这个 Task 相关的信息（如果你加了 Log，这里也会显示 Log；ftrace event 同理）。</li></ol><p>Perfetto 界面最初看的时候会觉得很乱，花里胡哨的，但是用习惯了之后，真香～</p><p><a id="ops"></a></p><h1 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h1><p>Perfetto Trace 界面的操作是非常顺滑的，这是相比 Systrace 的一个巨大的优势，Systrace 打开稍大的 Trace 就会卡卡的，但是 Perfetto Trace 打开 500Mb 的 Trace 依然操作很顺滑。</p><p>操作看 Trace 的快捷键跟 Systrace 很像，w&#x2F;s 是放大&#x2F;缩小，a&#x2F;d 是左右移动，鼠标点击是选择。官方左下角的文档有详细的操作说明，忘记了的话可以随时去看看，熟能生巧：</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/8057095a-cd4f-42f4-a52e-6ae82ca3950a.webp" alt="基本操作"></p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/c7409a31-f1eb-4fde-a899-f103005f1c0d.webp" alt="SQL 相关的操作"></p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/df9f57b7-6303-4c56-9403-a1b51557e1e2.webp" alt="其他快捷键"></p><p>其他快捷键里面用的比较多的：</p><ol><li><p><strong>f</strong> 是放大选中</p></li><li><p><strong>m</strong> 是临时 Mark 一段区域（与 Systrace 一样）, 用来上下看时间、看其他进程信息等。临时的意思就是你如果按 m 去 mark 另外一个区域，那么上一个用 m mark 出来的 Mark 区域就会消失。退出临时选中：esc ，或者选择其他的 Slice 按 m，当前这个 Slice 的选中效果就会消失</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/041fc179-fb2f-4518-95ce-3d9fac8f11e4.webp"></p></li><li><p><strong>shift + m</strong> 是持续 Mark 一段区域（如果你不点，他就不会消失），主要是用来长时间 Mark 住一段信息，比如你把一份 Trace 中所有的掉帧点都 Mark 出来，就可以用 shift + m，这样就不会丢失。</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/31a72b92-36bd-4411-a529-2f7c6cd9e77d.webp"></p><p>点击小旗子，就可以看到这段区间内的执行信息<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/8e36d592-f351-4277-90e2-e0f2bb852a59.webp"></p></li></ol><ol start="4"><li><p><strong>删除持续 Mark</strong></p><ol><li>点击你选中的那个 Slice 的最上面那个三角</li><li>下面选择 Tab：Current Selection</li><li>点击最后边的 Remove ，就可以把他 Remove 掉了<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/151a3675-e88c-4d26-a6be-1b43d72ca21a.webp"></li></ol></li><li><p><strong>q</strong> ：隐藏和显示信息 Tab，由于 Perfetto 非常占屏幕，熟练使用 q 键很重要，看的时候快速打开，看完后快速关闭。</p></li><li><p><strong>插旗子</strong>：Perfetto 还可以通过插旗子的方法来在 Trace 上做标记，Perfetto 支持你把鼠标放到 Trace 最上面，就会出现一个旗子，点击左键即可插一个旗子在上面，方便我们标记某个事件发生，或者某个时间点</p></li></ol><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/2fbd5ce0-743f-4826-aa51-246ff78964ab.webp"></p><ol start="6"><li><strong>取消插的旗子</strong>：与退出持续选中一样，点击旗子，右下角有个 Remove ，点击就可以把这个旗子干掉了，就不插图了</li></ol><p><a id="tips"></a></p><h1 id="Perfetto-使用技巧"><a href="#Perfetto-使用技巧" class="headerlink" title="Perfetto 使用技巧"></a>Perfetto 使用技巧</h1><h2 id="查看唤醒源"><a href="#查看唤醒源" class="headerlink" title="查看唤醒源"></a>查看唤醒源</h2><p>我们可以通过查看某一个 Task 的唤醒源，来了解 App 和 Framework 的运转流程，Systrace 和 Perfetto 都可以查看唤醒源，不过 Perfetto 在这方面做的更丝滑一些。</p><p>在 <a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/#/1-1-%E5%A6%82%E4%BD%95%E5%88%86%E6%9E%90-Sleep-%E7%8A%B6%E6%80%81%E7%9A%84-Task">Android Systrace 响应速度实战 3 ：响应速度延伸知识</a> 这篇文章中，有讲 Systrace 是如何查看唤醒源的，其实略微还是有些麻烦的。 Perfetto 中查看唤醒源则非常方便且操作很顺滑：</p><p>比如我们想看下图中， RenderThread 是被谁唤醒的，我们可以有好几种方法：</p><h3 id="点击-Runnable-状态"><a href="#点击-Runnable-状态" class="headerlink" title="点击 Runnable 状态"></a>点击 Runnable 状态</h3><p>与 Systrace 操作一样，直接点击 Running 前面的 Runnable，就可以在下面的信息区看到 Related thread states：</p><ol><li>Waker：唤醒源</li><li>Previous state：这个 Task 的前一个状态</li><li>Next state：这个 Task 的后一个状态<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/d724e03f-6671-4e86-a461-a339b6e02550.webp"></li></ol><h3 id="点击他上方的-Running-状态，查看连续唤醒信息："><a href="#点击他上方的-Running-状态，查看连续唤醒信息：" class="headerlink" title="点击他上方的 Running 状态，查看连续唤醒信息："></a>点击他上方的 Running 状态，查看连续唤醒信息：</h3><p>或者我们可以点击 Running 状态，点击小箭头直接跳到对应的 CPU Info 区域，这里可以看到更详细的信息，也可以连续点击 Task，来追踪唤醒源，并可以通过信息区的小箭头来回在 CPU Info 区域和 Task 区域跳转</p><p>点击 RenderThread 上方的 Running 状态，通过小箭头跳转到 CPU Info 区域<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/97f05788-72de-4060-8a30-2d575d59b461.webp"></p><p>RenderThread 是被 MainThread 唤醒的<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/89b71ec9-c057-4346-b9b2-eee9a9e44cab.webp"></p><p>再点击 MainThread 可以看到他是被 SurfaceFlinger 唤醒的，下方信息区还有对应的唤醒延迟分析<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/f222b9d1-f59e-46d2-bda9-74495982819d.webp"></p><h2 id="查看-Critical-Path-Task"><a href="#查看-Critical-Path-Task" class="headerlink" title="查看 Critical Path(Task)"></a>查看 Critical Path(Task)</h2><p>Critical Task 指的是与当前我们选中的 Task 有依赖关系的 Task，比如我们的 Task 是 e，e 要等 d 执行结束后才能执行，d 要等 c，c 要等 b，b 要等 a，那么 e 的 Critical Task 就是 a、b、c、d。</p><p>Perfetto 上就可以查看某一个 Task 的 Critical Task，鉴于 Critical path lite 是 Critical path 的子集，我们这里只介绍 Critical path：</p><p>点击 Running 状态，然后点击在下面的信息区点击 Critical path<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/35f1c5bb-44dd-438e-82a9-a82aabaf7c12.webp"></p><p>稍等片刻就可以看到我们选择的 MainThread 对应的 Critical path：</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/259f60b3-506b-4808-a66d-784d8caed34b.webp"></p><p>放大来看，可以看到我们选择的 MainThread 的边缘，第一个 Critical Task 是唤醒他的 sf 的 app 线程<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/d045730a-e0a5-42b4-b185-ac3732063049.webp"></p><p>再往左看 sf 的 app 线程是被 sf 的 TimerDispatch 线程唤醒的，这里就不贴了。</p><p>其实可以看到，Perfetto 提供的 Critical Path 其实就是把连续唤醒的 Task 都聚集到一起了，方便我们来看各个 Task 之间的关系。</p><h2 id="Pin-（固定到最上面）"><a href="#Pin-（固定到最上面）" class="headerlink" title="Pin （固定到最上面）"></a>Pin （固定到最上面）</h2><p>在每个 Thread 的最左边，有一个图钉一样的按钮，点击之后，这个 Thread 就会被固定到最上面，方便我们把自己最关注的 Thread 放到一起。</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/e448681b-f759-4a24-beee-d112c9cffc7a.webp"></p><p>比如下面是我 Pin 的从 App 到 SF 的流程图，放到一起的话就会清晰很多，看掉帧的话也会更方便。<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/e903fd70-8ea0-47d1-8e3d-48e63155ba9b.webp"></p><h2 id="CPU-Info-区域-Task-高亮"><a href="#CPU-Info-区域-Task-高亮" class="headerlink" title="CPU Info 区域 Task 高亮"></a>CPU Info 区域 Task 高亮</h2><p>在 CPU Info 区域，鼠标放到某一个 Task 上，就会这个 Task 对应的 Thread 的其他 Task 都会高亮。</p><p>我们经常会用这个方法来初步看某些 Thread 的摆核信息</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/70316722-19e9-4148-a099-781bcb9d49fd.webp"></p><h2 id="查看-Buffer-消耗情况"><a href="#查看-Buffer-消耗情况" class="headerlink" title="查看 Buffer 消耗情况"></a>查看 Buffer 消耗情况</h2><p>App 的 Buffer 消费者是 SurfaceFlinger，通过 App Process 这边的 Actual Timeline 这行，我们可以看到 Buffer 具体是被 SurfaceFlinger 的哪一框消费了。</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/f6e9bc36-2919-4b15-82cd-1672d18e4c55.webp"></p><h2 id="快速查看-App-执行超时"><a href="#快速查看-App-执行超时" class="headerlink" title="快速查看 App 执行超时"></a>快速查看 App 执行超时</h2><p>由于 Android 多 Buffer 机制的存在，App 执行超时不一定会卡顿，但是超时是需要我们去关注的。</p><p>通过 Perfetto 提供给的 Expected Timeline 和 Actual Timeline 这两行，可以清楚看到执行超时的地方。<br><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/99a89fa4-773a-4f18-b8c7-ee852f9c44eb.webp"></p><p>点击 Actial Timeline 红色那一段，底下的信息栏会显示掉帧原因：</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/450afbdb-e902-4b3f-9941-6ba1dc3cc07a.webp"></p><h2 id="在-Perfetto-上查看-Log"><a href="#在-Perfetto-上查看-Log" class="headerlink" title="在 Perfetto 上查看 Log"></a>在 Perfetto 上查看 Log</h2><p>在信息栏上切换到 Android Logs 这个 Tab，鼠标放倒某一行上，Perfetto 就会把对应的 Timeline 拉一条直线，可以看到这个 Log 所对应的时间</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/e33520ff-b50f-4f36-a333-2b8063a5f322.webp"></p><p>同样切换到 Ftrace events tab 也可以查看对应的 ftrace 的 event 和对应的时间线</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/061c9177-7bfa-44ff-860d-6fe75ad2f93e.webp"></p><h2 id="分析-Thread-的-Running-信息"><a href="#分析-Thread-的-Running-信息" class="headerlink" title="分析 Thread 的 Running 信息"></a>分析 Thread 的 Running 信息</h2><p>可以通过鼠标左键按住滑动，选中一段区域来进行分析，比如选中 CPU State 这一栏的话，就可以看到这一段时间对应的 Running、Runnable、Sleep、Uninterruptible Sleep 的占比。</p><p>这在分析 App 启动的时候经常会用到。</p><p><img src="/images/Android-Perfetto-03-how-to-analysis-perfetto/6725a982-4f6b-48a5-b750-6c34aa5f342c.webp"></p><p><a id="summary"></a></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>上面分享了 Perfetto 基本的界面和操作，以及分享了一些比较常用的 Perfetto 的技巧。Google 目前在积极推广和维护 Perfetto，很多新功能指不定哪天就蹦出来了，到时候觉得有用我也会更新上来。</p><p>至此 Perfetto 基础篇就结束了，后续就是通过 Perfetto 这个工具，来了解 Android 系统运行的基本流程，以及使用 Perfetto 以及 Perfetto SQL 来分析遇到的性能、功耗等问题。</p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本篇是 Perfetto 系列文章的第三篇，前两篇介绍了 Perfetto 是什么以及 Perfetto Trace 怎么抓，本篇主要是在网页端打开 Perfetto Trace 之后，面对复杂的 Perfetto 信息该怎么看。&lt;/p&gt;
&lt;p&gt;随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 2：Perfetto Trace 抓取</title>
    <link href="https://androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/"/>
    <id>https://androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/</id>
    <published>2024-05-20T23:29:41.000Z</published>
    <updated>2026-02-07T05:17:47.831Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇文章 <a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a> 介绍了 Perfetto 是什么，这篇简单介绍一下 Perfetto 的抓取。</p><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列目录</a></li><li><a href="#content">正文</a></li><li><a href="#cli">1. 使用命令行来抓取 Perfetto（推荐）</a></li><li><a href="#script">2. 使用官方脚本抓取</a></li><li><a href="#system-tracing">3. 使用手机上的开发者工具来抓取</a></li><li><a href="#web-record">4. 使用网页端来抓取</a></li><li><a href="#refs">参考文档</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><p><a id="series"></a></p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>使用 Perfetto 分析问题跟使用 Systrace 分析问题的步骤是一样的：</p><ol><li>首先你需要抓取 Perfetto 文件</li><li>在<a href="https://ui.perfetto.dev/">ui.perfetto.dev</a> 中打开 Trace 文件进行分析或者使用命令行来进行分析</li></ol><p>这篇文章就简单介绍一下使用 Perfetto 抓取 Trace 文件的方法，个人比较推荐使用命令行来抓取，不管是自己配置的命令行还是官方的命令行抓取工具，都非常实用。</p><p><a id="cli"></a></p><h1 id="1-使用命令行来抓取-Perfetto（推荐）"><a href="#1-使用命令行来抓取-Perfetto（推荐）" class="headerlink" title="1. 使用命令行来抓取 Perfetto（推荐）"></a>1. 使用命令行来抓取 Perfetto（推荐）</h1><h2 id="基本命令-adb-shell-perfetto"><a href="#基本命令-adb-shell-perfetto" class="headerlink" title="基本命令 - adb shell perfetto"></a>基本命令 - adb shell perfetto</h2><p>对于之前一直用 Systrace 工具的小伙伴来说，命令行抓取 Trace 非常方便。同样，Perfetto 也提供了简单的命令行来抓取，最简单的使用方法与 Systrace 基本一致。你可以直接连到你的 Android 设备上使用<code>/system/bin/perfetto</code>命令来启动跟踪。例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//1. 首先执行命令</span><br><span class="line">adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 20s \</span><br><span class="line"> <span class="built_in">sched</span> freq idle am wm gfx view binder_driver hal dalvik camera input res memory</span><br><span class="line"></span><br><span class="line">// 2. 操作手机，复现场景，比如滑动或者启动等</span><br><span class="line"></span><br><span class="line">// 3. 将 trace 文件 pull 到本地</span><br><span class="line">adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace</span><br></pre></td></tr></table></figure><p>这个命令会启动一个 20 秒钟的跟踪，收集指定的数据源信息，并将跟踪文件保存到<code>/data/misc/perfetto-traces/trace_file.perfetto-trace</code>。</p><p>执行 adb pull 命令把 trace pull 出来，就可以直接在<a href="https://ui.perfetto.dev/">ui.perfetto.dev</a> 上打开了。</p><h2 id="进阶命令-adb-shell-perfetto-with-config-file"><a href="#进阶命令-adb-shell-perfetto-with-config-file" class="headerlink" title="进阶命令 adb shell perfetto with config file"></a>进阶命令 adb shell perfetto with config file</h2><p>这里就是 Perfetto 与 Systrace 不同的地方，Perfetto 可以抓取的信息非常多，其数据来源也非常多，每次都用命令行加一大堆配置的话会很不方便。这时候我们就可以使用一个单独的**配置文件(Config)**，来存储这些信息，每次抓取的时候，指定这个配置文件即可。</p><p>对于在 Android 12 之前和之后版本上使用 Perfetto 的配置文件传递，以下是详细的指南和对应的命令行示例。</p><h3 id="在-Android-12-及之后的设备上"><a href="#在-Android-12-及之后的设备上" class="headerlink" title="在 Android 12 及之后的设备上"></a>在 Android 12 及之后的设备上</h3><p>从 Android 12 开始，可以直接使用<code>/data/misc/perfetto-configs</code>目录来存储配置文件，这样就不需要通过 stdin 来传递配置文件了。具体命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb push config.pbtx /data/misc/perfetto-configs/config.pbtx</span><br><span class="line">adb shell perfetto --txt -c /data/misc/perfetto-configs/config.pbtx -o /data/misc/perfetto-traces/trace.perfetto-trace</span><br></pre></td></tr></table></figure><p>在这个例子中，首先将配置文件<code>config.pbtx</code>推送到<code>/data/misc/perfetto-configs</code>目录中。然后，直接在 Perfetto 命令中通过<code>-c</code>选项指定配置文件的路径来启动跟踪。</p><h3 id="在-Android-12-之前的设备上"><a href="#在-Android-12-之前的设备上" class="headerlink" title="在 Android 12 之前的设备上"></a>在 Android 12 之前的设备上</h3><p>由于 SELinux 的严格规则，直接通过文件路径传递配置文件在非 root 设备上会失败。因此，需要使用标准输入(stdin)来传递配置文件。这可以通过将配置文件的内容<code>cat</code>到 Perfetto 命令中实现。具体命令如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb push config.pbtx /data/local/tmp/config.pbtx</span><br><span class="line">adb shell <span class="string">&#x27;cat /data/local/tmp/config.pbtx | perfetto -c - -o /data/misc/perfetto-traces/trace.perfetto-trace&#x27;</span></span><br></pre></td></tr></table></figure><p>这里，<code>config.pbtx</code>是你的 Perfetto 配置文件，首先使用<code>adb push</code>命令将其推送到设备的临时目录中。然后，使用<code>cat</code>命令将配置文件的内容传递给 Perfetto 命令。</p><h3 id="Config-的来源"><a href="#Config-的来源" class="headerlink" title="Config 的来源"></a>Config 的来源</h3><p>Config 我建议使用 <a href="https://ui.perfetto.dev/">ui.perfetto.dev</a> 的 <a href="https://ui.perfetto.dev/#!/record"><strong>Record new trace</strong></a> 这里进行选择定制，然后再保存到本地的文件里面，不同的场景就加载不同的 Config 即可，文章最后一部分有详细讲到这部分，感兴趣的可以看一下。</p><p>官方也提供了 share 按钮，你可以把你自己的 config share 给其他人，非常方便。同时我也会建了一个 Github 的库，方便大家在分享（进行中）。</p><p>官方代码库也有一些已经配置好的，各位可以下下来自己使用：<a href="https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/">https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/</a></p><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><ul><li><strong>确保 adb 正常</strong>：在使用这些命令之前，请确保你的设备已经启用了 USB 调试，并且已经通过<code>adb devices</code>命令确认设备已经正确连接。</li><li><strong>Ctrl+C 中断：</strong> 当使用<code>adb shell perfetto</code>命令时，如果你尝试使用 Ctrl+C 来提前结束跟踪，这个信号不会通过 ADB 传播。如果你需要提前结束跟踪，建议使用一个交互式的 PTY-based session 来运行<code>adb shell</code>。</li><li><strong>SELinux 限制：</strong> 在 Android 12 之前的非 root 设备上，由于 SELinux 的严格规则，配置文件只能通过<code>cat config | adb shell perfetto -c -</code>的方式传递（其中<code>-c -</code>表示从标准输入读取配置）。从 Android 12 开始，可以使用<code>/data/misc/perfetto-configs</code>路径来存储配置文件。</li><li>在 Android 10 之前的版本, adb 没法直接把 &#x2F;data&#x2F;misc&#x2F;perfetto-traces pull 出来. 你可以使用 <code>adb shell cat /data/misc/perfetto-traces/trace &gt; trace</code> 来替代</li></ul><p><a id="script"></a></p><h1 id="2-使用-Perfetto-提供的官方脚本抓取（强烈推荐）"><a href="#2-使用-Perfetto-提供的官方脚本抓取（强烈推荐）" class="headerlink" title="2. 使用 Perfetto 提供的官方脚本抓取（强烈推荐）"></a>2. 使用 Perfetto 提供的官方脚本抓取（强烈推荐）</h1><p>Perfetto 团队还提供了一个便捷的脚本<code>tools/record_android_trace</code>，它简化了从命令行记录跟踪的流程。这个脚本会自动处理路径问题，完成跟踪后自动拉取跟踪文件，并在浏览器中打开它。本质上这个脚本还是使用的 adb shell perfetto 命令，不过官方帮你封装好了,使用示例：</p><p>On Linux and Mac：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace</span><br><span class="line"><span class="built_in">chmod</span> u+x record_android_trace</span><br><span class="line">./record_android_trace -o trace_file.perfetto-trace -t 10s -b 64mb \</span><br><span class="line"> <span class="built_in">sched</span> freq idle am wm gfx view binder_driver hal dalvik camera input res memory</span><br></pre></td></tr></table></figure><p>On Windows:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace</span><br><span class="line">python3 record_android_trace -o trace_file.perfetto-trace -t 10s -b 64mb \</span><br><span class="line">sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory</span><br></pre></td></tr></table></figure><p>同样的，这里也可以通过 -c 来指定配置文件，比如</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl -O https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace</span><br><span class="line"><span class="built_in">chmod</span> u+x record_android_trace</span><br><span class="line">./record_android_trace -c config.pbtx -o trace_file.perfetto-trace -t 10s -b 64mb \</span><br><span class="line"> <span class="built_in">sched</span> freq idle am wm gfx view binder_driver hal dalvik camera input res memory</span><br></pre></td></tr></table></figure><p>这将会记录一个 10 秒的跟踪，并将输出文件保存为<code>trace_file.perfetto-trace</code>。</p><p>执行后会自动抓取 Trace， 自动在浏览器自动打开，非常方便<br><img src="/images/Android-Perfetto-02-how-to-get-perfetto/ba56ef7e-77c4-4780-b975-33350c224275.webp"></p><p>脚本内容可以直接访问：<a href="https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace">https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace</a> 来查看，</p><p><a id="system-tracing"></a></p><h1 id="3-使用手机上的开发者工具来抓取"><a href="#3-使用手机上的开发者工具来抓取" class="headerlink" title="3. 使用手机上的开发者工具来抓取"></a>3. 使用手机上的开发者工具来抓取</h1><p>当然有时候会没有办法连接到电脑上，或者测试内容不能插 usb，这时候就可以使用 Android 上的自带的系统跟踪应用（System Tracing App）来抓取 Trace。这个应用内置于开发者选项中，可以让你通过几个简单的步骤来配置和启动性能跟踪。</p><h2 id="启动系统跟踪应用"><a href="#启动系统跟踪应用" class="headerlink" title="启动系统跟踪应用"></a>启动系统跟踪应用</h2><ol><li><p><strong>启用开发者选项</strong>：首先，确保你的设备已经启用了开发者选项。如果你的设置里面没有开发者选项，你需要在关于手机那里，找到编译编号，然后连续点击 7 次，就可以打开开发者选项。</p></li><li><p><strong>打开开发者选项</strong>：在设置菜单中找到并打开开发者选项。</p></li><li><p><strong>启动系统跟踪</strong>：在开发者选项中向下滚动直到找到”系统跟踪（System Trace）”或类似的选项。点击它，将打开系统跟踪应用。</p></li></ol><p>大概长下面这样（每个手机可能界面或者文字会有差异，但是功能是一样的）<br><img src="/images/Android-Perfetto-02-how-to-get-perfetto/bd5cd1ae-c00d-4359-b73a-bf8d822374c0.webp"></p><p>系统跟踪应用提供了一系列的配置选项，包括但不限于：</p><ul><li><strong>跟踪时长</strong>：你可以指定跟踪的持续时间，例如 10 秒或更长时间。</li><li><strong>数据源</strong>：选择你想要收集数据的来源。这可能包括 CPU、内存、网络等多种不同的数据源。</li><li><strong>输出文件位置</strong>：指定跟踪文件保存的位置。</li></ul><h2 id="启动和停止跟踪"><a href="#启动和停止跟踪" class="headerlink" title="启动和停止跟踪"></a>启动和停止跟踪</h2><p>配置好所有需要的参数后，你可以通过点击”录制跟踪记录”按钮来启动跟踪。再次”录制跟踪记录”按钮就可以结束抓取，完成抓取后，通常会有一个提示告诉你抓取已经完成，并提供查看或分享跟踪文件的选项。就可以将跟踪文件导出到电脑上，使用 Perfetto 网页 UI 进行更深入的分析。</p><p><a id="web-record"></a></p><h1 id="4-使用网页端来抓取"><a href="#4-使用网页端来抓取" class="headerlink" title="4. 使用网页端来抓取"></a>4. 使用网页端来抓取</h1><blockquote><p>网页端抓取的功能比较迷，很多时候你都会抓取失败，比如连不上 adb、连上之后说你需要执行 kill。所以我更推荐大家使用配置好的命令行来抓取，网页端更适合进行 Config 的配置。</p></blockquote><p>Perfetto 还提供了一个强大的 <a href="https://ui.perfetto.dev/#!/record">网页端工具（ui.perfetto.dev）</a>，允许开发者通过浏览器配置和启动跟踪。你只需要访问 <a href="https://ui.perfetto.dev/#!/record">网站</a>，点击”Record new trace”，然后根据需要选择数据源和配置参数。确保你的设备通过 ADB 连接到电脑，并且在网页端选择”Add ADB device”。之后，点击”Start Recording”即可开始收集跟踪数据。</p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/0255e95d-ca6b-4a59-9ae9-0b96b4f7347b.webp"></p><p>这里选好你想抓取的信息源之后，可以点击 Recording command 来查看，这里可以看到你选好的 Config 的具体内容，你可以分享或者保存到本地的文件里面，用命令行抓取的时候使用。</p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/266e0feb-4c10-4a0d-bc8f-0327341931ea.webp"></p><p>选取 Config 的时候，Android apps 那一栏里面的 Atrace userspace annotations、Event log (logcat)、Frame timeline 建议都选上（command + a）</p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/945daf30-f2da-4b0e-882f-e7abb7bdae63.webp"></p><p>另外如果想看调用栈，可以把 Stack Samples 这里的 Callstack sampling 勾选上（注意需要最新版本的 Android 才可以，而且所 debug 的进程得是 debugable 的）</p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/67e26de4-fb68-4dc6-a747-dbdc58b02402.webp"></p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/11570ba8-d688-462c-9178-a70b41a8fb76.webp"></p><p>至于其他的有啥用，可以自己探索，后续的 Perfetto 也会介绍到每个部分和他在 Trace 上的呈现，帮助大家更快入手 Perfetto。</p><h2 id="从网页端提取参数"><a href="#从网页端提取参数" class="headerlink" title="从网页端提取参数"></a>从网页端提取参数</h2><p>前面提到网页端的可以图形化选择 Config 这个很方便，选好之后，点击 Recording command 这里，就可以看到已经选好的 Config，你在保存的时候记得把下面这几行去掉就可以了</p><p><img src="/images/Android-Perfetto-02-how-to-get-perfetto/9d3bc292-da9e-4bf4-92fe-bfe9f2717c2e.webp"></p><p><a id="refs"></a></p><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ol><li><a href="https://perfetto.dev/docs/quickstart/android-tracing">https://perfetto.dev/docs/quickstart/android-tracing</a></li><li><a href="https://perfetto.dev/docs/concepts/config">https://perfetto.dev/docs/concepts/config</a></li><li><a href="https://developer.android.com/tools/releases/platform-tools?hl=zh-cn">https://developer.android.com/tools/releases/platform-tools?hl=zh-cn</a></li><li><a href="https://mp.weixin.qq.com/s/nsqc51L5T4mrTUVsPgkj6A">https://mp.weixin.qq.com/s/nsqc51L5T4mrTUVsPgkj6A</a></li><li><a href="https://juejin.cn/post/7344983784549400613">https://juejin.cn/post/7344983784549400613</a></li><li><a href="https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/">https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/test/configs/</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;上一篇文章 &lt;a href=&quot;https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/&quot;&gt;Android Perfetto 系列 1：Perfetto 工具简介&lt;/a&gt; 介绍了 Perfetto 是什么，这篇简单介绍一下 Perfetto 的抓取。&lt;/p&gt;
&lt;p&gt;随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列 1：Perfetto 工具简介</title>
    <link href="https://androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/"/>
    <id>https://androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/</id>
    <published>2024-05-20T23:29:22.000Z</published>
    <updated>2026-02-07T05:17:47.831Z</updated>
    
    <content type="html"><![CDATA[<p>本篇是 Perfetto 系列文章的第一篇，主要是简单介绍 Perfetto 工具，包括 Perfetto 的历史、发展，以及 Perfetto 能做什么。</p><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">Perfetto 系列目录</a></li><li><a href="#content">正文</a></li><li><a href="#godview">性能分析为什么需要上帝视角</a></li><li><a href="#perfetto-intro">Perfetto 介绍</a></li><li><a href="#vs-systrace">相比 Systrace 没那么好用的地方</a></li><li><a href="#refs">参考文档</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><p><a id="series"></a></p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>如果大家还没看过 Systrace 系列，下面是传送门：</p><ol><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/#/%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E7%9B%AE%E5%BD%95">Systrace 系列目录</a> ： 系统介绍了 Perfetto 的前身 Systrace 的使用，并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。</li><li><a href="https://www.androidperformance.com/">个人博客</a> ：个人博客，主要是 Android 相关的内容，也放了一些生活和工作相关的内容。</li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>2019 年开始写 Systrace 系列，陆陆续续写了 20 多篇，从基本使用到各个模块在 Systrace 上的呈现，再到启动速度、流畅性等实战，基本上可以满足初级系统开发者和 App 开发者对于 Systrace 工具的需求。通过博客也加了不少志同道合的小伙伴，光交流群就建了有 6 个。这里非常感谢大家的支持。</p><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><p>所以就有了这个系列，我也有在星球里面写了几条为什么要更新 Perfetto 系列的原因（之前一直觉得 Systrace 系列就够了）：</p><ol><li>目前 Oppo、Vivo 这些手机厂商内部，都已经切换成 Perfetto 了，不管是抓 Trace 还是看 Trace，都在使用 Perfetto ，很多新人接触的都是 Perfetto 而不是 Systrace ，守着之前的老 Systrace 系列会流失这部分读者</li><li>之前的 Systrace 系列，对应的 Code 已经比较老了，全新的 Perfetto 系列可以使用 Android 14 的 Code 来进行更新</li><li>个人对 Perfetto 的使用也没有很深入，有些高阶功能目前还只是浅尝辄止。可以通过重写 Perfetto 系列来进行这部分内容的强化</li><li>Perfetto 是个很强大的工具，他的背后是整个 Android + Linux 系统，所以在写这个系列的时候，应该是以他背后的这个 Android + Linux 为主，而不是仅仅局限于 Perfetto 这个工具。工具只是我们观测 Android + Linux 的方式，理解整个 Android 系统运行的规律，思考其运行的原理，通过工具挖掘问题，通过问题思考本质，这才是对开发者来说有意义的</li><li>很多 Android 系统运行相关的内容，Perfetto 的官方文档还是没有讲，这部分我这边可以作为补足；另外官方文档是英文版本的，中文博客可以补充这方面。</li><li>Perfetto 可以拿到 Google Dev Fest 上作为演讲内容～。</li></ol><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/e3e1da35-179a-457b-a9e1-6f6b91db740e.webp" alt="Perfetto"></p><p><a id="godview"></a></p><h1 id="性能分析为什么需要上帝视角"><a href="#性能分析为什么需要上帝视角" class="headerlink" title="性能分析为什么需要上帝视角"></a>性能分析为什么需要上帝视角</h1><p>在介绍 Perfetto 之前，我们需要了解为什么性能分析需要 Systrace 和 Perfetto 这样的工具：以 Android 系统为例，影响性能的因素是非常多的：App 自身质量、系统各个模块的性能、Linux 的性能、硬件性能，再加上各个厂商的策略、厂商定制的功能、系统自身的负载、低内存、发热、Android 各个版本的差异、用户的使用习惯等。这都不是通过分析某一个 App 或者某一个模块就能知道原因的，我们需要一个上帝视角，从更高的纬度来看 Android 系统的运行情况。</p><p>而 Perfetto 工具就提供了这样的一个上帝视角，通过上帝视角我们可以看到 Android 系统在运行时的各个细节，比如</p><ol><li>Input 事件是怎么流转的</li><li>你正在使用的 App 的每一帧是怎么从 App 产生到上屏的</li><li>CPU 的实时频率、负载、摆核、唤醒等</li><li>系统中的各个 App 是怎么运行的</li><li>….</li></ol><p>App 开发者和 Android 系统开发者也都会在重要的代码逻辑处加上 Trace 点，打开部分 Debug 选项之后，更是可以得到非常详细的信息，甚至一个 Task 为什么摆在某个 cpu 上，都会有详细的记载。通过这些在 Perfetto 上所展示的信息，我们能初步分析到性能问题的原因，接下来继续分析就会有针对性。</p><p>同样为了说明性能优化的复杂性，可以看看 &lt;<strong>性能之巅**&gt; 这本书中对于性能的描述，具体来说就是方法论，非常贴合本文的主题，也强烈推荐各位搞性能优化的同学，把这本书作为手头常读的方法论书籍：</strong>系统性能工程是一个充满挑战的领域，具体原因有很多，其中包括以下事实，系统性能是主观的、复杂的，而且常常是多问题并存的**</p><h2 id="性能是主观的"><a href="#性能是主观的" class="headerlink" title="性能是主观的"></a>性能是主观的</h2><ol><li>技术学科往往是客观的，太多的业界人士审视问题非黑即白。在进行软件故障查找的时候，判断 bug 是否存在或 bug 是否修复就是这样。bug 的出现总是伴随着错误信息，错误信息通常容易解读，进而你就明白错误为什么会出现了</li><li>与此不同，性能常常是主观性的。开始着手性能问题的时候，对问题是否存在的判断都有可能是模糊的，在问题被修复的时候也同样，被一个用户认为是”不好”的性能，另一个用户可能认为是”好”的</li></ol><h2 id="系统是复杂的"><a href="#系统是复杂的" class="headerlink" title="系统是复杂的"></a>系统是复杂的</h2><ol><li>除了主观性之外，性能工程作为一门充满了挑战的学科，除了因为系统的复杂性，还因为对于性能，我们常常缺少一个明确的分析起点。有时我们只是从猜测开始，比如，责怪网络，而性能分析必须对这是不是一个正确的方向做出判断</li><li>性能问题可能出在子系统之间复杂的互联上，即便这些子系统隔离时表现得都很好。也可能由于连锁故障（cascading failure）出现性能问题，这指的是一个出现故障的组件会导致其他组件产生性能问题。要理解这些产生的问题，你必须理清组件之间的关系，还要了解它们是怎样协作的</li><li>瓶颈往往是复杂的，还会以意想不到的方式互相联系。修复了一个问题可能只是把瓶颈推向了系统里的其他地方，导致系统的整体性能并没有得到期望的提升。</li><li>除了系统的复杂性之外，生产环境负载的复杂特性也可能会导致性能问题。在实验室环境很难重现这类情况，或者只能间歇式地重现</li><li>解决复杂的性能问题常常需要全局性的方法。整个系统——包括自身内部和外部的交互——都可能需要被调查研究。这项工作要求有非常广泛的技能，一般不太可能集中在一人身上，这促使性能工程成为一门多变的并且充满智力挑战的工作</li></ol><h2 id="可能有多个问题并存"><a href="#可能有多个问题并存" class="headerlink" title="可能有多个问题并存"></a>可能有多个问题并存</h2><ol><li>找到一个性能问题点往往并不是问题本身，在复杂的软件中通常会有多个问题</li><li>性能分析的又一个难点：真正的任务不是寻找问题，而是辨别问题或者说是辨别哪些问题是最重要的</li><li>要做到这一点，性能分析必须量化（quantify）问题的重要程度。某些性能问题可能并不适用于你的工作负载或者只在非常小的程度上适用。理想情况下，你不仅要量化问题，还要估计每个问题修复后能带来的增速。当管理层审查工程或运维资源的开销缘由时，这类信息尤其有用。</li><li>有一个指标非常适合用来量化性能，那就是 延时（latency）</li></ol><p><a id="perfetto-intro"></a></p><h1 id="Perfetto-介绍"><a href="#Perfetto-介绍" class="headerlink" title="Perfetto 介绍"></a>Perfetto 介绍</h1><p>Perfetto 是一个高级的开源工具，专为性能监测和分析而设计。它配备了一整套服务和库，能够捕获和记录系统层面以及应用程序层面的活动数据。此外，Perfetto 还提供了内存分析工具，既适用于本地应用也适用于 Java 环境。它的一个强大功能是，可以通过 SQL 查询库来分析跟踪数据，让你能够深入理解性能数据背后的细节。为了更好地处理和理解大规模数据集，Perfetto 还提供了一个基于 Web 的用户界面，使你能够直观地可视化和探索多 GB 大小的跟踪文件。简而言之，Perfetto 是一个全面的解决方案，旨在帮助开发者和性能工程师以前所未有的深度和清晰度来分析和优化软件性能。</p><p>谷歌在 2017 年开始了第一笔提交，随后的 6 年（截止到 2024）内总共有 100 多位开发者提交了近 3.7W 笔提交，几乎每天都有 PR 与 Merge 操作，是一个相当活跃的项目。 除了功能强大之外其野心也非常大，官网上号称它是下一代面向可跨平台的 Trace&#x2F;Metric 数据抓取与分析工具。应用也比较广泛，除了 Perfetto 网站，Windows Performance Tool 与 Android Studio，以及华为的 GraphicProfiler 也支持 Perfetto 数据的可视化与分析。 我们相信谷歌还会持续投入资源到 Perfetto 项目，可以说它应该就是下一代性能分析工具了，会完全取代 Systrace。</p><p>如果你已经习惯使用 Systrace，那么切换到 Perfetto 会非常顺滑，因为 Perfetto 是完全兼容 Systrace 的。你之前抓的 Systrace 文件，可以直接扔到 <a href="https://ui.perfetto.dev/#!/viewer">Perfetto Viewer</a> 网站里面直接打开。如果你还没有适应 Perfetto ，你也可以从 <a href="https://ui.perfetto.dev/#!/viewer">Perfetto Viewer</a> 一键打开 Systrace。</p><p>下图是 Perfetto 的架构图，可以看到 Perfetto 包含了三大块：</p><ol><li><strong>Record traces</strong> ：即数据抓取模块，可以看到抓取的内容和来源非常丰富，Java、 Native 、Linux 都有涉及到，相比 Systrace 要丰富很多。</li><li><strong>Analyze traces</strong> ：主要是 trace 分析模块，包括 Trace 解析、SQL 查询、Metrics 分析等，这部分有专门的命令行工具提供，方便大家直接调用或者在工具链里面去调用。</li><li><strong>Visualize Traces</strong>：Trace 的呈现、抓取等<br><img src="/../images/Android-Perfetto-01-What-is-perfetto/e8dd8f77-bda5-4755-ad47-9c1d5ff3e280.webp"></li></ol><p>这几个模块在后续的系列文章中都会详细介绍</p><h2 id="Perfetto-的核心优势和功能亮点："><a href="#Perfetto-的核心优势和功能亮点：" class="headerlink" title="Perfetto 的核心优势和功能亮点："></a>Perfetto 的核心优势和功能亮点：</h2><p>通过长时间的使用和对比，以及看各种分享，总结了一下 Perfetto 的核心优势和功能两点</p><ol><li><p><strong>支持长时间数据抓取</strong>：</p><ul><li>Perfetto 通过后台服务支持长时间数据抓取，利用 Protobuf 编码存储数据。</li></ul></li><li><p><strong>数据来源与兼容性</strong>：</p><ul><li>基于 Linux 内核的 Ftrace 机制，记录用户空间与内核空间的关键事件。</li><li>兼容 Systrace 的功能，并有望最终取代它。</li></ul></li><li><p><strong>全面的数据支持</strong>：</p><ul><li>支持 Trace、Metric 和 Log 类型的数据。</li><li>提供多种数据抓取方式，包括网页、命令行工具、开发者选项以及 Perfetto Trigger API。</li></ul></li><li><p><strong>高效的数据分析</strong>：</p><ul><li>提供数据可视化网页，支持大文件渲染，优于 Systrace。</li><li>Trace 文件可转换为 SQLite 数据库文件，支持 SQL 查询和脚本执行。</li><li>提供 Python API，允许将数据导出为 DataFrame 格式，为深入分析提供便利。</li><li>支持函数调用堆栈展示。</li><li>支持内存堆栈展示。</li><li>支持 pin 住你感兴趣的行到最上面，不用一直上下翻(通过脚本可以一打开就自动 pin)</li><li>支持可视化 Binder 调用和跳转</li><li>支持很方便的查询唤醒源</li><li>支持 Critical Task 的可视化查询</li></ul></li><li><p><strong>Google 的持续更新</strong>：</p><ul><li>Google 的工具团队在持续更新 Perfetto，版本 Release 和 Bugfix 都非常及时，可以在 Github 上观察。</li></ul></li></ol><p>这里专门提一下 SQL，Perfetto 可以使用 SQL 这是一个巨大的改进，他在解析 Trace 文件的时候，会内建许多 SQL 表和图，方便使用 SQL 语句进行查询，比如下面这几个查询，就是非常实用的（图来自<a href="https://blog.csdn.net/feelabclihu/article/details/126672666?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171017104616800215023623%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171017104616800215023623&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126672666-null-null.nonecase&utm_term=Perfetto&spm=1018.2226.3001.4450">内核工匠</a>）。</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/c86fd19a-cf7b-4fe8-9bef-484c113c69a9.webp"></p><p>另外他的官方文档里面，介绍到对应的部分，也会把对应的 SQL 以及查询结果示例贴出来。</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/481450e4-ace6-4b55-9de4-f74f8ef83aac.webp"></p><p>有了这个，就再也不怕老板说你没有数据了，分分钟 SQL 查出来，表格转图标，一份高质量的 Report 就出来了：优化前后，xxx 指标下降了 xx%，属实是非常方便的。</p><p><a id="vs-systrace"></a></p><h2 id="相比-Systrace-没那么好用的地方"><a href="#相比-Systrace-没那么好用的地方" class="headerlink" title="相比 Systrace 没那么好用的地方"></a>相比 Systrace 没那么好用的地方</h2><h3 id="Vsync-App-没那么直观"><a href="#Vsync-App-没那么直观" class="headerlink" title="Vsync-App 没那么直观"></a>Vsync-App 没那么直观</h3><p>Vsync-App 在 Perfetto 相对来说没那么直观，比如你看习惯了 Systrace 中 Vsync-App 那条贯穿整个 Trace 的竖线，你再看 Perfetto 就没有这个，就会觉得怪怪的：</p><p>Perfetto 中，你可以把 Vsync-App Pin 到最上面来看 Vsync 信息<br><img src="/../images/Android-Perfetto-01-What-is-perfetto/c9cc6e73-3f32-40f5-a1fc-7b06b2d6b436.webp"></p><p>Systrace 中，Vsync 以竖线的方式贯穿整个 Trace，很容易辨别：</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/98a79f53-d52f-49e4-a0a0-f5a07a994038.webp"></p><p><strong>当然 Perfetto 取消这个也是有理由的：Vsync-App 其实并不能说明 App 有性能问题，Perfetto 使用了另外一个方式来展示，如果你用 Perfetto 命令抓的 Trace，就会有下面这个信息，记录了 App 一帧的 Expected Timeli 和 Actual Timeline 。相比 Vsync-App，这两指标更能说明问题</strong>：<a href="https://perfetto.dev/docs/data-sources/frametimeline">原文档</a></p><ol><li>预期时间线每个切片代表应用程序用于渲染帧的时间。为了避免系统出现卡顿，应用程序需要在这个时间范围内完成。开始时间是调度 Choreographer 回调的时间。</li><li>实际时间线这些切片代表了应用程序完成帧的实际时间（包括 GPU 工作）并将其发送到 SurfaceFlinger 进行合成的时间。开始时间是应用程序开始运行的时间。这里的切片的结束时间代表的是。后处理时间是应用程序的帧被发布到 SurfaceFlinger 的时间。</li></ol><p>通过看 Expected Timeli 和 Actual Timeline 的差异，我们可以很快速定位到卡顿的点（红色标识的 Actual Timeline 那一帧就是卡顿）</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/79068cb6-4284-497c-981c-2d1c8082678e.webp"></p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/f7c2d09f-0392-44ae-ad44-6c4e6da1f2c9.webp"></p><p>其计算方式如下，看了图你就知道为什么这两个是更准确的(包含了 GPU 执行时间）</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/39da2304-9f14-4db7-a882-9203bcf37683.webp"></p><p>相对应的，SurfaceFlinger 也有这两个指标</p><h3 id="折叠功能比较烂，比较废屏幕"><a href="#折叠功能比较烂，比较废屏幕" class="headerlink" title="折叠功能比较烂，比较废屏幕"></a>折叠功能比较烂，比较废屏幕</h3><p>如果你是普通的宽屏，打开 Perfetto 随便 Pin 几个关键线程到最上面，你下面的可操作空间就很小了，如果碰到某个关键线程堆栈比较长，那就更是顶级折磨了，而且他这个堆栈还不能折叠（Systrace 可以）</p><p>解决办法：</p><ol><li>少 Pin 几个关键线程 （那还有啥乐趣）</li><li>把显示器竖起来（宽度就打折了）</li></ol><p>最终我们找到了完美的方案：换成 LG 那个魔方屏幕，16:18 ，看 Perfetto 简直是绝配（办公室已经被我安利有三台了）</p><p><img src="/../images/Android-Perfetto-01-What-is-perfetto/1689331a-b48c-4d4b-8e1a-65d64945ddd5.webp" alt="28MQ780"></p><ol><li>不差钱：<a href="https://item.jd.com/67812631556.html">LG 28MQ780 - 3599</a></li><li>平替：[联合创新 28C1Q - 2999](<a href="https://item.jd.com/100058985199.html%EF%BC%89">https://item.jd.com/100058985199.html）</a></li></ol><p><a id="refs"></a></p><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ol><li><a href="https://github.com/google/perfetto">Perfetto Github 库</a></li><li><a href="https://perfetto.dev/docs/">Perfetto 官方文档</a></li><li><a href="https://blog.csdn.net/feelabclihu/article/details/126672666?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171017104616800215023623%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171017104616800215023623&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-126672666-null-null.nonecase&utm_term=Perfetto&spm=1018.2226.3001.4450">内核工匠 - Perfetto 进阶</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本篇是 Perfetto 系列文章的第一篇，主要是简单介绍 Perfetto 工具，包括 Perfetto 的历史、发展，以及 Perfetto 能做什么。&lt;/p&gt;
&lt;p&gt;随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Perfetto 系列目录</title>
    <link href="https://androidperformance.com/2024/03/27/Android-Perfetto-101/"/>
    <id>https://androidperformance.com/2024/03/27/Android-Perfetto-101/</id>
    <published>2024-03-27T14:29:31.000Z</published>
    <updated>2026-02-07T05:17:47.838Z</updated>
    
    <content type="html"><![CDATA[<p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><span id="more"></span><p>所以就有了这个系列，我也有在星球里面写了几条为什么要更新 Perfetto 系列的原因（之前一直觉得 Systrace 系列就够了）：</p><ol><li>目前 Oppo、Vivo 这些手机厂商内部，都已经切换成 Perfetto 了，不管是抓 Trace 还是看 Trace，都在使用 Perfetto ，很多新人接触的都是 Perfetto 而不是 Systrace ，守着之前的老 Systrace 系列会流失这部分读者</li><li>之前的 Systrace 系列，对应的 Code 已经比较老了，全新的 Perfetto 系列可以使用 Android 14 的 Code 来进行更新</li><li>个人对 Perfetto 的使用也没有很深入，有些高阶功能目前还只是浅尝辄止。可以通过重写 Perfetto 系列来进行这部分内容的强化</li><li>Perfetto 是个很强大的工具，他的背后是整个 Android + Linux 系统，所以在写这个系列的时候，应该是以他背后的这个 Android + Linux 为主，而不是仅仅局限于 Perfetto 这个工具。工具只是我们观测 Android + Linux 的方式，理解整个 Android 系统运行的规律，思考其运行的原理，通过工具挖掘问题，通过问题思考本质，这才是对开发者来说有意义的</li><li>很多 Android 系统运行相关的内容，Perfetto 的官方文档还是没有讲，这部分我这边可以作为补足；另外官方文档是英文版本的，中文博客可以补充这方面。</li><li>Perfetto 系列写好了，可以拿到 Google Dev Fest 上作为演讲内容～。</li></ol><p>Paul Graham 说：<strong>要么给大部分人提供有点想要的东西，要么给小部分人提供非常想要的东西</strong>。Perfetto 其实就是小部分人非常想要的东西，那就开始写吧，欢迎大家多多交流和沟通，发现错误和描述不准确的地方请及时告知我，我会及时修改，以免误人子弟。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p>欢迎大家在 <a href="https://www.androidperformance.com/about/">关于我</a> 页面加入微信群或者星球，讨论你的问题、你最想看到的关于 Perfetto 的部分，以及跟各位群友讨论所有 Android 开发相关的内容</p><h1 id="Systrace-系列"><a href="#Systrace-系列" class="headerlink" title="Systrace 系列"></a>Systrace 系列</h1><p>另外 Systrace 工具尽管已经不更新了，但是之前的 Systrace 系列文章，内容依然没有过时，还是有很多公司在使用 Systrace 来分析各种系统问题，Systrace 工具是分析 Android 性能问题的利器，它可以从一个图形的角度，来展现整机的运行情况。Systrace 工具不仅可以分析性能问题，用它来进行 Framework 的学习也是很好的。</p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 产生与工作机制解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>  </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>2023 年的方方面面</title>
    <link href="https://androidperformance.com/2024/01/01/2023-review/"/>
    <id>https://androidperformance.com/2024/01/01/2023-review/</id>
    <published>2023-12-31T16:08:53.000Z</published>
    <updated>2026-02-07T05:17:47.818Z</updated>
    
    <content type="html"><![CDATA[<p>今年偷个懒，找了个模版，主要从以下几个方面回顾过去一年：健康 &#x2F; 锻炼、工作 &#x2F; 职业、友情 &#x2F; 社交、个人生活 &#x2F; 家庭、学习 &#x2F; 知识管理、旅游 &#x2F; 文化、兴趣 &#x2F; 创造、情绪 &#x2F; 精神状况、财务状况。</p><p>内容更多是自己对于 2023 年的一个记录，算不上总结，文采也不好。不过很多事情，如果你不记录，就慢慢消失了。希望每次我翻看这篇记录的时候，都会感慨 2023 年真是丰富多彩的一年：有难忘的瞬间、有低谷、有朋自远方来、有去看大山大川；也会感慨有些事情做得很糟糕，如此这般其实可以做得更好；也会责备自己的懒惰，明明知道怎么做是对的，却因为懒惰没有去坚持。</p><p>各位看官一笑而过即可～</p><span id="more"></span><h2 id="健康-x2F-锻炼"><a href="#健康-x2F-锻炼" class="headerlink" title="健康 &#x2F; 锻炼"></a>健康 &#x2F; 锻炼</h2><p>今年由于公司有骑行活动比赛，持续一整年，再加上成都环城绿道去年贯通，今年一整年的活动就是骑行。年初在闲鱼收了一个美利达瑞克多 4000，准备今年好好骑车。年初的目标是一圈（97km）骑到 3h 内，挑战目标是 2h 50min，没想到最终都达到了（总共骑了 35 圈绿道，最快用时是 2h 47min），如愿以偿在公司内部比赛中拿了第一名。同时把媳妇也带进了坑，一起骑了几圈。</p><p>自身叠加的那些 Debuff：哮喘、鼻炎，并没有明显的改善；不过最值得开心的是年底由于肺炎+甲流，住了 4 天院，打了四天吊瓶之后，鼻炎导致的味觉消失也被治好了，同时血氧也恢复到了 96% 以上（之前 always 在 94 以下，给我焦虑得不行）。成都的空气质量真是一言难尽，连华西的医生都直摇头，这一点想移居成都的同学可以慎重考虑一下。</p><p>新的一年还是得 骑车+跑步 一起抓，报了个都江堰半马，希望能完赛。减肥依然是主旋律，管住嘴迈开腿很重要，睡前保持饥饿感～</p><p><img src="/../images/2023-review/8d4ac346-1fff-4561-bf77-149bb5f483d8.webp" alt="2023 年骑行数据"></p><h2 id="工作-x2F-职业"><a href="#工作-x2F-职业" class="headerlink" title="工作 &#x2F; 职业"></a>工作 &#x2F; 职业</h2><p>工作方面，今年不算很顺利，做的东西很杂，很多东西也没有最终落地，自己总结了一下，还是因为缺乏规划能力和执行力。不过也算积累了很多知识和做事方法，调研了很多技术方案，与客户也合作完成了几个项目。另外公司内部卧虎藏龙，需要虚心向每一个人学习。</p><p>AI 今年算是整了个大新闻，不管是公司还是个人，工作流中都插入了 AI 相关的内容：利用 AI 提升工作效率、创造 AI 相关的工具。不过由于公司性质，AI 还没有产生那种颠覆性的影响，不过我们都知道这一天很快就会来临。</p><blockquote><p>“一种新技术一旦开始流行，你要么坐上压路机，要么成为铺路石。 —— Stewart Brand”</p></blockquote><h2 id="友情-x2F-社交"><a href="#友情-x2F-社交" class="headerlink" title="友情 &#x2F; 社交"></a>友情 &#x2F; 社交</h2><p>今年最重要的一次社交可能就是从成都去深圳参加 深圳 GDG DevFest 技术嘉年华，见到了几位老朋友和各位大佬，吃到了心心念念的砂锅粥和牛肉火锅，跟群里的小伙伴面了基，听了各位 GDE 的主题演讲，可谓是收获满满。</p><p><img src="/../images/2023-review/9d2d5e7b-bd3c-480e-9f6b-4f6d58712e4d.jpg"></p><p>剩下的时间就是跟公司的几个小伙伴骑车了，能让大家周五晚上 12 点还在绿道上疯狂拉扯的，只有骑完之后那一顿火锅&#x2F;麻辣烫了。@James @Hanzo @天宇 @宣总 @王 @陈大 @祥大 @果哥 @鹏哥 新的一年继续拉扯～</p><p>@福滕 和 @小陈 有了小宝宝，@周岩 的新房居然提前交房了（不过依然没有女朋友，狗头～）～，@老崔 跟 @和泉 都在深圳安家了。</p><p>交流最多的还是跟微信群里的小伙伴们，基本都是通过微信公众号或者博客添加的，基本都是 Android App 开发者或者国内的系统开发者，5 个群大概 2000 多人吧（虽然 90% 都是资深潜水员）。通过微信群认识了很多业内的大佬，手机圈本身就小，多个朋友多个家（狗头～），同时也感叹真是山外有山，人外有人，啥都别说了学就是了。</p><p>最大的感受是：最好跟在同一个频道上的人沟通，会减少很多沟通的成本。</p><h2 id="个人生活-x2F-家庭"><a href="#个人生活-x2F-家庭" class="headerlink" title="个人生活 &#x2F; 家庭"></a>个人生活 &#x2F; 家庭</h2><p>小宝宝橘橘快三岁了，橘橘妈也换了新的组（跟我做相同的工作：系统性能优化），一家人开开心心平平安安，就是最大的心愿了。今年由于各种原因，在家里陪宝宝和家人的时间有点少，出游计划也多被搁置。新的一年要提升效率，把时间多用在陪伴家人。</p><p>今天读到一个博主的 <a href="https://aoxiang.me/30/12/2023/the-end-of-2023/">2023 年终总结</a>，其中一段话让我感触很深，这才是生活的最终目标，不要本末倒置：</p><blockquote><p>我希望多丰富一些体验，做一些没做过的事情，看一些没看过的风景。我希望 <strong>有一天可以把时间用在自己身上，而不是用在工作上，不是在那些不重要的人，或者事身上</strong>。更具体一点，我希望家人身体健康、平平安安，有爱，有陪伴，我也希望有一天可以走遍中国、走遍世界，看那些没看过的风景，吃那些没吃过的美食。</p><p>当我想到这里，我豁然开朗，我知道我终其一生追求的是什么，剩余的，都不重要的。那么，我怎样才可以？我需要时间，我需要钱。</p><p>从此开始，我所有的事情都围绕着这个目的展开，<strong>只要某件事情，对我的人生最终目的是有推进作用，无论多么艰难，我都必须做，是的，必须，没有任何商量的余地</strong>。</p></blockquote><h2 id="学习-x2F-知识管理"><a href="#学习-x2F-知识管理" class="headerlink" title="学习 &#x2F; 知识管理"></a>学习 &#x2F; 知识管理</h2><p>今年学习方面一败涂地，英语学习基本上停滞，最后这个月才重新拾起来；技术学习更多停留在读，写和思考总结都比较少；文化学习，比如阅读，书架上的书很多，读完的很少；博客也只更新了几篇文章，费曼学习法那种输出倒逼输入，道理都懂，还是没有克服拖延症。</p><p>工作中倒是养成了一个习惯：在公司内部使用 Typora 记录每天的工作内容、技术专项、技术调研、Bug 记录等；新的一年切换到 Loop 之后，我会在上面更详细地记录和思考，以及组内分享，带动组内的技术氛围（今年年初吹下的牛皮年底了最终还是没有实现）。</p><p>从我自己的学习经历来看，学习有三大障碍：</p><ol><li><strong>如何获取优秀的输入源</strong>：互联网如此发达的年代，如何获取优秀的输入源很重要，这就需要在长期的学习过程中，记录那些优秀的人的博客、优质的 Github 库、高质量的周报、专注在技术输出上的技术团队等（话说我这篇文章：<a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">Android 性能优化必知必会</a> 本意也是搜集这些内容的，欢迎大家推荐和自荐)。</li><li><strong>静下心来把一个知识点吃透</strong>：短视频类的快消品在争夺人的注意力的同时，也把我们的耐心消耗殆尽，静下心来阅读和思考一篇文章、一个知识点、一本书，都成了一个很不容易的事情。这个只能自己锻炼自己，远离社交网络和短视频，重新夺回注意力，把心思放在真正需要的地方。</li><li><strong>克服拖延症</strong>：我们常说，道理都懂，依然过不好，拖延症在里面功不可没。今天看到张朝阳分析治疗拖延症的方法，分析出来与君共勉：<strong>拖延症的关键关键就在于拖延本质上是因为不熟悉这件事情想要逃避，因而产生恐惧和焦虑，那战胜它的方法就是在想要拖延时，先在脑子里过一遍要做的事情，梳理好脉络，构想出细节，让自己的神经元兴奋，立即行动。这样可以战胜拖延，并感受到正反馈</strong></li></ol><p>年初看了李自然的一个视频：人生如逆旅，我亦是行人，视频主要是讲了下面几个点，当时感触很深，也分享给大家：<a href="https://www.youtube.com/watch?v=JGHTcNmCcLU">【李自然说】人生如逆旅，我亦是行人</a></p><ol><li>不给自己设限，能快速融入到更有潜力的行业</li><li>向顶级的人拼命地学习</li><li>拼命地打开自己的视野和格局</li><li>拥抱变化，甚至主动去求变</li><li>一直坚持做，就会有一个长期的口碑</li><li>提高容错率</li></ol><h2 id="旅游-x2F-文化"><a href="#旅游-x2F-文化" class="headerlink" title="旅游 &#x2F; 文化"></a>旅游 &#x2F; 文化</h2><p>今年去了四个地方：四姑娘山，厦门，烟台+威海，乐山。</p><ol><li><strong>厦门</strong>：团队旅游目的地，看海，吃沙茶面，吃姜母鸭，不得不说厦门这天气太让人羡慕了，环海路也可以骑车，市内还有步道（可惜没去）。</li><li><strong>烟台+威海</strong>：烟台+威海，先去参加 @小婉 的婚礼，然后回母校去看看见了几位老师和老同学，韩餐和海鲜都吃到了，环海路依然风景漂亮，学校后面的沙滩落日很美～</li><li><strong>四姑娘山</strong>：开了一天车去到处于川西的四姑娘山，路上要经过海拔 4800 的垭口，宝宝和媳妇高反，没有怎么玩，很快就回来了。我们去的那天晚上下了雪，第二天到了景区后拍照很漂亮，我录了视频在这里：<a href="https://www.bilibili.com/video/BV1H84y1Q7oM">https://www.bilibili.com/video/BV1H84y1Q7oM</a> ，漂亮是真漂亮，值得再去一趟～</li><li><strong>乐山</strong>：看乐山大佛和吃小吃～甜皮鸭和炸串真好吃～</li></ol><p>带着宝宝出去玩虽然很折腾，但是还是很值的，宝宝在一路上会认识很多新的东西，交新的朋友，吃到新的食物。2024 依然会继续带着宝宝折腾～</p><h2 id="兴趣-x2F-创造"><a href="#兴趣-x2F-创造" class="headerlink" title="兴趣 &#x2F; 创造"></a>兴趣 &#x2F; 创造</h2><p>终于来到轻松一点的环节了，今年主要兴趣是骑车，购置了一台大疆 Action4 来拍骑车时候的视频，要技术没有，都是一镜到底，创造力这玩意是真得有天赋才行。</p><h2 id="情绪-x2F-精神状况"><a href="#情绪-x2F-精神状况" class="headerlink" title="情绪 &#x2F; 精神状况"></a>情绪 &#x2F; 精神状况</h2><p>自我感觉良好，除了稍微有一些焦虑。自我感觉良好很大程度上得益于家庭的支持，有爸妈在家帮忙看宝宝，我和媳妇双 it，很多决定媳妇都会坚定支持。成都美食比较多，比较杂，想吃哪种都可以吃到，玩的地方也比较多，川西、古镇、都江堰青城山，去重庆也不远；天府新区高新区这边也不怎么排外，呆着很舒服（除了雾霾，这个真的是负分）</p><p>2024 继续保持吧，心态要好，睡眠要好。</p><h2 id="财务状况"><a href="#财务状况" class="headerlink" title="财务状况"></a>财务状况</h2><p>房贷还剩余比较多，每个月工资大半都交给银行了，除了夫妻两个的工资基本没有其他的收入，这也是我目前比较焦虑的点，一旦遇到被辞退或者其他变故，周转就会有困难。所以最终目标还是要管好自己的钱袋子，控制花销。看了一个说法，分别是穷人、中产阶级、富人的资产负债情况：</p><ul><li>穷人疲于奔命，收入被衣食住行消耗殆尽。</li><li>中产阶级深陷「老鼠赛跑」的陷进，为各种账单、贷款奋斗半辈子。</li><li>富人买入并持有优质资产，形成「钱 → 资产 → 钱」的良性循环。</li></ul><p>所以最终还是要持有 <strong>优质资产</strong>，我理解这里的优质资产可以是房产，也可以是知识。结合个人的情况，我思索再三，又重新开启了 <strong>知识星球</strong>，简介在这里 <a href="https://androidperformance.com/2023/12/30/the-performance/">The Performance 知识星球简介</a>，我并没有做宣传，只在博客中有介绍。我觉得目前最有效的两种学习方法：费曼学习法和公开学习法，知识星球都可以满足我，我会把日常的一些思考、案例、学习的进度、工具等分享到星球上；如果在这个过程中，有些知识能帮助到你，那么我觉得就值了。</p><p><img src="/../images/2023-review/43ec5c99-9264-440c-9490-e3cb4082bb26.webp"></p><h1 id="最后三项"><a href="#最后三项" class="headerlink" title="最后三项"></a>最后三项</h1><h2 id="最骄傲的成就"><a href="#最骄傲的成就" class="headerlink" title="最骄傲的成就"></a>最骄傲的成就</h2><p>今年可能最值得拿出来说的就是去了 电子科技大学 ，给研究生们上了三天的课，蹭的当然是公司跟企业合作这个便利，不过过程还是很值得一说的：从接到这个任务，到开始备课（其实就是学习 @鹏哥 去年准备的 PPT），再到每个知识点的学习，再到真正去讲课，还是充满了挑战的，毕竟是第一次，还好没有翻车。</p><p>今年好像还是我，这次就没那么紧张了，不过去年的内容要好好更新一下，讲课的方式需要再优化优化，知识点该深入的还是要继续深入，毕竟 Android 从最上层到最底层都讲清楚也不是那么容易的.</p><h2 id="最大的挑战"><a href="#最大的挑战" class="headerlink" title="最大的挑战"></a>最大的挑战</h2><p>工作方面的就不说了，除了工作方面，最大的挑战应该还是英语口语了，今年有好几次需要用到英语口语的时候，结果都不是那么好，这也刺激我要好好学英语。这个感觉要放到 2024 年最重要的 Task 里面，目前也有一些小的规划在实施中。</p><h2 id="明年的目标和愿望"><a href="#明年的目标和愿望" class="headerlink" title="明年的目标和愿望"></a>明年的目标和愿望</h2><ol><li>多陪陪家人，做一些之前没有做过的事情，去一些之前没有去过的地方。</li><li>至少做一次 Google Dev Fest 级别的公开分享</li><li>出一次国</li><li>坚持把知识星球做大做强</li><li>坚持锻炼身体，一次马拉松完赛</li><li>多看书</li><li>坚持每 2 周更新博客，提早把 Perfetto 系列写完</li><li>录视频</li></ol><p>文笔有限，借用 aoxiang 的话来做个结尾吧：</p><blockquote><p>很感谢对我的认可，你花时间看到这里，我很希望这篇文章对你有价值。同时，我也很希望你能想清楚「你想要过什么样的人生」，当你有了这个判定标准，你任何一个抉择，都会无比轻松。</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今年偷个懒，找了个模版，主要从以下几个方面回顾过去一年：健康 &amp;#x2F; 锻炼、工作 &amp;#x2F; 职业、友情 &amp;#x2F; 社交、个人生活 &amp;#x2F; 家庭、学习 &amp;#x2F; 知识管理、旅游 &amp;#x2F; 文化、兴趣 &amp;#x2F; 创造、情绪 &amp;#x2F; 精神状况、财务状况。&lt;/p&gt;
&lt;p&gt;内容更多是自己对于 2023 年的一个记录，算不上总结，文采也不好。不过很多事情，如果你不记录，就慢慢消失了。希望每次我翻看这篇记录的时候，都会感慨 2023 年真是丰富多彩的一年：有难忘的瞬间、有低谷、有朋自远方来、有去看大山大川；也会感慨有些事情做得很糟糕，如此这般其实可以做得更好；也会责备自己的懒惰，明明知道怎么做是对的，却因为懒惰没有去坚持。&lt;/p&gt;
&lt;p&gt;各位看官一笑而过即可～&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="年终总结" scheme="https://androidperformance.com/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    
    <category term="知识星球" scheme="https://androidperformance.com/tags/%E7%9F%A5%E8%AF%86%E6%98%9F%E7%90%83/"/>
    
  </entry>
  
  <entry>
    <title>关于 The Android Performance 知识星球介绍</title>
    <link href="https://androidperformance.com/2023/12/30/the-performance/"/>
    <id>https://androidperformance.com/2023/12/30/the-performance/</id>
    <published>2023-12-30T00:59:18.000Z</published>
    <updated>2026-02-07T05:17:47.880Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>目前星球为付费模式，星球收入主要是是赚个博客的服务器费用以及给家里的豆子（猫）买个猫粮，同时也是我更新博客的动力。如果觉得内容还不错，就加入星球支持一波吧～ 非常感谢～</p></blockquote><p>知识星球名为 <strong>The Performance</strong>，一个分享 Android 开发领域性能优化相关的圈子，主理人是博主自己，国内一线手机厂商性能优化方面的一线开发者，有多年性能相关领域的知识积累和案例分析经验，可以提供性能、功耗分析知识的一站式服务，涵盖了基础、方法论、工具使用和最宝贵的案例分析。</p><span id="more"></span><p>随着 Android 的发展，性能优化成了所有 Android 手机厂商和 App 厂商的重中之重。然而性能优化又是一个非常宽泛的话题，涉及到的知识非常多，从底层 Kernel 到 Framework 再到 App，每一个环节都有大量的知识点需要了解。所以笔者团队建立了这个专门分享和讨论 Android Performance 相关的技术圈：提供高质量的技术分享和技术讨论，提供系统性的 Android 性能优化相关知识的学习。</p><p>所有加入星球的同学，都可以畅所欲言，分享知识和案例，提问或者解答他人的问题，以问题分析带动学习，共同学习，共同进步，所有人都可以分享和讨论下面话题相关的内容，或者享受相关的权益。</p><p>目前星球的内容规划如下（两个 ## 之间的是标签，相关的话题都会打上对应的标签，方便大家点击感兴趣的标签查看对应的知识），由于改为个人经营，精力有限，划线部分是暂时删除。</p><ul><li><strong>Trace 分析</strong> - 加入星球后，可以提供 1v1 的 Trace 教学和分析（Systrace、Perfetto、SimplePerf 等，需要提前预约），另外也会提供各种 Trace 相关的案例分析。</li><li><del><strong>#The Performance#</strong> — 可以提早阅读<strong>「Android 性能优化 - 系统性课程」</strong>的电子书，每周会放出已经写好的章节。「<strong>Android 性能优化 - 系统性课程</strong>」 是我们规划的一本讲 Android 性能优化的电子书，目前开发者社区有相当多高质量的性能优化理论知识和实践文章和开源库，但是目前市面上缺乏一个<strong>完整的</strong>、<strong>系统性的</strong>、<strong>包含了性能优化原理</strong>、<strong>工具</strong>、<strong>实践等内容</strong>、<strong>面向初级开发中和中级开发者</strong>、<strong>面向 App 开发者和系统开发者</strong>，且持<strong>续更新</strong>的 Android 性能优化工具书。书的大纲 (暂定) 我们已经基本上列好了，预计会花费一年左右的时间来完成，在星球中会放出写好的章节，让大家提前看到，也欢迎大家一起进行 Review</del></li><li><strong>#性能工具#</strong> — 分享 Android 开发中使用到的性能分析工具以及其使用方法，同时也提供 1V1 的 Systrace、Perfetto 等性能工具的视频指导。性能工具的使用，最好还是以视频的方式展示会直观很多，文章是静态的，很多地方比较难讲清楚，1V1 的视频会议指导也算是一个学习的方法。</li><li><strong>#案例分析#</strong> — 典型案例分析思路总结、球友提供的案例分析与讨论。案例分析是学习的一个很重要的途径，阅读大量的实际性能案例对以后自己分析和解决性能问题是非常有帮助的，同时也欢迎大家提供案例和解决方法，怕泄露信息的话，我们会对关键信息进行打码。</li><li><strong>#经典解读#</strong> — 经典方案、课程重读，例如优秀的三方库解析、Android 开发高手课重读等。比如可以对方案进行深度的剖析，横向对比等；对 Android 开发高手课进行重读和查漏补缺。</li><li><strong>#知识分享#</strong> — 优秀文章、博客、工具分享。业界有很大大牛的博客、经过实际业务考验的开源方案、各种性能工具等，我们会寻找这些优秀的内容，分享给大家。</li><li><strong>#知识沉淀#</strong> — 微信群聊精华、微信问答、博客留言解答等。</li><li><strong>#性能面试#</strong> — Android 性能相关的面试题搜集和解答，也算是刚需了吧。</li><li><strong>#编程语言#</strong> — 编程语言相关的使用技巧分享。</li><li><strong>#效能提升#</strong> — 效能提升分享，包括开发者开发效能、工作效能提升方法、工程效率、工具推荐等，磨刀不误砍柴工嘛。</li><li><strong>#行业动态#</strong> — 性能相关新技术第一时间解读报告，包括但不限于下面的内容。<ul><li>行业峰会、学术峰会新思路解读报告</li><li>论文、行业、书籍介绍、视频</li><li>Android 大版本性能相关介绍</li><li>Android 新硬件性能相关内容介绍</li><li>Android 性能相关开源项目解读</li></ul></li><li><strong>#大咖分享#</strong> — 每月定期邀请行业大咖进行经验分享、案例分析。</li><li><strong>#工作内推#</strong> — 各大厂商内推工作机会介绍。</li></ul><h1 id="付费星球二维码"><a href="#付费星球二维码" class="headerlink" title="付费星球二维码"></a>付费星球二维码</h1><p>微信扫码加入即可（iOS 用户最好用微信扫码）<br><img src="/images/17039900286551.jpg" alt="付费知识星球"></p><h1 id="免费星球二维码"><a href="#免费星球二维码" class="headerlink" title="免费星球二维码"></a>免费星球二维码</h1><p>当然星球还有一个免费的版本，定位是知识分享，感兴趣的也可以加入</p><p><img src="/images/the-performance/image-20231126142045297.webp" alt="image-20231126142045297"></p><h1 id="微信公众号"><a href="#微信公众号" class="headerlink" title="微信公众号"></a>微信公众号</h1><p>当然也欢迎关注微信公众号～</p><p><img src="/images/the-performance/WechatIMG581.webp" alt="微信扫一扫"></p><h1 id="微信群"><a href="#微信群" class="headerlink" title="微信群"></a>微信群</h1><p>新的微信交流群，没有加之前微信群都可以加这个，方便平时随时沟通和大家相互之间讨论</p><p>微信群旨在讨论 Android 及其相关的技术话题，包括但不限于 Android 性能优化课题（响应速度、流畅度、ANR、Crash、内存、耗电、性能监控等）Android App 开发、Framework 开发、Linux、大前端、面试分享、技术招聘等话题</p><p>鉴于群里的各位大佬们时间都很宝贵，大家聊天的内容尽量与上面的主题相关，禁止吹水、装机、购机、手机厂商优劣讨论……否则群会被贴上水群的标签，希望大家共同维护群氛围</p><p>另外还有 闲聊吹水群、跑步群、读书群、GPT 群，里面大家就随意发挥了，可以私我拉进去</p><p>如果群满 200 或者二维码失效，可以加我微信拉各位进去（553000664）</p><p><img src="/images/17039901616259.jpg"></p><h1 id="个人其他信息"><a href="#个人其他信息" class="headerlink" title="个人其他信息"></a>个人其他信息</h1><ol><li>博客地址：<a href="https://www.androidperformance.com/">https://www.androidperformance.com/</a></li><li>免费知识星球：<a href="https://t.zsxq.com/ZZ337Am">https://t.zsxq.com/ZZ337Am</a></li><li>付费知识星球 ：<a href="https://t.zsxq.com/Fuvvf6y">https://t.zsxq.com/Fuvvf6y</a></li><li>知乎地址：<a href="https://www.zhihu.com/people/gracker">https://www.zhihu.com/people/gracker</a>  </li><li>即刻：<a href="https://okjk.co/pJbjFa">https://okjk.co/pJbjFa</a></li><li>Android Weekly ：<a href="https://androidweekly.zhubai.love/">https://androidweekly.zhubai.love/</a></li><li>微信公众号：AndroidPerformance</li><li>掘金 ：<a href="https://juejin.cn/user/1816846860560749">https://juejin.cn/user/1816846860560749</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;目前星球为付费模式，星球收入主要是是赚个博客的服务器费用以及给家里的豆子（猫）买个猫粮，同时也是我更新博客的动力。如果觉得内容还不错，就加入星球支持一波吧～ 非常感谢～&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;知识星球名为 &lt;strong&gt;The Performance&lt;/strong&gt;，一个分享 Android 开发领域性能优化相关的圈子，主理人是博主自己，国内一线手机厂商性能优化方面的一线开发者，有多年性能相关领域的知识积累和案例分析经验，可以提供性能、功耗分析知识的一站式服务，涵盖了基础、方法论、工具使用和最宝贵的案例分析。&lt;/p&gt;</summary>
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>The Performance Design Of OS</title>
    <link href="https://androidperformance.com/2023/10/15/the-performance-design-of-os-en/"/>
    <id>https://androidperformance.com/2023/10/15/the-performance-design-of-os-en/</id>
    <published>2023-10-15T07:28:03.000Z</published>
    <updated>2026-02-07T05:17:47.879Z</updated>
    
    <content type="html"><![CDATA[<h1 id="1-Origin"><a href="#1-Origin" class="headerlink" title="1 Origin"></a>1 Origin</h1><p>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.</p><p>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.</p><span id="more"></span><p>In my opinion, the core differences between Android and iOS from the OS perspective are primarily manifested in:</p><ol><li>The IPC mechanism between applications and core services</li><li>Platform development environment, including programming languages, IDE tools, and the construction of the developer ecosystem</li><li>Application lifecycle management mechanisms and strategies</li><li>The runtime organizational structure of the kernel and core services</li></ol><p>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.</p><p>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.</p><p>We can summarize that:</p><ol><li>Under different stages and constraints of various considerations, software architectural designs on different projects may differ.</li><li>During the entire cycle of software product iteration, such design decisions are always evolving.</li></ol><p>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.</p><h1 id="2-Mechanisms-and-Policies"><a href="#2-Mechanisms-and-Policies" class="headerlink" title="2 Mechanisms and Policies"></a>2 Mechanisms and Policies</h1><p>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.</p><p>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.</p><p>In discussing the OS, it’s crucial to recognize that the vast majority of modern OSes consist of a kernel and system services.</p><ul><li>In macOS&#x2F;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.</li><li>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.</li></ul><p>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.</p><p>An increasingly common understanding is that the OS’s mechanisms, policies, and closely associated application programs vary greatly depending on the application scenario.</p><p>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.</p><p>This leads to another classic OS design concept: mechanism and policy.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>The OS for smartphones and smart cars probably faces a similar scenario—they cannot be directly transplanted.</p><p>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.</p><p>Now, narrowing it down to Android and iOS, where do their strategy designs originate?</p><h1 id="3-Butts-Decide-Heads"><a href="#3-Butts-Decide-Heads" class="headerlink" title="3 Butts Decide Heads"></a>3 Butts Decide Heads</h1><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>The most significant difference between iOS and Android stems from their respective strategies, rooted in their initial service objectives and developmental goals. </p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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:</p><ol><li>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.</li><li>Serve app developers well, assist them in improving app quality, and even identify and diagnose app issues.</li><li>Provide faster and more user-friendly APIs to efficiently meet the needs of app developers.</li><li>An excellent IDE tool can serve developers well, enabling the development of superior apps, and ensuring the OS’s survival.</li></ol><p>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.</p><h1 id="4-Strategy-of-“Overload-Protection”"><a href="#4-Strategy-of-“Overload-Protection”" class="headerlink" title="4 Strategy of “Overload Protection”"></a>4 Strategy of “Overload Protection”</h1><p>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.</p><p>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.</p><p>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).</p><p>In contrast, iOS has a plethora of mechanisms ranging from CPU, memory, to even restrictions on excessive IO writes, including:</p><ul><li>Termination when the device overheats</li><li>Termination of VoIP class applications when there are excessive CPU awakenings</li><li>Termination during BackgroundTask execution if CPU use exceeds a threshold</li><li>Termination if BackgroundTask is not completed within the specified time</li><li>Termination if a program’s thread exceeds CPU use threshold</li><li>Termination if a program’s disk write volume exceeds a threshold</li><li>Termination if program’s inter-thread interactions within a unit time exceed a threshold</li><li>Termination if a program’s memory usage is exceeded</li><li>Termination under excessive system memory pressure</li><li>Termination if a program opens too many files</li><li>Termination during PageCache Thrashing</li></ul><p>iOS outlines these behaviors in developer documentation to clarify the reasons for unexpected application exits.</p><p>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.</p><p>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.</p><p>In an ideal world:</p><ol><li>Overload protection mechanisms should be documented and explained in application development guides.</li><li>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).</li><li>Manufacturers should provide convenient and user-friendly debugging tools for developers to fix issues locally during development.</li><li>Developers should be mandated to fix issues when they exceed the quality standards set by the manufacturers, failing which their applications should be delisted.</li></ol><p>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.</p><p>The fault, in this case, is at the cognitive level.</p><h1 id="5-Strategy-on-“Lifecycle-Management”"><a href="#5-Strategy-on-“Lifecycle-Management”" class="headerlink" title="5 Strategy on “Lifecycle Management”"></a>5 Strategy on “Lifecycle Management”</h1><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>Summarizing the principles beyond the technology:</p><ul><li>Mobile devices have many constraints; therefore, application “freedom” must be restricted but not completely cut off, requiring corresponding solutions.</li><li>Common tasks among applications should be provided uniformly by the system, saving overall system load, especially crucial for devices with many constraints.</li><li>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.</li></ul><p>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.</p><h1 id="6-Above-Design"><a href="#6-Above-Design" class="headerlink" title="6 Above Design"></a>6 Above Design</h1><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>However, all these are contingent upon the designers’ ability to keep pace with their cognition.</p><p>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.</p><blockquote><p>“I leave my sword to those who can wield it.” - Charlie Munger</p></blockquote><h1 id="About-Me-amp-amp-Blog"><a href="#About-Me-amp-amp-Blog" class="headerlink" title="About Me &amp;&amp; Blog"></a>About Me &amp;&amp; Blog</h1><ol><li><a href="https://www.androidperformance.com/about/">About Me</a>: I am eager to interact and progress together with everyone. </li><li><a href="https://twitter.com/Gracker_Gao">Follow me on Twitter</a></li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">Blog Content Navigation</a></li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">Record of Excellent Blog Articles - Essential Skills and Tools for Android Performance Optimization</a></li></ol><p><strong>An individual can move faster, a group can go further.</strong></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;1-Origin&quot;&gt;&lt;a href=&quot;#1-Origin&quot; class=&quot;headerlink&quot; title=&quot;1 Origin&quot;&gt;&lt;/a&gt;1 Origin&lt;/h1&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
    <category term="iOS" scheme="https://androidperformance.com/tags/iOS/"/>
    
    <category term="Design" scheme="https://androidperformance.com/tags/Design/"/>
    
  </entry>
  
  <entry>
    <title>OS 设计之性能设计</title>
    <link href="https://androidperformance.com/2023/08/21/the-performance-design-of-os/"/>
    <id>https://androidperformance.com/2023/08/21/the-performance-design-of-os/</id>
    <published>2023-08-21T10:30:48.000Z</published>
    <updated>2026-02-07T05:17:47.879Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文是之前星球里 Yingyun 大佬的文章，由于星球已经关闭，所以把这个关于 OS 性能设计的系列文章发到博客上</p><p>Yingyun 是资深性能优化专家，他对于系统优化有非常深入的见解，本身在国内各个手机大厂都呆过，他本人的博客还在休整中，等休整好了我再发出来，目前他在我们的微信群里很活跃，对本文有什么建议或者意见，或者说想咨询问题的可以加我们的微信群（加我微信 553000664，备注博客加群，我会拉你进去）</p></blockquote><h1 id="1-缘起"><a href="#1-缘起" class="headerlink" title="1 缘起"></a>1 缘起</h1><p>新开系列文章，OS 架构设计中的各种考量因素。其实不止 OS，在设计任何大型软件都涉及到此类内容。</p><p>能力与知识面有限，而且还带了非常主观的看法，肯定有不足之处。希望听到不同的思路与观点，通过观点的碰撞进而达到更进一步的认知。</p><span id="more"></span><p>我认为，Android 与 iOS 在 OS 角度看最核心的差异主要表现为:</p><ol><li>应用与核心服务之间的 IPC 机制</li><li>平台开发环境，包括编程语言、IDE 工具、开发者生态建设</li><li>应用生命周期管理机制与策略</li><li>内核与核心服务的运行时组织结构</li></ol><p>他们为什么会有不同的策略决策呢？这跟架构设计时的考量因素有关。一个软件架构设计决策是在当前一系列考虑因素中选择了对当前与可见的未来选择的最合适的决策，是一系列决策的集合。所以，架构的设计上没有绝对的对与错，或者说合理与不合理。因为不同项目、不同决策者所面临的的考虑因素、追求的侧重点是都不尽相同。 如果架构师的水平差不多，把他们互换下情境，那大概率上所做出的决策是差不多的。这说明架构设计是一个工程，是一个技术手艺，它是可以被习得且有规律可循的。</p><p>架构设计中的挑战，可能更多的是在于准确理解组织所处的环境、当前与可见未来所要满足的考量因素，并从已有工程实践中找出最合适的方法论或者技术栈。由此可见，考虑因素就起了重要作用，在软件架构设计里要考虑哪些因素呢？包括但不限于，可测试性、组件发布效率、组件开发效率、安全性、可靠性、性能、扩容性等等。有经验的架构师，特别是对类似业务有操盘经验，他更能把握好不同时期应该注重什么，哪些是必须坚持的，哪些是适当放开甚至舍弃的。考虑到边际收益递减，影响因素的考量与决策行为会贯穿整个生命周期，这又是另一个「决策艺术」了。</p><p>我们可以总结出:</p><ol><li>不同阶段与不同考量因素的限制下，不同项目上软件架构设计可能是不同的。</li><li>软件产品迭代的整个周期内，这种设计决策是随时会发生。</li></ol><p>有意思的是一个考量因素可能会与另外一个有冲突，会造成鱼和熊掌不可兼得局面。比如提高了组件开发效率但有可能会影响到程序性能。那针对一个移动 OS 的设计，当初 Android 与 iOS 的设计者们的考量因素是什么呢？为了回答这个问题，首先要解释 OS 与应用程序以及内核之间的关系。</p><h1 id="2-机制与策略"><a href="#2-机制与策略" class="headerlink" title="2 机制与策略"></a>2 机制与策略</h1><p>首先要说明的是 OS 是硬件之上的软件栈的一部分，它与应用程序一道构建了完整的软件程序栈，通过发挥硬件的能力为用户提供服务。从硬件的角度看，甭管 OS 还是应用程序，它们都是软件，只是 OS 的权限比较高，可以在 CPU 的高级别模式下运行，比如用于直接跟硬件打交道、执行中断处理程序等。但是在软件开发者角度来看，OS 与应用程序，那可是完全不一样的。应用程序利用 OS 提供的能力，实现自身的业务需求、或者使用硬件的能力，比如播放音乐、存储数据等。</p><p>再拔高一个层次，在用户体验角度来看，应用程序、OS 以及硬件，都是一回事情。 当他们协作出现问题，普通消费者可能就觉得这台设备就不够理想。因此，这三方都有义务互相配合好、互相为彼此提供便利，只有把消费者伺候舒服了，这三家才有可持续的利润可赚。</p><p>当我们说到 OS，绝大部分现代 OS 是由内核（Kernel）跟系统服务组成。</p><ul><li>在 macOS&#x2F;iOS 中，它的内核是 Darwin，而 Darwin 又是由 XNU 与 MACH 微内核组而成。它的系统服务，是由一大堆 daemon 服务提供，他们封装了内核的能力与数据管理、界面显示等级别的 API。</li><li>在 Android 中，它的内核是 Linux 内核，它的系统服务既有 C++ 编写的 daemon 服务（如，SurfaceFlinger）、又有 Java 编写的 daemon 服务（ 如，SystemServer），他们同样也是封装了内核的能力与数据管理、界面渲染与合成等级别的 API。</li></ul><p>现在主流的操作系统有 macOS、Windows 以及基于 Linux 的各种衍生版本。显然，Android 是属于基于 Linux 的衍生版本，当然还有个在开发者圈子中更有名的延伸版本，那就是 Ubuntu。 由于一个完整的 OS 的复杂性与路径依赖特性，往往会将一个延伸版本部署到不同的硬件与不同的应用场景上。有一阵子 Linux 就是以此为标榜，即它辐射到了多少台设备，应用场景有多广之类。能运行一个 OS 跟是否运行得好，即把硬件、应用程序连接起来，提供了最优的用户体验，完全是两码事情。</p><p>一个越来越普遍的认知是，不同的应用场景下，所需要的 OS 的机制与策略以及与之紧密配合的应用程序，是完全不一样的。</p><p>比如，即使是基于同一个 Linux 内核，用于嵌入式设备、智能手表以及智能手机、大型服务器甚至是智能汽车，它们使用 Linux 的方式与构建在它之上的系统服务均是不一样的，当然应用程序的运行策略也不尽相同。Linux 内核与设备驱动可以理解为硬件与系统服务之间的桥梁，是一个标准的桥梁，但是跑在它上面的车辆与行人，是完全不同的。</p><p>这就要引申出另一个非常经典的 OS 设计理念，机制与策略。</p><p>UNIX 编程一书中有提到「分离原则：策略同机制分离，接口同引擎分离」，机制提供能力，策略提供能力方法。</p><p>其中，Linux 提供的内存管理、进程管理、VFS 层、网络编程接口均是机制。内存分配与释放机制、进程调度器、频率与核分配器以及不同的文件系统，他们均是策略。更进一步，由系统服务识别出哪些进程是跟用户有交互、哪些是后台任务，将此类信息同步到调度器后找出下一个调度周期窗口里的最佳进程，本质上也是基于 Linux 进程管理机制之上的策略。 同样道理，根据未来所需的计算任务需求将其信息通知到 CPU 频率分配器进行动态调整（DVFS），前者是策略后者是机制。</p><p>再比如，所有的现代 OS 都支持内存压缩能力，但是不同的 OS 要根据自身的特点来使用此机制，目的是尽可能满足业务特点。iOS 中可以看到针对进程级别的压缩，而 Android 中反倒是依赖 Linux 的 ZRAM。</p><p>OS 的机制或许类似，但是策略千差万别，他们之间的差异有多大，取决于 OS 设计者对自身业务的理解。 自身业务，指的是运行在 OS 之上的应用程序，到底要为用户提供什么样的体验与服务。早期的 Android 其实就是桌面机架构，随着国内厂家对它的魔改，反倒是越来越像 iOS 了，越来越符合一个移动设备操作系统所需要的系统能力了。</p><p>智能手机、智能汽车的 OS，估计也是同样局面，不可生搬硬套。</p><p>策略还有个很有意思的特点，当你实施一个策略的时候会引申出另外一个问题，为此你要引入另一种策略。当一个策略弥补另外一个策略，逐渐会形成一个链条，你会发现你形成了一个闭环的机制。 即，所有的策略同时生效，才能使你的系统发挥出最大的效益。 当我们学习竞品 OS 的策略的时候，一定要记得这一点，否则只学会了皮毛，功能上线后会带来更大的「负优化」。</p><p>那具体到 Android 与 iOS，他们的策略设计是从何而来？</p><h1 id="3-屁股决定脑袋"><a href="#3-屁股决定脑袋" class="headerlink" title="3 屁股决定脑袋"></a>3 屁股决定脑袋</h1><p>苹果推出的操作系统有 macOS，iOS，iPadOS，watchOS 与 tvOS。它们之间并不是单纯的品牌名称差别，而是有具体的策略差异。底层的机制大部分有共享，但是策略却截然不同。iOS 上的后台运行机制与 macOS 就截然不同，iOS 更像是「限制版多任务」，而 macOS 是真正的多任务。所以，iOS 并不是不能实现多任务，而是它的有意为之。</p><p>我们所说的安卓，其实是谷歌开源的项目，即 AOSP（Android Open Source Project）。设备厂商拿到 AOSP 与与硬件的相关的代码之后会在此基础上加入自己的各种服务，这是基于它们自身的商业模式、目标用户的理解，所创造出来的。苹果只有一个苹果，但是设备厂家就有很多，它们各自的盈利模式跟对目标用户理解不同，在 AOSP 基础上魔改了一遭，至于哪个好，就让市场先生来做判断吧。</p><p>回到技术本身，AOSP 中有大量的机制但是缺乏策略。谷歌把这些策略实现在了 GMS 服务中。 如果你在非大陆地区使用安卓手机，如 Pixel、三星手机，会体验到谷歌的全家桶。大家都会说国内的生态比较烂，垃圾应用比较多，到了海外这可能会缓解。 其实也就那样，谷歌的生态控制跟影响能力跟苹果是没法比的。</p><p>连谷歌自身的策略表现不尽人意，那就更为国内厂商发挥拳脚腾出了空间。中国大陆手机出货量累计合是全球最大的，而且竞争尤为激烈。因此它们会非常重视消费者的反馈，各种各样的需求与痛点挖掘，自然不在话下。 简单总结就是，国内厂商更懂消费者的需求，这也是国内厂家对 AOSP 做各种魔改与优化成立的底层逻辑了。</p><p>所以当你再次见到有个老板说 “做手机很简单嘛，拿开源安卓跟厂商的方案整合一下就行了”，离他赶紧远一点，跟着他混简直就是枉费青春。</p><p>iOS 与安卓之间两者最大的差异来自于策略，他们之间拥有的机制都差不多顶多效率上可能有差异，但更大的差异来自于策略上。</p><p>这跟它两刚开始时不同的服务对象与发展目标，导致了技术选型上的巨大差异。</p><p>「乔布斯传」与「成为乔布斯」两本书中，关于 iPhone 研发章节中都提到过关于 AppStore 的讨论。起初乔布斯坚持认为移动设备上由于功耗 &#x2F; 性能与安全的考虑，不允许让三方应用开发程序。由于系统复杂，所以直接采用了 macOS 的内核以此实现多媒体、浏览器，以及播放音频与视频等功能。macOS 内核本身的运行成本较高，在此情况下再让三方应用运行，硬件根本吃不消。他们原本可以基于 iPod 上的系统实现 iPhone，但乔布斯又要实现世人从未见过的智能手机，上马 macOS 也是他不得已的选择。</p><p>随着第一代 iPhone 在用户侧的成功（商业上成功还没有开始），大家都在呼吁在 iTunes 上可以下载应用程序。其实第一代黑客们就是这么做的，因为很多 API 是跟 macOS 共享，因此通过一些逆向手段观察到了 iPhone 上编写应用程序的方法。但乔布斯坚决反对，因为还是担心这会破坏安全性跟设备使用体验。从此处就能看出，乔布斯的目的是打造一个拥有完美用户体验的设备，用户的呼吁或者需求，其实是并不是首要的。</p><p>但 VP 们不这么想，由于之前的 iTunes + iPod 组合的成功，VP 们私底下开始安排相关的工程研究了。直到后来来乔布斯也没有再坚持反对，只是说自己不再管了。</p><p>对 UX 界面的优雅性，特别是图标与界面的一流体验是刻在苹果骨子里的基因。即使是开放出 API，他们也对整个应用运行机制做了修改，从此开始与 macOS 上的程序执行策略有差异了。</p><p>由于第一批应用都是苹果自己写的，因此他们积攒了大量在嵌入式设备上良好设计的应用应该是怎样的，也设计出了非常有效的 API 体系。 在这种局面下，苹果有自己的 OS、自己的 IDE、自己的商店系统（当时还是跟 iTunes 共用），自然而然会设计出「最佳应用」应该长什么样。</p><p>这里头缺一不可，还记得前面提到过的观点吗？ 当你要实施一个策略的时候，可需要另一个策略来解决前一个策略带来的问题，当策略变多的时候就有可能形成了一个环路。</p><p>从这儿可看出，为了打造出一个完美的体验，从硬件到软件全部打通，是必然的结果。 这不是为了封闭而封闭，而是为了打造最佳体验而做出的唯一一条路。</p><p>反观 07 - 08 年的 Android 阵营，他们还在忙着如何让系统跑起来。</p><p>在由 Cheet 著作的 「The Team That Built the Android Operating Systems」书中讲述了安卓从零开始被 Andy Rubin 创建的过程。它是由 Andy Rubin 离职后创业的公司，起初目标是给相机提供 OS。但是随着市场的演变，他们的目标变成了提供给移动设备，特别是手机的移动操作系统。Andy 当初的目标是创建一种可以平衡开发者与设备制造商以及运营商利益的真正意义上的完全开放的操作系统。这就要求它尽可能采用市面上已有的组件，将他们简单改在后适应嵌入式环境后快速部署上线。毕竟是有创业压力嘛，也不可能精雕细琢，只能用快速发展来解决各种体验问题了（主要是这时候 iPhone 还没出来）。再加上从零开始，他们没有配套的 IDE，也只能先提供简单的基于命令行的工具来构建应用程序， 因此从开始他们就缺乏整个应用运行环境的管控能力。不过这也是相对于苹果而言，毕竟两者的目的完全不同。</p><p>Android 的目标之一就是有大量应用程序可用，因此照顾到市面上人群最多的开发者是很想当然的思考方式。当时市场上开发者最多的编程语言是 Java，而 Java 在嵌入式领域里也是很受欢迎的。你可能很难相信，07 年的时候嵌入式设备性能很差，但为什么会是受欢迎的编程语言呢？ 个人理解原因是，除特殊设备之外大部分设备其实不关心性能，基本维持在能用就行的程度。而且 Java 的可移植性，也大大降低了开发者的负担。为了使 Java 速度更快，Andy 团队还聘请了一位大神重新写一套适合嵌入式设备的虚拟机，这在当时看来都是正确的选择，只是没有 iPhone 出来之前。 不过运气好在，智能手机刚好碰上了黄金的 CPU 单核性能与制程爆发的十年，因此它跟 iOS 相比并没有逊色太多。</p><p>在安卓开发的早期，使用的是基于 Eclipse 的构建与开发工具，虽然谈不上非常优秀但是够用。但是痛苦的根源来源于对比，苹果很早之前开始构建自己的 IDE 工具，即 Xcode。这套工具里集成了大量的应用开发与调试以及分发功能，这极大的提高了应用开发工程师的效率。 虽然安卓将开发环境切入到 AndroidStudio 之后相比之前进步了很多，但这主要还是得益于 IntelliJ 本身的优秀，单纯应用开发角度来看跟 Xcode 相比还是弱了一些。Xcode 提供了非常方便的编写与调试程序性能的工具，这会使开发者通过简单的学习就能快速找出程序上低性能的代码段，大大提高了程序的质量而且还使整个过程很愉快，这在安卓上可不是这么一回事了。</p><p>由此可见，苹果能够在应用生态管控上的强势，其中一个重要的原因是得益于它的强大的 IDE 工具，提供方便的来帮助开发者解决问题，而不是一味地给他们压力。更进一步，LLVM、Swift 以及 SwiftUI，这些都是苹果了编写出更好地应用而所做的基础工具与语言。 目的当然是为了自身应用生态的发展，为消费者提供最佳的体验。 它的思路是尽可能服务好开发者，让他们编写更能契合系统机制的应用程序，即给你限制又给你解决方案。</p><p>设计 OS 的目的是盈利，因此要想办法帮助应用开发者开发好程序。很多 OS 提供了能力之后，应用如何编写就不归他们管了，他们往往会把这个责任放到应用开发者身上，从苹果身上可以看到，这种做法可能不利于整个设备的最优体验，吃亏的还是消费者自己以及厂商，因为把消费者给磨没了。</p><p>所以当我看到苹果的强大时候，除了硬件的强大，在软件生态的建设上面的思路是非常值得借鉴的，简单总结就是:</p><ol><li>构建 OS 是手段，提供完整且优秀的体验是目的，双方通力合作才能达到此目的。特别是 OS 与设备制造商，可能要做出更多的努力。</li><li>服务好应用开发者，努力帮助他们写好应用，甚至发现与诊断能出应用的问题，协助开发者改进应用程序。</li><li>提供更快更好用的 API，尽可能高效的满足应用开发者的需求。</li><li>通过优秀的 IDE 工具服务好开发者，让他们在此基础上开发更多更优秀的应用，OS 才能有更好的机会存活下去。</li></ol><p>可以看到虽然苹果有绝对的话语权，但同时也提供了远超于行业平局水平的软件服务。 再次强调，提供 OS 只是手段，要认识到与开发者建立怎样的关系、提供怎样的开发者服务（如 IDE），是在认知层面更为有意的事情。</p><h1 id="4-策略之「过载保护」"><a href="#4-策略之「过载保护」" class="headerlink" title="4 策略之「过载保护」"></a>4 策略之「过载保护」</h1><p>移动设备的最大特性就是可移动，它是由电池供电实现了可移动性。除此之外，由于是手持设备因此散热基本靠被动散热，没有主动散热一说（奇葩的游戏手机与外挂式风扇另说）。现在有两个趋势，其一是晶体管的制作工艺越来越趋近于物理极限，其二是越来越多的功能直接封装在同一颗芯片上。处于活跃状态的晶体管数量变大（或者面积）之后发热量也是蹭蹭往上涨，这在智能手机刚开始普及那一会儿倒不是主要的矛盾。对于智能手机来说，现在更大的矛盾是，功耗与性能的平衡。</p><p>活跃的线程多了，就会使 CPU 一直处于工作状态，当然分给用户相关程序的 CPU 时间片也会少一些，性能也就受到影响了。所以移动设备 OS 的设计上，就自然的引申出了要对应用程序的资源使用上的限制，如像服务器、台式机一样完全放开，性能与功耗是无法保证了（当然还有发热）。越是性能跟功耗约束大的设备，对应用程序的管控就越严苛，比如智能手表。</p><p>Android 与 iOS 各自均有资源保护机制，Android 中最为常见的当属 OOM 机制了。当 Java 应用的堆内存使用超过一定阈值之后，就会被系统终止。它也有 CPU 使用过度的检测，但从实现上比较简陋，而且只会监听普通应用程序的 CPU 使用量，不监控系统以及由 Native 编写的线程（不用 Java 写）。</p><p>iOS 中可谓百花齐放，从 CPU 到内存，甚至 IO 过多写入也有限制，具体为:</p><ul><li>设备过热时被终止</li><li>VoIP 类应用有过多 CPU 唤醒时被终止</li><li>执行 BackgroundTask 时使用 CPU 超过阈值时会被终止（备注: BackgroundTask 是 iOS 上后台执行时的状态）</li><li>执行 BackgroundTask 时没有在规定时间内完成时会被终止</li><li>程序的线程使用 CPU 超过阈值时被终止</li><li>程序写数据到磁盘的量超过阈值时被终止</li><li>单位时间内程序的线程之间的交互超过阈值时被终止（如两个线程互相唤醒）</li><li>程序的内存超标时被终止</li><li>系统内存压力过大时被终止</li><li>程序打开了过多的文件时被终止</li><li>系统遭受 PageCache Thrashing 时被终止</li></ul><p>显然不止于此（可见未来，iOS 会增加更多限制），但以上是跟普通应用开发者最为密切的。iOS 将这些行为写在了开发者文档，让开发者知道自己的应用被异常退出时的原因。</p><p>谷歌对 Android 设计上的” 放松 “ ，可就给国内厂家留出了很多发挥空间。各家都有各自的资源过载保护策略（与 iOS 的类似，仅有或多或少的差异），它们尽可能保护手机不会被垃圾应用给搞坏了。但问题也恰恰出在这部分，由于系统的查杀行为没有明文化，开发者不知道自己的应用为什么会被终止。 即使知道了原因，也无法获取被终止时的调试信息，也就没办法做改进，因为没有哪家会把这类信息开放给应用开发者。</p><p>这导致的结果是，应用开发者不得不想出各种各样的所谓黑科技来使自己保活、绕过系统的各种检测机制。本应该由开发者一起共建的生态，现在变成了两家的攻防战了。到最后，是两败俱伤，而其中最受伤的就是消费者。</p><p>理想中的世界:</p><ol><li>明文化资源过载保护机制，写在应用开发文档上。</li><li>当系统进行过载保护时将上下文调试信息保存下来，开发者可以查阅此类信息（具体权限、范围、有效期可以另定。总之要有方法，可以由开发者拿到此类调试信息）。</li><li>厂商提供方便好用的调试工具，可以由开发者在本地进行开发时修复问题使用。</li><li>当超出厂商制定的质量标准水位线，责令开发者进行修复，若不修复则下架应用。</li></ol><p>厂商跟开发者应当是合作关系，可能厂商要做更多的事情用于帮助开发者，因为很多能力只有厂家才有。只是一味地责怪质量差是开发者的问题，这种厂商我觉得不太会有竞争力。</p><p>在认知维度上，就已经错了。</p><h1 id="5-策略之「生命周期管理」"><a href="#5-策略之「生命周期管理」" class="headerlink" title="5 策略之「生命周期管理」"></a>5 策略之「生命周期管理」</h1><p>不同的设备形态所追求的体验要求不同，因此对 OS 的设计要求也不尽相同。在桌面机 OS 中一个应用程序的生命周期完全是由自己掌控的，目的是尽可能发挥程序的能力。能这么设计的原因是桌面机里没有功耗跟散热的限制，很少也会有性能上的瓶颈。对它来说首要考虑的问题是如何把机器的能力榨干。</p><p>而在智能手机上却是另一种情况，原因是它有功耗跟发热的限制。与此类似，智能手表上也有功耗与散热的限制，但是它比手机设备更为严苛。谁都不希望手腕里戴着一个会发热的表，而且续航一天都撑不了。除此之外，由于它的性能跟内存受限，不能驻留太多的程序在后台，因此需要有一个集中式管理的模块来统一实现绝大部分常见应用的服务，即所谓的托管式架构。智能汽车虽然没有性能、功耗以及发热的限制，但是它对稳定性的要求非常高。除非是汽车彻底断电，核心系统服务需要保持一直运行，因此对系统防老化的设计尤为重要。</p><p>智能手机 OS 设计中一个核心策略是关于生命周期管理的，它决定了应用程序的由生到死的整个过程。Android 的设计上更偏向于桌面机系统，因此策略的设计上比较「宽松」，留给开发者的发挥空间比较多。而 iOS 上限制比较多，一个应用退到后台之后只有 5 秒左右的时间用于执行后台任务，随后便进入到 Suspend 状态。在这种状态下应用程序是得不到 CPU 调度执行，因此在后台的时候应用会比较「安静」。</p><p>国内厂家拿到 AOSP 代码之后实现了类似 iOS 的这种 Suspend 机制，不过碍于 AOSP 原生的不支持，因此做了很多让步，这导致了整体效果上不如 iOS 来的彻底。Android 把这种运行策略解释为把应用写好应用开发者的责任，而我觉得这个想法是幼稚且不切实际的。如果这个逻辑成立的话，人类发展历史上都不需要有法律了，这是违背人性的事情。 不过好在谷歌可能意识到了问题，每年的更新中也逐步完善了俗称「冻结」的策略。不过能力奇差，远不及国内厂商所做的改进。AOSP 的进步也是比较缓慢，目测未来两三年内，这部分的进步速度也会一直缓慢，没有实质性的改变。</p><p>如果应用在后台被 Suspend 住了，那在 iOS 上如何实现需要后台计算的任务呢？ 它引入了 BackgroundTask 的机制，让应用程序申请后台执行任务权限，由系统智能的调度执行应用程序的后台任务。所以，iOS 是给了一套策略让应用程序执行后台任务，但是决定权是交给系统执行的。这有利于系统根据当时手机的不同的状态调度后台任务，比如系统负载比较高的时候就不会执行后台任务了，目的是降低系统整体的负载。系统给每个应用还设置了每日配额的概念，比如一天之内允许你最多执行多少次等等，当然执行时间也是其中一个很重要的考量因素。一般允许执行 30 秒左右，超过之后就会被系统终止了。</p><p>但是后台任务不止于计算一种，播放音乐、位置定位，这种需求如何处理？ 应用想要使用此类服务，需要在 IDE 中显示声明之后才可以使用，App Store 会检查应用与所申请权限是否匹配，不匹配时不给予通过。App Store 是 iOS 能够实现这套生命周期管理机制的很核心的一环，通过它实现了在上架期与运行期间的质量管控。当发现一个应用的质量较差的时候，会通知开发者让其修复，否则就会下架应用。 继 Suspend 之后系统也会随之终止应用程序，目的是过载保护。最常见的理由是回收内存，毕竟内存芯片非常贵，不上大内存的前提 下只能通过终止应用来腾出更多的内存了。</p><p>那我连应用程序都不执行了，又怎么执行后台任务？接收消息呢？ 得益于 BackgroundTask 的设计，即使应用被终止了，当条件满足的时候系统还会自动的拉起你的程序执行后台任务。至于消息接收，则是通过消息通知机制来实现。分为两种，一种是在通知栏里能看到具体的内容，只有用户当点击此通知的时候才会唤醒应用程序。另一种则是 VoIP 类应用，可以主动拉起被终止的应用程序。</p><p>其实 Android 也有类似的机制，但是需要配合它的 GMS 服务使用，由于不确定的原因这个服务在国内是无法使用。因此国内的 App 不得不又要上各种各样的黑科技、商业合作等手段，使自己的程序在后台保活用于接收消息。这就造成了一个非常畸形的局面，头部应用由于是用户常用的，因此厂商也对它一路开绿灯。而厂家也发现这个现象之后，不断加码各类的服务，把手机当做自己的来用，尽可能榨干系统内存。 那有没有可能厂商自己提供类似的通知服务呢？ 有，但是由于建设与运营成本跟自己的销售利润完全不成比例，大家也就只能加大内存容量了，可以把价格压力传递到消费者身上。整机的成本是有上限的，在内存这部分加大那就要在其他地方减弱。国内厂家要突破高端，首先要意识到整个环路的问题所在，与应用共建整个生态才是唯一的破局之道。</p><p>纵观 iOS 的设计，相比于 macOS， 限制了应用的自由度，但也不是一刀切方案。而是尽可能的提供了各种各样的「窗口期」或者「统一的解决方案」，以满足开发者的不同需求。目的是尽可能让开发者在合理的范围内做事情，而不是榨干用户的电量与性能。</p><p>总结下技术之上的设计原则</p><ul><li>移动设备受限因素多，因此要对应用「自由度」给予限制但不能一刀切，需要给予对应的解决方案。</li><li>应用之间的共性任务要由系统统一提供，可节省系统整体负担，这对限制因素较多设备尤为重要。</li><li>程序最终执行权交由系统而定，由他综合各类信息之后统一调度，这有利于保护最终的用户体验。</li></ul><p>写到此处，似乎是两个体制的碰撞，一个崇尚自由，个体优先、一个崇尚统一安排与调度。无论是哪种体制，要看最终目的是什么，如果是要提供最佳的设备用户体验，显然后者体制被市场证明是正确的。</p><h1 id="6-设计之上"><a href="#6-设计之上" class="headerlink" title="6 设计之上"></a>6 设计之上</h1><p>纵观电子消费品的发展史，以专业设备平民化、 N in 1 式的功能整合，两个主旋律方向发展。纯靠 CPU 的计算也会被专用硬件代替，比如 DSA (Domain Specific Architecture)。在 DSA 之上，会构建领域专用的编程语言与编译器，与之最接近的就是图形处理领域，如 GPU 与 Shader Language。 软件吃 CPU 性能提升的红利已经接近了尾声，下一个十年的「大飞跃」，目前看来也就这 DSA 机会点了。</p><p>M1 本身代表了正常的微架构与制程工艺带来的红利，只是友商阵营太拉跨，把这种差距拉大了。当一个产品的核心部件由某几个特定供应商提供的时候，基本上也判定了其发展上限。这也就是常说的核心技术要掌握自己手里，是同样的道理。M1 中除 CPU 能力外，它在多媒体处理，特别是视频流的处理场景下相比 Intel 芯片性能指标超出一大截，能实现这些性能提升的得益于处理器的特定场景下的性能提升。</p><p>但这不代表基于 CPU 的性能提升已经走到尽头，正是因为 CPU 性能提升的停滞，通过对需求的准确理解，优化矩阵与架构设计来实现在已有 CPU 上的性能提升，变得更为紧迫。对硬件、编译器、算法以及操作系统（框架与内核）的理解，变得越来越重要。因为当你优化到一定层度的业务代码之后，注意力必然会往底层走。</p><p>只有相当多的失败的经验，才能未雨绸缪，以高屋建瓴的方式设计出最佳的架构与优化矩阵。 优化矩阵是指为了提供一个极佳的体验，并不是由一个 OS 就能搞定，他要有相配套的其他技术一起支撑才能做好。比如 IDE、比如云端配合、以及正确的认知。 想要提供一个极致的体验，是非常难的事情，但也正因为如此，你会发现了解越多可做的事情就越多。同样道理，也有别样的说法 → 使自己始终处于「知道自己不知道」的状态。</p><p>不过以上成立的前提，是设计者的认知要跟得上。</p><p>查理芒格说过，投资不是看看财报，看看走势图就能做好的。除经济学与金融学外，心理学、社会学、政治学、甚至生物学都有关系。只有当你踏平学科间的隔阂，不设边界地把多个学科内容融合在一起之后，才能看到别人看不到的世界。 我当然也没达到这种境界，芒格给世人的经验是我们值得学习的宝贵经验。努力跨学科、跨领域的刻意练习，如果能在此基础上做到思考输出，那对学习更有帮助。</p><blockquote><p>我的剑留给能够挥舞它的人 - 查理 芒格</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文是之前星球里 Yingyun 大佬的文章，由于星球已经关闭，所以把这个关于 OS 性能设计的系列文章发到博客上&lt;/p&gt;
&lt;p&gt;Yingyun 是资深性能优化专家，他对于系统优化有非常深入的见解，本身在国内各个手机大厂都呆过，他本人的博客还在休整中，等休整好了我再发出来，目前他在我们的微信群里很活跃，对本文有什么建议或者意见，或者说想咨询问题的可以加我们的微信群（加我微信 553000664，备注博客加群，我会拉你进去）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;1-缘起&quot;&gt;&lt;a href=&quot;#1-缘起&quot; class=&quot;headerlink&quot; title=&quot;1 缘起&quot;&gt;&lt;/a&gt;1 缘起&lt;/h1&gt;&lt;p&gt;新开系列文章，OS 架构设计中的各种考量因素。其实不止 OS，在设计任何大型软件都涉及到此类内容。&lt;/p&gt;
&lt;p&gt;能力与知识面有限，而且还带了非常主观的看法，肯定有不足之处。希望听到不同的思路与观点，通过观点的碰撞进而达到更进一步的认知。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>当 App 有了系统权限，真的可以为所欲为？</title>
    <link href="https://androidperformance.com/2023/05/14/bad-android-app-with-system-permissions/"/>
    <id>https://androidperformance.com/2023/05/14/bad-android-app-with-system-permissions/</id>
    <published>2023-05-14T12:15:17.000Z</published>
    <updated>2026-02-07T05:17:47.871Z</updated>
    
    <content type="html"><![CDATA[<p>前一段时间有个 App 很火，是 Android App 利用了 Android 系统漏洞，获得了系统权限，做了很多事情。想看看这些个 App 在利用系统漏洞获取系统权限之后，都干了什么事，于是就有了这篇文章。由于准备仓促，有些 Code 没有仔细看，感兴趣的同学可以自己去研究研究，多多讨论，对应的文章和 Code 链接都在下面：</p><ol><li><a href="https://mp.weixin.qq.com/s/P_EYQxOEupqdU0BJMRqWsw">深蓝洞察：2022 年度最 “不可赦” 漏洞</a></li><li><a href="https://github.com/davinci1010/pinduoduo_backdoor">XXX apk 内嵌提权代码，及动态下发 dex 分析</a></li><li><a href="https://mp.weixin.qq.com/s/x6BSQZ3sE7vDcM0qvsC1EQ">Android 反序列化漏洞攻防史话</a></li></ol><p>关于这个 App 是如何获取这个系统权限的，<a href="https://mp.weixin.qq.com/s/x6BSQZ3sE7vDcM0qvsC1EQ">Android 反序列化漏洞攻防史话</a>，这篇文章讲的很清楚，就不再赘述了，我也不是安全方面的专家，但是建议大家多读几遍这篇文章</p><span id="more"></span><blockquote><p>序列化和反序列化是指将内存数据结构转换为字节流，通过网络传输或者保存到磁盘，然后再将字节流恢复为内存对象的过程。在 Web 安全领域，出现过很多反序列化漏洞，比如 PHP 反序列化、Java 反序列化等。由于在反序列化的过程中触发了非预期的程序逻辑，从而被攻击者用精心构造的字节流触发并利用漏洞从而最终实现任意代码执行等目的。</p></blockquote><p>这篇文章主要来看看 <a href="https://github.com/davinci1010/pinduoduo_backdoor">XXX apk 内嵌提权代码，及动态下发 dex 分析</a> 这个库里面提供的 Dex ，看看 App 到底想知道用户的什么信息？总的来说，App 获取系统权限之后，主要做了下面几件事（正常 App 无法或者很难做到的事情），各种不把用户当人了。</p><ol><li>自启动、关联启动相关的修改，偷偷打开或者默认打开：与手机厂商斗智斗勇。</li><li>开启通知权限。</li><li>监听通知内容。</li><li>获取用户的使用手机的信息，包括安装的 App、使用时长、用户 ID、用户名等。</li><li>修改系统设置。</li><li>整一些系统权限的工具方便自己使用。</li></ol><p>另外也可以看到，这个 App 对于各个手机厂商的研究还是比较深入的，针对华为、Oppo、Vivo、Xiaomi 等终端厂商都有专门的处理，这个也是值得手机厂商去反向研究和防御的。</p><p>最好我还加上了这篇文章在微信公众号发出去之后的用户评论，以及知乎回答的评论区（问题已经被删了，但是我可以看到：如何评价拼多多疑似利用漏洞攻击用户手机，窃取竞争对手软件数据，防止自己被卸载？ - Gracker的回答 - 知乎 <a href="https://www.zhihu.com/question/587624599/answer/2927765317%EF%BC%8C%E7%9B%AE%E5%89%8D%E4%B8%BA%E6%AD%A2%E6%98%AF">https://www.zhihu.com/question/587624599/answer/2927765317，目前为止是</a> 2471 个赞）可以说是脑洞大开（关于 App 如何作恶）。</p><h1 id="0-Dex-文件信息"><a href="#0-Dex-文件信息" class="headerlink" title="0. Dex 文件信息"></a>0. Dex 文件信息</h1><p>本文所研究的 dex 文件是从 <a href="https://github.com/davinci1010/pinduoduo_backdoor">XXX apk 内嵌提权代码，及动态下发 dex 分析</a> 这个仓库获取的，Dex 文件总共有 37 个，不多，也不大，慢慢看。这些文件会通过后台服务器动态下发，然后在 App 启动的时候进行动态加载，可以说是隐蔽的很，然而 Android 毕竟是开源软件，要抓你个 App 的行为还是很简单的，这些 Dex 就是被抓包抓出来的，可以说是人脏货俱全了。</p><p><img src="/../images/bad-android-app-with-system-permissions/5c11aaf4-2f7e-4927-ad35-63da2b24d06e.webp"></p><p>由于是 dex 文件，所以直接使用 <a href="https://github.com/tp7309/TTDeDroid">https://github.com/tp7309/TTDeDroid</a> 这个库的反编译工具打开看即可，比如我配置好之后，直接使用 showjar 这个命令就可以</p><blockquote><p>showjar 95cd95ab4d694ad8bdf49f07e3599fb3.dex</p></blockquote><p>默认是用 jadx 打开，就可以看到反编译之后的内容，我们重点看 Executor 里面的代码逻辑即可</p><p><img src="/../images/bad-android-app-with-system-permissions/aa2e2237-e69f-40dc-94e9-ac459d5e1255.webp"></p><p>打开后可以看到具体的功能逻辑，可以看到一个 dex 一般只干一件事，那我们重点看这件事的核心实现部分即可</p><h1 id="1-通知监听和通知权限相关"><a href="#1-通知监听和通知权限相关" class="headerlink" title="1. 通知监听和通知权限相关"></a>1. 通知监听和通知权限相关</h1><h2 id="1-1-获取-Xiaomi-手机通知内容"><a href="#1-1-获取-Xiaomi-手机通知内容" class="headerlink" title="1.1 获取 Xiaomi 手机通知内容"></a>1.1 获取 Xiaomi 手机通知内容</h2><ol><li>文件 ： 95cd95ab4d694ad8bdf49f07e3599fb3.dex</li><li>功能 ：获取用户的 Active 通知</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.xm_ntf_info.XMGetNtfInfoExecutor</li></ol><h3 id="1-反射拿到-ServiceManager"><a href="#1-反射拿到-ServiceManager" class="headerlink" title="1. 反射拿到 ServiceManager"></a>1. 反射拿到 ServiceManager</h3><p>一般我们会通过 ServiceManager 的 getService 方法获取系统的 Service，然后进行远程调用<br><img src="/../images/bad-android-app-with-system-permissions/20af4e4c-b61a-4a33-9534-0a080ab64d51.webp"></p><h3 id="2-通过-NotificationManagerService-获取通知的详细内容"><a href="#2-通过-NotificationManagerService-获取通知的详细内容" class="headerlink" title="2. 通过 NotificationManagerService 获取通知的详细内容"></a>2. 通过 NotificationManagerService 获取通知的详细内容</h3><p>通过 getService 传入 NotificationManagerService 获取 NotificationManager 之后，就可以调用 <strong>getActiveNotifications</strong> 这个方法了，然后具体拿到 Notification 的下面几个字段</p><ol><li>通知的 Title</li><li>发生通知的 App 的包名</li><li>通知发送时间</li><li>key</li><li>channelID ：the id of the channel this notification posts to.</li></ol><p>可能有人不知道这玩意是啥，下面这个图里面就是一个典型的通知</p><p><img src="/../images/bad-android-app-with-system-permissions/b63015fb-7531-42fc-8caa-3d68051afa31.webp"></p><p>其代码如下<br><img src="/../images/bad-android-app-with-system-permissions/fe63fdbc-a45d-44b6-909f-b393dcf7206a.webp"></p><p>可以看到 getActiveNotifications 这个方法，是 <strong>System-only</strong> 的，普通的 App 是不能随便读取 Notification 的，但是这个 App 由于有权限，就可以获取</p><p><img src="/../images/bad-android-app-with-system-permissions/e19519c3-ee58-4fa4-a3ae-9b29109dbd8b.webp"></p><p>当然微信的防撤回插件使用的一般是另外一种方法，比如辅助服务，这玩意是合规的，但是还是推荐大家能不用就不用，它能帮你防撤回，他就能获取通知的内容，包括你知道的和不知道的</p><h2 id="1-2-打开-Xiaomi-手机上的通知权限（Push）"><a href="#1-2-打开-Xiaomi-手机上的通知权限（Push）" class="headerlink" title="1.2. 打开 Xiaomi 手机上的通知权限（Push）"></a>1.2. 打开 Xiaomi 手机上的通知权限（Push）</h2><ol><li>文件 ：0fc0e98ac2e54bc29401efaddfc8ad7f.dex</li><li>功能 ：可能有的时候小米用户会把 App 的通知给关掉，App 想知道这个用户是不是把通知关了，如果关了就偷偷打开</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.xm_permission.XMPermissionExecutor</li></ol><p>这么看来这个应该还是蛮实用的，你个调皮的用户，我发通知都是为了你好，你怎么忍心把我关掉呢？让我帮你偷偷打开吧<br><img src="/../images/bad-android-app-with-system-permissions/b8ab6557-355f-4de8-b0f6-3d0900792f26.webp"></p><p>App 调用 NotificationManagerService 的 setNotificationsEnabledForPackage 来设置通知，可以强制打开通知<br>frameworks&#x2F;base&#x2F;services&#x2F;core&#x2F;java&#x2F;com&#x2F;android&#x2F;server&#x2F;notification&#x2F;NotificationManagerService.java<br><img src="/../images/bad-android-app-with-system-permissions/b21b6515-6636-4e2e-9dd1-a287d11bd1de.webp"></p><p>然后查看 NotificationManagerService 的 setNotificationsEnabledForPackage 这个方法，就是查看用户是不是打开成功了<br>frameworks&#x2F;base&#x2F;services&#x2F;core&#x2F;java&#x2F;com&#x2F;android&#x2F;server&#x2F;notification&#x2F;NotificationManagerService.java<br><img src="/../images/bad-android-app-with-system-permissions/af06c302-13d6-4ead-b411-40c4c8c8a252.webp"></p><p>还有针对 leb 的单独处理~ 细 ！</p><h2 id="1-3-打开-Vivo-机器上的通知权限（Push）"><a href="#1-3-打开-Vivo-机器上的通知权限（Push）" class="headerlink" title="1.3. 打开 Vivo 机器上的通知权限（Push）"></a>1.3. 打开 Vivo 机器上的通知权限（Push）</h2><ol><li>文件 ：2eb20dc580aaa5186ee4a4ceb2374669.dex</li><li>功能 ：Vivo 用户会把 App 的通知给关掉，这样在 Vivo 手机上 App 就收不到通知了，那不行，得偷偷打开</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.vivo_open_push.VivoOpenPushExecutor</li></ol><p>核心和上面那个是一样的，只不过这个是专门针对 vivo 手机的</p><p><img src="/../images/bad-android-app-with-system-permissions/05ac00b1-0ccc-49e4-9365-a2710e3daccc.webp"></p><h2 id="1-4-打开-Oppo-手机的通知权限"><a href="#1-4-打开-Oppo-手机的通知权限" class="headerlink" title="1.4 打开 Oppo 手机的通知权限"></a>1.4 打开 Oppo 手机的通知权限</h2><ol><li>文件 ：67c9e686004f45158e94002e8e781192.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.oppo_notification_ut.OppoNotificationUTExecutor</li></ol><p>没有反编译出来，看大概的逻辑应该是打开 App 在 oppo 手机上的通知权限</p><p><img src="/../images/bad-android-app-with-system-permissions/4f295b86-20be-47ad-b59e-e7f0be7d01c6.webp"></p><h2 id="1-5-Notification-监听"><a href="#1-5-Notification-监听" class="headerlink" title="1.5 Notification 监听"></a>1.5 Notification 监听</h2><ol><li>文件 ：ab8ed4c3482c42a1b8baef558ee79deb.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.ud_notification_listener.UdNotificationListenerExecutor</li></ol><p>这个就有点厉害了，在监听 App 的 Notification 的发送，然后进行统计</p><p><img src="/../images/bad-android-app-with-system-permissions/7dd432c9-64e7-488a-a150-6a14e38bfd27.webp"></p><p>监听的核心代码<br><img src="/../images/bad-android-app-with-system-permissions/a2715b1c-f524-4a68-9bf2-b52502ff08d0.webp"></p><p>这个咱也不是很懂，是时候跟做了多年 SystemUI 和 Launcher 的老婆求助了....@史工</p><h2 id="1-6-App-Notification-监听"><a href="#1-6-App-Notification-监听" class="headerlink" title="1.6 App Notification 监听"></a>1.6 App Notification 监听</h2><ol><li>文件 ：4f260398-e9d1-4390-bbb9-eeb49c07bf3c.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.notification_listener.NotificationListenerExecutor</li></ol><p>上面那个是 UdNotificationListenerExecutor ， 这个是 NotificationListenerExecutor，UD 是啥？</p><p><img src="/../images/bad-android-app-with-system-permissions/e7569089-5bf1-4dad-8c7f-9b2b4a962b2d.webp"></p><p>这个反射调用的 setNotificationListenerAccessGranted 是个 SystemAPI，获得通知的使用权，果然有权限就可以为所欲为</p><p><img src="/../images/bad-android-app-with-system-permissions/9b302ec0-c210-4454-8c0b-8dc90378a61a.webp"></p><p><img src="/../images/bad-android-app-with-system-permissions/9cb6feaf-8771-41f3-8b39-fd1f2837523b.webp"></p><h2 id="1-7-打开华为手机的通知监听权限"><a href="#1-7-打开华为手机的通知监听权限" class="headerlink" title="1.7 打开华为手机的通知监听权限"></a>1.7 打开华为手机的通知监听权限</h2><ol><li>文件 ：a3937709-b9cc-48fd-8918-163c9cb7c2df.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.hw_notification_listener.HWNotificationListenerExecutor</li></ol><p>华为也无法幸免，哈哈哈<br><img src="/../images/bad-android-app-with-system-permissions/06658085-ffa9-4f1f-b578-75963dc40ce7.webp"></p><h2 id="1-8-打开华为手机通知权限"><a href="#1-8-打开华为手机通知权限" class="headerlink" title="1.8 打开华为手机通知权限"></a>1.8 打开华为手机通知权限</h2><ol><li>文件 ：257682c986ab449ab9e7c8ae7682fa61.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.hw_permission.HwPermissionExecutor</li></ol><p><img src="/../images/bad-android-app-with-system-permissions/984dc753-4004-41a4-b2e9-4d528f54918f.webp"></p><h1 id="2-Backup-状态"><a href="#2-Backup-状态" class="headerlink" title="2. Backup 状态"></a>2. Backup 状态</h1><h2 id="2-1-鸿蒙-OS-上-App-Backup-状态相关，保活用？"><a href="#2-1-鸿蒙-OS-上-App-Backup-状态相关，保活用？" class="headerlink" title="2.1. 鸿蒙 OS 上 App Backup 状态相关，保活用？"></a>2.1. 鸿蒙 OS 上 App Backup 状态相关，保活用？</h2><ol><li>文件 ：6932a923-9f13-4624-bfea-1249ddfd5505.dex</li><li>功能 ：Backup 相关</li></ol><p>这个看了半天，应该是专门针对华为手机的，收到 IBackupSessionCallback 回调后，执行 PackageManagerEx.startBackupSession 方法</p><p><img src="/../images/bad-android-app-with-system-permissions/f47e03f2-8412-478c-9b06-07090fe2b191.webp"></p><p><img src="/../images/bad-android-app-with-system-permissions/ab7364d2-55d8-40a6-a667-3f7c8faadfa9.webp"></p><p>查了下这个方法的作用，启动备份或恢复会话</p><p><img src="/../images/bad-android-app-with-system-permissions/a208a5c1-9900-4c2d-9b82-775125ac8dfb.webp"></p><h2 id="2-2-Vivo-手机-Backup-状态相关"><a href="#2-2-Vivo-手机-Backup-状态相关" class="headerlink" title="2.2. Vivo 手机 Backup 状态相关"></a>2.2. Vivo 手机 Backup 状态相关</h2><ol><li>文件 ：8c34f5dc-f04c-40ba-98d4-7aa7c364b65c.dex</li><li>功能 ：Backup 相关</li></ol><p><img src="/../images/bad-android-app-with-system-permissions/d3a472cf-28ea-4b45-b92e-5555926a4924.webp"></p><h1 id="3-文件相关"><a href="#3-文件相关" class="headerlink" title="3. 文件相关"></a>3. 文件相关</h1><h2 id="3-1-获取华为手机-SLog-和-SharedPreferences-内容"><a href="#3-1-获取华为手机-SLog-和-SharedPreferences-内容" class="headerlink" title="3.1 获取华为手机 SLog 和 SharedPreferences 内容"></a>3.1 获取华为手机 SLog 和 SharedPreferences 内容</h2><ol><li>文件 ： da03be2689cc463f901806b5b417c9f5.dex</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.hw_get_input.HwGetInputExecutor</li></ol><p>拿这个干嘛呢？拿去做数据分析？</p><p><img src="/../images/bad-android-app-with-system-permissions/cd29a622-f059-4ff6-bf71-b14179ebc4ec.webp"></p><p>获取 SharedPreferences</p><p><img src="/../images/bad-android-app-with-system-permissions/516bf269-d1fe-4a46-bc75-5612a743e055.webp"></p><p>获取 slog</p><p><img src="/../images/bad-android-app-with-system-permissions/f67ba385-70c2-4968-91a0-0499527fc84f.webp"></p><h2 id="4-用户数据"><a href="#4-用户数据" class="headerlink" title="4. 用户数据"></a>4. 用户数据</h2><h2 id="4-1-获取用户使用手机的数据"><a href="#4-1-获取用户使用手机的数据" class="headerlink" title="4.1 获取用户使用手机的数据"></a>4.1 获取用户使用手机的数据</h2><ol><li>文件 ： 35604479f8854b5d90bc800e912034fc.dex</li><li>功能 ：看名字就知道是获取用户的使用手机的数据</li><li>类名 ：com.google.android.sd.biz_dynamic_dex.usage_event_all.UsageEventAllExecutor</li></ol><p>看核心逻辑是同 usagestates 服务，来获取用户使用手机的数据，难怪我手机安装了什么 App、用了多久这些，其他 App 了如指掌<br><img src="/../images/bad-android-app-with-system-permissions/5268eebe-b3c8-45c7-9153-79a2e57c3437.webp"></p><p>那么他可以拿到哪些数据呢？应有尽有~，包括但不限于 App 启动、退出、挂起、Service 变化、Configuration 变化、亮灭屏、开关机等，感兴趣的可以看一下：</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/usage/UsageEvents.java</span><br><span class="line">    <span class="keyword">private</span> static String eventToString(int eventType) &#123;</span><br><span class="line">        switch (eventType) &#123;</span><br><span class="line">            case Event.NONE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;NONE&quot;</span>;</span><br><span class="line">            case Event.ACTIVITY_PAUSED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;ACTIVITY_PAUSED&quot;</span>;</span><br><span class="line">            case Event.ACTIVITY_RESUMED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;ACTIVITY_RESUMED&quot;</span>;</span><br><span class="line">            case Event.FOREGROUND_SERVICE_START:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;FOREGROUND_SERVICE_START&quot;</span>;</span><br><span class="line">            case Event.FOREGROUND_SERVICE_STOP:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;FOREGROUND_SERVICE_STOP&quot;</span>;</span><br><span class="line">            case Event.ACTIVITY_STOPPED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;ACTIVITY_STOPPED&quot;</span>;</span><br><span class="line">            case Event.END_OF_DAY:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;END_OF_DAY&quot;</span>;</span><br><span class="line">            case Event.ROLLOVER_FOREGROUND_SERVICE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;ROLLOVER_FOREGROUND_SERVICE&quot;</span>;</span><br><span class="line">            case Event.CONTINUE_PREVIOUS_DAY:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;CONTINUE_PREVIOUS_DAY&quot;</span>;</span><br><span class="line">            case Event.CONTINUING_FOREGROUND_SERVICE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;CONTINUING_FOREGROUND_SERVICE&quot;</span>;</span><br><span class="line">            case Event.CONFIGURATION_CHANGE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;CONFIGURATION_CHANGE&quot;</span>;</span><br><span class="line">            case Event.SYSTEM_INTERACTION:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SYSTEM_INTERACTION&quot;</span>;</span><br><span class="line">            case Event.USER_INTERACTION:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;USER_INTERACTION&quot;</span>;</span><br><span class="line">            case Event.SHORTCUT_INVOCATION:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SHORTCUT_INVOCATION&quot;</span>;</span><br><span class="line">            case Event.CHOOSER_ACTION:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;CHOOSER_ACTION&quot;</span>;</span><br><span class="line">            case Event.NOTIFICATION_SEEN:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;NOTIFICATION_SEEN&quot;</span>;</span><br><span class="line">            case Event.STANDBY_BUCKET_CHANGED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;STANDBY_BUCKET_CHANGED&quot;</span>;</span><br><span class="line">            case Event.NOTIFICATION_INTERRUPTION:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;NOTIFICATION_INTERRUPTION&quot;</span>;</span><br><span class="line">            case Event.SLICE_PINNED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SLICE_PINNED&quot;</span>;</span><br><span class="line">            case Event.SLICE_PINNED_PRIV:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SLICE_PINNED_PRIV&quot;</span>;</span><br><span class="line">            case Event.SCREEN_INTERACTIVE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SCREEN_INTERACTIVE&quot;</span>;</span><br><span class="line">            case Event.SCREEN_NON_INTERACTIVE:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;SCREEN_NON_INTERACTIVE&quot;</span>;</span><br><span class="line">            case Event.KEYGUARD_SHOWN:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;KEYGUARD_SHOWN&quot;</span>;</span><br><span class="line">            case Event.KEYGUARD_HIDDEN:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;KEYGUARD_HIDDEN&quot;</span>;</span><br><span class="line">            case Event.DEVICE_SHUTDOWN:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;DEVICE_SHUTDOWN&quot;</span>;</span><br><span class="line">            case Event.DEVICE_STARTUP:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;DEVICE_STARTUP&quot;</span>;</span><br><span class="line">            case Event.USER_UNLOCKED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;USER_UNLOCKED&quot;</span>;</span><br><span class="line">            case Event.USER_STOPPED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;USER_STOPPED&quot;</span>;</span><br><span class="line">            case Event.LOCUS_ID_SET:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;LOCUS_ID_SET&quot;</span>;</span><br><span class="line">            case Event.APP_COMPONENT_USED:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;APP_COMPONENT_USED&quot;</span>;</span><br><span class="line">            default:</span><br><span class="line">                <span class="keyword">return</span> <span class="string">&quot;UNKNOWN_TYPE_&quot;</span> + eventType;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h2 id="4-2-获取用户使用数据"><a href="#4-2-获取用户使用数据" class="headerlink" title="4.2 获取用户使用数据"></a>4.2 获取用户使用数据</h2><ol><li>文件：b50477f70bd14479a50e6fa34e18b2a0.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.usage_event.UsageEventExecutor</li></ol><p>上面那个是 UsageEventAllExecutor，这个是 UsageEventExecutor，主要拿用户使用 App 相关的数据，比如什么时候打开某个 App、什么时候关闭某个 App，6 得很，真毒瘤</p><p><img src="/../images/bad-android-app-with-system-permissions/9c96abde-38b2-479f-b169-35e94b74058d.webp"></p><h2 id="4-3-获取用户使用数据"><a href="#4-3-获取用户使用数据" class="headerlink" title="4.3 获取用户使用数据"></a>4.3 获取用户使用数据</h2><ol><li>文件：1a68d982e02fc22b464693a06f528fac.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.app_usage_observer.AppUsageObserver</li></ol><p>看样子是注册了 App Usage 的权限，具体 Code 没有出来，不好分析</p><p><img src="/../images/bad-android-app-with-system-permissions/9927ea29-7466-4fa3-9023-64b89eba0c89.webp"></p><h1 id="5-Widget-和-icon-相关"><a href="#5-Widget-和-icon-相关" class="headerlink" title="5. Widget 和 icon 相关"></a>5. Widget 和 icon 相关</h1><p>经吃瓜群众提醒，App 可以通过 Widget 伪造一个 icon，用户在长按图标卸载这个 App 的时候，你以为卸载了，其实是把他伪造的这个 Widget 给删除了，真正的 App 还在 （不过我没有遇到过，这么搞真的是脑洞大开，且不把 Android 用户当人）</p><h2 id="5-1-Vivo-手机添加-Widget"><a href="#5-1-Vivo-手机添加-Widget" class="headerlink" title="5.1. Vivo 手机添加 Widget"></a>5.1. Vivo 手机添加 Widget</h2><ol><li>文件：f9b6b139-4516-4ac2-896d-8bc3eb1f2d03.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_widget.VivoAddWidgetExecutor</li></ol><p>这个比较好理解，在 Vivo 手机上加个 Widget</p><p><img src="/../images/bad-android-app-with-system-permissions/8daee130-2e64-45e9-93ba-f89da87cc06c.webp"></p><h2 id="5-2-获取-icon-相关的信息"><a href="#5-2-获取-icon-相关的信息" class="headerlink" title="5.2 获取 icon 相关的信息"></a>5.2 获取 icon 相关的信息</h2><ol><li>文件：da60112a4b2848adba2ac11f412cccc7.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.get_icon_info.GetIconInfoExecutor</li></ol><p>这个好理解，获取 icon 相关的信息，比如在 Launcher 的哪一行，哪一列，是否在文件夹里面。问题是获取这玩意干嘛？？？迷</p><p><img src="/../images/bad-android-app-with-system-permissions/eeb3b34d-520d-4b30-87de-8d4e934cb766.webp"></p><h2 id="5-3-Oppo-手机添加-Widget"><a href="#5-3-Oppo-手机添加-Widget" class="headerlink" title="5.3 Oppo 手机添加 Widget"></a>5.3 Oppo 手机添加 Widget</h2><ol><li>文件：75dcc8ea-d0f9-4222-b8dd-2a83444f9cd6.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.oppoaddwidget.OppoAddWidgetExecutor</li></ol><p><img src="/../images/bad-android-app-with-system-permissions/3456e3f2-2960-42f4-b26f-ce0e83c4975c.webp"></p><h2 id="5-4-Xiaomi-手机更新图标？"><a href="#5-4-Xiaomi-手机更新图标？" class="headerlink" title="5.4 Xiaomi 手机更新图标？"></a>5.4 Xiaomi 手机更新图标？</h2><ol><li>文件：5d372522-b6a4-4c1b-a0b4-8114d342e6c0.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.xm_akasha.XmAkashaExecutor</li></ol><p>小米手机上的桌面 icon 、shorcut 相关的操作，小米的同学来认领</p><p><img src="/../images/bad-android-app-with-system-permissions/93161c32-4d8f-43b2-8812-c4ff5c87b081.webp"></p><h1 id="6-自启动、关联启动、保活相关"><a href="#6-自启动、关联启动、保活相关" class="headerlink" title="6. 自启动、关联启动、保活相关"></a>6. 自启动、关联启动、保活相关</h1><h2 id="6-1-打开-Oppo-手机自启动"><a href="#6-1-打开-Oppo-手机自启动" class="headerlink" title="6.1 打开 Oppo 手机自启动"></a>6.1 打开 Oppo 手机自启动</h2><ol><li>文件：e723d560-c2ee-461e-b2a1-96f85b614f2b.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.oppo_boot_perm.OppoBootPermExecutor</li></ol><p>看下面这一堆就知道是和自启动相关的，看来自启动权限是每个 App 都蛋疼的东西啊</p><p><img src="/../images/bad-android-app-with-system-permissions/70128e44-0cdb-4081-bf79-30009c4846e9.webp"></p><p><img src="/../images/bad-android-app-with-system-permissions/72ce602f-0f5b-4939-b78f-23b4d3de1c23.webp"></p><h2 id="6-2-打开-Vivo-关联启动权限"><a href="#6-2-打开-Vivo-关联启动权限" class="headerlink" title="6.2 打开 Vivo 关联启动权限"></a>6.2 打开 Vivo 关联启动权限</h2><ol><li>文件：8b56d820-cac2-4ca0-8a3a-1083c5cca7ae.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_association_start.VivoAssociationStartExecutor</li></ol><p>看名字就是和关联启动相关的权限，vivo 的同学来领了<br><img src="/../images/bad-android-app-with-system-permissions/a0a34e29-2a15-41c8-a172-8bd288766b1b.webp"></p><p>直接写了个节点进去</p><p><img src="/../images/bad-android-app-with-system-permissions/d87b6aaf-3d5d-480a-8307-6310f65c0aa8.webp"></p><h2 id="6-3-关闭华为耗电精灵"><a href="#6-3-关闭华为耗电精灵" class="headerlink" title="6.3 关闭华为耗电精灵"></a>6.3 关闭华为耗电精灵</h2><ol><li>文件：7c6e6702-e461-4315-8631-eee246aeba95.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.hw_hide_power_window.HidePowerWindowExecutor</li></ol><p>看名字和实现，应该是和华为的耗电精灵有关系，华为的同学可以来看看</p><p><img src="/../images/bad-android-app-with-system-permissions/a1415c92-cd11-45e6-a490-622f5cceeaee.webp"></p><p><img src="/../images/bad-android-app-with-system-permissions/3199ec82-4927-4a36-b02b-9600ca1c5657.webp"></p><h2 id="6-4-Vivo-机型保活相关"><a href="#6-4-Vivo-机型保活相关" class="headerlink" title="6.4 Vivo 机型保活相关"></a>6.4 Vivo 机型保活相关</h2><ol><li>文件：7877ec6850344e7aad5fdd57f6abf238.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_get_loc.VivoGetLocExecutor</li></ol><p>猜测和保活相关，Vivo 的同学可以来认领一下</p><p><img src="/../images/bad-android-app-with-system-permissions/fdcd9428-0f8e-4057-ae6c-8ac906f70727.webp"></p><p><img src="/../images/bad-android-app-with-system-permissions/60e1e0bc-5075-4369-8434-90a970bec7b3.webp"></p><h1 id="7-安装卸载相关"><a href="#7-安装卸载相关" class="headerlink" title="7. 安装卸载相关"></a>7. 安装卸载相关</h1><h2 id="7-1-Vivo-手机回滚卸载"><a href="#7-1-Vivo-手机回滚卸载" class="headerlink" title="7.1 Vivo 手机回滚卸载"></a>7.1 Vivo 手机回滚卸载</h2><ol><li>文件：d643e0f9a68342bc8403a69e7ee877a7.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_rollback_uninstall.VivoRollbackUninstallExecutor</li></ol><p>这个看上去像是用户卸载 App 之后，回滚到预置的版本,好吧，这个是常规操作<br><img src="/../images/bad-android-app-with-system-permissions/ef50f808-3b59-4094-83cc-8a687146dbbf.webp"></p><h2 id="7-2-Vivo-手机-App-卸载"><a href="#7-2-Vivo-手机-App-卸载" class="headerlink" title="7.2 Vivo 手机 App 卸载"></a>7.2 Vivo 手机 App 卸载</h2><ol><li>文件：be7a2b643d7e8543f49994ffeb0ee0b6.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_official_uninstall.OfficialUntiUninstallV3</li></ol><p>看名字和实现，也是和卸载回滚相关的<br><img src="/../images/bad-android-app-with-system-permissions/e7309401-dad1-45fa-a8f6-cf36bd6b628f.webp"></p><h2 id="7-3-Vivo-手机-App-卸载相关"><a href="#7-3-Vivo-手机-App-卸载相关" class="headerlink" title="7.3 Vivo 手机 App 卸载相关"></a>7.3 Vivo 手机 App 卸载相关</h2><ol><li>文件：183bb87aa7d744a195741ce524577dd0.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_official_uninstall.VivoOfficialUninstallExecutor</li></ol><p>同上<br><img src="/../images/bad-android-app-with-system-permissions/ecb189bb-537b-4094-8324-63ff483bc181.webp"></p><h1 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h1><h2 id="SyncExecutor"><a href="#SyncExecutor" class="headerlink" title="SyncExecutor"></a>SyncExecutor</h2><ol><li>文件：f4247da0-6274-44eb-859a-b4c35ec0dd71.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.sync.SyncExecutor</li></ol><p>没看懂是干嘛的，核心应该是 Utils.updateSid ，但是没看到实现的地方</p><p><img src="/../images/bad-android-app-with-system-permissions/9bcca5a3-11c5-47f2-8afd-3fea659d481d.webp"></p><h2 id="UdParseNotifyMessageExecutor"><a href="#UdParseNotifyMessageExecutor" class="headerlink" title="UdParseNotifyMessageExecutor"></a>UdParseNotifyMessageExecutor</h2><ol><li>文件：f35735a5cbf445c785237797138d246a.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.ud_parse_nmessage.UdParseNotifyMessageExecutor</li></ol><p>看名字应该是解析从远端传来的 Notify Message，具体功能未知<br><img src="/../images/bad-android-app-with-system-permissions/2399f610-74d0-4a2c-a0f1-6fcc24e93e1c.webp"></p><h2 id="6-3-TDLogcatExecutor"><a href="#6-3-TDLogcatExecutor" class="headerlink" title="6.3 TDLogcatExecutor"></a>6.3 TDLogcatExecutor</h2><ol><li>文件<ol><li>8aeb045fad9343acbbd1a26998b6485a.dex</li><li>2aa151e2cfa04acb8fb96e523807ca6b.dex</li></ol></li><li>类名<ol><li>com.google.android.sd.biz_dynamic_dex.td.logcat.TDLogcatExecutor</li><li>com.google.android.sd.biz_dynamic_dex.td.logcat.TDLogcatExecutor</li></ol></li></ol><p>没太看懂这个是干嘛的，像是保活又不像，后面有时间了再慢慢分析<br><img src="/../images/bad-android-app-with-system-permissions/706197f5-9e65-4d23-9bf5-27ad363066c2.webp"></p><h2 id="6-4-QueryLBSInfoExecutor"><a href="#6-4-QueryLBSInfoExecutor" class="headerlink" title="6.4 QueryLBSInfoExecutor"></a>6.4 QueryLBSInfoExecutor</h2><ol><li>文件：74168acd-14b4-4ff8-842e-f92b794d7abf.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.query_lbs_info.QueryLBSInfoExecutor</li></ol><p>获取 LBS Info</p><p><img src="/../images/bad-android-app-with-system-permissions/25a798bd-2480-4481-b535-e7d398324465.webp"></p><h2 id="6-5-WriteSettingsExecutor"><a href="#6-5-WriteSettingsExecutor" class="headerlink" title="6.5 WriteSettingsExecutor"></a>6.5 WriteSettingsExecutor</h2><ol><li>文件：6afc90e406bf46e4a29956aabcdfe004.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.write_settings.WriteSettingsExecutor</li></ol><p>看名字应该是个工具类，写 Settings 字段的，至于些什么应该是动态下发的</p><p><img src="/../images/bad-android-app-with-system-permissions/c62895d1-9e94-47e1-9440-0b77287e7506.webp"></p><h2 id="6-6-OppoSettingExecutor"><a href="#6-6-OppoSettingExecutor" class="headerlink" title="6.6 OppoSettingExecutor"></a>6.6 OppoSettingExecutor</h2><ol><li>文件：61517b68-7c09-4021-9aaa-cdebeb9549f2.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.opposettingproxy.OppoSettingExecutor</li></ol><p>Setting 代理？？没看懂干嘛的，Oppo 的同学来认领，难道是另外一种形式的保活？</p><p><img src="/../images/bad-android-app-with-system-permissions/0e4a2313-9f30-4eac-832c-044853582742.webp"></p><h2 id="6-7-CheckAsterExecutor"><a href="#6-7-CheckAsterExecutor" class="headerlink" title="6.7 CheckAsterExecutor"></a>6.7 CheckAsterExecutor</h2><ol><li>文件：561341f5f7976e13efce7491887f1306.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.check_aster.CheckAsterExecutor</li></ol><p>Check aster ？不是很懂</p><p><img src="/../images/bad-android-app-with-system-permissions/a6867dc8-0800-4387-83c1-942dca12ed3c.webp"></p><h2 id="6-8-OppoCommunityIdExecutor"><a href="#6-8-OppoCommunityIdExecutor" class="headerlink" title="6.8 OppoCommunityIdExecutor"></a>6.8 OppoCommunityIdExecutor</h2><ol><li>文件：538278f3-9f68-4fce-be10-12635b9640b2.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.oppo_community_id.OppoCommunityIdExecutor</li></ol><p>获取 Oppo 用户的 ID？要这玩意干么？</p><p><img src="/../images/bad-android-app-with-system-permissions/b4f11ad0-7e6e-4fad-99ce-2ec63a08962e.webp"></p><h2 id="6-9-GetSettingsUsernameExecutor"><a href="#6-9-GetSettingsUsernameExecutor" class="headerlink" title="6.9 GetSettingsUsernameExecutor"></a>6.9 GetSettingsUsernameExecutor</h2><ol><li>文件：4569a29c-b5a8-4dcf-a3a6-0a2f0bfdd493.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.oppo_get_settings_username.GetSettingsUsernameExecutor</li></ol><p>获取 Oppo 手机用户的 username，话说你要这个啥用咧？</p><p><img src="/../images/bad-android-app-with-system-permissions/0341902d-f1ed-4344-a724-9110e3fe22d7.webp"></p><h2 id="6-10-LogcatExecutor"><a href="#6-10-LogcatExecutor" class="headerlink" title="6.10 LogcatExecutor"></a>6.10 LogcatExecutor</h2><ol><li>文件：218a37ea-710d-49cb-b872-2a47a1115c69.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.logcat.LogcatExecutor</li></ol><p>配置 Log 的参数<br><img src="/../images/bad-android-app-with-system-permissions/1e57c31e-533c-40dc-99c4-ddfe261af614.webp"></p><h2 id="6-11-VivoBrowserSettingsExecutor"><a href="#6-11-VivoBrowserSettingsExecutor" class="headerlink" title="6.11 VivoBrowserSettingsExecutor"></a>6.11 VivoBrowserSettingsExecutor</h2><ol><li>文件：136d4651-df47-41b4-bb80-2ec0ab1bc775.dex</li><li>类名：com.google.android.sd.biz_dynamic_dex.vivo_browser_settings.VivoBrowserSettingsExecutor</li></ol><p>Vivo 浏览器相关的设置，不太懂要干嘛</p><p><img src="/../images/bad-android-app-with-system-permissions/a8513806-df24-4ec8-bc3f-930ad0cf6ce4.webp"></p><h1 id="评论区比文章更精彩"><a href="#评论区比文章更精彩" class="headerlink" title="评论区比文章更精彩"></a>评论区比文章更精彩</h1><h2 id="微信公众号评论区"><a href="#微信公众号评论区" class="headerlink" title="微信公众号评论区"></a>微信公众号评论区</h2><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514203931411.webp" alt="image-20230514203931411"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514203940833.webp" alt="image-20230514203940833"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514203951666.webp" alt="image-20230514203951666"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204055973.webp" alt="image-20230514204055973"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204002395.webp" alt="image-20230514204002395"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204022808.webp" alt="image-20230514204022808"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204042836.webp" alt="image-20230514204042836"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204123412.webp" alt="image-20230514204123412"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514204200492.webp" alt="image-20230514204200492"></p><h2 id="知乎评论区"><a href="#知乎评论区" class="headerlink" title="知乎评论区"></a>知乎评论区</h2><p>知乎回答已经被删了，我通过主页可以看到，但是点进去是已经被删了：如何评价拼多多疑似利用漏洞攻击用户手机，窃取竞争对手软件数据，防止自己被卸载？ - Gracker的回答 - 知乎 <a href="https://www.zhihu.com/question/587624599/answer/2927765317">https://www.zhihu.com/question/587624599/answer/2927765317</a></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514205638861.webp" alt="image-20230514205638861"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514205909534.webp" alt="image-20230514205909534"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514205857945.webp" alt="image-20230514205857945"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514205937705.webp" alt="image-20230514205937705"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514205947268.webp" alt="image-20230514205947268"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210010062.webp" alt="image-20230514210010062"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210020926.webp" alt="image-20230514210020926"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210040479.webp" alt="image-20230514210040479"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210107839.webp" alt="image-20230514210107839"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210122906.webp" alt="image-20230514210122906"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210141653.webp" alt="image-20230514210141653"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210152755.webp" alt="image-20230514210152755"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210226176.webp" alt="image-20230514210226176"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210235233.webp" alt="image-20230514210235233"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210255912.webp" alt="image-20230514210255912"></p><p><img src="/../images/bad-android-app-with-system-permissions/image-20230514210344475.webp" alt="image-20230514210344475"></p><h2 id="iOS-和-Android-哪个更安全？"><a href="#iOS-和-Android-哪个更安全？" class="headerlink" title="iOS 和 Android 哪个更安全？"></a>iOS 和 Android 哪个更安全？</h2><p>这里就贴一下安全大佬 sunwear 的评论</p><p><img src="/../images/bad-android-app-with-system-permissions/v2-87f6eef24742784cc52cd1e0060c103d_1440w.webp" alt="img"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前一段时间有个 App 很火，是 Android App 利用了 Android 系统漏洞，获得了系统权限，做了很多事情。想看看这些个 App 在利用系统漏洞获取系统权限之后，都干了什么事，于是就有了这篇文章。由于准备仓促，有些 Code 没有仔细看，感兴趣的同学可以自己去研究研究，多多讨论，对应的文章和 Code 链接都在下面：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/P_EYQxOEupqdU0BJMRqWsw&quot;&gt;深蓝洞察：2022 年度最 “不可赦” 漏洞&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/davinci1010/pinduoduo_backdoor&quot;&gt;XXX apk 内嵌提权代码，及动态下发 dex 分析&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/x6BSQZ3sE7vDcM0qvsC1EQ&quot;&gt;Android 反序列化漏洞攻防史话&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于这个 App 是如何获取这个系统权限的，&lt;a href=&quot;https://mp.weixin.qq.com/s/x6BSQZ3sE7vDcM0qvsC1EQ&quot;&gt;Android 反序列化漏洞攻防史话&lt;/a&gt;，这篇文章讲的很清楚，就不再赘述了，我也不是安全方面的专家，但是建议大家多读几遍这篇文章&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>The Performance 星球茶话会 - 第一期</title>
    <link href="https://androidperformance.com/2022/03/27/the-performance-tea-part-01/"/>
    <id>https://androidperformance.com/2022/03/27/the-performance-tea-part-01/</id>
    <published>2022-03-27T15:46:37.000Z</published>
    <updated>2026-02-07T05:17:47.880Z</updated>
    
    <content type="html"><![CDATA[<p>2022.3.25 ，周五晚上九点，The Performance 知识星球(付费版本)举办了第一次线上茶话会，3 位星球主理人 + 5 位星球嘉宾，50 多位球友参加，非常感谢各位！</p><p>第一次茶话会没有预设主题，预计 1 个小时就可以结束，结果聊了 2 个半小时，光是主理人和嘉宾的个人介绍就花了一个小时。平时大家在群里和星球里交流很多，但是通过语音在线交流还是第一次，再者大家的公司基本上涵盖了 Android 上下游：既有 App 大厂的大佬，也有一线手机厂商的系统大咖，也有芯片公司和造车新势力的资深专家。所以在每个人自我介绍的时候，会聊一些公司相关的东西，再发散一下，其他人再问一些问题，时间就过去了。</p><p>后续会不定期举办星球茶话会，主题会更加明确，也会邀请更多嘉宾来一起聊聊，不局限于技术，欢迎大家多多参与和提问。考虑到隐私问题，</p><p>本次茶话会没有录屏，下面的内容也不会涉及到各位的隐私，文字稿是根据聊天内容部分还原的。</p><span id="more"></span><h1 id="关于职业发展"><a href="#关于职业发展" class="headerlink" title="关于职业发展"></a>关于职业发展</h1><h2 id="领域的应届生真的被庞大的知识体系压的有点喘不过气"><a href="#领域的应届生真的被庞大的知识体系压的有点喘不过气" class="headerlink" title="领域的应届生真的被庞大的知识体系压的有点喘不过气"></a>领域的应届生真的被庞大的知识体系压的有点喘不过气</h2><ol><li><strong>改变心态</strong>：学校跟专业领域是不一样的。学校是有课本，学完一门课就算结业了。从事专业事项的时候，两个不一样，而且很不一样。</li><li><strong>从手边的问题开始学习</strong>：把工作中遇到的不懂的问题弄懂，做到 100 分甚至 110 分，平时储备自己不具备的知识。对于新人来说，把工作做好有很多好处。一是增加经验，二是获得更多学习与接触新东西的机会。切忌主业没做好，做另外一个方向，除非你确定目前的主业不适合自己干。</li><li><strong>面向解决问题编程</strong>：工作中很重要的一个技能就是解决问题，老板发钱就是让你解决问题的。掌握好度，以经济学来说就是控制好边际收益，投入与产出应最大化是比较理想的情况。</li><li><strong>掌握学习的方法和技巧</strong>：爬山的时候直接看山顶，更让人容易放弃。更好的方式是，<strong>头朝下一步一步走，偶尔看看地图是否走错，或者走着走着发现更适合自己的路。</strong></li></ol><h2 id="刚从-App-开发转到安卓系统性能优化的小白"><a href="#刚从-App-开发转到安卓系统性能优化的小白" class="headerlink" title="刚从 App 开发转到安卓系统性能优化的小白"></a>刚从 App 开发转到安卓系统性能优化的小白</h2><ol><li><strong>跟踪公司的项目走</strong>：如果有人带那就好，跟着公司项目走，这是最快的。</li><li><strong>自驱力</strong>：如果愿意平时学习，会的多了争取到的机会也多一些。还是那个观点，对于新人来说，把手头工作做好的前提下努力学习跟主业相关的新知识。当你做手头工作的时候你的主观能动性会变强、周边同事与领导也会支持你，而且给你的奖励也是不错的。</li><li><strong>不要给自己设限</strong>：知识都是融会贯通的，不要把自己局限在自己的一亩三分地，尤其是做性能的，App、Framework、Kernel、Hardware 这些，都可以去看去学习。努力使自己处于「知道自己不知道」的状态，最糟糕的是「不知道自己不知道」还觉得优化没啥可做，从经验与常识来看，这都是错误的。</li><li><strong>知识体系化</strong>：性能优化涉及到的知识非常广，平时分析和解决问题的时候，要多看代码、多记录、总结和归纳，把学到的知识体系化，不要想着一蹴而就，要厚积薄发。</li><li><strong>利用好现有的资源</strong>：跟着星球内容、博客内容、大厂的优秀文章这些学习，多提问，不管有多初级。</li></ol><h1 id="关于提问"><a href="#关于提问" class="headerlink" title="关于提问"></a>关于提问</h1><ol><li>星球是一个很好的平台，很多让你困惑的问题别人也经历过。但是你要利用好他的前提是能问出准确的问题。如果问问题，是个技术活。</li><li>可以参考这篇文章：<a href="https://mp.weixin.qq.com/s/l_Iz5pZ5yXhBAoPzNi4m-Q">到底如何提问？</a>：<a href="https://mp.weixin.qq.com/s/l_Iz5pZ5yXhBAoPzNi4m-Q">https://mp.weixin.qq.com/s/l_Iz5pZ5yXhBAoPzNi4m-Q</a><ul><li>决定探讨、思考、回答的质量和效率。「提出好的问题已经解决了问题的一半」，说得没什么科学依据，但也不妨理解好问题的重要性。</li><li>决定做事方向。曾经有人问爱因斯坦：「如果您有一个小时来拯救世界，您将如何使用？」他回答说：「我将花 55 分钟确定问题，然后花 5 分钟解决问题。」</li><li>提问是认识世界和人的桥梁。好问题能激发优秀者的教学欲望，将宝贵信息倾囊相授。</li><li>其实可以从一个人提问的能力来判断其段位。就像杨澜说的，「一个人的提问力，彰显在与外界互动质量的高低。」</li></ul></li><li>尽量使用 Google 来搜索答案，百度和 Google 的答案差异很大。答案不区分中文资料和英文资料，能解决疑惑就是好资料，值得保存和分享(到星球或者星球微信群)。</li><li>需要提供日志分析问题时<ul><li>如果涉及到敏感内容，比如 Systrace 中涉及到敏感信息，可以使用文本软件，比如 VSCode 打开 Systrace 文件，一键将敏感信息(比如 package name)替换。</li><li>Log 中如果涉密，尽量还是不要发出来。</li><li>如果是单独发给三个主理人，那么不脱敏也无所谓，我们承诺不会公开这些内容，只会做分析使用。</li></ul></li><li>一个好的闭环：大家伙提问 -&gt; 一起分析原因 -&gt; 尝试各种方法解决 -&gt; 通过数据验证有效果 -&gt; 把结果分享给大家。</li></ol><h1 id="关于面试"><a href="#关于面试" class="headerlink" title="关于面试"></a>关于面试</h1><p>有时候需要出去跟友商、新赛道的人沟通下，可以看看外部环境都在发生怎样的变化。首要目的不是为了跳槽（如果有好机会，跳也无妨），而是为了让自己在人力资源市场上有比较高的竞争力。</p><p>这里首先要区分职业与工作的区别。我是软件工程师，这是我的职业。我在 A 工作任职负责某个模块，这是我的工作。这两个是不一样的，如果你能把两者区分开（前提是能区分得开），那在择业、面对公司变化的时候都能从容应对。</p><p>最理想情况下，应该追求自己喜欢的职业，因为它伴随你绝大部分时间，如果这个职业本身不让你感到兴奋，你不太可能做出比较好的成绩，进而无法获得比较高的回报。</p><p>遇到好的工作，那就看天意了。这很复杂，与很多很多因素有关系。当你遇到面试不顺利，只能说这个工作不适合你，没有缘分，无法进一步说明更多的事情。</p><h1 id="关于公司环境"><a href="#关于公司环境" class="headerlink" title="关于公司环境"></a>关于公司环境</h1><p>不同的公司在不同的行业赛道、竞争格局上所采用的策略是不一样的。微观上，你的部门领导的风格以及同事们的结构，都影响到了你具体做事的环境。不同环境所追求的「回报最优值」是不一样的。</p><p>有的是需要你做到行业 5%，得到组织认可。有的是能把问题搞定就行而不关心如何搞定的，得到组织认可。</p><p>要么直接硬刚这种环境，按照你认为对的方式行事，要么就适应它。无论选择哪种，要知道局面是什么局面，而不是遇到与自己想象不一样的时候要么怀疑自己，要么以为这个世界就是这样。</p><h1 id="关于学习新技术"><a href="#关于学习新技术" class="headerlink" title="关于学习新技术"></a>关于学习新技术</h1><p>对于优化业务来说，它是有底层逻辑可寻的。而这些底层逻辑遇到不同的技术栈、不同的局面的时候会藏的比较深。平时学习中，要努力找出这种底层不变的东西，将可变的东西套进去，一是增强对新技术的判断力，二是可以举一反三提出更进一步的优化方案。</p><p>不要在新技术的表面上游荡，一是自己觉得累，二是这没啥意义。</p><p><strong>优化做到一定深度，肯定是往底层走。比如编译器、硬件特性，甚至硬件整合。因为这些东西直接决定了程序的速度，所以是绕不开的一道坎。</strong></p><h1 id="关于-GPU"><a href="#关于-GPU" class="headerlink" title="关于 GPU"></a>关于 GPU</h1><ul><li><strong>讲 GPU 架构相关</strong>：下面是一些 PDF，各时代、各厂商的 GPU 架构都不一样，当然也有相似之处，建议都看看。初学 GPU 架构的人会遇到很多专业名词，遇到了不懂的词要去搜索，然后弄懂，会有文章介绍，看完后会更加拓展你的视野，发现新的一片天地。<ol><li><a href="https://drive.google.com/file/d/12ahbqGXNfY3V-1Gj5cvne2AH4BFWZHGD/view">GPU Architectures</a></li><li><a href="http://www.irisa.fr/alf/downloads/collange/cours/ada2020_gpu_1.pdf">Introdution to GPU architecture</a></li><li><a href="http://download.nvidia.com/developer/cuda/seminar/TDCI_Arch.pdf">Introduction to Modern GPU Architecture</a></li><li><a href="https://www.comp.nus.edu.sg/~cs2100/2_resources/AppendixA_Graphics_and_Computing_GPUs.pdf">Graphics and Computing GPUs</a></li><li><a href="http://meseec.ce.rit.edu/551-projects/spring2015/3-1.pdf">GPU Architecture and Function</a></li></ol></li><li><strong>讲 GPU 架构相关的书，几乎没啥书</strong>：<ul><li>Mobile 3D Graphics SoC From Algorithm to Chip 这本书网上只有付费的，免费的 PDF 可以在我们星球上搜。</li></ul></li><li><strong>讲 GPU 相关的博客，很多我找不到了，就列举了一些我能找到的</strong>：<ol><li><a href="https://www.cnblogs.com/timlly/p/11471507.html">深入 GPU 硬件架构及运行机制 - 0 向往 0 - 博客园</a>，这个论坛讲的内容比较成体系，作者面向的读者应该是游戏开发的人群，不过学习图形的人都可以看看。</li><li><a href="https://www.cnblogs.com/gameknife/p/3515714.html">Tile Based 架构下的性能调校</a>，关于 Tile base 如果性能调优。 <strong>厂商一般都没有 OpenGL 实现库源码，如果又对源码感兴趣，可以下载下面两个库代码看</strong>：</li><li>谷歌的 swiftshader，在谷歌 android 源码根目录 external&#x2F;swiftshader 下，一个完全用 CPU 实现的 OpenGL 3.0 的版本，代码可读性非常高，可以帮助大家理解 OpenGL 的各种原如：状态机、资源管理、API 实现等。</li><li><a href="https://github.com/mesa3d/mesa">Mesa 3D</a>，一个开源的图形驱动库，网上有一些文档介绍可以搜搜。</li></ol></li><li><strong>如果没有图形开发经验，想学习 Vulkan 开发个人推荐先学习 OpenGL</strong>：因为 GPU 底层原理和机制都一样，学习了 OpenGL 入门 Vulkan 会更加简单，因为 Vulkan API 更加底层如果直接学习 Vulkan 估计会比较困难。如果有了 OpenGL 开发基础，想学 Vulkan，我分享一下我的学习之路（可能不是最优的，大家参考一下即可）：<ol><li><a href="https://vulkan-tutorial.com/">Vulkan Tutoria l</a> 学习最简单的 Vulkan 例子，如：如何绘制一个三角形。</li><li>《Vulkan Programming Guide》By Graham Sellers and John Kessenich，网上有 PDF，也有中文版，这本是官方出版的书，一本没有感情介绍 Vulkan API 的书，比较赤裸裸的介绍 API，几乎不讲原理，我学习它的目的是要熟悉 Vulkan 的 API。</li><li>《Vulkan 学习指南》作者帕敏德·辛格，也是一本入门的书，我是微信读书看的中文版，不过翻译得很一般，能看英文的尽量看原版，这本书比较注重渲染的流程和原理的讲解，不过 API 的介绍不全，需要配合《Vulkan Programming Guide》一起看。</li><li>看了入门的书，那就要动手写代码了，所以我推荐大家看一本用 Vulkan 来介绍游戏开发的书，我个人看的吴亚峰的《Vulkan 开发实践指南》，这本书个人觉得写的很一般，我看它的主要原因是，我大学的时候看过这本书 OpenGL 版本的，这本书基本就是 OpenGL 版本直译过来，讲的比较冰冷。</li></ol></li><li><strong>为什么都在推 Vulkan，Vulkan 比 OpenGL 有什么优势？可以看</strong>：<ul><li>苹果开发官网的<a href="https://developer.apple.com/videos/play/wwdc2019/611/">Bringing OpenGL Apps to Metal</a>视频，Metal 和 Vulkan API 基本一样，目的也是一样，都是来自 AMD 的 Mantle，把视频中 Metal 当做 Vulkan 就可以了</li><li>上面书中《Vulkan 学习指南》作者帕敏德·辛格，开编就有讲 OpenGL 和 Vulkan 差异</li></ul></li></ul><p>最后就是：<strong>学习图形开发的人一定要沉得住气耐得住寂寞，多看书多练习，不然很难学得精通，这是一个长期的过程，不是看几本书就能精通的。学习 GPU 架构的人，国内基本没有一家像样的 GPU 供应商，GPU 方面资料会很少，所以一定要多到外网去收集学习资料，可以多用 GPU 调试工具调试去分析应用程序，去了解 GPU 内部的 Counters，如 DS5 Streamline、Snapdragon Profiler 等，另外在没有很全面的学习资料的前提下，去学习一门更加底层的图形开发技术对学习和了解 GPU 架构也很重要，如 Vulkan，这会有助于自顶而下的去了解 GPU 内部的工作原理</strong>。</p><h1 id="关于提到的文章、书"><a href="#关于提到的文章、书" class="headerlink" title="关于提到的文章、书"></a>关于提到的文章、书</h1><h2 id="文章：十年创业者万字长文分享招人"><a href="#文章：十年创业者万字长文分享招人" class="headerlink" title="文章：十年创业者万字长文分享招人"></a>文章：十年创业者万字长文分享招人</h2><p>文章地址：<a href="https://presence.feishu.cn/docs/doccn71hTTKbaRGF8RvD2XzLJEK%EF%BC%8C%E9%87%8C%E9%9D%A2%E5%88%97%E4%B8%BE%E4%BA%86%E4%BD%9C%E8%80%85%E6%80%BB%E7%BB%93%E7%9A%84">https://presence.feishu.cn/docs/doccn71hTTKbaRGF8RvD2XzLJEK，里面列举了作者总结的</a> S、A、B、C 类人才，大家可以对照一下自己做事的方式，看看更高级别的人才是怎么做事的</p><p><img src="/images/the-performance-tea-part-01/3c462f41-7cb3-42ab-a37a-93ba7a67588a.jpg"></p><h2 id="书籍：程序员的自我修养-by-俞甲子-x2F-石凡-x2F-潘爱民"><a href="#书籍：程序员的自我修养-by-俞甲子-x2F-石凡-x2F-潘爱民" class="headerlink" title="书籍：程序员的自我修养 by 俞甲子 &#x2F; 石凡 &#x2F; 潘爱民"></a>书籍：程序员的自我修养 by 俞甲子 &#x2F; 石凡 &#x2F; 潘爱民</h2><p><img src="/images/the-performance-tea-part-01/7028f2d7-c7eb-43d9-be82-c4699ff3476b.webp"></p><p>豆瓣地址：<a href="https://book.douban.com/subject/3652388/">https://book.douban.com/subject/3652388/</a></p><blockquote><p>这本书主要介绍系统软件的运行机制和原理，涉及在 Windows 和 Linux 两个系统平台上，一个应用程序在编译、链接和运行时刻所发生的各种事项，包括：代码指令是如何保存的，库文件如何与应用程序代码静态链接，应用程序如何被装载到内存中并开始运行，动态链接如何实现，C&#x2F;C++运行库的工作原理，以及操作系统提供的系统服务是如何被调用的。每个技术专题都配备了大量图、表和代码实例，力求将复杂的机制以简洁的形式表达出来。本书最后还提供了一个小巧且跨平台的 C&#x2F;C++运行库 MiniCRT，综合展示了与运行库相关的各种技术。</p><p>对装载、链接和库进行了深入浅出的剖析，并且辅以大量的例子和图表，可以作为计算机软件专业和其他相关专业大学本科高年级学生深入学习系统软件的参考书。同时，还可作为各行业从事软件开发的工程师、研究人员以及其他对系统软件实现机制和技术感兴趣者的自学教材。</p></blockquote><h2 id="书籍：软件调试（第-2-版）-by-张银奎"><a href="#书籍：软件调试（第-2-版）-by-张银奎" class="headerlink" title="书籍：软件调试（第 2 版） by 张银奎"></a>书籍：软件调试（第 2 版） by 张银奎</h2><p>本书堪称是软件调试的“百科全书”。作者围绕软件调试的“生态”系统（ecosystem）、异常（exception）和调试器 3 条主线，介绍软件调试的相关原理和机制，探讨可调试性（debuggability）的内涵、意义以及实现软件可调试性的原则和方法，总结软件调试的方法和技巧。</p><h3 id="卷-1：硬件基础"><a href="#卷-1：硬件基础" class="headerlink" title="卷 1：硬件基础"></a>卷 1：硬件基础</h3><p>豆瓣地址：<a href="https://book.douban.com/subject/30379453/">https://book.douban.com/subject/30379453/</a></p><p><img src="/images/the-performance-tea-part-01/ff7e33e6-f9bb-483a-8410-c84320b5160a.webp"></p><blockquote><p>第 1 卷主要围绕硬件技术展开介绍。全书分为 4 篇，共 16 章。第一篇“绪论”（第 1 章），介绍了软件调试的概念、基本过程、分类和简要历史，并综述了本书后面将详细介绍的主要调试技术。第二篇“CPU 及其调试设施”（第 2 ～ 7 章），以英特尔和 ARM 架构的 CPU 为例系统描述了 CPU 的调试支持。第三篇“GPU 及其调试设施”（第 8 ～ 14 章），深入探讨了 Nvidia、AMD、英特尔、ARM 和 Imagination 这五大厂商的 GPU。第四篇“可调试性”（第 15 ～ 16 章），介绍了提高软件可调试性的意义、基本原则、实例和需要注意的问题，并讨论了如何在软件开发实践中实现可调试性。</p><p>本书理论与实践紧密结合，既涵盖了相关的技术背景知识，又针对大量具有代表性和普遍意义的技术细节进行了讨论，是学习软件调试技术的宝贵资料。本书适合所有从事软件开发工作的读者阅读，特别适合从事软件开发、测试、支持的技术人员，从事反病毒、网络安全、版权保护等工作的技术人员，以及高等院校相关专业的教师和学生学习参考。</p></blockquote><h3 id="卷-2：Windows-平台调试"><a href="#卷-2：Windows-平台调试" class="headerlink" title="卷 2：Windows 平台调试"></a>卷 2：Windows 平台调试</h3><p>书籍信息：<a href="https://book.douban.com/subject/35233332/">https://book.douban.com/subject/35233332/</a></p><p><img src="/images/the-performance-tea-part-01/a96af099-86cb-4a63-a89f-ee92e684c1e6.webp"></p><blockquote><p>第 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 调试器的历史、结构和用法。</p><p>本书理论与实践结合，不仅涵盖了相关的技术背景知识，还深入研讨了大量具有代表性的技术细节，是学习软件调试技术的珍贵资料。</p></blockquote><p><strong>这本书应当还有卷 3，此卷里会讲基于 Linux 平台的调试方法</strong>。</p><h1 id="关于-The-Performance-知识星球"><a href="#关于-The-Performance-知识星球" class="headerlink" title="关于 The Performance 知识星球"></a>关于 The Performance 知识星球</h1><p>The Performance 是一个分享 Android 开发领域性能优化相关的圈子，主理人是三个国内一线手机厂商性能优化方面的一线开发者，有多年性能相关领域的知识积累和案例分析经验，可以提供性能、功耗分析知识的一站式服务，涵盖了基础、方法论、工具使用和最宝贵的案例分析</p><p>星球 <strong>免费版</strong>，定位是知识分享和交流，也可以微信扫码加入</p><p><img src="/images/the-performance-tea-part-01/f675c08e-3afe-4c8c-8e8e-efc8652c1f80.jpg"></p><p><img src="/images/the-performance-tea-part-01/WechatIMG581.webp" alt="微信扫一扫"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;2022.3.25 ，周五晚上九点，The Performance 知识星球(付费版本)举办了第一次线上茶话会，3 位星球主理人 + 5 位星球嘉宾，50 多位球友参加，非常感谢各位！&lt;/p&gt;
&lt;p&gt;第一次茶话会没有预设主题，预计 1 个小时就可以结束，结果聊了 2 个半小时，光是主理人和嘉宾的个人介绍就花了一个小时。平时大家在群里和星球里交流很多，但是通过语音在线交流还是第一次，再者大家的公司基本上涵盖了 Android 上下游：既有 App 大厂的大佬，也有一线手机厂商的系统大咖，也有芯片公司和造车新势力的资深专家。所以在每个人自我介绍的时候，会聊一些公司相关的东西，再发散一下，其他人再问一些问题，时间就过去了。&lt;/p&gt;
&lt;p&gt;后续会不定期举办星球茶话会，主题会更加明确，也会邀请更多嘉宾来一起聊聊，不局限于技术，欢迎大家多多参与和提问。考虑到隐私问题，&lt;/p&gt;
&lt;p&gt;本次茶话会没有录屏，下面的内容也不会涉及到各位的隐私，文字稿是根据聊天内容部分还原的。&lt;/p&gt;</summary>
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="知识星球" scheme="https://androidperformance.com/tags/%E7%9F%A5%E8%AF%86%E6%98%9F%E7%90%83/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</title>
    <link href="https://androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/"/>
    <id>https://androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/</id>
    <published>2022-03-13T00:38:12.000Z</published>
    <updated>2026-02-07T05:17:47.866Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 线程 CPU 运行状态分析技巧系列的第三篇，本文主要讲了使用 Systrace 分析 CPU 状态时遇到的 <strong>Sleep</strong> 与 <strong>Uninterruptible Sleep</strong> 状态的原因排查方法与优化方法，这两个状态导致性能变差概率非常高，而且排查起来也比较费劲，网上也没有系统化的文档。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a> 或者 <a href="https://www.androidperformance.com/2019/12/01/BlogMap/">博客文章目录</a> 这里看到完整的目录</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#sleep-overview">Linux 中的 Sleep 状态是什么</a></li><li><a href="#sleep-analysis">Sleep 状态分析</a></li><li><a href="#usleep-analysis">UninterruptibleSleep 状态分析</a></li><li><a href="#appendix">附录</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><ol><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="sleep-overview"></a></p><h1 id="Linux-中的-Sleep-状态是什么"><a href="#Linux-中的-Sleep-状态是什么" class="headerlink" title="Linux 中的 Sleep 状态是什么"></a>Linux 中的 Sleep 状态是什么</h1><h2 id="TASK-INTERUPTIBLE-vs-TASK-UNINTERRUPTIBLE"><a href="#TASK-INTERUPTIBLE-vs-TASK-UNINTERRUPTIBLE" class="headerlink" title="TASK_INTERUPTIBLE vs TASK_UNINTERRUPTIBLE"></a>TASK_INTERUPTIBLE vs TASK_UNINTERRUPTIBLE</h2><p>一个线程的状态不属于 Running 或者 Runnable 的时候，那就是 Sleep 状态了（严谨来说，还有其他状态，不过对性能分析来说不常见，比如 STOP、Trace 等）。</p><p>在 Linux 中的Sleep 状态可以细分为 3 个状态：</p><ul><li><strong>TASK_INTERUPTIBLE</strong> → 可中断 </li><li><strong>TASK_UNINTERRUPTIBLE</strong> → 不可中断 </li><li><strong>TASK_KILLABLE</strong>  → 等同于 TASK_WAKEKILL | TASK_UNINTERRUPTIBLE</li></ul><p><img src="/images/android-systrace-cpu-state-sleep/image.webp" alt=" &quot;图 1: 性能之巅 2 CPU 优化&quot;"></p><p>在 Systrace&#x2F;Perfetto 中，Sleep 状态指的是 Linux 中的TASK_INTERUPTIBLE，trace 中的颜色为白色。Uninterruptible Sleep 指的是 Linux 中的 TASK_UNINTERRUPTIBLE，trace 中的颜色为橙色。 </p><p>本质上他们都是处于睡眠状态，拿不到 CPU的时间片，只有满足某些条件时才会拿到时间片，即变为 Runnable，随后是 Running。  </p><p>TASK_INTERRUPTIBLE 与 TASK_UNINTERRUPTIBLE 本质上都是 Sleep，<strong>区别在于前者是可以处理 Signal 而后者不能，即使是 Kill 类型的Signal</strong>。因此，除非是拿到自己等待的资源之外，没有其他方法可以唤醒它们。 TASK_WAKEKILL 是指可以接受 Kill 类型的Signal 的TASK_UNINTERRUPTIBLE。</p><p>Android 中的 Looper、Java&#x2F;Native 锁等待都属于 TAKS_INTERRUPTIBLE，因为他们可以被其他进程唤醒，应该说绝大部分的程序都处于 TAKS_INTERRUPTIBLE 状态，即 Sleep 状态。 看看 Systrace 中的一大片进程的白色状态就知道了（trace 中表现为白色块），它们绝大部分时间都是在 Runnning 跟 Sleep 状态之间转换，零星会看到几个 Runnable 或者 UninterruptibleSleep，即蓝色跟橙色。</p><h2 id="TASK-UNINTERRUPTIBLE-作用"><a href="#TASK-UNINTERRUPTIBLE-作用" class="headerlink" title="TASK_UNINTERRUPTIBLE 作用"></a>TASK_UNINTERRUPTIBLE 作用</h2><p>似乎看来 TASK_INTERUPTIBLE 就可以了，那为什么还要有 TASK_UNINTERRUPTIBLE 状态呢？</p><p>中断来源有两个，一个是硬件，另一个就是软件。硬件中断是外围控制芯片直接向 CPU 发送了中断信号，被 CPU 捕获并调用了对应的硬件处理函数。软件中断，前面说的 Signal、驱动程序里的 softirq 机制，主要用来在软件层面触发执行中断处理程序，也可以用作进程间通讯机制。</p><p>一个进程可以随时处理软中断或者硬件中断，他们的执行是在当前进程的上下文上，意味着共享进程的堆栈。但是在某种情况下，程序不希望有任何打扰，它就想等待自己所等待的事情执行完成。比如与硬件驱动打交道的流程，如 IO 等待、网络操作。 这是为了保护这段逻辑不会被其他事情所干扰，<code>避免它进入不可控的状态</code>。</p><p>Linux 处理硬件调度的时候也会临时关闭中断控制器、调度的时候会临时关闭抢占功能，本质上为了 <code>防止程序流程进入不可控的状态</code>。这类状态本身执行时间非常短，但系统出异常、运行压力较大的时候可能会影响到性能。</p><p><a href="https://elixir.bootlin.com/linux/latest/ident/TASK_UNINTERRUPTIBLE">https://elixir.bootlin.com/linux/latest/ident/TASK_UNINTERRUPTIBLE</a> </p><p>可以看到内核中使用此状态的情况，典型的有 Swap 读数据、信号量机制、mutex 锁、内存慢路径回收等场景。  </p><h2 id="分析时候的注意点"><a href="#分析时候的注意点" class="headerlink" title="分析时候的注意点"></a>分析时候的注意点</h2><p>首先要认识到 TASK_INTERUPTIBLE、TASK_UNINTERRUPTIBLE 状态的出现是正常的，但是如果这些这些状态的累计占比达到了一定程度，就要引起注意了。特别是在关键操作路径上这类状态的占比较多的时候，需要排查原因之后做相应的优化。 分析问题以及做优化的时候需要牢牢把握两个关键点，它类似于内功心法一样: </p><ol><li><strong>原因的排查方法</strong></li><li><strong>优化方法论</strong></li></ol><p>你需要知道是什么原因导致了这次睡眠，是主动的还是被动的？如果是主动的，通过走读代码调查是否是正常的逻辑。如果是被动的，故事的源头是什么？ 这需要你对系统有足够多的认识，以及分析问题的经验，你需要经常看案例以增强自己的知识。 </p><p>以下把 TASK_INTERUPTIBLE 称之为 Sleep，TASK_UNINTERRUPTIBLE称之为 UninterruptibleSleep，目的是与 Systrac 中的用词保持一致。 </p><p>初期分析 Sleep 与 UninterruptibleSleep 状态的经验不足时你会感到困惑，这种困惑主要是来自于对系统的不了解。你需要读大量的框架层、内核层的代码才能从 Trace 中找出蛛丝马迹。目前并没有一种 Trace 工具能把整个逻辑链路描述的很清楚，而且他们有时候还有不准的时候，比如 Systrace 中的 wakeup_from 信息。只有广泛的系统运行原理做为支持储备，再结合 Trace 工具分析问题，才能做到准确定位问题根因。否则就是我经常说的「性能优化流氓」，你说什么是什么，别人也没法证伪。反复折磨测试同学复测，没测出来之后，这个问题也就不了了之了。 </p><p>本文没办法列举完所有状态的原因，因此只能列举最为常见的类型，以及典型的实际案例。更重要的是，你需要掌握诊断方法，并结合源代码来定位问题。</p><h2 id="Trace-中的可视化效果"><a href="#Trace-中的可视化效果" class="headerlink" title="Trace 中的可视化效果"></a>Trace 中的可视化效果</h2><p><img src="/images/android-systrace-cpu-state-sleep/image_1.webp" alt="Pefetto 中支持显示的状态"></p><p><img src="/images/android-systrace-cpu-state-sleep/image_2.webp" alt="Systrace 支持显示的状态"></p><p><a id="sleep-analysis"></a></p><h1 id="Sleep-状态分析"><a href="#Sleep-状态分析" class="headerlink" title="Sleep 状态分析"></a>Sleep 状态分析</h1><p><img src="/images/android-systrace-cpu-state-sleep/image_3.webp" alt="图 1: UIThread 等待 RenderThread"></p><p><img src="/images/android-systrace-cpu-state-sleep/image_4.webp" alt="图 2: Binder 调用等待"></p><h2 id="诊断方法"><a href="#诊断方法" class="headerlink" title="诊断方法"></a>诊断方法</h2><h3 id="通过-wakeup-from-tid-查看唤醒线程"><a href="#通过-wakeup-from-tid-查看唤醒线程" class="headerlink" title="通过 wakeup from tid: ***查看唤醒线程"></a>通过 <code>wakeup from tid: ***</code>查看唤醒线程</h3><p>Sleep 最常见的有图 1（UIThread 与 RenderThread 同步）的情况与图 2（Binder 调用）的情况。 Sleep 状态一般是由程序主动等待某个事件的发生而造成的，比如锁等待，因此它有个比较明确的唤醒源。比如图 1，UIThread 等待的是 RenderThread，你可以通过阅读代码来了解这种多线程之间的交互关系。虽然最直接，但是对开发者的要求非常高，因为这需要你熟读图形栈的代码。这可不是一般的难度，是追求的目标，但不具备普适性。 </p><p>更简单的方法是通过所谓的 <code>wakeup from tid: ***</code> 来调查线程之间的交互关系。从前面的 <a href="https://articles.zsxq.com/id_wzkkiwop5pgm.html">Runnable 文章</a> 中讲过，任何线程进入 Running 之前会先进入到 Runnable 状态，由此再转换成 Running。从 Sleep 状态切换到 Running，必然也要经过 Runnable。</p><p>进入到 Runnable 有两种方式，一种是 Running 中的程序被抢占了，暂时进入到 Runnable。还有一种是由另外一个线程将此线程（处于 Sleep 的线程）变成了 Runnable。 </p><p>我们在调查Sleep 唤醒线程关系的时候，应用到的原理是第二种情况。在 Systrace 中这种是被 <code>wakeup from tid: ***</code> 信息所呈现。线程被抢占之后变成 Runnable，在 Systrace 中是被 <code>Running Instead</code> 呈现。 </p><p><img src="/images/android-systrace-cpu-state-sleep/image_5.webp"></p><p>需要特别注意的是 <code>wakeupfrom</code> 这个有时候不准，原因是跟具体的 tracepoint 类型有关。分析的时候要注意甄别，不要一味地相信这个数据是对的。</p><h3 id="其他方法"><a href="#其他方法" class="headerlink" title="其他方法"></a>其他方法</h3><ol><li>Simpleperf 还原代码执行流</li><li>在 Systrace 寻找时间点对齐的事件</li></ol><p>方法 1 适合用来看程序到底在执行什么操作进入到这种状态，是 IO 还是锁等待？球里连载 Simpleperf 工具的使用方法，其中「<a href="https://articles.zsxq.com/id_xc0a7uwmsf3z.html">Simpleperf 分析篇 (1): 使用 Firefox Profiler 可视分析 Simpleperf 数据</a>」介绍了可以按时间顺序看函数调用的可视化方法。其他使用也会陆续更新，直接搜关键字即可。</p><p>方法 2 是个比较笨的方法，但有时候也可以通过它找到蛛丝马迹，不过缺点是错误率比较高。</p><h2 id="耗时过长的常见原因"><a href="#耗时过长的常见原因" class="headerlink" title="耗时过长的常见原因"></a>耗时过长的常见原因</h2><ul><li><strong>Binder 操作</strong> → 通过打开 Binder 对应的 trace，可方便地观察到调用到远端的 Binder 执行线程。如果 Binder 耗时长，要分析远端的 Binder 执行情况，是否是锁竞争？得不到CPU 时间片？要具体问题具体分析  </li><li><strong>Java\futex锁竞争等待</strong> → 最常见也是最容易引起性能问题，当负载较高时候特别容易出现，特别是在 SystemServer 进程中。这是 Binder 多线程并行化或抢占公共资源导致的弊端。  </li><li><strong>主动等待</strong> → 线程主动进入 Sleep 状态，等待其它线程的唤醒，比如等待信号量的释放。优化建议：需要看代码逻辑分析等待是否合理，不合理就要优化掉。</li><li><strong>等待 GPU 执行完毕</strong> → 等 GPU 任务执行完毕，Trace 中可以看到等 GPU fence 时间。常见的原因有渲染任务过重、 GPU 能力弱、GPU 频率低等。优化建议：提升 GPU 频率、降低渲染任务复杂度，比如精简 Shader、降低渲染分辨率、降低Texture 画质等。</li></ul><p><a id="usleep-analysis"></a></p><h1 id="UninterruptibleSleep-状态分析"><a href="#UninterruptibleSleep-状态分析" class="headerlink" title="UninterruptibleSleep 状态分析"></a>UninterruptibleSleep 状态分析</h1><h2 id="诊断方法-1"><a href="#诊断方法-1" class="headerlink" title="诊断方法"></a>诊断方法</h2><p>本质上UninterruptibleSleep 也是一种 Sleep，因此分析 Sleep 状态时用到的方法也是通用的。不过此状态有两个特殊点与 Sleep 不同，因此在此特别说明。 </p><ol><li><strong>UninterruptibleSleep 分为 IOWait 与 Non-IOWait</strong></li><li><strong>UninterruptibleSleep 有 Block reason</strong></li></ol><h3 id="UninterruptibleSleep-分为-IOWait-与-Non-IOWait"><a href="#UninterruptibleSleep-分为-IOWait-与-Non-IOWait" class="headerlink" title="UninterruptibleSleep 分为 IOWait 与 Non-IOWait"></a>UninterruptibleSleep 分为 IOWait 与 Non-IOWait</h3><p>IO 等待好理解，就是程序执行了 IO 操作。最简单的，程序如果没法从 PageCache 缓存里快速拿到数据，那就要与设备进行 IO 操作。CPU 内部缓存的访问速度是最快的，其次是内存，最后是磁盘。它们之间的延迟差异是数量级差异，因此系统越是从磁盘中读取数据，对整体性能的影响就越大。  </p><p>非 IO 等待主要是指内核级别的锁等待，或者驱动程序中人为设置的等待。Linux 内核中某些路径是热点区域，因此不得不拿锁来进行保护。比如Binder 驱动，当负载大到一定程度，Binder 的内部的锁竞争导致的性能瓶颈就会呈现出来。 </p><h3 id="Block-Reason"><a href="#Block-Reason" class="headerlink" title="Block Reason"></a>Block Reason</h3><p>谷歌的 Riley Andrews(<a href="mailto:&#114;&#x69;&#97;&#110;&#100;&#114;&#101;&#x77;&#115;&#64;&#x67;&#x6f;&#111;&#103;&#108;&#x65;&#x2e;&#x63;&#x6f;&#x6d;">&#114;&#x69;&#97;&#110;&#100;&#114;&#101;&#x77;&#115;&#64;&#x67;&#x6f;&#111;&#103;&#108;&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>) 15年左右往内核里提交了一个 tracepoint 补丁，用于记录当发生 UninterruptibleSleep 的时候是否是 IO 等待、调用函数等信息。Systrace 中的展示的 IOWait 与 BlockReason，就是通过解析这条 tracepoint 而来的。这条代码提交的介绍如下（由于这笔提交未合入到 Linux 上游主线，因此要注意你用的内核是否单独带了此补丁）: </p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sched: add sched blocked tracepoint which dumps out context of sleep.</span><br><span class="line">Decare war on uninterruptible sleep. Add a tracepoint which</span><br><span class="line">walks the kernel stack and dumps the first non-scheduler function</span><br><span class="line">called before the scheduler is invoked.</span><br><span class="line"></span><br><span class="line">Change-Id: [I19e965d5206329360a92cbfe2afcc8c30f65c229](https://android-review.googlesource.com/#/q/I19e965d5206329360a92cbfe2afcc8c30f65c229)</span><br><span class="line">Signed-off-by: Riley Andrews [riandrews@google.com](mailto:riandrews@google.com)</span><br></pre></td></tr></table></figure><p>在 ftrace（Systrace 使用的数据抓取机制） 中的被记录为</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sched_blocked_reason: pid=<span class="number">30235</span> iowait=<span class="number">0</span> caller=get_user_pages_fast+<span class="number">0x34</span>/<span class="number">0x70</span> </span><br></pre></td></tr></table></figure><p>这句话被 Systrace 可视化的效果为: </p><p><img src="/images/android-systrace-cpu-state-sleep/image_6.webp"></p><p>主线程中有一段 Uninterruptible Sleep 状态，它的 BlockReason 是 <code>get_user_pages_fast</code>。它是一个 Linux 内核中函数的名字，代表着是线程是被它切换到了 UninterruptibleSleep 状态。为了查看具体的原因，需要查看这个函数的具体实现。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * get_user_pages_fast() - pin user pages in memory</span></span><br><span class="line"><span class="comment"> * @start:      starting user address</span></span><br><span class="line"><span class="comment"> * @nr_pages:   number of pages from start to pin</span></span><br><span class="line"><span class="comment"> * @gup_flags:  flags modifying pin behaviour</span></span><br><span class="line"><span class="comment"> * @pages:      array that receives pointers to the pages pinned.</span></span><br><span class="line"><span class="comment"> *              Should be at least nr_pages long.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Attempt to pin user pages in memory without taking mm-&gt;mmap_lock.</span></span><br><span class="line"><span class="comment"> * If not successful, it will fall back to taking the lock and</span></span><br><span class="line"><span class="comment"> * calling get_user_pages().</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Returns number of pages pinned. This may be fewer than the number requested.</span></span><br><span class="line"><span class="comment"> * If nr_pages is 0 or negative, returns 0. If no pages were pinned, returns</span></span><br><span class="line"><span class="comment"> * -errno.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">get_user_pages_fast</span><span class="params">(<span class="type">unsigned</span> <span class="type">long</span> start, <span class="type">int</span> nr_pages,</span></span><br><span class="line"><span class="params">      <span class="type">unsigned</span> <span class="type">int</span> gup_flags, <span class="keyword">struct</span> page **pages)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="keyword">if</span> (!is_valid_gup_flags(gup_flags))</span><br><span class="line">    <span class="keyword">return</span> -EINVAL;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   * The caller may or may not have explicitly set FOLL_GET; either way is</span></span><br><span class="line"><span class="comment">   * OK. However, internally (within mm/gup.c), gup fast variants must set</span></span><br><span class="line"><span class="comment">   * FOLL_GET, because gup fast is always a &quot;pin with a +1 page refcount&quot;</span></span><br><span class="line"><span class="comment">   * request.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  gup_flags |= FOLL_GET;</span><br><span class="line">  <span class="keyword">return</span> internal_get_user_pages_fast(start, nr_pages, gup_flags, pages);</span><br><span class="line">&#125;</span><br><span class="line">EXPORT_SYMBOL_GPL(get_user_pages_fast);</span><br></pre></td></tr></table></figure><p>从函数解释上可以看到，函数首先是通过无锁的方式pin 应用侧的 pages，如果失败的时候不得不尝试持锁后走慢速执行路径。此时，无法持锁的时候那就要等待了，直到先前持锁的人释放锁。那之前被谁持有了呢？这时候可以利用之前介绍的Sleep 诊断方法，如下图。</p><p><img src="/images/android-systrace-cpu-state-sleep/image_7.webp"></p><p>UninterruptibleSleep 状态相比 Sleep 有点复杂，因为它涉及到 Linux 内部的实现。可能是内核本身的机制有问题，也有可能是应用层使用不对，因此要联合上层的行为综合诊断才行。毕竟内核也不是万能的，它也有自己的能力边界，当应用层的使用超过其边界的时候，就会出现影响性能的现象。 </p><h2 id="IOWait-常见原因与优化方法"><a href="#IOWait-常见原因与优化方法" class="headerlink" title="IOWait 常见原因与优化方法"></a>IOWait 常见原因与优化方法</h2><h3 id="1-主动IO-操作"><a href="#1-主动IO-操作" class="headerlink" title="1. 主动IO 操作"></a>1. <strong>主动IO 操作</strong></h3><ul><li>程序进行频繁、大量的读或者写 IO 操作，这是最常见的情况。  </li><li>多个应用同时下发 IO 操作，导致器件的压力较大。同时执行的程序多的时候 IO 负载高的可能性也大。</li><li>器件本身的 IO 性能较差，可通过 IO Benchmark 来进行排查。 常见的原因有磁盘碎片化、器件老化、剩余空间较少（越是低端机越明显）、读放大、写放大等等。</li><li>文件系统特性，比如有些文件系统的内部操作会表现为 IO 等待。</li><li>开启 Swap 机制的内核下，数据从 Swap 中读取。</li></ul><p><strong>优化方法</strong></p><ul><li>调优 Readahead 机制</li><li>指定文件到 PageCache，即 PinFile 机制 </li><li>调整 PageCache 回收策略 </li><li>调优清理垃圾文件策略</li></ul><h3 id="2-低内存导致的-IO-变多"><a href="#2-低内存导致的-IO-变多" class="headerlink" title="2. 低内存导致的 IO 变多"></a>2. 低内存导致的 IO 变多</h3><p>内存是个非常有意思的东西，由于它的速度比磁盘快，因此 OS 设计者们把内存当做磁盘的缓存，通过它来避免了部分IO操作的请求，非常有效的提升了整体 IO 性能。有两个极端情况，当系统内存特别大的时候，绝大部分操作都可以在内存中执行，此时整体 IO 性能会非常好。当系统内存特别低，以至于没办法缓存 IO 数据的时候，几乎所有的 IO 操作都直接与器件打交道，这时候整体性能相比内存多的时候而言是非常差的。</p><p>所以系统中的内存较少的时候 IO 等待的概率也会变高。所以，这个问题就变成了如何让系统中有足够多的内存？如何调节磁盘缓存的淘汰算法？</p><p><strong>优化方法</strong></p><ul><li>关键路径上减少 IO 操作</li><li>通过Readahead 机制读数据</li><li>将热点数据尽量聚集在一起，使被 Readahead 机制命中的概率高 </li><li>最后一个老生常谈的，减少大量的内存分配、内存浪费等操作</li></ul><p>系统中的内存是被各个进程所共用。当app 只考虑自己，肆无忌惮的使用计算资源，必然会影响到其他程序。这时候系统还是会回来压制你，到头来亏损的还是自己。 不过能想到这一步的开发者比较少，也不现实。明文化的执行系统约定，可能是个终极解决方案。 </p><h2 id="Non-IOWait-常见原因"><a href="#Non-IOWait-常见原因" class="headerlink" title="Non-IOWait 常见原因"></a>Non-IOWait 常见原因</h2><ul><li><strong>低内存导致等待</strong> → 低内存的时候要回收其他程序或者缓存上的内存。</li><li><strong>Binder 等待</strong> → 有大量 Binder 操作的时候出现概率较高。  </li><li><strong>各种各样的内核锁，不胜枚举</strong>。结合「诊断方法」来分析。</li></ul><h2 id="系统调度与-UninterruptibleSleep-耦合的问题"><a href="#系统调度与-UninterruptibleSleep-耦合的问题" class="headerlink" title="系统调度与 UninterruptibleSleep   耦合的问题"></a>系统调度与 UninterruptibleSleep   耦合的问题</h2><p>当线程处于 UninterruptibleSleep 非 IO等待状态（即内核锁），而持有该锁的其他线程因 CPU 调度原因，较长时间处于 Runnable 状态。这时候就出现了有意思的现象，即使被等待的线程处于高优先级，它的依赖方没有被调度器及时的识别到，即使是非常短的锁持有，也会出现较长时间的等待。 </p><p>规避或者彻底解决这类问题都是件比较难的事情，不同厂家实现了不同的解决方案，也是比较考虑厂家技术能力的一个问题。 </p><p><a id="appendix"></a></p><h1 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h1><h2 id="Linux-线程状态释义"><a href="#Linux-线程状态释义" class="headerlink" title="Linux 线程状态释义"></a>Linux 线程状态释义</h2><table><thead><tr><th>线程状态</th><th>描述</th></tr></thead><tbody><tr><td>S</td><td>SLEEPING</td></tr><tr><td>R、R+</td><td>RUNNABLE</td></tr><tr><td>D</td><td>UNINTR_SLEEP</td></tr><tr><td>T</td><td>STOPPED</td></tr><tr><td>t</td><td>DEBUG</td></tr><tr><td>Z</td><td>ZOMBIE</td></tr><tr><td>X</td><td>EXIT_DEAD</td></tr><tr><td>x</td><td>TASK_DEAD</td></tr><tr><td>K</td><td>WAKE_KILL</td></tr><tr><td>W</td><td>WAKING</td></tr><tr><td>D</td><td>K</td></tr><tr><td>D</td><td>W</td></tr></tbody></table><h2 id="案例-从-Swap-读取数据时的等待"><a href="#案例-从-Swap-读取数据时的等待" class="headerlink" title="案例: 从 Swap 读取数据时的等待"></a>案例: 从 Swap 读取数据时的等待</h2><p><img src="/images/android-systrace-cpu-state-sleep/image_8.webp"></p><h2 id="案例-同进程的多个线程进行-mmap"><a href="#案例-同进程的多个线程进行-mmap" class="headerlink" title="案例: 同进程的多个线程进行 mmap"></a>案例: 同进程的多个线程进行 mmap</h2><p><img src="/images/android-systrace-cpu-state-sleep/image_9.webp"></p><p>共享同一个 mm_struct 的线程同时执行 mmap() 系统调用进行 vma 分配时发生锁竞争。</p><p>mmap_write_lock_killable() 与 mmap_write_unlock() 包起来的区域就是由锁受保护的区域。 </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">long</span> <span class="title function_">vm_mmap_pgoff</span><span class="params">(<span class="keyword">struct</span> file *file, <span class="type">unsigned</span> <span class="type">long</span> addr,</span></span><br><span class="line"><span class="params">  <span class="type">unsigned</span> <span class="type">long</span> len, <span class="type">unsigned</span> <span class="type">long</span> prot,</span></span><br><span class="line"><span class="params">  <span class="type">unsigned</span> <span class="type">long</span> flag, <span class="type">unsigned</span> <span class="type">long</span> pgoff)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> ret;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">mm_struct</span> *<span class="title">mm</span> =</span> current-&gt;mm;</span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> populate;</span><br><span class="line">  LIST_HEAD(uf);</span><br><span class="line"></span><br><span class="line">  ret = security_mmap_file(file, prot, flag);</span><br><span class="line">  <span class="keyword">if</span> (!ret) &#123;</span><br><span class="line">    <span class="keyword">if</span> (mmap_write_lock_killable(mm))</span><br><span class="line">      <span class="keyword">return</span> -EINTR;</span><br><span class="line">    ret = do_mmap(file, addr, len, prot, flag, pgoff, &amp;populate,</span><br><span class="line">            &amp;uf);</span><br><span class="line">    mmap_write_unlock(mm);</span><br><span class="line">    userfaultfd_unmap_complete(mm, &amp;uf);</span><br><span class="line">    <span class="keyword">if</span> (populate)</span><br><span class="line">      mm_populate(ret, populate);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 线程 CPU 运行状态分析技巧系列的第三篇，本文主要讲了使用 Systrace 分析 CPU 状态时遇到的 &lt;strong&gt;Sleep&lt;/strong&gt; 与 &lt;strong&gt;Uninterruptible Sleep&lt;/strong&gt; 状态的原因排查方法与优化方法，这两个状态导致性能变差概率非常高，而且排查起来也比较费劲，网上也没有系统化的文档。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 &lt;a href=&quot;https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/&quot;&gt;Systrace 基础知识 - Systrace 预备知识&lt;/a&gt; 或者 &lt;a href=&quot;https://www.androidperformance.com/2019/12/01/BlogMap/&quot;&gt;博客文章目录&lt;/a&gt; 这里看到完整的目录&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Systrace 线程 CPU 运行状态分析技巧 - Running 篇</title>
    <link href="https://androidperformance.com/2022/03/13/android-systrace-cpu-state-running/"/>
    <id>https://androidperformance.com/2022/03/13/android-systrace-cpu-state-running/</id>
    <published>2022-03-13T00:38:04.000Z</published>
    <updated>2026-02-07T05:17:47.866Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 线程 CPU 运行状态分析技巧系列的第二篇，主要分析了 Systrace 中 cpu 的 Running 状态出现的原因和 Running 过长时的一些优化思路。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a> 或者 <a href="https://www.androidperformance.com/2019/12/01/BlogMap/">博客文章目录</a> 这里看到完整的目录</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#running">Running 时间长</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><ol><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="running"></a></p><h1 id="Running-时间长"><a href="#Running-时间长" class="headerlink" title="Running 时间长"></a>Running 时间长</h1><h2 id="显示方式"><a href="#显示方式" class="headerlink" title="显示方式"></a>显示方式</h2><p>Trace 中显示绿色，表示线程处于运行态</p><h2 id="原因-1-代码本身复杂度高，执行耗时久"><a href="#原因-1-代码本身复杂度高，执行耗时久" class="headerlink" title="原因 1: 代码本身复杂度高，执行耗时久"></a>原因 1: 代码本身复杂度高，执行耗时久</h2><p>这是最常见的一种方式，当然不排除平台有bug，有时候厂商在libc、syscal等高频核心函数，加了一些逻辑导致了代码运行时间长。</p><p>优化建议: 优化逻辑、算法，降低复杂度。为了进一步判断具体是哪个函数耗时，可使用 AS CPU Profiler、simpleperf，或者自己通过 Trace.begin&#x2F;end() API 添加更多 tracepoint 点</p><p>当然不排除有的时候平台有bug，在关键的libc或内核函数加了一些逻辑</p><h2 id="原因-2-代码以解释方式执行"><a href="#原因-2-代码以解释方式执行" class="headerlink" title="原因 2: 代码以解释方式执行"></a>原因 2: 代码以解释方式执行</h2><p>Trace 中看到 「Compiling」字眼时可能意味着它是解释执行方式进行。刚安装的应用（未做 odex）的程序经常会出现这种情况</p><p>优化建议: 使用 dex2oat 之后的版本试试，解释执行方式下的低性能暂无改善方法，除非执行 dex2oat 或者提高代码效率本身</p><p>除此之外，使用了编程语言的某种特性，如频繁的调用 JNI，反复性反射调用。除了通过积攒经验方式之外，通过工具解决的方法就是通过 CPU Profiler、simpleperf 等工具进行诊断</p><h2 id="原因-3-线程跑小核，导致执行时间长"><a href="#原因-3-线程跑小核，导致执行时间长" class="headerlink" title="原因 3: 线程跑小核，导致执行时间长"></a>原因 3: 线程跑小核，导致执行时间长</h2><p>对 CPU  Bound 的操作来说跑在小核可能没法满足性能需求，因为小核的定位是处理非UX 强相关的线程。不过 Android 没办法保证这一点，有时候任务就是会安排在小核上执行。 </p><p>优化建议：线程绑核、SchedBoost 等操作，让线程跑尽量跑更高算力的核上，比如大核。有时候即使迁核了也不见效，这时候要看看频率是否拉得足够高，见“原因 4”</p><h2 id="原因-4-线程所跑的大核运行频率太低"><a href="#原因-4-线程所跑的大核运行频率太低" class="headerlink" title="原因 4: 线程所跑的大核运行频率太低"></a>原因 4: 线程所跑的大核运行频率太低</h2><p>优化建议：</p><ol><li>优化代码逻辑，主动降低运行负载，CPU 频率低也能流畅运行</li><li>修改调度器升频相关的参数，让 CPU 根据负载提频更激进</li><li>用平台提供的接口锁定 CPU 频率（俗称的「锁频」）</li></ol><h2 id="原因-5-温升导致-CPU-关核、限频"><a href="#原因-5-温升导致-CPU-关核、限频" class="headerlink" title="原因 5: 温升导致 CPU 关核、限频"></a>原因 5: 温升导致 CPU 关核、限频</h2><p>优化建议:</p><p>手机因结构原因导致散热能力差或温升参数过于激进时，为了保护体验跟不烫伤人，几乎所有手机厂家的系统会限制 CPU 频率或者直接关核。排查思路是首先需要找到触发温升的原因。</p><p>温升的排查的第一步，首先要看是外因导致还是内因导致。外因是指是否由外部高温导致，如太阳底下，火炉边；往往夏天的时候导致手机发热的情况越严重</p><p>内因主要由 CPU、Modem、相机模组或者其他发热比较厉害的器件导致的。以 CPU 为例，如果后台某个线程吃满 CPU，那就首先要解决它。如果是前台应用负载高导致大电流消耗，同样道理，那就降低前台本身的负载。其他器件也是同样道理，首先要看是否是无意义的运行，其次是优化业务逻辑本身</p><p>除此之外，温升参数过于激进的话导致触发限频关核的概率也会提高，因此通过与竞品对比等方式调优温升参数本身来达到优化目的</p><h2 id="原因-6-CPU-算力弱"><a href="#原因-6-CPU-算力弱" class="headerlink" title="原因 6: CPU 算力弱"></a>原因 6: CPU 算力弱</h2><p>优化建议:</p><p>ARM 处理器在相同频率下不同微架构的配置导致的性能差异是非常明显的，不同运行频率、L1&#x2F;L2 Cache 的容量均能影响 CPU 的 MIPS（<strong>Million Instructions Per Second</strong>） 执行结果。</p><p>优化思路有两条:</p><ol><li>编译器参数</li><li>优化代码逻辑</li></ol><p>第一条比较难，大部分应用开发者来说也不太现实，系统厂商如华为，方舟编译器优化 JNI 的思路本质是不改应用代码情况下提高代码执行效率来达到性能上的提升</p><p>第二条可以通过 simpleperf 等工具，找到热点代码或者观察 CPU 行为后做进一步的改善，如:</p><ul><li>Cache miss 率过高导致执行耗时，就要优化内存访问相关逻辑</li><li>代码复杂指令过多导致耗时，就要优化代码逻辑，降低代码复杂度</li><li>设计好业务缓存，尽量提高缓存命中率，避免抖动（反复地申请与释放）</li></ul><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 线程 CPU 运行状态分析技巧系列的第二篇，主要分析了 Systrace 中 cpu 的 Running 状态出现的原因和 Running 过长时的一些优化思路。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 &lt;a href=&quot;https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/&quot;&gt;Systrace 基础知识 - Systrace 预备知识&lt;/a&gt; 或者 &lt;a href=&quot;https://www.androidperformance.com/2019/12/01/BlogMap/&quot;&gt;博客文章目录&lt;/a&gt; 这里看到完整的目录&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</title>
    <link href="https://androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/"/>
    <id>https://androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/</id>
    <published>2022-01-21T02:40:18.000Z</published>
    <updated>2026-02-07T05:17:47.865Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 线程 CPU 运行状态分析技巧系列的第一篇，主要分析了 Systrace 中 cpu 的 runnable 状态出现的原因和 Runnable 过长时的一些优化思路。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a> 或者 <a href="https://www.androidperformance.com/2019/12/01/BlogMap/">博客文章目录</a> 这里看到完整的目录</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#overview">Runnable 状态说明</a></li><li><a href="#reasons">Runnable 过长的原因和优化思路</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><ol><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="overview"></a></p><h1 id="Runnable-状态说明"><a href="#Runnable-状态说明" class="headerlink" title="Runnable 状态说明"></a>Runnable 状态说明</h1><h2 id="Runnable-状态在-Trace-中的显示方式"><a href="#Runnable-状态在-Trace-中的显示方式" class="headerlink" title="Runnable 状态在 Trace 中的显示方式"></a>Runnable 状态在 Trace 中的显示方式</h2><p><a href="https://articles.zsxq.com/id_bp46saqr6ish.html">Perfetto&#x2F;Systrace: 不同 CPU 运行状态异常原因 101 - Running 长</a> 中讲解了导致 CPU 的 Running 状态耗时久的原因与优化方法，这一节介绍 Runnable 状态切换原理与对应的排查与优化思路。在 Systrace 中显示为蓝色，表示线程处于 Runnable，等待被 CPU 调度执行。</p><p><img src="/images/android-systrace-cpu-state-runnable/image.webp" alt="图 1: Systrace 中 Runnable 的可视化效果展示"></p><p><img src="/images/android-systrace-cpu-state-runnable/image_1.webp" alt="图 2: 性能之巅 2 CPU 优化"></p><p>从图 2 可知，一个 CPU 核在某个时刻只能执行一个线程，因此所有待执行的任务都在一个「可执行队列」里排队，一个 CPU 核就有一个队列。能插入到这个队列里排队的，代表着这个线程除了 CPU 资源，其他资源均已获取，如 IO、锁、信号量等。处于「可执行队列」的时候，线程的状态就会被置为 RUNNABLE，也就是 Systrace 里看到的 Runnable 状态。</p><p>Linux 内核是通过赋予不同线程执行时间片并通过轮转的方式来达到同时执行多个线程的效果，因此当一个 Running 中的线程的时间片用完时（通常是 ms 级别）将此线程置为 Runnable，等待下一次被调度。也有比较特殊的情况，那就是抢占。有些高优先级的线程可以抢占当前执行的线程，而不必等到此线程的时间片到期。</p><p>当一个 CPU 有多个核的时候显然可以多个核同时工作，这时候不必都在一个 CPU 核上排队，根据负载情况（也就是排队情况），将线程迁移到其他核执行是必要的操作。掌管这些调度策略的，是通过 Linux 的调度器来实现的，它具体通过多个调度类（Schedule Class）来管理不同线程的优先级，常见的有:</p><ol><li>SCHED_RR、SCHED_FIFO: 实时调度类，整体优先级上高于 NORMAL。</li><li>SCHED_NORMAL: 普通调度类，目前常用的是 CFS（Complete Fair Scheduler）调度器。<br>实时类的优先级高于普通调度类，高优先级的能抢占低优先级，并且要等待高优先级的执行完才能执行低优先级的。一般情况下 Runnable 的时间都很短，但出异常的的时候它会影响关键线程的关键任务在指定时间内完成。</li></ol><p><img src="/images/android-systrace-cpu-state-runnable/image_2.webp" alt="图 3: AOSP 渲染架构"></p><p>这个可能不止是一个线程，甚至是多个。特别是涉及到 UI 相关的任务，这种情况就更为复杂了。AOSP 体系下典型的一帧绘制是经过 <strong>UI Thread → Render Thread → SurfaceFlinger → HWC</strong>（参考 图 3），其中任何一个线程被 Runnable 阻塞导致没有在规定时间内完成渲染任务，都将会导致界面的卡顿（也就是掉帧）。</p><p><a id="reasons"></a></p><h1 id="Runnable-过长的原因和优化思路"><a href="#Runnable-过长的原因和优化思路" class="headerlink" title="Runnable 过长的原因和优化思路"></a>Runnable 过长的原因和优化思路</h1><p>我们从实践中总结出以下 5 大门类，系统层面出异常的原因较多，但也见过应用自身逻辑导致 Runnable 过长情况。</p><h2 id="原因-1-优先级设置错误"><a href="#原因-1-优先级设置错误" class="headerlink" title="原因 1: 优先级设置错误"></a>原因 1: 优先级设置错误</h2><ul><li>应用设置了过高的优先级：至于抢占了其他线程的任务，对后者来说显得自己优先级太低了。</li><li>应用设置了过低的优先级：当此线程处于「关键链路」时，以 Runnable 执行的概率就越高，导致卡顿概率也高。</li><li>系统出 Bug 时把线程优先级设为过高或者过低。</li></ul><p>优化思路:</p><ol><li>应用视情况调整线程优先级，可从 Trace 中可以看到是被哪个线程抢占了。</li><li>系统将关键线程调度策略设置成 FIFO。</li></ol><p>我们在实践中见到过不少应用因为设置错了优先级反而导致更卡。原因比较复杂，可能开发者所使用的机器用当时的优先级策略没问题，但是在别的厂商的调度器（头部大厂基本都有自己改动调度器）下就会出现水土不兼容的情况。一般情况下，三方应用开发者不建议直接调用这类 API，弄巧成拙，屡见不鲜。</p><p>长远看来更靠谱的方式是合理安排自己的任务模型，不要把对实时性要求很高的任务放到 worker 线程上。</p><h2 id="原因-2-绑核不合理"><a href="#原因-2-绑核不合理" class="headerlink" title="原因 2: 绑核不合理"></a>原因 2: 绑核不合理</h2><p>有时候为了让线程运行得更快，会把线程绑定到大核，在前面解决 Running 时间长时也有建议绑大核，但是绑核一定要谨慎，因为一旦把线程绑定在某个核，表示线程只能运行在这个核上即使其它核很空闲。如果多个线程都绑定在某个核，当这个核很繁忙调度不过来时，这些线程就会出现 Runnable 时间很长的情况。所以绑核一定要谨慎！下面是绑核需要注意的一些事项：</p><ol><li>线程绑核不要绑定在单个核上，这样容错率会特别低，因为一旦这个核被其它线程抢占绑定这个核的线程就要等着，所以尽量以 CPU 簇为单位进行绑核，比如线程要绑定大核，可以指定 4-7 大核而不是指定某个一大核。</li><li>2 个大核平台尽可能减少绑定大的核线程数目，不然会使得大核很容易繁忙，把绑核会变成「负优化」。</li><li>要正确区分大小核，比如 8 个核的平台，4-7 不一定就是大核，有的平台可能 0-3 才是大核。</li><li>只能在 CPUSET 允许范围内绑核，如果 CPUSET 只允许进程跑 0-3，如果进程试图绑定在 4-7 会绑核失败，甚至会有一些意料之外的致命错误。</li></ol><h2 id="原因-3-软件架构设计不合理"><a href="#原因-3-软件架构设计不合理" class="headerlink" title="原因 3: 软件架构设计不合理"></a>原因 3: 软件架构设计不合理</h2><p>重申下，Runnable 是指在 CPU 核上的排队耗时，按常识可可知道排队长、频繁排队时出问题概率也就越高。一个绘制任务所依赖的线程数量越多，出问题的概率也越高，因为排队次数变多了嘛。</p><p>软件架构不止要满足业务需求，也要在性能、扩展性方面上做思考，从上面推导可知，如果你程序编程模型需要大量线程协同运行来完成关键操作，如绘制，那出问题的概率就越高。</p><p>最常见的有，两个线程之间有频繁的有通讯与等待（线程 A 把任务转移到线程 B 执行，A 等待 B 任务执行完后被唤醒）， CPU 繁忙时很容易打出 Runnable 等待状态，CPU 越忙概率越高。</p><p>优化思路:</p><ol><li>应用调整线程优先级，见「原因 1」。</li><li>优化代码架构&#x2F;逻辑，免频繁等待其他线程的唤醒，在 Trace 中可以看到线程的依赖关系。可借助 CPU Profiler 探查代码执行逻辑，提高分析唤醒关系的效率。</li><li>平台通过修改调度器来识别有关系链的线程组，优先调度这个组里的线程。</li></ol><h2 id="原因-4-应用自己或系统整体负载高导致排队的任务非常多"><a href="#原因-4-应用自己或系统整体负载高导致排队的任务非常多" class="headerlink" title="原因 4: 应用自己或系统整体负载高导致排队的任务非常多"></a>原因 4: 应用自己或系统整体负载高导致排队的任务非常多</h2><p>从上述的调度原理可知，如果大量任务挤在一个核的「可执行队列」上，显然越是后面，优先级越低的任务排队时间就越长。</p><p>排查的时候你可以在 Perfetto&#x2F;Systrace 的 CPU 核维度任务上，即使在放大后的界面看到排满了密密麻麻的任务，这基本上就意味着系统整体负载较高了。通过计算，可算出 CPU 每时刻的使用量，基本上都会在 90%以上。 你可以通过选择一个区间，以时间来排序，看看都在执行什么任务，以此来逐个排查同时执行大量程序的原因是什么。</p><p>简单总结就是，同时执行的任务太多了，主要原因来自两方面:</p><h3 id="1-应用自身高占用"><a href="#1-应用自身高占用" class="headerlink" title="1.应用自身高占用"></a>1.应用自身高占用</h3><p>应用自身就把 CPU 资源都给占满了，狂开十来个线程来做事情，即使是头部大厂也会做这种事。</p><p>优化建议:</p><ol><li>找出应用所有占用高的线程，看看各线程此刻跑起来的行为是否异常，如果异常则要优化它。</li><li>优化线程负载本身，可使用 simpleperf 等工具进行函数级别的定位。</li><li>调整优先级，使用比 CFS 更高优先级的调度器，如设置为 RT。不过它带来的隐患也较多，需要慎重。</li><li>优化软件架构，区分关键与非关键线程，通过合理设置「绑核 &amp; 优先级」来为关键线程让出资源。 如，不重要线程绑到小核运行或设置低优先级、渲染相关线程设置高优先级等，让渲染线程相关的线程能占用到更多的 CPU 资源。设计架构的时候一定要考虑运行环境恶劣的情况，因为安卓从设计上就不敢保证所有资源都优先供给你，肯定有别人跟你抢资源。</li></ol><h3 id="2-系统服务高占用"><a href="#2-系统服务高占用" class="headerlink" title="2.系统服务高占用"></a>2.系统服务高占用</h3><p>有的厂商 ROM 自己本身就有很多任务，设计不合理的话自己家程序就吃满了大量资源，导致留给应用运行的资源较少。还有些是管控措施设计的一般，以至于留给了大量流氓应用可乘之机，各路神仙利用自己的「黑科技」在后台保活后进行各种拉活同步操作。</p><h3 id="3-平台厂家的黑科技"><a href="#3-平台厂家的黑科技" class="headerlink" title="3.平台厂家的黑科技"></a>3.平台厂家的黑科技</h3><p>厂家除了要优化自身服务，以做到「点到为止」外，可以实现如下功能来尽可能把资源分配合理化，让出更多资源给前台应用。</p><ol><li>通过 CGROUP 的 CPUSET 子系统，让不同优先级的线程运行在不同的 CPU 核心。AOSP 自带了 CPUSET 分组功能，不过有些缺陷如：<ol><li>分组不够精细，很多后台都可以跑满所有核</li><li>没有考虑进程的工作状态，如 音乐、导航、录音、视频、通话、下载</li><li>对 Java 进程 fork 的子进程放任不管</li></ol></li><li>通过 CGROUP 的 CPUCTL 子系统，进行资源配额，如限制异常进程、普通后台进程的不同量级的 CPU 最高使用量。</li><li>通过线程&amp;进程级别的冻结技术，在应用退出后台之后冻结进程让其拿不到 CPU 资源，类似 iOS 的做法。难点在于：<ol><li>切断和恢复各跨进程通信</li><li>进程关系的梳理</li><li>兼容性问题，需要有大量的测试验证</li></ol></li><li>按需启动系统进程与管控好后台进程自启动。</li></ol><p>每一个优化说简单也简单，说难也难，依赖厂家的技术积累。</p><h2 id="原因-5-CPU-算力限制、锁频、锁核、状态异常"><a href="#原因-5-CPU-算力限制、锁频、锁核、状态异常" class="headerlink" title="原因 5: CPU 算力限制、锁频、锁核、状态异常"></a>原因 5: CPU 算力限制、锁频、锁核、状态异常</h2><p>排队做核酸检测一样，检测窗口多的队列排队时间少。CPU 算力差、关核、限频，导致 Runnable 的概率也更高。通常的原因有:</p><ol><li><strong>场景控制</strong><ul><li>不同场景模式下的不同频率、核心策略</li><li>高温下的锁频锁核</li></ul></li><li><strong>CPU 省电模式</strong>：如高通的 Low Power Mode。</li><li><strong>CPU 状态切换</strong>：如 C2&#x2F;C1 切换到 C0 耗时久。</li><li><strong>CPU 损坏</strong>，概率小但也有可能会出现。</li><li><strong>低端机</strong> ：安卓上的低端机。</li></ol><p>其中：</p><ol><li>原因 1 <strong>场景控制</strong>， 考验厂家的能力与各自的标准，应用程序能做的还是那句名言 → 降低自己负载，少惹平台。 厂家为了设计好「场景控制」，需要有精细化的场景识别与合理的控制能力，将功耗与性能的平衡做到全局最优化，不同场景下应突出不同的业务能力，而不是一杆子拍死。</li><li>高温下的优化建议请参考「<a href="https://articles.zsxq.com/id_bp46saqr6ish.html">Perfetto&#x2F;Systrace: 不同 CPU 运行状态异常原因 101 - Running 长</a>」中的「原因 5: 温升导致 CPU 关核、限频」。</li><li>原因 3 <strong>CPU 状态切换</strong> 是芯片固有的特性，出现的概率小，但也不是不可能，每个芯片架构升级换代的时候就时不时遇到「妥协」版的 CPU 产品。厂家对芯片的评估是个比较隐性的能力，很少会被大众提及，但是非常重要的一个能力。电子消费品历史中，也总是重演关键器件选错了，导致厂家走入万劫不复境地的真实案例。</li><li>原因 5，<strong>安卓上的低端机</strong>，真的就指配备里低算力的 CPU，这与苹果的做法不一样，它的 CPU 至少跟当期旗舰是一样的。同样参考 「<a href="https://articles.zsxq.com/id_bp46saqr6ish.html">Perfetto&#x2F;Systrace: 不同 CPU 运行状态异常原因 101 - Running 长</a>」中的「原因 6: 算力弱」。</li></ol><h2 id="原因-6-调度器异常"><a href="#原因-6-调度器异常" class="headerlink" title="原因 6: 调度器异常"></a>原因 6: 调度器异常</h2><p>几乎所有的厂家都做了调度器优化方面的工作，虽然概率小，但也有可能会出异常。场景锁频锁核机制有问题、内核各种 governor 的出问题的时候，会出现明明 CPU 的其他核都很闲，但任务都挤在某几个核上。</p><p>系统开发者能做的就是把基础「可观测性技术」建好，出问题时可以快速诊断，因为这类问题一是不好复现，二是现象出现时机较短，可能立马就恢复了。</p><h2 id="原因-7-处理器区分执行-32-位与-64-位进程"><a href="#原因-7-处理器区分执行-32-位与-64-位进程" class="headerlink" title="原因 7: 处理器区分执行 32 位与 64 位进程"></a>原因 7: 处理器区分执行 32 位与 64 位进程</h2><p>有些过渡期的芯片，如最近推出的骁龙 8Gen1 与 天玑 9000，会有非常奇葩的运行限制。32 位的程序只能运行某个特定微架构上，64 位的则畅通无阻。且先不说这种「脑残设计」是处于什么所谓「平衡」，他带来的问题是，当你用的应用大量还是 32 位的时候，很多任务（以进程为单位）都挤在某个核心上运行，结合前面的理论，都挤在一起，出现 Runnable 的概率就更高。</p><ol><li>对应用开发者，建议尽快升级至 64 位程序。如果你用的是第三方方案，尽早通知改进或者改用其他方案。</li><li>对系统开发者，一是根据问题联系应用厂商做更新，二是特殊加强后台管理功能，进一步降低 32 位程序的运行负载。</li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 线程 CPU 运行状态分析技巧系列的第一篇，主要分析了 Systrace 中 cpu 的 runnable 状态出现的原因和 Runnable 过长时的一些优化思路。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。Systrace 基础和实战系列大家可以在 &lt;a href=&quot;https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/&quot;&gt;Systrace 基础知识 - Systrace 预备知识&lt;/a&gt; 或者 &lt;a href=&quot;https://www.androidperformance.com/2019/12/01/BlogMap/&quot;&gt;博客文章目录&lt;/a&gt; 这里看到完整的目录&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Techniques, Philosophy, and Tools for Android Performance Optimization</title>
    <link href="https://androidperformance.com/2022/01/07/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/"/>
    <id>https://androidperformance.com/2022/01/07/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/</id>
    <published>2022-01-06T16:42:03.000Z</published>
    <updated>2026-02-07T05:17:47.856Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/cc4162d9-7c2b-43b1-b9a1-f14d5c8e8605.jpg" alt="黑客与画家"></p><blockquote><p>In his book <em>Hackers &amp; Painters</em>, 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 &amp; Painters</p></blockquote><p>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.</p><p>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.</p><span id="more"></span><p>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.</p><h1 id="1-Observability-Techniques-in-Performance-Analysis"><a href="#1-Observability-Techniques-in-Performance-Analysis" class="headerlink" title="1 Observability Techniques in Performance Analysis"></a>1 Observability Techniques in Performance Analysis</h1><ul><li>Has this operation been executed? How long did it take?</li><li>Why is there a significant difference between two versions?</li><li>What operations is the system executing when CPU usage is high?</li><li>Why has the startup speed slowed down?</li><li>Why does this page always stutter when scrolling?</li></ul><p>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?</p><p>Of course, there is (otherwise this article wouldn’t exist)—we refer to it as observability techniques.</p><p>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.</p><p>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.</p><p>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.</p><p>We will explore data types, data acquisition methods, and analysis methods to help you understand observability techniques in the sections below.</p><h2 id="1-1-Data-Types"><a href="#1-1-Data-Types" class="headerlink" title="1.1 Data Types"></a>1.1 Data Types</h2><p>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.</p><h3 id="Log-Type"><a href="#Log-Type" class="headerlink" title="Log Type"></a>Log Type</h3><p>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.</p><p>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.</p><h3 id="Metric-Type"><a href="#Metric-Type" class="headerlink" title="Metric Type"></a>Metric Type</h3><p>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.</p><p>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.</p><p>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.</p><p>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.</p><h3 id="Trace-Type"><a href="#Trace-Type" class="headerlink" title="Trace Type"></a>Trace Type</h3><p>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.</p><p>Trace types are particularly suitable for Android application and system-level analysis scenarios because they can diagnose:</p><ol><li>Function call chains</li><li>Binder call chains during invocation</li><li>Cross-process event stream tracing</li></ol><p>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.</p><p>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.</p><p>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.</p><h2 id="1-2-Data-Acquisition-Methods"><a href="#1-2-Data-Acquisition-Methods" class="headerlink" title="1.2 Data Acquisition Methods"></a>1.2 Data Acquisition Methods</h2><h3 id="Static-Code-and-Dynamic-Tracing"><a href="#Static-Code-and-Dynamic-Tracing" class="headerlink" title="Static Code and Dynamic Tracing"></a>Static Code and Dynamic Tracing</h3><p>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.</p><p>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.</p><p>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.</p><p>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.</p><h3 id="Unconditional-and-Conditional-Capture"><a href="#Unconditional-and-Conditional-Capture" class="headerlink" title="Unconditional and Conditional Capture"></a>Unconditional and Conditional Capture</h3><p>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.</p><p>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.</p><h3 id="Disk-Write-Strategy"><a href="#Disk-Write-Strategy" class="headerlink" title="Disk Write Strategy"></a>Disk Write Strategy</h3><p>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.</p><h2 id="1-3-Analysis-Methods"><a href="#1-3-Analysis-Methods" class="headerlink" title="1.3 Analysis Methods"></a>1.3 Analysis Methods</h2><h3 id="Data-Visualization-Analysis"><a href="#Data-Visualization-Analysis" class="headerlink" title="Data Visualization Analysis"></a>Data Visualization Analysis</h3><p>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&#x2F;Perfetto and, earlier, KernelShark, are fundamentally of this type. The “Trace Type” mentioned in “Data Types” often employs this kind of visualization.</p><p>Systrace’s visualization framework is built on a Chrome subproject called Catapult. The <a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview">Trace Event Format</a> 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.</p><h3 id="Database-Analysis"><a href="#Database-Analysis" class="headerlink" title="Database Analysis"></a>Database Analysis</h3><p>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 <a href="https://www.elastic.co/cn/what-is/elk-stack">ELK</a> offer flexible formatted search and statistical functions. With databases and Python, you can even create an automated data diagnostic toolchain.</p><p>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.</p><p>Choosing the right analysis method according to your objectives can make your work highly efficient.</p><p>For Android developers, Google provides several essential performance analysis tools to assist both system and app developers in optimizing their programs.</p><h1 id="2-Google’s-Android-Performance-Analysis-Tools"><a href="#2-Google’s-Android-Performance-Analysis-Tools" class="headerlink" title="2 Google’s Android Performance Analysis Tools"></a>2 Google’s Android Performance Analysis Tools</h1><p>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.”</p><h2 id="2-1-First-Generation-System-Performance-Analysis-Tool-Systrace"><a href="#2-1-First-Generation-System-Performance-Analysis-Tool-Systrace" class="headerlink" title="2.1 First Generation System Performance Analysis Tool - Systrace"></a>2.1 First Generation System Performance Analysis Tool - Systrace</h2><p>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.</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/8ea0e7de-1f32-471f-929a-5a37d73e3270.webp" alt="Systrace"></p><h3 id="Systrace-Design-Philosophy"><a href="#Systrace-Design-Philosophy" class="headerlink" title="Systrace Design Philosophy"></a><strong>Systrace Design Philosophy</strong></h3><p>Systrace embeds information similar to logs, known as TracePoints (essentially Ftrace information), at <strong>key system operations</strong> (such as Touch operations, Power button actions, sliding operations, etc.), <strong>system mechanisms</strong> (including input distribution, View drawing, inter-process communication, process management mechanisms, etc.), and <strong>software and hardware information</strong> (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.</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/56bebe5b-e5fd-4b69-8e36-3197fe205034.webp"></p><p>In the Android system, some essential modules have default inserted TracePoints, classified by TraceTag, with information sources as follows:</p><ol><li>TracePoints in the Framework Java layer are implemented through the <code>android.os.Trace</code> class.</li><li>TracePoints in the Framework Native layer are executed using the ATrace macro.</li><li>App developers can customize Trace through the <code>android.os.Trace</code> class.</li></ol><p>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.</p><h3 id="Practical-Applications"><a href="#Practical-Applications" class="headerlink" title="Practical Applications"></a>Practical Applications</h3><p>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.</p><ol><li><p><strong>From a Technical Perspective:</strong><br>Systrace can cover major categories involved in performance, such as <strong>response speed</strong>, <strong>frame drops or janks</strong>, and <strong>ANR (Application Not Responding)</strong> issues.</p></li><li><p><strong>From a User Perspective:</strong><br>Systrace can analyze various performance issues encountered by users, including but not limited to:</p><ul><li><strong>Application Launch Speed Issues:</strong> Including cold start, warm start, and hot start.</li><li><strong>Slow Interface Transitions:</strong> Including slow transitions and janky animations.</li><li><strong>Slow Non-Transition Click Operations:</strong> Such as toggles, pop-ups, long presses, selections, etc.</li><li><strong>Slow Screen Brightness Adjustment Speed:</strong> Including slow on&#x2F;off speed, slow unlocking, slow face recognition, etc.</li><li><strong>List Scrolling Jankiness:</strong></li><li><strong>Window Animation Lag:</strong></li><li><strong>Interface Loading Jankiness:</strong></li><li><strong>Overall System Lag:</strong></li><li><strong>App Unresponsiveness:</strong> Including freeze and crash issues.</li></ul></li></ol><p>When encountering the above problems, various methods can be employed to capture Systrace. The parsed file can then be opened in Chrome for analysis. </p><p>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.</p><h2 id="2-2-The-Next-Generation-Performance-Analysis-Full-Stack-Tool-Perfetto"><a href="#2-2-The-Next-Generation-Performance-Analysis-Full-Stack-Tool-Perfetto" class="headerlink" title="2.2 The Next-Generation Performance Analysis Full Stack Tool - Perfetto"></a>2.2 The Next-Generation Performance Analysis Full Stack Tool - Perfetto</h2><p>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&#x2F;Metric data capture and analysis. Its application is also quite extensive; apart from the Perfetto website, <a href="https://devblogs.microsoft.com/performance-diagnostics/new-tools-for-analyzing-android-linux-and-chromium-browser-performance/">Windows Performance Tool</a>, <a href="https://developer.android.com/studio">Android Studio</a>, and Huawei’s <a href="https://developer.huawei.com/consumer/cn/doc/development/Tools-Guides/overview-0000001050741459">GraphicProfiler</a> 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.</p><h3 id="Highlighted-Features"><a href="#Highlighted-Features" class="headerlink" title="Highlighted Features"></a>Highlighted Features</h3><p>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.</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/fc328f61-6e2a-4695-b712-c42a7e9ab02b.webp" alt="Perfetto"></p><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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&#x2F;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.</p><h3 id="Practical-Application"><a href="#Practical-Application" class="headerlink" title="Practical Application"></a>Practical Application</h3><p>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.</p><p>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.</p><h2 id="2-3-Android-Studio-Profiler-Tool"><a href="#2-3-Android-Studio-Profiler-Tool" class="headerlink" title="2.3 Android Studio Profiler Tool"></a>2.3 Android Studio Profiler Tool</h2><p>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.</p><p>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.</p><h3 id="Highlighted-Features-1"><a href="#Highlighted-Features-1" class="headerlink" title="Highlighted Features"></a><strong>Highlighted Features</strong></h3><p>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:</p><ol><li><strong>System Trace Recording</strong>: 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 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a>.</li><li><strong>Java Method Trace Recording</strong>: It gathers function call stack information from the virtual machine, used for analyzing Java function calls and duration.</li><li><strong>C&#x2F;C++ Function Trace</strong>: Information captured with Simpleperf. Simpleperf gathers data from the CPU’s performance monitoring unit (PMU) hardware component. <strong>C&#x2F;C++ Method Trace</strong> has only partial functionalities of Simpleperf, used for analyzing C&#x2F;C++ function calls and durations.</li></ol><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/92246083-124c-4290-a5c4-5b57529741cb.webp" alt="CPU Profiler"></p><h3 id="Practical-Application-1"><a href="#Practical-Application-1" class="headerlink" title="Practical Application"></a>Practical Application</h3><p>Application performance issues are mainly divided into two categories: slow response and lack of smoothness.</p><ul><li>Slow response issues include slow app startup, slow page transitions, slow list loading, slow button responses, etc.</li><li>Lack of smoothness issues include unsmooth list scrolling, page sliding not following hand movements, animation judders, etc.</li></ul><p>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&#x2F;C++ Function Trace.</p><p>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 <strong>record a System Trace with CPU Profiler</strong> (the tool usage will be introduced in later articles), as shown below:</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/359770f0-ccf4-4857-9f7d-89db47e44d0b.webp"></p><p>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 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 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, <strong>we need to record a Java Method Trace</strong>, as follows:</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/ee7e0852-9643-46f1-86b6-23e3975c1e54.webp"></p><p>From the above Trace, it’s easy to see that a native function called Utils.onDraw is time-consuming. Because it involves C&#x2F;C++ code, <strong>another C&#x2F;C++ Function Trace needs to be recorded for further analysis</strong>, as shown below:</p><p><img src="/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/25a10f6e-4465-4ea4-addb-624203e99dfd.webp"></p><p>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!</p><p>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.</p><h2 id="2-4-Comparative-Analysis"><a href="#2-4-Comparative-Analysis" class="headerlink" title="2.4 Comparative Analysis"></a>2.4 Comparative Analysis</h2><table><thead><tr><th>Tool Name</th><th>Application Scenario</th><th>Data Type</th><th>Data Acquisition Method</th><th>Analysis Method</th></tr></thead><tbody><tr><td>Systrace</td><td>Android System &amp; App Performance Analysis</td><td>Trace Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis</td></tr><tr><td>Perfetto</td><td>Android System &amp; App Performance Analysis</td><td>Metric Type, Trace Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis, Database Analysis</td></tr><tr><td>AS Profiler</td><td>Android System &amp; App Performance Analysis</td><td>Trace Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis</td></tr><tr><td>SimplePerf</td><td>Java&#x2F;C++ Function Execution Time Analysis, PMU Counters</td><td>Trace Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis, Text Analysis</td></tr><tr><td>Snapdragon Profiler Tools &amp; Resources</td><td>Primarily for Qualcomm GPU Performance Analyzer</td><td>Trace Type, Metric Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis</td></tr><tr><td>Mali Graphics Debugger</td><td>ARM GPU Analyzer (for MTK, Kirin chips)</td><td>Trace Type, Metric Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis</td></tr><tr><td>Android Log&#x2F;dumpsys</td><td>Comprehensive Analysis</td><td>Log Type</td><td>Conditional Capture, Continuous Capture but not Logging</td><td>Text Analysis</td></tr><tr><td>AGI (Android GPU Inspector)</td><td>Android GPU Analyzer</td><td>Trace Type, Metric Type</td><td>Unconditional Capture, Continuous Logging</td><td>Visual Analysis</td></tr><tr><td>eBPF</td><td>Dynamic Tracing of Linux Kernel Behavior</td><td>Metric Type</td><td>Dynamic Tracing, Conditional Capture, Continuous Capture but not Logging</td><td>Text Analysis</td></tr><tr><td>FTrace</td><td>Linux Kernel Tracing</td><td>Log Type</td><td>Static Code, Conditional Capture, Continuous Capture but not Logging</td><td>Text Analysis</td></tr></tbody></table><h1 id="3-On-“Instruments-Techniques-Philosophy”"><a href="#3-On-“Instruments-Techniques-Philosophy”" class="headerlink" title="3 On “Instruments, Techniques, Philosophy”"></a>3 On “Instruments, Techniques, Philosophy”</h1><p>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.</p><p>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.</p><p>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?</p><p>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.</p><h1 id="About-Me-amp-amp-Blog"><a href="#About-Me-amp-amp-Blog" class="headerlink" title="About Me &amp;&amp; Blog"></a>About Me &amp;&amp; Blog</h1><ol><li><a href="https://www.androidperformance.com/about/">About Me</a>: I am eager to interact and progress together with everyone. </li><li><a href="https://twitter.com/Gracker_Gao">Follow me on Twitter</a></li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">Blog Content Navigation</a></li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">Record of Excellent Blog Articles - Essential Skills and Tools for Android Performance Optimization</a></li></ol><p><strong>An individual can move faster, a group can go further.</strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;/images/Techniques-Philosophy-and-Tools-for-Android-Performance-Optimization/cc4162d9-7c2b-43b1-b9a1-f14d5c8e8605.jpg&quot; alt=&quot;黑客与画家&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In his book &lt;em&gt;Hackers &amp;amp; Painters&lt;/em&gt;, 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 &amp;amp; Painters&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Framework" scheme="https://androidperformance.com/tags/Framework/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化的术、道、器</title>
    <link href="https://androidperformance.com/2022/01/07/The-Performace-1-Performance-Tools/"/>
    <id>https://androidperformance.com/2022/01/07/The-Performace-1-Performance-Tools/</id>
    <published>2022-01-06T16:42:03.000Z</published>
    <updated>2026-02-07T05:17:47.857Z</updated>
    
    <content type="html"><![CDATA[<p><img src="/images/The-Performace-1-Performance-Tools/cc4162d9-7c2b-43b1-b9a1-f14d5c8e8605.jpg" alt="黑客与画家"></p><blockquote><p>Paul Graham 在其著作 &lt;黑客与画家&gt; 中断言：“不同语言的执行效率差距正变得越来越大，所以性能分析器（profiler）将变得越来越重要。目前，性能分析并没有受到重视。许多人好像仍然相信，程序运行速度提升的关键在于开发出能够生成更快速代码的编译器。代码效率与机器性能的差距正在不断加大，我们将会越来越清楚地看到，应用软件运行速度提升的关键在于有一个好的性能分析器帮助指导程序开发。”<br>by Paul Graham 黑客与画家 </p></blockquote><p>谷歌搜索 「Android 优化工具」，你会找到很多与此相关的内容。他们的问题在于要么是内容高度重复、要么是直接讲使用方法，很少会给你介绍整体性的架构，一不小心就会让人会种「一个工具搞定一切」的错误认知。以笔者团队的多年经验来看，在性能分析领域这种银弹级别的工具是不存在的。工具在发展，老问题会以新的方式变样出现，不掌握核心逻辑的话始终会让你浮于技术的表面。 </p><p>本文首先系统性的梳理性能分析中的可观测性技术，它涵盖数据类型、抓取方法以及分析方法等三部分内容，之后是介绍谷歌提供的「三大件」分析工具。目的是想让你了解不变的理论性的知识，以及与之对应的在安卓环境中可用的工具，这些可以让你少走一些弯路，直接复用前辈们的经验。 </p><span id="more"></span><p>需要特别说明的是，对于性能优化肯定不止有这三个工具可用，但这个三个工具是我们平时用到的「第一手工具」。进行进一步分析之前，你都需要依赖这三个工具进行瓶颈定位，之后才应不同领域特性选择对应的工具进行下钻分析。  </p><h1 id="1-性能分析中的可观测性技术"><a href="#1-性能分析中的可观测性技术" class="headerlink" title="1 性能分析中的可观测性技术"></a>1 性能分析中的可观测性技术</h1><ul><li>这个操作到底有没有被执行？执行时间有多长？</li><li>为什么两个版本的前后差异这么大？</li><li>当 CPU 使用量变高的时候系统都在执行什么操作？</li><li>为什么启动速度变慢了？</li><li>为什么这个页面滑动总是会卡一下？</li></ul><p>相信你不止一次被同事、被老板问到过类似的问题。最原始的想法应该是，首先是拿到相关的日志进行逐个分析。根据以往经验，通过查找关键字寻找蛛丝马迹。如果没有想看的信息，那就加上日志尝试本地复现。费时费力不说，也还费研发资源。但你有没有想过行业里有没有更高效的方法？可以提高一个数量级的那种，把我们的时间花在问题解决上而不是无聊的重复性体力活儿上？ </p><p>答案当然是有的（否则就不会有这篇文章了），我们称他为可观测性技术。 </p><p>计算机行业发展至今，计算机前辈们捣鼓出了所谓的「可观测性技术」的类别。它研究的是通过工具，来观测复杂系统的运行细节，内容越细越好。 移动操作系统之前是由嵌入式发展而来的，现在的中高端安卓手机算力都能赶得上二十几年前的一个主机的算力，在此算力基础上所带来的软件复杂度也是非常巨大的。  </p><p>如果你的程序部署了一个精心设计且运行良好的可观测性技术，可以大大加快研发软件的效率，因为即使我们使用了各种各样的前置性静态代码检测、人工代码审查，也无法 100% 拦截软件的问题。只有在真实环境里运行之后才知道是否真正发生了问题，即使这个环境可能是一个你的自动化测试用例。即使这样，你还需要翻阅你的日志，重读代码来找出问题。出于这些原因，每个工程团队都需要有一个功能完备的可观测性工具作为他们的基础设施之一。 </p><p>可观测性技术是一个系统性工程，它能够让你更深入的了解软件里发生的事情。可用于了解软件系统内部运行过程（特别是对于业务逻辑或者交互关系复杂的系统）、排查问题甚至通过寻找瓶颈点优化程序本身。对于复杂的系统来说，你通过阅读代码来了解整个运行过程其实是很困难的事情，更高效的方法就是借助此类工具，以最直观的的方式获取软件运行的状态。</p><p>下面将从 数据类型、数据获取方法、分析方法 这三个主题来帮助你了解可观测性技术。</p><h2 id="1-1-数据类型"><a href="#1-1-数据类型" class="headerlink" title="1.1 数据类型"></a>1.1 数据类型</h2><p>日志的形式可能是键值对（key&#x3D;Value），JSON、CSV，关系型数据库或者其他任何格式。其次我们通过日志还原出系统当时运行的整个状态，目的是为了解决某个问题，观察某个模块的运行方式，甚至刻画系统使用者的行为模式。在可观测性技术上把日志类型分类为 Log 类型、Metric 类型，以及 Trace 类型。  </p><p><img src="/images/The-Performace-1-Performance-Tools/d9548a10-cc3d-497d-9d4c-5efa92de1a43-20220107005708401.webp" alt="数据类型"></p><h3 id="Log-类型"><a href="#Log-类型" class="headerlink" title="Log 类型"></a>Log 类型</h3><p>Log 是最朴素的数据记录方式，一般记录了什么模块在几点发生了什么事情，日志等级是警告还是错误。 绝大部分系统，不管是嵌入式设备还是汽车上的计算机，他们所使用的日志形式几乎都是这种形式。这是最简单，最直接也最好实现的一种方式。几乎所有的 Log 类型是通过 string 类型的方式存储，数据呈现形式是一条一条的文本数据。Log 是最基本的类型，因此通过转换，可以将 Log 类型转换成 Metric 或者 Trace 类型，当然成本就是转换的过程，当数据量非常巨大的时候这可能会成为瓶颈。</p><p>为了标识出不同的日志类型等级，一般使用错误、警告、调试等级别来划分日志等级。显然，错误类型的是你首要关注的日志等级。不过实践中也不会严格按照这种方式划分，因为很多工程师不会严格区分他们之间的差异，这可能是他们的工程开发环境中不太会对不同等级的日志进行分类分析有关。总之，你可以根据你的目的，将 Log 类型进行等级划分，它就像一个索引一样，可以进一步可以提高分析问题、定位目标信息的效率。 </p><h3 id="Metric-类型"><a href="#Metric-类型" class="headerlink" title="Metric 类型"></a>Metric 类型</h3><p>Metric 类型相比 Log 类型使用目的上更为聚焦，它记录的是某个维度上数值的变化。知识点是「维度」与「数值」的变化。维度可能是 CPU 使用率、CPU Cluster 运行频率，或者上下文切换次数。数值变化既可以是采样时候的瞬时值（成为快照型）、与前一次采样时的差值（增或减）、或者某个时段区间的统计聚合值。实践中经常会使用统计值，比如我想看问题发生时刻前 5 分钟的 CPU 平均使用量。这时候需要将这五分钟内的所有数值做算数平均计算，或者是加权平均（如: 离案发点越近的样本它的权重就越高）。Log 类型当然可以实现 Metric 类型的效果，但是操作起来非常麻烦而且其性能损耗可能也不小。</p><p>聚合是非常有用的工具，因为人不可能逐个分析所有的 Metric 值，因此借助聚合的方式判断是否出了问题之后再进行详细的分析是更为经济高效的方法。</p><p>Metric 类型的另外一个好处是它的内容格式是比较固定的，因此可以通过预编码的方式进行数据存储，空间的利用率会更紧凑进而占用的磁盘空间就更少。最简单的应用就是数据格式的存储上，如果使用 Log 类型，一般采用的是 ASCII 编码，而 Metric 使用的是整数或者浮点等固定 byte 数的数据，当存储较大数值时显然 ASCII 编码需要的字节数会多于数字型数据，并且在进行数据处理的时候你可以直接使用 Metric 数据，而不需要把 Log 的 ASCII 转换成数字型后再做转换。 </p><p>除了是具体的数值之外，也可以存储枚举值（某种程度上它的本质就是数值）。不同的枚举值代表不同的意义，可能是开和关、可能是不同的事件类型。 </p><h3 id="Trace-类型"><a href="#Trace-类型" class="headerlink" title="Trace 类型"></a>Trace 类型</h3><p>Trace 类型标识了事件发生的时间、名称、耗时。多个事件通过关系，标识出了是父与子还是兄弟。当分析多个线程间复杂的调用关系时 Trace 类型是最方便的数据分析方式。</p><p>Trace 类型特别适用于 Android 应用与系统级的分析场景，因为用它可以诊断: </p><ol><li>函数调用链</li><li>Binder 调用时的调用链</li><li>跨进程事件流跟踪</li></ol><p>Android 的应用程序运行环境的设计中，一个应用程序是无法独自完成所有的功能的，它需要跟 SystemServer 有大量的交互才能完成它的很多功能。与 SystemServer 间的通讯是通过 Binder 完成，它的通讯方式后面的文章再详细介绍，到目前为止你只需要知道它的调用关系是跨进程调用即可。这需要本端与远端的数据才能准确还原出调用关系，Trace 类型是完成这种信息记录的最佳方式。 </p><p>Trace 类型可以由你手动添加开始与结束点，在一个函数里可以添加多个这种区间。通过预编译技术或者编程语言的特性，在函数的开头与结尾里自动插桩 Trace 区间。理想情况下后者是最好的方案，因为我们能知道系统中运行的所有的函数是哪些、执行情况与调用关系是什么。可以拿这些信息统计出调用次数最多（最热点）的函数是什么，最耗时的函数又是什么。可想而知这种方法带来的性能损耗非常大，因为函数调用的频次跟量级是非常大的，越是复杂的系统量级就越大。</p><p>因此有一种迂回的方法，那就通过采样获取调用栈的方式近似拟合上面的效果。采样间隔越短，就越能拟合真实的调用关系与耗时，但间隔也不能太小因为取堆栈的操作本身的负载就会变高因为次数变多了。这种方法，业界管他叫 Profiler，你所见过的绝大部分编程语言的 Profiler 工具都是基于这个原理实现的。</p><h2 id="1-2-数据获取方法"><a href="#1-2-数据获取方法" class="headerlink" title="1.2 数据获取方法"></a>1.2 数据获取方法</h2><p><img src="/images/The-Performace-1-Performance-Tools/b0fd0720-1b25-4546-a98e-f25947a52505-20220107005708428.webp" alt="数据获取方法"></p><h3 id="静态代码与动态跟踪"><a href="#静态代码与动态跟踪" class="headerlink" title="静态代码与动态跟踪"></a>静态代码与动态跟踪</h3><p>静态代码的采集方式是最原始的方式，优点是实现简单缺点是每次新增内容的时候需要重新编译、安装程序。当遇到问题之后你想看的信息恰好没有的话，就没有任何办法进一步定位问题，只能重新再来一遍整个过程。更进一步的做法是预先把所有可能需要的地方上加入数据获取点，通过动态判断开关的方式选择是否输出，这既可以控制影响性能又能够在需要日志的时候可以动态打开，只不过这种方法的成本非常高。</p><p>动态跟踪技术其实一直都存在，只是它的学习成本比较高，被誉为调试跟踪领域里的屠龙刀。它需要你懂比较底层的技术，特别是编译、ELF 格式、内核、以及熟悉代码中的预设的探针、动态跟踪所对应的编程语言。对，你没看错，这种技术甚至还有自己的一套编程语言用于「动态」的实现开发者需求。这种方式兼具性能、灵活性，甚至线上版本里遇到异常后可以动态查看你想看的信息。</p><p>Android 应用开发、系统级开发中用的比较少，内核开发中偶尔会用一些。只有专业、专职的性能分析人员才可能会用上这类工具。它有两个关键点，探针与动态语言，程序运行过程中需要有对应的探针点将程序执行权限交接到动态跟踪框架，框架执行的逻辑是开发者使用动态语言来编写的逻辑。</p><p>所以，你的程序里首先是要有探针，好在 Linux 内核等框架埋好了对应的探针点，但是 android 应用层是没有现成的。所以目前 Android 上能用动态框架，如 eBPF 基本都是内核开发者在使用。 </p><h3 id="无条件式抓取与有条件式抓取"><a href="#无条件式抓取与有条件式抓取" class="headerlink" title="无条件式抓取与有条件式抓取"></a>无条件式抓取与有条件式抓取</h3><p>无条件式抓取比较好理解，触发抓取之后不管发生任何事情，都会持续抓取数据。缺点是被观测对象产生的数据量非常大的时候可能会对系统造成比较大的影响，这种时候只能通过降低数据量的方式来缓解。需要做到既能满足需求，性能损失又不能太大。 </p><p>有条件式抓取经常用在可以识别出的异常的场景里。比如当系统的某个观测值超过了预先设定的阈值时，此时触发抓取日志并且持续一段时间或者达到另外一种阈值之后结束抓取。这相比于前面一个方法稍微进步了一些，仅在出问题的时候对系统有影响，其他时候没有任何影响点。但它需要你能够识别出异常，并且这种异常是不需要异常发生之前的历史数据。当然你可以通过降低阈值来更容易达到触发点，这可能会提高触发数据抓取的概率，这时候会遇到前面介绍的无条件式抓取遇到的同样的问题，需要平衡性能损失。</p><h3 id="落盘策略"><a href="#落盘策略" class="headerlink" title="落盘策略"></a>落盘策略</h3><p>持续落盘是存储整个数据抓取过程中的所有数据，代价是存储的压力。如果能知道触发点，比如能够检测到异常点，这时候可以选择性的落盘。为了保证历史数据的有效性，因此把日志先暂存储到 RingBuffer 中，只有接受到落盘指令后再进行落盘存储。这种方式兼顾了性能与存储压力，但成本是运行时内存损耗与触发器的准确性。</p><h2 id="1-3-分析方式"><a href="#1-3-分析方式" class="headerlink" title="1.3 分析方式"></a>1.3 分析方式</h2><p><img src="/images/The-Performace-1-Performance-Tools/0d8c6651-c62f-4f0f-aacc-0ebb56667e23-20220107005708419.webp" alt="分析方式"></p><h3 id="数据可视化分析"><a href="#数据可视化分析" class="headerlink" title="数据可视化分析"></a>数据可视化分析</h3><p>随着问题分析的复杂化，出现了要解决多个模块间交互的性能问题需求，业界就出现了以时间为横轴把对应事件放到各自泳道上的数据可视化分析方法，可以方便的看到所关心事件什么时候发生、与其他系统的交互信息等等。在 Android 里我们常用的 Systrace&#x2F;Perfetto 以及更早之前的 KernelShark 等工具本质上都是这一类工具。在「数据类型」提到的 「Trace 类型」，经常采用这种可视化分析方法。 </p><p>Systrace 的可视化框架是基于 Chrome 的一个叫 Catapult 的子项目构建。<a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview">Trace Event Format</a> 讲述了 Catapult 所支持的数据格式，如果你有 Trace 类型的数据，完全可以使用此框架来展示可视化数据。AOSP 编译系统，安卓应用的编译过程，也都有相应的 Trace 文件输出，它们也都基于 Catapult 实现了可视化效果。 </p><h3 id="数据库分析"><a href="#数据库分析" class="headerlink" title="数据库分析"></a>数据库分析</h3><p>面对大量数据分析的分析，通过对数据进行格式化，把他们转换成二维数据表，借助 SQL 语言可实现高效的查询操作。在服务器领域中 <a href="https://www.elastic.co/cn/what-is/elk-stack">ELK</a> 等技术栈可以实现更为灵活的格式化搜索与统计功能。借助数据库与 Python，你甚至可以实现一套自动化数据诊断工具链。 </p><p>从上面的讨论可知，从文本分析到数据库分析他们要面对的分析目的是不一样的。单纯的看一个模块的耗时用文本分析就够用了，多个系统间的交互那就要用可视化工具，复杂的数据库分析就要用到 SQL 的工具。无论哪种分析方式，本质上都是针对数据的分析，在实战中我们经常会通过其他工具对数据进行转换以支持不同的分析方式，比如从文本分析方式改成数据库分析方式。 </p><p>根据自己的目的，选择合适的分析方式才会让你的工作事倍功半。 </p><p>对于 Android 开发者来说，Google 提供了几个非常重要的性能分析工具，帮助系统开发者、应用开发者来优化他们的程序。</p><h1 id="2-谷歌提供的-Andorid-性能分析工具"><a href="#2-谷歌提供的-Andorid-性能分析工具" class="headerlink" title="2 谷歌提供的 Andorid 性能分析工具"></a>2 谷歌提供的 Andorid 性能分析工具</h1><p>从实践经验来看最常用的工具有 Systrace，Perfetto 与 Android Studio 中的 Profiler 工具。通过他们定位出主要瓶颈之后，你才需要用到其他领域相关工具。因此，会重点介绍这三个工具的应用场景，它的优点以及基本的使用方法。 工具之间的横向对比，请参考下一个「综合对比」这一章节的内容。</p><h2 id="2-1-初代系统性能分析工具-Systrace"><a href="#2-1-初代系统性能分析工具-Systrace" class="headerlink" title="2.1 初代系统性能分析工具 - Systrace"></a>2.1 初代系统性能分析工具 - Systrace</h2><p>Systrace 是 Trace 类型的可视化分析工具，是第一代系统级性能分析工具。Trace 类型所支持的功能它都有支持。在 Perfetto 出现之前，基本上是唯一的性能分析工具，它将 Android 系统和 App 的运行信息以图形化的方式展示出来，与 Log 相比，Systrace 的图像化方式更为直观；与 TraceView 相比，抓取 Systrace 时候的性能开销基本可以忽略，最大程度地减少观察者效应带来的影响。</p><p><img src="/images/The-Performace-1-Performance-Tools/8ea0e7de-1f32-471f-929a-5a37d73e3270.webp" alt="Systrace"></p><h3 id="Systrace-的设计思路"><a href="#Systrace-的设计思路" class="headerlink" title="Systrace 的设计思路"></a><strong>Systrace 的设计思路</strong></h3><p>在<strong>系统的一些关键操作</strong>（比如 Touch 操作、Power 按钮、滑动操作等）、<strong>系统机制</strong>（input 分发、View 绘制、进程间通信、进程管理机制等）、<strong>软硬件信息</strong>（CPU 频率信息、CPU 调度信息、磁盘信息、内存信息等）的关键流程上，插入类似 Log 的信息，我们称之为 TracePoint（本质是 Ftrace 信息），通过这些 TracePoint 来展示一个核心操作过程的执行时间、某些变量的值等信息。然后 Android 系统把这些散布在各个进程中的 TracePoint 收集起来，写入到一个文件中。导出这个文件后，Systrace 通过解析这些 TracePoint 的信息，得到一段时间内整个系统的运行信息。</p><p><img src="/images/The-Performace-1-Performance-Tools/56bebe5b-e5fd-4b69-8e36-3197fe205034.webp"></p><p>Android 系统中，一些重要的模块都已经默认插入了一些 TracePoint，通过 TraceTag 来分类，其中信息来源如下</p><ol><li>Framework Java 层的 TracePoint 通过 android.os.Trace 类完成</li><li>Framework Native 层的 TracePoint 通过 ATrace 宏完成</li><li>App 开发者可以通过 android.os.Trace 类自定义 Trace</li></ol><p>这样 Systrace 就可以把 Android 上下层的所有信息都收集起来并集中展示，对于 Android 开发者来说，Systrace 最大的作用就是把整个 Android 系统的运行状态，从黑盒变成了白盒。全局性和可视化使得 Systrace 成为 Android 开发者在分析复杂的性能问题的时候的首选。</p><h3 id="实践中的应用情况"><a href="#实践中的应用情况" class="headerlink" title="实践中的应用情况"></a>实践中的应用情况</h3><p>解析后的 Systrace 由于有大量的系统信息，天然适合分析 Android App 和 Android 系统的性能问题， Android 的 App 开发者、系统开发者、Kernel 开发者都可以使用 Systrace 来分析性能问题。</p><ol><li>从技术角度来说，Systrace 可覆盖性能涉及到的 <strong>响应速度</strong> 、<strong>卡顿丢帧</strong>、 <strong>ANR</strong> 这几个大类。</li><li>从用户角度来说，Systrace 可以分析用户遇到的性能问题，包括但不限于: <ol><li>应用启动速度问题，包括冷启动、热启动、温启动</li><li>界面跳转速度慢、跳转动画卡顿</li><li>其他非跳转的点击操作慢（开关、弹窗、长按、选择等）</li><li>亮灭屏速度慢、开关机慢、解锁慢、人脸识别慢等</li><li>列表滑动卡顿</li><li>窗口动画卡顿</li><li>界面加载卡顿</li><li>整机卡顿</li><li>App 点击无响应、卡死闪退</li></ol></li></ol><p>在遇到上述问题后，可以使用多种方式抓取 Systrace ，将解析后的文件在 Chrome 打开，然后就可以进行分析</p><h2 id="2-2-新一代性能分析全栈工具-Perfetto"><a href="#2-2-新一代性能分析全栈工具-Perfetto" class="headerlink" title="2.2 新一代性能分析全栈工具 - Perfetto"></a>2.2 新一代性能分析全栈工具 - Perfetto</h2><p>谷歌在 2017 年开始了第一笔提交，随后的 4 年（截止到 2021.12）内总共有 100 多位开发者提交了近 3.7W 笔提交，几乎每天都有 PR 与 Merge 操作，是一个相当活跃的项目。 除了功能强大之外其野心也非常大，官网上号称它是下一代面向可跨平台的 Trace&#x2F;Metric 数据抓取与分析工具。应用也比较广泛，除了 Perfetto 网站，<a href="https://devblogs.microsoft.com/performance-diagnostics/new-tools-for-analyzing-android-linux-and-chromium-browser-performance/">Windows Performance Tool</a> 与 <a href="https://developer.android.com/studio">Android Studio</a>，以及华为的 <a href="https://developer.huawei.com/consumer/cn/doc/development/Tools-Guides/overview-0000001050741459">GraphicProfiler</a> 也支持 Perfetto 数据的可视化与分析。 我们相信谷歌还会持续投入资源到 Perfetto 项目，可以说它应该就是下一代性能分析工具了，会完全取代 Systrace。</p><h3 id="提供的亮点功能"><a href="#提供的亮点功能" class="headerlink" title="提供的亮点功能"></a>提供的亮点功能</h3><p>Perfetto 相比 Systrace 最大的改进是可以支持长时间数据抓取，这是得益于它有一个可在后台运行的服务，通过它实现了对收集上来的数据进行 Protobuf 的编码并存盘。从数据来源来看，核心原理与 Systrace 是一致的，也都是基于 Linux 内核的 Ftrace 机制实现了用户空间与内核空间关键事件的记录（ATRACE、CPU 调度）。Systrace 提供的功能 Perfetto 都支持，由此才说 Systrace 最终会被 Perfetto 替代。</p><p><img src="/images/The-Performace-1-Performance-Tools/fc328f61-6e2a-4695-b712-c42a7e9ab02b.webp" alt="Perfetto"></p><p>Perfetto 所支持的数据类型、获取方法，以及分析方式上看也是前所未有的全面，它几乎支持所有的类型与方法。数据类型上通过 ATRACE 实现了 Trace 类型支持，通过可定制的节点读取机制实现了 Metric 类型的支持，在 UserDebug 版本上通过获取 Logd 数据实现了 Log 类型的支持。</p><p>你可以通过 Perfetto.dev 网页、命令行工具手动触发抓取与结束，通过设置中的开发者选项触发长时间抓取，甚至你可以通过框架中提供的 Perfetto Trigger API 来动态开启数据抓取，基本上涵盖了我们在项目上能遇到的所有的情境。 </p><p>在数据分析层面，Perfetto 提供了类似 Systrace 操作的数据可视化分析网页，但底层实现机制完全不同，最大的好处是可以支持超大文件的渲染，这是 Systrace 做不到的（超过 300M 以上时可能会崩溃、可能会超卡）。在这个可视化网页上，可以看到各种二次处理的数据、可以执行 SQL 查询命令、甚至还可以看到 logcat 的内容。Perfetto Trace 文件可以转换成基于 SQLite 的数据库文件，既可以现场敲 SQL 也可以把已经写好的 SQL 形成执行文件。甚至你可以把他导入到 Jupyter 等数据科学工具栈，将你的分析思路分享给其他伙伴。 </p><p>比如你想要计算 SurfaceFlinger 线程消耗 CPU 的总量，或者运行在大核中的线程都有哪一些等等，可以与领域专家合作，把他们的经验转成 SQL 指令。如果这个还不满足你的需求， Perfetto 也提供了 Python API，将数据导出成 DataFrame 格式近乎可以实现任意你想要的数据分析效果。 </p><p>这一套下来供开发者可挖掘的点就非常多了，从笔者团队的实践来看，他几乎可以覆盖从功能开发、功能测试、CI&#x2F;CD 以及线上监控、专家系统等方方面面。本星球的后续系列文章中，也会重点介绍 Perfetto 的强大功能与基于它开发的专家系统，可以帮助你「一键解答」性能瓶颈。 </p><h3 id="实践中的应用情况-1"><a href="#实践中的应用情况-1" class="headerlink" title="实践中的应用情况"></a>实践中的应用情况</h3><p>性能分析首要用到的工具就是 Perfetto，使用 Systrace 的场景是越来越少了。所以，你首要掌握的工具应该是 Perfetto，学习它的用法以及它提供的指标。 </p><p>不过 Perfetto 也有一些边界，首先它虽然提供了较高的灵活性但本质上还是静态数据收集器，不是动态跟踪工具，跟 eBPF 还是有本质上的差异。其次运行时成本比较高，因为涉及到在手机中实现 Ftrace 数据到 Perfetto 数据的转换。最后他不提供文本分析方式，只能通过网页可视化或者操作 SQLite 来进行额外的分析了。综合来看 Perfetto 是功能强大，几乎涵盖了可观测性技术的方方面面，但是使用门槛也比较高。值得挖掘与学习的知识点比较多，我们后续的文章中也会重点安排此部分的内容。</p><h2 id="2-3-Android-Studio-Profiler-工具"><a href="#2-3-Android-Studio-Profiler-工具" class="headerlink" title="2.3 Android Studio Profiler  工具"></a>2.3 Android Studio Profiler  工具</h2><p>Android 的应用开发集成环境（官方推荐）是 Android Studio （之前是Eclipse，不过已经淘汰了） ，它自然而然也需要把开发和性能调优集成一起。非常幸运的是，随着 Android Studio 的迭代、演进，到目前，Android Studio 有了自己的性能分析工具 Android Profiler，它是一个集合体，集成了多种性能分析工具于一体，让开发者可以在 Android Studio 做开发应用，也不用再下载其它工具就能让能做性能调优工作。</p><p>目前 Android Studio Profiler 已经集成了 4 类性能分析工具： CPU、Memory、Network、Battery，其中 CPU 相关性能分析工具为 CPU Profiler，也是本章的主角，它把 CPU 相关的性能分析工具都集成在了一起，开发者可以根据自己需求来选择使用哪一个。可能很多人都知道，谷歌已经开发了一些独立的 CPU 性能分析工具，如 Perfetto、Simpleperf、Java Method Trace 等，现在又出来一个 CPU Profiler，显然不可能去重复造轮子，CPU Profiler 目前做法就是：从这些已知的工具中获取数据，然后把数据解析成自己想要的样式，通过统一的界面展示出来。</p><h3 id="提供的亮点功能-1"><a href="#提供的亮点功能-1" class="headerlink" title="提供的亮点功能"></a><strong>提供的亮点功能</strong></h3><p>CPU Profiler 集成了性能分析工具：Perfetto、Simpleperf、Java Method Trace，它自然而然具备了这些工具的全部或部分功能，如下：</p><ol><li><strong>System Trace Recording，它是</strong>用 Perfetto 抓取的信息，可用于分析进程函数耗时、调度、渲染等情况，但是它一个精简版，只能显示进程强相关的信息且会过滤掉耗时短的事件，建议将 Trace 导出文件后在 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 上进行分析。</li><li><strong>Java Method Trace Recording，它是</strong>从虚拟机获取函数调用栈信息，用于分析 Java 函数调用和耗时情况。</li><li><strong>C&#x2F;C++ Function Trace，它是用</strong> Simpleperf 抓取的信息，Simpleperf  是从 CPU 的性能监控单元 PMU 硬件组件获取数据。 <strong>C&#x2F;C++ Method Trace</strong> 只具备 Simpleperf 部分功能，用于分析 C&#x2F;C++ 函数调用和耗时情况。</li></ol><p><img src="/images/The-Performace-1-Performance-Tools/92246083-124c-4290-a5c4-5b57529741cb.webp" alt="CPU Profiler"></p><h3 id="实践中的应用情况-2"><a href="#实践中的应用情况-2" class="headerlink" title="实践中的应用情况"></a>实践中的应用情况</h3><p>应用的性能问题主要分为两类：响应慢、不流畅。</p><ul><li>响应慢问题常有：应用启动慢、页面跳转慢、列表加载慢、按钮响应慢等</li><li>不流畅问题常有：列表滑动不流畅、页面滑动不跟手、动画卡顿等</li></ul><p>CPU Profiler 在这些场景中要如何使用呢？基本的思路是：首先就要抓 System Trace，先用System Trace 分析、定位问题，如果不能定位到问题，再借助 Java Method Trace 或 C&#x2F;C++ Function Trace 进一步分析定位。</p><p>以一个性能极差的应用为例，在系统的关键位置插了 Systrace TracePoint，假设对代码不熟悉，那要怎么找到性能瓶颈呢？我们先把应用跑起来，<strong>通过 CPU Profiler</strong> <strong>录制一个 System Trace</strong> （后面文章会介绍工具的使用方法）如下：</p><p><img src="/images/The-Performace-1-Performance-Tools/359770f0-ccf4-4857-9f7d-89db47e44d0b.webp"></p><p>通过上面 Trace 可以知道是在 egl_core 线程中的 onDrawFrame 操作耗时，如果发现不了问题，建议导出到 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 进一步分析，可以查找源代码看看 onDrawFrame 是什么东西， 我们通过查找发现 onDrawFrame 是 Java 函数 onDrawFrame 的耗时，要分析 Java 函数耗时情况，<strong>我们要录制一个 Java Method Trace</strong>，如下：</p><p><img src="/images/The-Performace-1-Performance-Tools/ee7e0852-9643-46f1-86b6-23e3975c1e54.webp"></p><p>通过上面 Trace 很容易发现是一个叫做 Utils.onDraw 的 native 函数耗时，因为涉及到C&#x2F;C++ 代码，所以要再录制一个 <strong>C&#x2F;C++ Function Trace 进一步分析，如下：</strong></p><p><img src="/images/The-Performace-1-Performance-Tools/25a10f6e-4465-4ea4-addb-624203e99dfd.webp"></p><p>可以发现在 native 的 Java_com_gl_shader_Utils_onDraw 中代码执行了 sleep，它就是导致了性能低下的罪魁祸首！</p><p>AS 中的 CPU Profiler 最大优势是集成了各种子工具，在一个地方就能操作一切，对应用开发者来说是非常方便的，不过对系统开发者来说可能没那么幸运。</p><h2 id="2-4-综合对比"><a href="#2-4-综合对比" class="headerlink" title="2.4 综合对比"></a>2.4 综合对比</h2><table><thead><tr><th>工具名称</th><th>应用场景</th><th>数据类型</th><th>获取方法</th><th>分析方式</th></tr></thead><tbody><tr><td>Systrace</td><td>Android 系统与应用性能分析</td><td>Trace 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析</td></tr><tr><td>Perfetto</td><td>Android 系统与应用性能分析</td><td>Metric 类型 Trace 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析 数据库分析</td></tr><tr><td>AS Profiler</td><td>Android 系统与应用性能分析</td><td>Trace 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析</td></tr><tr><td>SimplePerf</td><td>Java&#x2F;C++ 函数执行耗时 分析 PMU 计数器</td><td>Trace 类性</td><td>无条件抓取 持续落盘</td><td>可视化分析 文本分析</td></tr><tr><td>Snapdragon Profiler Tools &amp; Resources</td><td>主要是高通 GPU 性能分析器</td><td>Trace 类型 Metric 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析</td></tr><tr><td>Mali Graphics Debugger</td><td>ARM GPU 分析器（MTK、麒麟芯片）</td><td>Trace 类型 Metric 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析</td></tr><tr><td>Android Log&#x2F;dumpsys</td><td>综合分析</td><td>Log 类型</td><td>有条件抓取 持续抓取但不落盘</td><td>文本分析</td></tr><tr><td>AGI(Android GPU Inspector)</td><td>Android GPU 分析器</td><td>Trace 类型 Metric 类型</td><td>无条件抓取 持续落盘</td><td>可视化分析</td></tr><tr><td>eBPF</td><td>Linux 内核行为动态跟踪</td><td>Metric 类型</td><td>动态跟踪 有条件抓取 持续抓取但不落盘</td><td>文本分析</td></tr><tr><td>FTrace</td><td>Linux 内核埋点</td><td>Log 类型</td><td>静态代码 有条件抓取 持续抓取但不落盘</td><td>文本分析</td></tr></tbody></table><h1 id="3-关于「器、术、道」"><a href="#3-关于「器、术、道」" class="headerlink" title="3 关于「器、术、道」"></a>3 关于「器、术、道」</h1><p>技术上的变革、改进更多是体现在「器」层面，Linux 社区以及谷歌所开发的工具发展方向朝着提高工具的集成化使得在一个地方可以方便查到所需的信息、或者是朝着获取更多信息的方向发展。总之，器层面他们的发展轨迹是可寻的，可总结出发展规律。 我们需要在工具快速迭代的时候准确的认识到他们能力以及应用场景，其目的是提高解决问题的效率，而不是把时间花在学习新工具上。 </p><p>「术」层面依赖具体的业务知识，知道一帧是如何被渲染的、CPU 是如何选择进程调度的、IO 是如何被下发的等等。只有了解了业务知识才能正确的选择工具并正确的解读工具所提供的信息。随着经验的丰富，有时候你都不需要看到工具提供的详细信息，也可以查到蛛丝马迹，这就是当你业务知识丰富到一定程度，大脑里形成了复杂的关联性信息之后凌驾于工具之上的一种能力。</p><p>「道」层面思考的是要解决什么问题，问题的本质是什么？做到什么程度以及需要投入什么样的成本达成什么样的效果。为了解决一个问题，什么样的路径的「投入产出比」是最高的？整体打法是什么样？为了完成一件事，你首先要做什么其次是做什么，前后依赖关系的逻辑又是什么？ </p><p>后续的文章中，会依照「器、术、道」方式讲解一个技术、一个功能，我们不止想让你学习到一个知识点，更想激发你举一反三的能力。遇到类似的工具或者类似的问题、更进一步是完全不同的系统，都能够从容应对。牢牢抓住本质，通过评估「投入产出比」选择合适的工具或信息，高效解决问题。</p><h1 id="4-关于「The-Performance-知识星球」"><a href="#4-关于「The-Performance-知识星球」" class="headerlink" title="4  关于「The Performance 知识星球」"></a>4  关于「The Performance 知识星球」</h1><p>为了更好地交流与输出高质量文章，我们创建了名为 「The Performance」的知识星球，主理人是三个国内一线手机厂商性能优化方面的一线开发者，有多年性能相关领域的工作经验，提供Android 性能相关的一站式知识服务，涵盖了基础、方法论、工具使用和最宝贵的案例分析。</p><p>目前星球的内容规划如下（两个 ## 之间的是标签，相关的话题都会打上对应的标签，方便大家点击感兴趣的标签查看对应的知识）</p><ul><li><strong>#The Performance#</strong> — 可以提早阅读「Android 性能优化 - 系统性课程」的电子书，每周会放出已经写好的章节。「Android 性能优化 - 系统性课程」是我们规划的一本讲 Android 性能优化的电子书，目前开发者社区有相当多高质量的性能优化理论知识和实践文章和开源库，但是目前市面上缺乏一个完整的、系统性的、包含了性能优化原理、工具、实践等内容、面向初级开发中和中级开发者、面向 App 开发者和系统开发者，且持续更新的 Android 性能优化工具书。书的大纲 (暂定) 我们已经基本上列好了，预计会花费一年左右的时间来完成，在星球中会放出写好的章节，让大家提前看到。<ul><li>Part 1: → <strong>性能工程</strong></li><li>Part 2: → <strong>以性能角度分析 Android 交互与核心系统</strong></li><li>Part 3: → <strong>以性能角度分析 Linux 内核核心子系统设计与实现</strong></li><li>Part 4: → <strong>问题场景分析思路</strong></li><li>Part 5: → <strong>分析与调试工具</strong></li><li>Part 6: → <strong>质量守护 - 性能监控方法与工具</strong></li></ul></li><li>#<strong>性能工具</strong># — 分享 Android 开发中使用到的性能分析工具以及其使用方法，同时也提供 1V1 的 Systrace、Perfetto 等性能工具的视频指导。性能工具的使用，最好还是以视频的方式展示会直观很多，文章是静态的，很多地方比较难讲清楚，1V1 的视频会议指导也算是一个学习的方法</li><li><strong>#案例分析#</strong> — 典型案例分析思路总结、球友提供的案例分析与讨论。案例分析是学习的一个很重要的途径，阅读大量的实际性能案例对以后自己分析和解决性能问题是非常有帮助的，同时也欢迎大家提供案例和解决方法，怕泄露信息的话，我们会对关键信息进行打码</li><li><strong>#经典解读#</strong> — 经典方案、课程重读，例如优秀的三方库解析、Android 开发高手课重读等。比如可以对方案进行深度的剖析，横向对比等；对 Android 开发高手课进行重读和查漏补缺</li><li><strong>#知识分享#</strong> — 优秀文章、博客、工具分享。业界有很大大牛的博客、经过实际业务考验的开源方案、各种性能工具等，我们会寻找这些优秀的内容，分享给大家</li><li><strong>#知识沉淀#</strong> — 微信群聊精华、微信问答、博客留言解答等</li><li><strong>#性能面试#</strong> — Android 性能相关的面试题搜集和解答，也算是刚需了吧</li><li><strong>#编程语言#</strong> — 编程语言相关的使用技巧分享</li><li><strong>#效能提升#</strong> — 效能提升分享，包括开发者开发效能、工作效能提升方法、工程效率、工具推荐等，磨刀不误砍柴工嘛</li><li><strong>#行业动态#</strong> — 性能相关新技术第一时间解读报告，包括但不限于下面的内容<ul><li>行业峰会、学术峰会新思路解读报告</li><li>论文、行业、书籍介绍、视频</li><li>Android 大版本性能相关介绍</li><li>Android 新硬件性能相关内容介绍</li><li>Android 性能相关开源项目解读</li></ul></li><li><strong>#大咖分享#</strong> — 每月定期邀请行业大咖进行经验分享、案例分析</li><li><strong>#工作内推#</strong> — 各大厂商内推工作机会介绍</li></ul><p><img src="/images/The-Performace-1-Performance-Tools/image-20220308234016600.webp" alt="TeamWork - 付费知识星球"></p><p><strong>注意:</strong> <strong>iOS 手机用户不要直接在星球里面付款，在微信界面长按图片扫描二维码加入即可，否则苹果会收取高昂的手续</strong></p><h1 id="5-附录"><a href="#5-附录" class="headerlink" title="5 附录"></a>5 附录</h1><ul><li>Perfetto 项目地址 <ul><li>官方使用文档: <a href="https://perfetto.dev/docs/">https://perfetto.dev/docs/</a>  </li><li>Github 代码库 <a href="https://github.com/google/perfetto">https://github.com/google/perfetto</a> ，可以提 bug 或者需求，当然也可以提 PR 贡献你的力量。</li></ul></li><li>Android GPU Inspector<ul><li>官方使用文档: <a href="https://developer.android.com/agi">https://developer.android.com/agi</a></li></ul></li><li>其他大厂的性能分析工具 <ul><li><a href="https://docs.microsoft.com/en-us/windows-hardware/test/wpt/">Windows Performance Toolkit</a></li></ul></li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;img src=&quot;/images/The-Performace-1-Performance-Tools/cc4162d9-7c2b-43b1-b9a1-f14d5c8e8605.jpg&quot; alt=&quot;黑客与画家&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Paul Graham 在其著作 &amp;lt;黑客与画家&amp;gt; 中断言：“不同语言的执行效率差距正变得越来越大，所以性能分析器（profiler）将变得越来越重要。目前，性能分析并没有受到重视。许多人好像仍然相信，程序运行速度提升的关键在于开发出能够生成更快速代码的编译器。代码效率与机器性能的差距正在不断加大，我们将会越来越清楚地看到，应用软件运行速度提升的关键在于有一个好的性能分析器帮助指导程序开发。”&lt;br&gt;by Paul Graham 黑客与画家 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;谷歌搜索 「Android 优化工具」，你会找到很多与此相关的内容。他们的问题在于要么是内容高度重复、要么是直接讲使用方法，很少会给你介绍整体性的架构，一不小心就会让人会种「一个工具搞定一切」的错误认知。以笔者团队的多年经验来看，在性能分析领域这种银弹级别的工具是不存在的。工具在发展，老问题会以新的方式变样出现，不掌握核心逻辑的话始终会让你浮于技术的表面。 &lt;/p&gt;
&lt;p&gt;本文首先系统性的梳理性能分析中的可观测性技术，它涵盖数据类型、抓取方法以及分析方法等三部分内容，之后是介绍谷歌提供的「三大件」分析工具。目的是想让你了解不变的理论性的知识，以及与之对应的在安卓环境中可用的工具，这些可以让你少走一些弯路，直接复用前辈们的经验。 &lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Framework" scheme="https://androidperformance.com/tags/Framework/"/>
    
  </entry>
  
  <entry>
    <title>回顾 2021</title>
    <link href="https://androidperformance.com/2022/01/03/2021-Review/"/>
    <id>https://androidperformance.com/2022/01/03/2021-Review/</id>
    <published>2022-01-03T15:25:35.000Z</published>
    <updated>2026-02-07T05:17:47.817Z</updated>
    
    <content type="html"><![CDATA[<p>2021 已经过去，趁着元旦假期，回顾一下 2021，随意一些，想到哪里写哪里吧。主要是对 2021 年的一个回顾，以及 2022 年的展望，2021 年当了爸爸，换了工作（中间还居家无聊了好久），收获了更多的朋友，也算是过的还可以</p><p>不过在个人成长方面，甚至感觉有点退步，这让我觉得有点慌，学如逆水行舟，不进则退，2022 年是需要好好深耕的一年，希望能和看到这篇文章的同学一起进步，共勉</p><p>另外也盘点了一下知识分享相关的数据，分享了一下这方面的收入，个人新增和推荐的硬件、个人推荐的软件等，感兴趣的可以自取</p><span id="more"></span><h1 id="2021-最大收获-小橘子"><a href="#2021-最大收获-小橘子" class="headerlink" title="2021 最大收获 - 小橘子"></a>2021 最大收获 - 小橘子</h1><p>今年的最大收获那必然是家里多了一个小橘子，体验了一下当爸爸的感觉，多了个小棉袄，双人行变成了三人行，到现在快十个月了，妥妥小天使。我们对小橘子要求不高，健健康康快快乐乐长大就可以了，老父亲老母亲是你坚强的后盾<br><img src="/images/2021-Review/b97e79e2-8222-45bf-9ff2-a1932e451ea7.jpg" alt="橘子小棉袄"></p><h1 id="知识分享数据统计"><a href="#知识分享数据统计" class="headerlink" title="知识分享数据统计"></a>知识分享数据统计</h1><h2 id="个人博客数据"><a href="#个人博客数据" class="headerlink" title="个人博客数据"></a>个人博客数据</h2><p>博客 2021 新增了 8 篇文章（Systrace 系列总算是完结了，总共 18 篇内容，你们先看，我去准备准备 Perfetto 版本的….）</p><ol><li>Systrace 流畅性实战 1 ：了解卡顿原理 - <a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/</a></li><li>Systrace 流畅性实战 2 ：案例分析 - MIUI 桌面滑动卡顿分析 - <a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/</a></li><li>Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问 - <a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/</a></li><li>Systrace 响应速度实战 1 ：了解响应速度原理 - <a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/</a></li><li>Systrace 响应速度实战 2 ：响应速度实战分析 - 以启动速度为例 - <a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/</a></li><li>Systrace 响应速度实战 3 ：响应速度延伸知识 - <a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/</a></li><li>Android 系统开发系列（1）：Android 12 源代码下载、编译和刷机 - <a href="https://www.androidperformance.com/2021/10/26/build-android-12/">https://www.androidperformance.com/2021/10/26/build-android-12/</a></li><li>一本讲 Android 流畅性的书，应该有什么内容？ - <a href="https://www.androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/">https://www.androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/</a></li></ol><p>一年就写了这几篇文章，跟年初定的目标 <strong>一周一篇</strong> 差的实在是有点远，就离谱（2022 年不能再立这种 Flag 了，量力而行，一个月两篇我觉得还可以一战。</p><p>另外去年还维护了一个 Android Weekly 的知乎专栏，感兴趣的也可以订阅一下 <a href="https://www.zhihu.com/column/c_1278963991947780096%EF%BC%89">https://www.zhihu.com/column/c_1278963991947780096）</a></p><p>博客 <a href="https://www.androidperformance.com/">AndroidPerformance</a> 使用了 Google Analytics 来进行统计，我看了一下 2021 和 2020 年的对比数据，还是不错的，下面是 Google Analytics 的统计数据</p><p>**访问用户数 **对比 2020 年增长了 38.9%，总的来说还是不错的（那几个明显的低谷是新年假期、五一和十一，好好过节，咱不卷）</p><p><img src="/images/2021-Review/c33ba71e-2d1a-45f6-bc78-bc2558ca29aa.webp"></p><p><strong>用户最常访问的页面</strong>，还是主要以 Android Systrace 系列为主<br><img src="/images/2021-Review/edd399b3-17b7-421e-b37f-4b2a1bfdf68a.webp"></p><h2 id="微信公众号数据"><a href="#微信公众号数据" class="headerlink" title="微信公众号数据"></a>微信公众号数据</h2><p>微信公众号 <strong>AndroidPerformance</strong> 的关注数目前是 ：<strong>7364</strong>，由于原创文章不多，且有很多转载，所以公众号增长比较慢，活跃的都是老用户了。微信公众号文章由于是封闭的，所以数据没有什么意义，就不贴了，希望 2022 能突破 1W 吧</p><p>微信公众号主要是微信里面阅读方便，但是对于写作的人来说就没那么友好了，主要是没法贴微信之外的超链接，这个设定也太 XX 了，把互联网搞成了局域网，无力吐槽</p><p><img src="/images/2021-Review/WechatIMG581.webp" alt="微信扫一扫关注公众号"></p><h2 id="知乎"><a href="#知乎" class="headerlink" title="知乎"></a>知乎</h2><p><a href="https://www.zhihu.com/people/gracker">知乎</a> 的关注者目前有 2W+ ，不过知乎貌似越来越不重视技术这一块了，所以也就没怎么活跃了，刷到的文章和回答大部分都是搬运工或者水军，遇到好的文章都要赶紧点赞收藏分享一键三连，后续还是刷刷即刻和 Twitter 吧，上面的真实活跃开发者还是多</p><h2 id="掘金"><a href="#掘金" class="headerlink" title="掘金"></a>掘金</h2><p><a href="https://juejin.cn/user/3051900006837213">掘金</a> 技术氛围还是很浓厚的，我也经常刷，不过总感觉掘金差了点啥，又说不上来是啥… 掘金的粉丝可以忽略不计了，不过后续有技术文章还是会同步上去的（你不同步，就会有人替你同步，当然作者就换人了）</p><h2 id="即刻"><a href="#即刻" class="headerlink" title="即刻"></a>即刻</h2><p>即友可以加个好友</p><p><img src="/images/2021-Review/image-20220207233253977.webp" alt="即刻"></p><h2 id="其他平台"><a href="#其他平台" class="headerlink" title="其他平台"></a>其他平台</h2><ol><li>CSDN ……可以忽略</li><li><a href="https://weibo.com/u/1315612820">微博</a> ……可以忽略</li><li><a href="https://twitter.com/Gracker_Gao">Twitter</a> ：关注了很多国内外的技术大佬，技术氛围还是蛮不错的</li></ol><h1 id="赚钱？交个朋友而已"><a href="#赚钱？交个朋友而已" class="headerlink" title="赚钱？交个朋友而已"></a>赚钱？交个朋友而已</h1><p>今天听了 <a href="https://happyxiao.com/category/podcast/">Happy Xiao 的播客</a>，讲了他一年下来，通过知识分享的各个平台，每个月大概有 2500 RMB 左右的收入，所以也想了想自己去年一年在这方面的收入，想想也还蛮惨淡的</p><ol><li>博客收入：0 ，甚至应该还是负的，因为还有域名+服务器的费用….</li><li>微信公众号：因为我没有开文中广告，所以广告收入几乎可以忽略不计，主要是靠各位的打赏了，我看了下打赏数据，总共是 1039</li><li>小专栏：总共 560</li></ol><p>所以算下来，总收入是 1039 + 560 &#x3D; <strong>1596</strong> 。平均每个月 <strong>133</strong>，可见是真的不赚钱……用老罗的话来说，交个朋友..不过真心感谢微信打赏的小伙伴，每天早餐加个蛋就靠你们了（各位想赞赏的话，可以扫描文末的二维码，随便找一篇原创的文章打赏即可）</p><p>说到交个朋友，确实今年新加了很多技术的小伙伴，拉了四个微信群，时不时在群里水一水，在群里总的感觉是：<strong>群除我佬</strong>，时常怀疑自己是不是出现在了错的地方。说到底还是太菜，很多东西都不知道，或者一知半解。立个 Flag：今年要把基础打牢，知识体系化，争取跟上群里的大佬们的讨论</p><p>微信赞赏码，各位觉得有用，可以加个鸡腿</p><p><img src="/images/2021-Review/wechat.webp" alt="微信赞赏码"></p><h1 id="2022-有什么计划？"><a href="#2022-有什么计划？" class="headerlink" title="2022 有什么计划？"></a>2022 有什么计划？</h1><p>新年立 Flag 一般就那几样，我也没法免俗，不管怎么说先立了再说，等后续细化成每周每天的行动项，说不定就成了呢？</p><ol><li><strong>健身</strong>：应该还是主要以 走路上下班 + 篮球 + 跑步 + 划船机 + 家里的健身器材为主（抱橘橘也算是一种健身了）</li><li><strong>强化英语阅读和听说</strong>：最近在有意识地听英文播客、看比较好懂的英文视频、看英文版本的文章和电子书，iPad 上的分屏看英文电子书配合有道词典，左边复制右边自动翻译+加入生词本，还是很不错的</li><li><strong>知识体系化</strong>：<strong>读和写更多的代码</strong>、<strong>读更多的书</strong>、写一本 <strong>性能相关电子书</strong></li><li>**更频繁地更新 <a href="https://www.androidperformance.com/">博客</a>**：这个 flag 上面已经立了</li><li>**跟小团队运营好 <a href="https://t.zsxq.com/Fuvvf6y">The Performance 知识星球(2022-06-20更新：暂时停止付费星球加入)</a>**：一个人的力量还是太小了，跟几个小伙伴搞了一个收费版本的知识星球，希望能提供服务的同时，也给自己一点压力（你不压榨压榨自己，怎么知道自己不能用来榨油呢？）</li><li><strong>尝试一些新鲜的东西</strong>：试试播客、视频、VLog 、拍照等</li><li><strong>夯实基础，高效工作</strong>：多思考，多总结，多分享，多读书，多写代码，用工具提升工作效率</li></ol><p><img src="/images/2021-Review/0d2d6300-16d8-4069-b9af-4fff436c58c0.jpg" alt=" TeamWork - 付费知识星球"></p><h1 id="2021-有什么软件推荐？"><a href="#2021-有什么软件推荐？" class="headerlink" title="2021 有什么软件推荐？"></a>2021 有什么软件推荐？</h1><p>2021 用的最舒心的软件</p><ol><li>Notion，笔记软件，谁用谁知道：<a href="https://www.notion.so/">https://www.notion.so/</a></li><li>Typora，笔记软件，Blog 就是用这个写的：<a href="https://typora.io/">https://typora.io/</a></li><li>Github Copilot，你的 AI 编程伴侣：<a href="https://copilot.github.com/">https://copilot.github.com/</a></li><li>flomo，笔记软件，记录转瞬即逝的小想法：<a href="https://flomoapp.com/">https://flomoapp.com/</a></li><li>DeepL，号称全球最准确的翻译：<a href="https://www.deepl.com/zh/translator">https://www.deepl.com/zh/translator</a></li></ol><p><img src="/images/2021-Review/9fd3dfde-906e-4240-b752-6b9c1a157b48.webp" alt="DeepL"></p><h1 id="2021-有什么硬件推荐？"><a href="#2021-有什么硬件推荐？" class="headerlink" title="2021 有什么硬件推荐？"></a>2021 有什么硬件推荐？</h1><p>2021 还是折腾了不少硬件的，觉得还不错的推荐给大家</p><ol><li>NAS，家庭私有云：<a href="https://item.jd.com/100014187272.html">https://item.jd.com/100014187272.html</a></li><li>静电容键盘，机械键盘退烧神器，程序员和文字工作者必备：<a href="https://item.jd.com/100013910167.html">https://item.jd.com/100013910167.html</a></li><li>小米的 27 寸 4K 显示器，你买不了吃亏，买不了上当：<a href="https://item.jd.com/100016659987.html">https://item.jd.com/100016659987.html</a></li><li>Apple TV 4K，也就是最新的那款，请注意这玩意有前置条件，如果你都满足，那么我推荐你搞一个，部分满足也行，就是体验会打一些折扣（1. 你有一种科学上网的方法 2. 你有一个软路由或者旁路由 3. 你有一台 NAS 4. 你有一台 4K 电视 5. 你有港区或者美区的 AppleID 6.你有一台 iphone）：<a href="https://npcitem.jd.hk/100011599035.html">https://npcitem.jd.hk/100011599035.html</a></li><li>趣动乐 JOY25 电动升降桌，1.8m 的宽度是真的爽，乐歌就没这么宽的，感觉可以用个 10 年，站累了坐一会，坐累了站一会，吃嘛嘛香，身体倍儿棒！<a href="https://item.jd.com/10043115461593.html">https://item.jd.com/10043115461593.html</a></li></ol><p><img src="/images/2021-Review/1f5f8c15-4e79-45b5-9935-1e3906e80caf.jpg" alt="我的家庭工作环境"></p><h1 id="2022-最想要什么？"><a href="#2022-最想要什么？" class="headerlink" title="2022 最想要什么？"></a>2022 最想要什么？</h1><p>池大已经替我说了，我感觉我手上这台闲鱼淘的 M1 版本的 Mac Mini，在 M1 Max 芯片的 MacBook Pro 面前瞬间就不香了，操作起来也变卡了……一定是库克在远程施法了</p><p><img src="/images/2021-Review/c2b443fb-eefd-49e1-8294-5204f5dc6eeb.webp" alt="池大的微博"></p><p>不过想要归想要，需要归需要，要理性消费（Mini 坏了的话就可以换了）</p><p>话说这个椅子也太好看了吧！（<a href="https://item.taobao.com/item.htm?id=614505490996">https://item.taobao.com/item.htm?id=614505490996</a> 各位自取）<br><img src="/images/2021-Review/002606e3-8164-4ae0-8b75-f3f0c07634fd.webp" alt="躺椅"></p><p>当然还有最新款的 iPhone 13 Pro Max 谁会拒绝呢？</p><p><img src="/images/2021-Review/image-20220104001033091.webp" alt="image-20220104001033091"></p><h1 id="结尾"><a href="#结尾" class="headerlink" title="结尾"></a>结尾</h1><p>2021 总的来说工作上不怎么理想，生活上有了小棉袄还是添加了不少乐趣，个人成长上几乎停滞，有各方面的原因，归根结底还是自己对自己的要求太低了，逆水行舟，不进则退</p><p>希望 2022 年能和看到这篇文章的各位一起努力，共勉！</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;2021 已经过去，趁着元旦假期，回顾一下 2021，随意一些，想到哪里写哪里吧。主要是对 2021 年的一个回顾，以及 2022 年的展望，2021 年当了爸爸，换了工作（中间还居家无聊了好久），收获了更多的朋友，也算是过的还可以&lt;/p&gt;
&lt;p&gt;不过在个人成长方面，甚至感觉有点退步，这让我觉得有点慌，学如逆水行舟，不进则退，2022 年是需要好好深耕的一年，希望能和看到这篇文章的同学一起进步，共勉&lt;/p&gt;
&lt;p&gt;另外也盘点了一下知识分享相关的数据，分享了一下这方面的收入，个人新增和推荐的硬件、个人推荐的软件等，感兴趣的可以自取&lt;/p&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://androidperformance.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
  </entry>
  
  <entry>
    <title>一本讲 Android 流畅性的书，应该有什么内容？</title>
    <link href="https://androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/"/>
    <id>https://androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/</id>
    <published>2021-10-27T07:21:31.000Z</published>
    <updated>2026-02-07T05:17:47.876Z</updated>
    
    <content type="html"><![CDATA[<p>最近读了一本新书：《打造流畅的 Android App》，京东链接：<a href="https://item.jd.com/10035215362170.html">https://item.jd.com/10035215362170.html</a> 。因为书名所以买了这本书，读完之后觉得有必要写一篇文章，让还没有买此书的同学了解一下</p><p>我个人的建议是：如果你是个老鸟，不建议买，这本书里面没有介绍太多原理性的东西，对于 Android 流畅性也没有一个比较全面的介绍；如果你是新手，这本书用来当做开阔视野 + 查漏补缺还可以，想更深入的了解 Android 流畅度还是差了点东西</p><p>之所以我会这么建议，是因为这本书确实没有讲太多性能或者流畅度相关的东西，也没有比较深入的原理部分，篇幅更多在讲<strong>静态代码审查</strong>、<strong>AS Profiler 的使用</strong>、<strong>App 架构</strong>、<strong>保活</strong>、<strong>网络性能优化</strong>、<strong>APK 大小优化</strong>、<strong>App 耗电</strong>等，内容也不深，浅尝辄止</p><span id="more"></span><p><img src="/images/16352192103990.webp" alt="打造"></p><h1 id="内容介绍"><a href="#内容介绍" class="headerlink" title="内容介绍"></a>内容介绍</h1><p>简单介绍一下这本书的内容，其章节如下</p><ol><li><strong>概述</strong> ：简单介绍为何要做性能优化，以及 Android Studio 的配置</li><li><strong>静态代码审查</strong> ：大篇幅降了各种静态代码审查工具，比如 Android Lint 、CheckStyle、SpotBugs、PMD 等，除了 Lint 其他的我接触不多，也算是查漏补缺了</li><li><strong>使用 Android Profiler 优化性能</strong> ：主要降了 AS Profiler 工具里面的 CPU Profiler、Memory Profiler、Network Profiler、Network Profiler ，这里主要重点是工具的使用，大概性地介绍了一下</li><li><strong>高质量的 App 从架构开始</strong>：主要是架构原则、MVC、MVP、MVVM 这些</li><li><strong>优雅地保活 App</strong> ：简单介绍了下保活相关的技术</li><li><strong>网络性能优化专题</strong> ：网络交互与多线程 + 海量数据传输优化</li><li><strong>优化 APK 体积</strong> ：老生常谈的 APK 大小优化，多渠道打包 + 优化资源文件 + 代码混淆</li><li><strong>App 耗电及 Crash 体验优化</strong>：简单介绍了一下</li></ol><p>从上面章节标题大家也可以看到，跟流畅性相关的内容比较少，内容相对会比较杂一些，感兴趣的可以买一本看看</p><h1 id="我认为一本讲流畅性的书，应该有什么？"><a href="#我认为一本讲流畅性的书，应该有什么？" class="headerlink" title="我认为一本讲流畅性的书，应该有什么？"></a>我认为一本讲流畅性的书，应该有什么？</h1><p>如果让我写这么一本书，我肯定是写不来的，非常钦佩能出书的技术小伙伴，给作者点个赞。</p><p>不过这并不妨碍我嘴炮打个山响（I am good at it）：所以我觉得如果让我来写这本书，我会加入下面这些内容，确保大家通过这本书，就可以深入理解 Android 的流畅性原理，且可以熟练使用各种工具来分析所遇到的流畅性问题</p><p>鉴于在讨论 Android 性能问题的时候，<strong>卡顿(流畅性)<strong>、</strong>响应速度</strong>、<strong>ANR</strong> 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿(流畅性)、响应慢、ANR 三种，所以我们可以定义广义上的<strong>流畅性</strong>，包含了卡顿(流畅性)、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿(流畅性)，大部分情况下都是广义上的卡顿(流畅性)，需要搞清楚，到底出现了哪一种问题</p><p>所以我设想的章节应该包含下面的内容</p><ol><li><strong>第一章：Android 流畅性概述</strong>：这一章主要会讲性能相关的一些概念，包括从用户角度、开发角度、测试角度、AOSP 的角度、硬件角度等，讲述流畅性的一些概念。这一点很重要，因为在实践中发现，用户和开发、测试往往是同不同的角度来看待流畅度的，思考问题的时候别把自己的思维定在某一个角色，往往会有不一样的结果</li><li><strong>第二章：Android 运行机制概述</strong>：这一章主要会讲一些 Android 运行机制相关的内容，了解这些知识点，对于分析 Android 流畅性问题是必须的，当下面这些知识点你非常熟悉之后，碰到流畅性的问题，你的脑海中就有一个图形化的工具在运转：用户怎么操作的、系统怎么反馈的、App 运行到了哪里、最有可能是哪里出现了问题、用什么工具去 Debug 最方便<ol><li>App 主线程运行原理（主线程和渲染线程）</li><li>Message、Handler、MessageQueue、Looper 机制</li><li>屏幕刷新机制和 Vsync</li><li>Choreogrepher 机制</li><li>Buffer 工作流和 SurfaceFlinger 工作流</li><li>Input 流程</li><li>ANR 的设计思想</li></ol></li><li><strong>第三章：性能分析工具介绍</strong>：正所谓 <strong>工欲善其事必先利其器</strong>，趁手的工具对于分析性能问题至关重要，这一章主要会讲性能分析经常遇到的工具，并非是简单的介绍，会结合 Android 系统机制来讲解，工具主要包括但不限于 <strong>Systrace(Perfetto)</strong> 、<strong>AS Profiler</strong>、<strong>SimplePerf</strong>、<strong>MAT</strong>、<strong>Log 工具</strong>（Log 内容分析和 Log 原理）、<strong>命令行工具</strong>（dumpsys meminfo、dumpsys gfxinfo、dumpsys cpuinfo、dumpsys SurfaceFlinger、dumpsys activity、dumpsys input、dumpsys window 等）、<strong>三方性能库</strong>（Koom、Matrix、Facebook profilo、BlockCanary、LeakCanary、Tailor&#x2F;Raphael 等）</li><li><strong>第四章：深入分析 Android 卡顿问题</strong>：运行机制和工具都介绍完了，那么接下来就是如何进行实战了，这一章主要会讲卡顿出现的原因、分析卡顿问题的套路、案例分享、编码最佳实践等</li><li><strong>第五章：深入分析 Android 响应速度问题</strong>：同上，响应速度问题实战环节，这一章主要会讲响应速度问题出现的原因、分析响应速度问题的套路、案例分享、编码最佳实践等</li><li><strong>第六章：深入分析 Android ANR 问题</strong>：ANR 也是用户体验的一部分，这里主要会讲 ANR 的设计思想、ANR 的几种类型、ANR 出现的原因、ANR 问题的分析套路、案例分享、编码最佳实践等（目测会有很大的篇幅）</li><li><strong>第七章：深入分析 Android 内存问题</strong>：内存问题同样是影响用户体验一部分，而且是一个比较重要的性能指标，你懂得。本章会介绍 App 的内存占用、App 内存分析工具、内存泄漏分析、内存持续增长分析等，这里面的内容估计会牵扯到比较多的知识点，任重而道远啊….</li><li><strong>第八章：性能测试</strong>：从测试的角度来看流畅性问题，这里会讲一些 <strong>性能指标获取（侵入式和非侵入式）</strong>、<strong>性能标准制定</strong>、<strong>竞品分析</strong>、<strong>提 Bug 的标准和流程</strong>、<strong>整机测试方法</strong>、<strong>权威第三方的性能测试方法和标准介绍(绿色联盟、鲁大师、友盟、Bugly 等)<strong>、</strong>性能监控工具开发(比如 Matrix、Koom、Fastbot、UI Automator、内存增长测试等)<strong>，以及一些软技能：</strong>如何区分 Android 系统问题和 App 问题</strong>、<strong>如何与开发和 PM 扯皮(开个玩笑)</strong> 等</li><li><strong>第九章：线上性能监控</strong>：上一章讲的是本地性能测试，而这一章会讲线上是如何监控流畅度的，跟线下监控有区别的是，线上监控既要能体现真实的用户体验，又要尽量减少对用户的影响，还需要在发现问题的时候，能及时进行数据上报</li><li><strong>第十章：系统性能优化介绍</strong>：App 开发者使用各种方法和黑科技来进行性能监控和性能分析，那么 Android 系统开发者又是如何做的呢？这一章会介绍一些各种厂商的性能优化、AOSP 的性能优化、高通和 MTK 的优化等</li><li><strong>第十一章：高效工作指南</strong>：内容暂定，包括但不限于<ol><li>AOSP 代码编译的必要性和流程</li><li>阅读 AOSP 代码的技巧，比如 cs.android.com、导入 AS、导入 vscode 等、画流程图等</li><li>Windows、Linux 、Mac 开发环境推荐、配置命令行等</li><li>工作方式推荐：多写、多记、多总结、多分享</li></ol></li></ol><p><strong>嘴炮输出完毕，万事俱备，只欠大佬来完善内容了…</strong></p><h1 id="市面上还有哪些讲性能的书？"><a href="#市面上还有哪些讲性能的书？" class="headerlink" title="市面上还有哪些讲性能的书？"></a>市面上还有哪些讲性能的书？</h1><p>讲道理目前市面上的书都有点年代了，倒是掘金社区的 Android 性能优化文章非常多，各种大厂也乐意将他们的内部工具开源，给这些热爱分享小伙伴点个赞，让我们站在巨人的肩膀上前行</p><p>我本人看过的几本书</p><ol><li>腾讯 TMQ 专项测试团队出的：《<strong>移动 App 性能评测与优化</strong>》，2016 年出版，专业性和实战拉满，值得一看，<a href="https://item.jd.com/11976603.html">https://item.jd.com/11976603.html</a> <strong>微信读书：有电子书</strong></li><li>邓老师的 《<strong>深入理解 Android：Java 虚拟机 ART</strong>》，ART 虚拟机的大部头书，对于了解 ART 虚拟机的运行有很大的帮助，App 的不少黑科技都会涉及到虚拟机 <a href="https://item.jd.com/12510921.html">https://item.jd.com/12510921.html</a> <strong>微信读书：有电子书</strong></li><li>道格・西勒斯（Doug Sillars）的 《<strong>高性能 Android 应用开发</strong>》，2016 年出版，英文原版更早一些，算是一个比较早的全方位讲解 Android App 性能的书了，感兴趣的可以收藏一本 <a href="https://item.jd.com/11995735.html">https://item.jd.com/11995735.html</a> <strong>微信读书：没有电子书</strong></li><li>腾讯大佬出的：《<strong>Android 应用性能优化最佳实践</strong>》，2017 年出版，内容也是性能相关 <a href="https://item.jd.com/12043655.html">https://item.jd.com/12043655.html</a> <strong>微信读书：有电子书</strong></li><li>Brendan Gregg 大师新作：《<strong>BPF 之巅：洞悉 Linux 系统和应用性能</strong>》，中文版 2020 年出版，大部头工具书，屯之 <a href="https://item.jd.com/12769029.html">https://item.jd.com/12769029.html</a> <strong>微信读书：没有电子书</strong></li><li>同样是 Brendan Gregg 的 《<strong>性能之巅：洞悉系统、企业与云计算</strong>》，中文版 2020 年出版，大部头工具书，搞性能的应该人手一本… <a href="https://item.jd.com/12749867.html">https://item.jd.com/12749867.html</a> <strong>微信读书：有电子书</strong></li><li>张绍文的《<strong>Android 开发高手课</strong>》<a href="https://time.geekbang.org/column/intro/142">https://time.geekbang.org/column/intro/142</a> 最近重新听的感悟：<strong>高手就是高手</strong><br> <img src="/images/16353194842965.jpg"></li><li>倪朋飞的 Linux 性能优化实践<br> <img src="/images/16353195166496.jpg"></li></ol><h1 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h1><ol><li>欢迎大家留言分享自己看过的觉得非常不错的 Android 性能相关的书籍、博客、视频课、官方教程等</li><li>欢迎大家留言分享你们认为一本讲 Android 流畅性的书，应该包含哪些内容</li><li>本文不涉及任何推广，大家放心食用</li><li>博客交流不方便，有疑问的可以在知乎或者微信公众号下面留言，或者直接加我微信(553000664)，备注 Blog 即可<ol><li>本文知乎地址：<a href="https://zhuanlan.zhihu.com/p/423605434">https://zhuanlan.zhihu.com/p/423605434</a></li><li>本文微信公众号地址：<a href="https://mp.weixin.qq.com/s/WUGWJx5FRJqXboQ2KKGRwA">https://mp.weixin.qq.com/s/WUGWJx5FRJqXboQ2KKGRwA</a></li></ol></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近读了一本新书：《打造流畅的 Android App》，京东链接：&lt;a href=&quot;https://item.jd.com/10035215362170.html&quot;&gt;https://item.jd.com/10035215362170.html&lt;/a&gt; 。因为书名所以买了这本书，读完之后觉得有必要写一篇文章，让还没有买此书的同学了解一下&lt;/p&gt;
&lt;p&gt;我个人的建议是：如果你是个老鸟，不建议买，这本书里面没有介绍太多原理性的东西，对于 Android 流畅性也没有一个比较全面的介绍；如果你是新手，这本书用来当做开阔视野 + 查漏补缺还可以，想更深入的了解 Android 流畅度还是差了点东西&lt;/p&gt;
&lt;p&gt;之所以我会这么建议，是因为这本书确实没有讲太多性能或者流畅度相关的东西，也没有比较深入的原理部分，篇幅更多在讲&lt;strong&gt;静态代码审查&lt;/strong&gt;、&lt;strong&gt;AS Profiler 的使用&lt;/strong&gt;、&lt;strong&gt;App 架构&lt;/strong&gt;、&lt;strong&gt;保活&lt;/strong&gt;、&lt;strong&gt;网络性能优化&lt;/strong&gt;、&lt;strong&gt;APK 大小优化&lt;/strong&gt;、&lt;strong&gt;App 耗电&lt;/strong&gt;等，内容也不深，浅尝辄止&lt;/p&gt;</summary>
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android 系统开发系列（1）：Android 12 源代码下载、编译和刷机</title>
    <link href="https://androidperformance.com/2021/10/26/build-android-12/"/>
    <id>https://androidperformance.com/2021/10/26/build-android-12/</id>
    <published>2021-10-26T03:49:33.000Z</published>
    <updated>2026-02-07T05:17:47.871Z</updated>
    
    <content type="html"><![CDATA[<p>Android 12 <a href="https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw">正式版</a> 已经发布：<a href="https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw">https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw</a> 。Android 12 正式版的代码也已经发布，<a href="https://source.android.google.cn/">官方文档</a> 也进行了更新：<a href="https://source.android.google.cn/">https://source.android.google.cn/</a></p><p>本文就带大家下载和编译最新的 Android 12 代码，本地编译的代码有下面几个好处</p><ol><li>可以刷真机，方便开发者进行本地 Debug，同时代码可以导入 Android Studio 进行 Debug</li><li>可以编译 Userdebug 版本，可以 root 和 remount，方便对系统和 App 进行 Debug，Debug 模式下可以看到许多 User 版本上看不到的问题；同时由于可以看到更多的信息，也方便进行 App 竞品分析、App 行为分析</li><li>可以更方便地进行 Android 源代码的学习，本地版本可以打开很多系统级别的 Debug Log，也可以自己加 Log，或者自己修改流程</li></ol><span id="more"></span><p>如果大家没有下载编译 Debug 的需求，只是单纯的看代码的话，推荐使用 <a href="https://cs.android.com/">cs.android.com</a> 即可。想深入了解 Android 系统的小伙伴和 Android 系统开发初学者可以看看，建议编译配置如下</p><ol><li>闲鱼搞一个二手不带锁的 Pixel 3 以上（Android 12 只支持 Pixel 3 及以上）</li><li>一个有足够大硬盘（最好是 ssd）、足够大内存（最好是 32g，不行就设 swap）、没那么弱的 cpu（否则会影响编译时间）、安装了 Linux 的台式机</li></ol><h1 id="1-代码下载"><a href="#1-代码下载" class="headerlink" title="1. 代码下载"></a>1. 代码下载</h1><p>由于在国内使用 Google 的官方下载站点，会有下不动的情况，有时候 .repo 都下载不下来，所以本教程是以国内的镜像站点为例子，如果你有方法可以爬墙，那么可以简单参考 <a href="https://source.android.google.cn/source/downloading">官方的教程 https://source.android.google.cn/source/downloading</a></p><p>科大 AOSP 镜像站点地址：<a href="https://mirrors.ustc.edu.cn/help/aosp.html">https://mirrors.ustc.edu.cn/help/aosp.html</a></p><p>下载只需要跟着下面几个步骤走即可（以下方法可以在 Ubuntu、WSL、WSL2、Mac 上运行，但是后面进行代码编译的时候，只能使用 Linux ，所以建议大家还是使用 Ubuntu 这样的 Linux 系统来进行代码的下载、编译、开发工作）</p><h2 id="1-1-步骤1：Repo-工具下载"><a href="#1-1-步骤1：Repo-工具下载" class="headerlink" title="1.1 步骤1：Repo 工具下载"></a>1.1 步骤1：Repo 工具下载</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> ~/bin</span><br><span class="line">PATH=~/bin:<span class="variable">$PATH</span></span><br><span class="line">curl -sSL  <span class="string">&#x27;https://gerrit-googlesource.proxy.ustclug.org/git-repo/+/master/repo?format=TEXT&#x27;</span> |<span class="built_in">base64</span> -d &gt; ~/bin/repo</span><br><span class="line"><span class="built_in">chmod</span> a+x ~/bin/repo</span><br></pre></td></tr></table></figure><h2 id="1-2-步骤2：配置个人信息"><a href="#1-2-步骤2：配置个人信息" class="headerlink" title="1.2 步骤2：配置个人信息"></a>1.2 步骤2：配置个人信息</h2><p>如果没有安装 git，先自己安装一下 git，然后执行下面的命令，填上自己的 Name 和 Email</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config <span class="attr">--global</span> user<span class="selector-class">.name</span> <span class="string">&quot;Your Name&quot;</span> </span><br><span class="line">git config <span class="attr">--global</span> user<span class="selector-class">.email</span> <span class="string">&quot;you@example.com&quot;</span></span><br></pre></td></tr></table></figure><p>比如我填的</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git config <span class="attr">--global</span> user<span class="selector-class">.name</span> <span class="string">&quot;Gracker&quot;</span></span><br><span class="line">git config <span class="attr">--global</span> user<span class="selector-class">.email</span> <span class="string">&quot;dreamtale.jg@gmail.com&quot;</span></span><br></pre></td></tr></table></figure><h2 id="1-2-步骤3：创建工程目录"><a href="#1-2-步骤3：创建工程目录" class="headerlink" title="1.2 步骤3：创建工程目录"></a>1.2 步骤3：创建工程目录</h2><p>在本地建立一个工作目录（名字任意，这里以 Android_12_AOSP 为例子）</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> Android_12_AOSP</span><br><span class="line"><span class="built_in">cd</span> Android_12_AOSP</span><br></pre></td></tr></table></figure><h2 id="1-4-步骤4：初始化仓库"><a href="#1-4-步骤4：初始化仓库" class="headerlink" title="1.4 步骤4：初始化仓库"></a>1.4 步骤4：初始化仓库</h2><p>仓库初始化有两种方式，一种是直接下载，另外一种是加 Tag，下载特定的 Tag 版本，下面会对这两种方法分别进行介绍，大家可以自己选择哪一种方式 （<strong>注意：这里的两种下载方式会影响后续的驱动下载，所以要记清楚自己使用的是哪种方式，在 驱动下载 章节选择合适的驱动</strong>）</p><h3 id="1-4-1-直接下载-推荐"><a href="#1-4-1-直接下载-推荐" class="headerlink" title="1.4.1 直接下载(推荐)"></a>1.4.1 直接下载(推荐)</h3><p>这种方法会下载所有的代码，默认分支是 master ，不愁空间的话，直接用这种方法下载即可</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">repo init -u git:<span class="regexp">//mi</span>rrors.ustc.edu.cn<span class="regexp">/aosp/</span>platform/manifest</span><br><span class="line"><span class="comment">## 如果提示无法连接到 gerrit.googlesource.com，可以编辑 ~/bin/repo，把 REPO_URL 一行替换成下面的：</span></span><br><span class="line"><span class="comment">## REPO_URL = &#x27;https://gerrit-googlesource.proxy.ustclug.org/git-repo&#x27;</span></span><br></pre></td></tr></table></figure><p>这里需要注意，默认的 repo 使用的地址是 REPO_URL &#x3D; ‘<a href="https://gerrit.googlesource.com/git-repo">https://gerrit.googlesource.com/git-repo</a>‘ ，这里我们需要修改 REPO_URL，否则会出现无法下载的情况</p><ol><li>修改方法1：在你的 rc 文件里面，加入一条配置即可：REPO_URL&#x3D;”<a href="https://gerrit-googlesource.proxy.ustclug.org/git-repo">https://gerrit-googlesource.proxy.ustclug.org/git-repo</a>“</li><li>修改方法2：直接打开 ～&#x2F;bin&#x2F;repo, 把 REPO_URL 一行替换成下面的： REPO_URL &#x3D; ‘<a href="https://gerrit-googlesource.proxy.ustclug.org/git-repo">https://gerrit-googlesource.proxy.ustclug.org/git-repo</a>‘</li></ol><p>下载好 .repo 之后会有下面的信息</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">➜  Android12 repo init -u git:<span class="regexp">//mi</span>rrors.ustc.edu.cn<span class="regexp">/aosp/</span>platform/manifest</span><br><span class="line">Downloading Repo source from https:<span class="regexp">//g</span>errit-googlesource.proxy.ustclug.org/git-repo</span><br><span class="line"></span><br><span class="line">... A new version of repo (<span class="number">2.17</span>) is available.</span><br><span class="line">... You should upgrade soon:</span><br><span class="line">    cp <span class="regexp">/home/g</span>racker<span class="regexp">/Code/</span>Android12<span class="regexp">/.repo/</span>repo<span class="regexp">/repo /</span>home<span class="regexp">/gracker/</span>bin/repo</span><br><span class="line"></span><br><span class="line">Downloading manifest from git:<span class="regexp">//mi</span>rrors.ustc.edu.cn<span class="regexp">/aosp/</span>platform/manifest</span><br><span class="line">remote: Enumerating objects: <span class="number">91965</span>, done.</span><br><span class="line">remote: Total <span class="number">91965</span> (delta <span class="number">0</span>), reused <span class="number">0</span> (delta <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">Your identity is: Gracker &lt;dreamtale.jg@gmail.com&gt;</span><br><span class="line">If you want to change this, please re-run <span class="string">&#x27;repo init&#x27;</span> with --config-name</span><br><span class="line"></span><br><span class="line">repo has been initialized <span class="keyword">in</span> <span class="regexp">/home/g</span>racker<span class="regexp">/Code/</span>Android12</span><br></pre></td></tr></table></figure><p>如果选择了直接下载，那么就不需要看 3.2 了</p><h3 id="1-4-2-下载特定的-Tag"><a href="#1-4-2-下载特定的-Tag" class="headerlink" title="1.4.2 下载特定的 Tag"></a>1.4.2 下载特定的 Tag</h3><p>这种方法指的是只下载单个 Tag 所对应的代码，这里的 Tag 可以 <a href="https://source.android.google.cn/setup/start/build-numbers">查看这里 https://source.android.google.cn/setup/start/build-numbers</a>，比如我的开发机是 Google Pixel 3 XL，我在 Tag 列表查看对应的机型都有哪些 TAG，目前 Android 12 只发布了两个，如下</p><p><img src="/images/16352189119144.webp"></p><p>对应的 Tag 分别是 android-12.0.0_r3 和 android-12.0.0_r1 ，所以下载的时候我可以制定对应的 TAG,这样的好处是下载的代码比较少，下载速度会快一些；不方便的点是更新不方便，Google 会定期发邮件告诉你哪些新的 Tag 发布了，你可以根据这个来更新代码</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">repo init -u git:<span class="regexp">//mi</span>rrors.ustc.edu.cn<span class="regexp">/aosp/</span>platform/manifest -b  android-<span class="number">12.0</span>.<span class="number">0</span>_r3</span><br></pre></td></tr></table></figure><h2 id="1-5-步骤5-：同步代码"><a href="#1-5-步骤5-：同步代码" class="headerlink" title="1.5 步骤5 ：同步代码"></a>1.5 步骤5 ：同步代码</h2><p>上面步骤三只是下载了 .repo 文件，具体的代码还需要执行 repo sync 来进行下载。由于镜像站的限制和下载过程中可能会遇到的问题，建议大家用 -j4 来下载</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">repo <span class="built_in">sync</span> -j4</span><br></pre></td></tr></table></figure><p>然后就开始了漫长的下载，由于下载过程中可能会出现失败的情况，你可以搞一个 sh 脚步来循环下载，一觉醒来就下载好了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">repo <span class="built_in">sync</span> -j4</span><br><span class="line"><span class="keyword">while</span> [ $? -ne 0 ]</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;======sync failed ,re-sync again======&quot;</span></span><br><span class="line"><span class="built_in">sleep</span> 3</span><br><span class="line">repo <span class="built_in">sync</span> -j4</span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>具体方法</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">touch</span> repo.sh  <span class="comment"># 1. 创建 repo.sh 文件</span></span><br><span class="line">vim repo.sh <span class="comment"># 2. 复制上面的脚本内容到 repo.sh 里面，这里你可以使用你自己喜欢的方法打开并修改文件，比如 vscode</span></span><br><span class="line"><span class="built_in">chmod</span> a+x repo.sh <span class="comment">#3. 修改权限</span></span><br><span class="line">./repo.sh <span class="comment"># 4. 运行脚本，万事大吉</span></span><br></pre></td></tr></table></figure><h1 id="2-驱动下载"><a href="#2-驱动下载" class="headerlink" title="2. 驱动下载"></a>2. 驱动下载</h1><p>代码下载完成之后，我们先不着急编译，如果要想在真机上跑，需要下载一些厂商闭源的驱动文件，这样后续编译的代码才可以跑到真机上,<a href="https://source.android.google.cn/setup/build/downloading#obtaining-proprietary-binaries">此处对应的 官方文档 https://source.android.google.cn/setup/build/downloading#obtaining-proprietary-binaries</a></p><p><strong>上面下载代码的时候，我们提到了两种方式，直接下载和下载特定 Tag，不同的下载方式对应的驱动也不一样</strong></p><h2 id="2-1-直接下载方式所对应的驱动"><a href="#2-1-直接下载方式所对应的驱动" class="headerlink" title="2.1 直接下载方式所对应的驱动"></a>2.1 直接下载方式所对应的驱动</h2><p>直接下载的代码使用的是 master 分支，驱动程序需要在<a href="https://developers.google.cn/android/blobs-preview">这里下载 https://developers.google.cn/android/blobs-preview</a></p><p>以我的 pixel 3 XL 为例，我需要下载的驱动是</p><p><img src="/images/16352189119178.webp"></p><p>点击 Link 下载两个文件，然后进行解压到代码根目录，然后执行 sh 脚本释放驱动到合适的位置,二进制文件及其对应的 makefile 将会安装在源代码树的 vendor&#x2F; 层次结构中</p><h2 id="2-2-下载特定-Tag-的代码所对应的驱动"><a href="#2-2-下载特定-Tag-的代码所对应的驱动" class="headerlink" title="2.2 下载特定 Tag 的代码所对应的驱动"></a>2.2 下载特定 Tag 的代码所对应的驱动</h2><p>如果下载的时候加了 -b ，那么就需要查看对应的 tag 所对应的驱动，<a href="https://developers.google.cn/android/drivers">地址如下：https://developers.google.cn/android/drivers</a></p><p>以我的 pixel 3 XL 为例，下载的 TAG 为 <strong>android-12.0.0_r3</strong> (repo init -u git:&#x2F;&#x2F;mirrors.ustc.edu.cn&#x2F;aosp&#x2F;platform&#x2F;manifest -b  android-12.0.0_r3)</p><p>那么我们需要找到下面的部分，这里的 <strong>SP1A.210812.016.A1</strong> 跟上面 4.2 节是对应的，即 Tag <strong>android-12.0.0_r3</strong> 对应的 Build ID 是 <strong>SP1A.210812.016.A1</strong>。大家可以根据自己下载的 TAG 找到对应的 Build ID，然后根据 Build ID 寻找对应的驱动即可 <a href="https://developers.google.cn/android/drivers">https://developers.google.cn/android/drivers</a> </p><p><img src="/images/16352189119199.webp"></p><p>跟 4.2 节下载的 Tag 是对应的：<br><img src="/images/16352189119217.webp"></p><h2 id="2-3-驱动提取"><a href="#2-3-驱动提取" class="headerlink" title="2.3 驱动提取"></a>2.3 驱动提取</h2><p>下载的内容解压后，是两个 sh 文件，以我的 Pixel 3 XL 为例，在代码根目录执行，使用 D 来向下翻页，直到最后手动输入 I ACCEPT</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 解压缩 extract-google_devices-crosshatch.sh</span></span><br><span class="line">./extract-google_devices-crosshatch.sh</span><br></pre></td></tr></table></figure><p><img src="/images/16352189119232.webp"></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 解压缩  ./extract-qcom-crosshatch.sh</span></span><br><span class="line"> ./extract-qcom-crosshatch.sh</span><br></pre></td></tr></table></figure><p><img src="/images/16352189119242.webp"></p><h1 id="3-代码编译"><a href="#3-代码编译" class="headerlink" title="3. 代码编译"></a>3. 代码编译</h1><p>代码和驱动都下载好之后，就可以开始代码的编译工作了，由于新版本不再支持 Mac 编译，所以建议大家还是使用 Linux 来进行编译，推荐使用 Ubuntu</p><p><img src="/images/16352189119252.webp" alt="不再支持Mac编译"></p><h2 id="3-1-设置编译环境"><a href="#3-1-设置编译环境" class="headerlink" title="3.1 设置编译环境"></a>3.1 设置编译环境</h2><p>参考：<a href="https://source.android.google.cn/setup/build/initializing">https://source.android.google.cn/setup/build/initializing</a></p><p>Ubuntu 18.04 以上直接运行：</p><figure class="highlight q"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-<span class="built_in">get</span> install git-core gnupg flex bison build-essential zip curl zlib1g-<span class="built_in">dev</span> gcc-multilib g++-multilib libc6-<span class="built_in">dev</span>-i386 libncurses5 lib32ncurses5-<span class="built_in">dev</span> x11proto-core-<span class="built_in">dev</span> libx11-<span class="built_in">dev</span> lib32z1-<span class="built_in">dev</span> libgl1-mesa-<span class="built_in">dev</span> libxml2-utils xsltproc unzip fontconfig</span><br></pre></td></tr></table></figure><h2 id="3-2-设置代码编译环境"><a href="#3-2-设置代码编译环境" class="headerlink" title="3.2 设置代码编译环境"></a>3.2 设置代码编译环境</h2><p>每次关闭 Shell 之后都需要重新执行下面这个脚本，相当于配置了一下编译环境</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> build/envsetup.sh</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">. </span>build/envsetup.sh</span><br></pre></td></tr></table></figure><h2 id="3-3-选择编译目标"><a href="#3-3-选择编译目标" class="headerlink" title="3.3 选择编译目标"></a>3.3 选择编译目标</h2><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">lunch</span></span><br></pre></td></tr></table></figure><p>运行 lunch 之后，会有一堆设备出来让你选择，还是以我的 Pixel 3 XL 为例，其代号是 ，在<a href="https://source.android.google.cn/setup/build/running#selecting-device-build">这里可以查看所有机型对应的代号：https://source.android.google.cn/setup/build/running#selecting-device-build</a><br>Pixel 3 XL 对应的代号是：<strong>crosshatch</strong> </p><p><img src="/images/16352189119263.webp"></p><p>所以我选择编译的是 aosp_crosshatch-userdebug ，这里可以输入编号也可以直接输入 aosp_crosshatch-userdebug</p><p><img src="/images/16352189119273.webp" alt="lunch 选项"></p><p>然后脚本会进行一系列的配置，输出下面的内容</p><p><img src="/images/16352189119284.webp"></p><h2 id="3-4-全部编译"><a href="#3-4-全部编译" class="headerlink" title="3.4 全部编译"></a>3.4 全部编译</h2><p>使用 m 构建所有内容。m 可以使用 -jN 参数处理并行任务。如果您没有提供 -j 参数，构建系统会自动选择您认为最适合您系统的并行任务计数。</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">m</span></span><br></pre></td></tr></table></figure><p>如上所述，您可以通过在 m 命令行中列出相应名称来构建特定模块，而不是构建完整的设备映像。此外，m 还针对各种特殊目的提供了一些伪目标。以下是一些示例：</p><ol><li>droid - m droid 是正常 build。此目标在此处，因为默认目标需要名称。</li><li>all - m all 会构建 m droid 构建的所有内容，加上不包含 droid 标记的所有内容。构建服务器会运行此命令，以确保包含在树中且包含 Android.mk 文件的所有元素都会构建。</li><li>m - 从树的顶部运行构建系统。这很有用，因为您可以在子目录中运行 make。如果您设置了 TOP 环境变量，它便会使用此变量。如果您未设置此变量，它便会从当前目录中查找相应的树，以尝试找到树的顶层。您可以通过运行不包含参数的 m 来构建整个源代码树，也可以通过指定相应名称来构建特定目标。</li><li>mma - 构建当前目录中的所有模块及其依赖项。</li><li>mmma - 构建提供的目录中的所有模块及其依赖项。</li><li>croot - cd 到树顶部。</li><li>clean - m clean 会删除此配置的所有输出和中间文件。此内容与 rm -rf out&#x2F; 相同。</li></ol><p>运行 m help 即可查看 m 提供的其他命令</p><p>输入 m 之后开始第一次全部编译，漫长的等待，编译时间取决于你的电脑配置…主要是 cpu 和内存，建议内存 32G 走起，cpu 也别太烂</p><p><img src="/images/16352189119296.webp"></p><p>编译成功之后，会有下面的输出</p><p><img src="/images/16352189119308.webp"></p><h1 id="4-刷机"><a href="#4-刷机" class="headerlink" title="4. 刷机"></a>4. 刷机</h1><p>自己编译的 UserDebug 固件用来 Debug 是非常方便的，不管是用来 Debug Framework 还是 App</p><p>编译好之后下面开始刷机，以我的测试机器 Pixel 3 XL 为例,依次执行下面的命令</p><figure class="highlight nsis"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">adb <span class="keyword">reboot</span> fastboot</span><br><span class="line"></span><br><span class="line"><span class="comment"># 等待手机进入 fastboot 界面之后</span></span><br><span class="line">fastboot flashall -w</span><br><span class="line"></span><br><span class="line"><span class="comment"># 刷机完成之后，执行 fastboot reboot 长期系统即可</span></span><br><span class="line">fastboot <span class="keyword">reboot</span></span><br></pre></td></tr></table></figure><p>刷机截图如下<br><img src="/images/16352189119323.webp" alt="刷机"></p><p>之后手机会自动重启，然后进入主界面，至此，我们的代码下载-编译-刷机的这部分就结束了</p><p>自己编译的 AOSP 的 Launcher 比较丑，因为没有 Google 闭源的那些套件的加持，看上去还是很简陋的，自带的 App 非常少，而且基本上没怎么维护，给到手机厂商的就是这么一个东西</p><p><img src="/images/16352189119342.webp"></p><p>还是官方的 Pixel 带的 Launcher 好看（Google 开发和维护）</p><p><img src="/images/16352189119363.jpg"></p><p>如果在刷机的过程中遇到问题，可刷官方的刷机包拯救  ：<a href="https://developers.google.cn/android/images">https://developers.google.cn/android/images</a></p><h1 id="5-End"><a href="#5-End" class="headerlink" title="5. End"></a>5. End</h1><p>本文主要是讲如何下载、编译、刷机，后续的代码导入、修改和编译模块、代码 Debug 等，会另起一篇文章来介绍</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 12 &lt;a href=&quot;https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw&quot;&gt;正式版&lt;/a&gt; 已经发布：&lt;a href=&quot;https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw&quot;&gt;https://mp.weixin.qq.com/s/OiFSWEnc-0N2z7JYWTJluw&lt;/a&gt; 。Android 12 正式版的代码也已经发布，&lt;a href=&quot;https://source.android.google.cn/&quot;&gt;官方文档&lt;/a&gt; 也进行了更新：&lt;a href=&quot;https://source.android.google.cn/&quot;&gt;https://source.android.google.cn/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文就带大家下载和编译最新的 Android 12 代码，本地编译的代码有下面几个好处&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;可以刷真机，方便开发者进行本地 Debug，同时代码可以导入 Android Studio 进行 Debug&lt;/li&gt;
&lt;li&gt;可以编译 Userdebug 版本，可以 root 和 remount，方便对系统和 App 进行 Debug，Debug 模式下可以看到许多 User 版本上看不到的问题；同时由于可以看到更多的信息，也方便进行 App 竞品分析、App 行为分析&lt;/li&gt;
&lt;li&gt;可以更方便地进行 Android 源代码的学习，本地版本可以打开很多系统级别的 Debug Log，也可以自己加 Log，或者自己修改流程&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 响应速度实战 3 ：响应速度延伸知识</title>
    <link href="https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/"/>
    <id>https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/</id>
    <published>2021-09-13T02:46:47.000Z</published>
    <updated>2026-02-07T05:17:47.865Z</updated>
    
    <content type="html"><![CDATA[<p>在讨论 Android 性能问题的时候，卡顿、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题</p><p>如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 狭义的卡顿，对应的英文描述我觉得应该是 Jank；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 响应慢，对应的英文描述我觉得应该是 Slow ；如果是发生了 ANR，那就是 <strong>应用无响应问题</strong> 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲</p><p>另外在 App 或者厂商内部，卡顿、响应速度、ANR 这几个性能指标都是有单独的标准的，比如 掉帧率、启动速度、ANR 率等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了</p><p>本文是响应速度系列的第三篇，主要是讲在使用 Systrace 分析应用响应速度问题的时候，其中的一些延伸知识，包括启动速度测试、Log 输出解读、Systrace 状态解读、三方启动库等内容</p> <span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#states">1. Systrace 中进程三种状态解读</a></li><li><a href="#traceview">2. TraceView 工具在响应速度方面的使用</a></li><li><a href="#simpleperf">3. SimplePerf 工具在启动速度分析的使用</a></li><li><a href="#components">4. 其他组件启动时在 Systrace 中的位置</a></li><li><a href="#appstartup">5. AppStartup 是否能优化启动速度？</a></li><li><a href="#idlehandler">6. IdleHandler 在 App 启动场景下的使用</a></li><li><a href="#series">系列文章</a></li><li><a href="#refs">参考文章</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a><br>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</li></ul><p>Systrace 系列文章如下</p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="states"></a></p><h1 id="1-Systrace-中进程三种状态解读"><a href="#1-Systrace-中进程三种状态解读" class="headerlink" title="1. Systrace 中进程三种状态解读"></a>1. Systrace 中进程三种状态解读</h1><p>Systrace 中，进程的任务最常见的有三种状态：Sleep、Running、Runnable。在优化的过程中，这几个状态也需要我们关注。进程任务状态在最上面，以颜色来做区分：</p><ol><li>绿色：Running</li><li>蓝色：Runnable</li><li>白色：Sleep</li></ol><h2 id="1-1-如何分析-Sleep-状态的-Task"><a href="#1-1-如何分析-Sleep-状态的-Task" class="headerlink" title="1.1 如何分析 Sleep 状态的 Task"></a>1.1 如何分析 Sleep 状态的 Task</h2><p>一般白色的 Sleep 有两种，即应用主动 Sleep 和被动 Sleep</p><ol><li>nativePoll 这种，一般属于主动 Sleep，因为没有消息处理了，所以进入 Sleep 状态等待 Message，这种一般是正常的，我们不需要去关注。比如两帧之间的那段，就是主动 sleep 的</li><li>被动 Sleep 一般是由用户主动调用 sleep，或者用 Binder 与其他进程进行通信，这个是我们最常见的，也是分析性能问题的时候经常会遇到的，需要重点关注</li></ol><p>如下图，这种在启动过程中，有较长时间的 sleep 情况，一般下面就可以看到是否在进行 Binder 通信，如果在启动过程中有频繁的 Binder 通信，那么应用等待的时间就会变长，导致响应时间变慢</p><p><img src="/images/QBslSMaNhoheCWfY.webp" alt="QBslSMaNhoheCWfY"></p><p>这种一般可以点击这个 Task 最下面的 binder transaction 来查看 Binder 调用信息，比如</p><p><img src="/images/FqqYKpVnNGUuivrq.webp" alt="FqqYKpVnNGUuivrq"></p><p>有时候没有 Binder 信息，是被其他的等待的线程唤醒，那么可以查看唤醒信息，也可以找到应用是在等待什么</p><p><img src="/images/zPFoHiQJvTWMmUuF.webp" alt="zPFoHiQJvTWMmUuF"></p><p>放大上图中我们点击的 Runnable 的地方</p><p><img src="/images/YJH0UWMlVDuJjSro.webp" alt="YJH0UWMlVDuJjSro"></p><h2 id="1-2-如何分析-Running-状态的-Task"><a href="#1-2-如何分析-Running-状态的-Task" class="headerlink" title="1.2 如何分析 Running 状态的 Task"></a>1.2 如何分析 Running 状态的 Task</h2><p>Running 状态的任务就是目前在 CPU 某一个核心上运行的任务，如果某一段任务是 Running 状态，且耗时变长，那么需要分析：</p><ol><li>是否应用的本身逻辑耗时，比如新增了某些代码逻辑</li><li>是否跑在了对应的核心上</li></ol><p><img src="/images/NDvF1X4GFiUpC6vN.webp" alt="NDvF1X4GFiUpC6vN"></p><p><img src="/images/K49yBsgPUrHkYyw6.webp" alt="K49yBsgPUrHkYyw6"></p><p>在某些 Android 机器上，大家一般会对 App 的主线程和渲染线程进行调度方面的优化：一般前台应用的 UI Thread 和 RenderThread 都是跑在大核上的</p><h2 id="1-3-如何分析-Runnable-状态的-Task"><a href="#1-3-如何分析-Runnable-状态的-Task" class="headerlink" title="1.3 如何分析 Runnable 状态的 Task"></a>1.3 如何分析 Runnable 状态的 Task</h2><p>一个 Task 要从 Sleep 状态转到 Running 状态，必须先变成 Runnable 状态，其状态转换图如下</p><p><img src="/images/3gilFNQA8lh1A7l0.webp" alt="3gilFNQA8lh1A7l0"></p><p>在 Systrace 上的表现如下</p><p><img src="/images/GR7crsOwDOy4X8qL.webp" alt="GR7crsOwDOy4X8qL"></p><p>正常情况下，应用进入 Runnable 状态之后，会马上被调度器调度，进入 Running 状态，开始干活；但是在系统繁忙的时候，应用就会有大量的时间在 Runnable 状态，因为 cpu 已经跑满，各种任务都需要排队等待调度</p><p>如果应用启动的时候出现大量的 Runnable 任务，那么需要查看系统的状态</p><p><a id="traceview"></a></p><h1 id="2-TraceView-工具在响应速度方面的使用"><a href="#2-TraceView-工具在响应速度方面的使用" class="headerlink" title="2. TraceView 工具在响应速度方面的使用"></a>2. TraceView 工具在响应速度方面的使用</h1><p>TraceView 指的是我们在 AS Profiler 里面抓取 CPU 信息的时候出现的那个，大家看下面的截图就知道了</p><p><img src="/images/image-20211028011509615.webp" alt="image-20211028011509615"></p><h2 id="2-1-如何抓取应用启动时候的-TraceView"><a href="#2-1-如何抓取应用启动时候的-TraceView" class="headerlink" title="2.1 如何抓取应用启动时候的 TraceView"></a>2.1 如何抓取应用启动时候的 TraceView</h2><p>使用下面的命令可以抓取应用的冷启动，这些命令也可以分开执行，需要把里面的包名和 Activity 名切换成自己应用的包名</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell am start -n com.aboback.wanandroidjetpack/.splash.SplashActivity --start-profiler /data/local/tmp/traceview.trace --sampling 1 &amp;&amp; sleep 10 &amp;&amp; adb shell am profile stop com.aboback.wanandroidjetpack &amp;&amp; adb pull /data/local/tmp/traceview.trace .</span><br></pre></td></tr></table></figure><p>或者分开执行上面的命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// 1. 冷启动 App，sampleing = 1 意思是 1ms 采样一次</span><br><span class="line">adb shell am start -n com.aboback.wanandroidjetpack/.splash.SplashActivity --start-profiler /data/local/tmp/traceview.trace --sampling 1</span><br><span class="line"></span><br><span class="line">// 2. 等待应用完全启动之后，结束 profile</span><br><span class="line">adb shell am profile stop com.aboback.wanandroidjetpack</span><br><span class="line"></span><br><span class="line">// 3. 将 Trace 文件从手机里面 pull 出来</span><br><span class="line">adb pull /data/local/tmp/traceview.trace .</span><br><span class="line"></span><br><span class="line">// 4. 使用 Android Studio 打开 traceview.trace 文件</span><br></pre></td></tr></table></figure><h2 id="2-2-TraceView-工具怎么看"><a href="#2-2-TraceView-工具怎么看" class="headerlink" title="2.2 TraceView 工具怎么看"></a>2.2 TraceView 工具怎么看</h2><p>抓出来的 TraceView 可以直接在 Android Studio 中打开</p><p>其中图里面用绿色标记的函数，就是应用自己的函数，黄色标注的是系统的函数</p><p>Application.onCreate</p><p><img src="/images/vRpdMHl8CTzCGcxS.webp" alt="vRpdMHl8CTzCGcxS"></p><p>Activity.onCreate</p><p><img src="/images/Jl9s7vZbMQTTrWIT.webp" alt="Jl9s7vZbMQTTrWIT"></p><p>doFrame</p><p><img src="/images/X4f8vX27JS5x4TyF.webp" alt="X4f8vX27JS5x4TyF"></p><p>WebView 初始化</p><p><img src="/images/jxmFl0K9M8cpNCWy.webp" alt="jxmFl0K9M8cpNCWy"></p><h2 id="2-3-TraceView-工具的弊端"><a href="#2-3-TraceView-工具的弊端" class="headerlink" title="2.3 TraceView 工具的弊端"></a>2.3 TraceView 工具的弊端</h2><p>由于采样比较细，所以会性能损耗比较大，所以抓出来的 TraceView，其中每个方法的执行时间是不准的，所以不可用作为真实的时间参考，但是可以用来定位具体的函数调用栈。</p><p>需要跟 Systrace 来进行互补</p><p><a id="simpleperf"></a></p><h1 id="3-SimplePerf-工具在启动速度分析的使用"><a href="#3-SimplePerf-工具在启动速度分析的使用" class="headerlink" title="3. SimplePerf 工具在启动速度分析的使用"></a>3. SimplePerf 工具在启动速度分析的使用</h1><p>使用 SimplePerf 工具也可以抓取启动时候的堆栈信息，既包括 Java 也包括 Native</p><p>比如我们要抓取 com.aboback.wanandroidjetpack 这个应用的冷启动，可以执行下面的命令（SimplePerf 的环境初始化参考 <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md</a> 这篇文章 ，其中 app_profiler.py 就是 SimplePerf 的工具）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python app_profiler.py -p com.aboback.wanandroidjetpack</span><br></pre></td></tr></table></figure><p>执行上面的命令之后，需要手动在手机上启动 App，然后主动结束</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">python app_profiler.py -p com.aboback.wanandroidjetpack</span>  </span><br><span class="line">INFO:root:prepare profiling                                </span><br><span class="line">INFO:root:start profiling1</span><br><span class="line">INFO:root:run adb cmd: [&#x27;adb&#x27;, &#x27;shell&#x27;, &#x27;/data/local/tmp/simpleperf&#x27;, &#x27;record&#x27;, &#x27;-o&#x27;, &#x27;/data/local/tmp/perf.data&#x27;, &#x27;-e task-clock:u -f 1000 -g --duration 10&#x27;, &#x27;--log&#x27;, &#x27;info&#x27;, &#x27;--app&#x27;, &#x27;com.aboback.wanandroidjetpack&#x27;]                                simpleperf I environment.cpp:601] Waiting for process of app com.aboback.wanandroidjetpack</span><br><span class="line">simpleperf I environment.cpp:593] Got process 32112 for package com.aboback.wanandroidjetpack  </span><br></pre></td></tr></table></figure><p>抓取结束之后，调用解析脚本来生成 html 报告</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">python</span> report_html.<span class="keyword">py</span></span><br></pre></td></tr></table></figure><p>就会得到下面这个</p><p><img src="/images/itcLtktb5xxv5gp0.webp" alt="itcLtktb5xxv5gp0"></p><p>不仅可以看到 Java 层的堆栈，也可以看到 Native 的堆栈，这里只是简单的使用，更详细的方法可以参考下面几个文档</p><p>SimplePerf 初步试探 <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md</a></p><ol><li>Android application profiling <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md</a></li><li>Android platform profiling <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_platform_profiling.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_platform_profiling.md</a></li><li>Executable commands reference <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/executable_commands_reference.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/executable_commands_reference.md</a></li><li>Scripts reference <a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/scripts_reference.md">https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/scripts_reference.md</a></li></ol><p><a id="components"></a></p><h1 id="4-其他组件启动时在-Systrace-中的位置"><a href="#4-其他组件启动时在-Systrace-中的位置" class="headerlink" title="4. 其他组件启动时在 Systrace 中的位置"></a>4. 其他组件启动时在 Systrace 中的位置</h1><h2 id="4-1-Service-的启动"><a href="#4-1-Service-的启动" class="headerlink" title="4.1 Service 的启动"></a>4.1 Service 的启动</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">scheduleCreateService</span><span class="params">(IBinder token,</span></span><br><span class="line"><span class="params">        ServiceInfo info, CompatibilityInfo compatInfo, <span class="type">int</span> processState)</span> &#123;</span><br><span class="line">    updateProcessState(processState, <span class="literal">false</span>);</span><br><span class="line">    <span class="type">CreateServiceData</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CreateServiceData</span>();</span><br><span class="line">    s.token = token;</span><br><span class="line">    s.info = info;</span><br><span class="line">    s.compatInfo = compatInfo;</span><br><span class="line">    sendMessage(H.CREATE_SERVICE, s);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">scheduleBindService</span><span class="params">(IBinder token, Intent intent,</span></span><br><span class="line"><span class="params">        <span class="type">boolean</span> rebind, <span class="type">int</span> processState)</span> &#123;</span><br><span class="line">    updateProcessState(processState, <span class="literal">false</span>);</span><br><span class="line">    <span class="type">BindServiceData</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BindServiceData</span>();</span><br><span class="line">    s.token = token;</span><br><span class="line">    s.intent = intent;</span><br><span class="line">    s.rebind = rebind;</span><br><span class="line">    sendMessage(H.BIND_SERVICE, s);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，代码执行都是往 H 这个 Handler 中发送 Message,所以如果我们在代码里面启动 Service，并不是马上就执行的，而是由 MessageQueue 里面的 Message 顺序决定的</p><p><img src="/images/NRY3YuU1Bsgk6wvv.webp" alt="NRY3YuU1Bsgk6wvv"></p><p>放大真正执行的部分可以看到，其执行的时机是在 MessageQueue 按照 Message 的顺序执行(这里是在应用第一帧执行结束后)，后面的 Message 就是应用自己的 Message、启动 Service、执行广播接收器</p><p><img src="/images/r463HjnnO0KgdOFl.webp" alt="r463HjnnO0KgdOFl"></p><h2 id="4-2-执行自己的-Message"><a href="#4-2-执行自己的-Message" class="headerlink" title="4.2 执行自己的 Message"></a>4.2 执行自己的 Message</h2><p>执行自定义的 Message 在 Systrace 中的显示</p><p><img src="/images/iUZhyqUStVIyZHtf.webp" alt="iUZhyqUStVIyZHtf"></p><h2 id="4-3-启动-Service"><a href="#4-3-启动-Service" class="headerlink" title="4.3 启动 Service"></a>4.3 启动 Service</h2><p>Service 启动在 Systrace 中的显示</p><p><img src="/images/8RzVxF9ai3r420W0.webp" alt="8RzVxF9ai3r420W0"></p><h2 id="4-4-启动-BroadcastReceiver"><a href="#4-4-启动-BroadcastReceiver" class="headerlink" title="4.4 启动 BroadcastReceiver"></a>4.4 启动 BroadcastReceiver</h2><p>执行 Receiver 在 Systrace 中的显示</p><p><img src="/images/kY3rkAhlqk7klnOL.webp" alt="kY3rkAhlqk7klnOL"></p><p>Broadcast 的注册：一般是在 Activity 生命周期函数中注册，在哪里注册就在哪里执行</p><p><img src="/images/UR245WLGUvPEcvTz.webp" alt="UR245WLGUvPEcvTz"></p><h2 id="4-5-ContentProvider-的启动时机"><a href="#4-5-ContentProvider-的启动时机" class="headerlink" title="4.5 ContentProvider 的启动时机"></a>4.5 ContentProvider 的启动时机</h2><p><img src="/images/6PdzLLkZ7hdA61Tk.webp" alt="6PdzLLkZ7hdA61Tk"></p><p><img src="/images/019y0cn5kNNGFVnm.webp" alt="019y0cn5kNNGFVnm"></p><p><a id="appstartup"></a></p><h1 id="5-AppStartup-是否能优化启动速度？"><a href="#5-AppStartup-是否能优化启动速度？" class="headerlink" title="5. AppStartup 是否能优化启动速度？"></a>5. AppStartup 是否能优化启动速度？</h1><h4 id="三方库的初始化"><a href="#三方库的初始化" class="headerlink" title="三方库的初始化"></a>三方库的初始化</h4><p>很多三方库都需要在 Application 中进行初始化，并顺便获取到 Application 的上下文</p><p>但是也有的库不需要我们自己去初始化，它偷偷摸摸就给初始化了，用到的方法就是使用 ContentProvider 进行初始化，定义一个 ContentProvider，然后在 onCreate 拿到上下文，就可以进行三方库自己的初始化工作了。而在 APP 的启动流程中，有一步就是要执行到程序中所有注册过的 ContentProvider 的 onCreate 方法，所以这个库的初始化就默默完成了。</p><p>这种做法确实给集成库的开发者们带来了很大的便利，现在很多库都用到了这种方法，比如 Facebook，Firebase，WorkManager</p><p>ContentProvider 的初始化时机如下：</p><p><img src="/images/VhpUlBTz1UNteVQz.webp" alt="VhpUlBTz1UNteVQz"></p><p>但是当大部分三方库使用这种方法初始化的时候，就会有下面几个问题</p><ol><li>启动过程中的 ContentProvider 过多</li><li>应用开发者无法控制使用这种方式初始化的库的初始化时机</li><li>无法处理这些三方库的依赖</li></ol><h4 id="AppStartup-库"><a href="#AppStartup-库" class="headerlink" title="AppStartup 库"></a>AppStartup 库</h4><p>针对上面的情况，Google 推出了 <a href="https://developer.android.google.cn/topic/libraries/app-startup">AppStartup</a> 库，AppStartup 库的优点</p><ul><li>可以共享单个 Contentprovider</li><li>可以明确地设置初始化顺序</li><li>通过这个库可以移除三方库的 ContentProvider 启动时候自动初始化的步骤，手动通过 LazyLoad 的方式启动，这样可以起到优化启动速度的作用</li></ul><p>根据测算结果来看，使用 AppStartup 库并不能显著加快应用启动速度，除非你有非常多 (50+）的 ContentProvider 在应用启动的时候初始，那么 AppStartup 才会有比较明显的效果</p><p>如果三方的 SDK 使用 ContentProvider 初始化耗时，那么可以考虑针对这个 ContentProvider 进行延迟初始化，比如</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">provider</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;androidx.startup.InitializationProvider&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:authorities</span>=<span class="string">&quot;$&#123;applicationId&#125;.androidx-startup&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:exported</span>=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">tools:node</span>=<span class="string">&quot;merge&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta-data</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.ExampleLoggerInitializer&quot;</span></span></span><br><span class="line"><span class="tag">              <span class="attr">tools:node</span>=<span class="string">&quot;remove&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">provider</span>&gt;</span></span><br></pre></td></tr></table></figure><p>ExampleLoggerInitializer 的 meta-data 当中加入了一个 tools:node&#x3D;”remove”的标记</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ol><li>App Startup 的设计是为了解决一个问题：即不同的库使用不同的 ContentProvider 进行初始化，导致 ContentProvider 太多，管理杂乱，影响耗时的问题</li><li>App Startup 具体能减少多少耗时时间:根据测试，如果二三十个三方库都集成了 App Startup，减少的耗时大概在 20ms 以内</li><li>App Startup 的使用场景应该<ol><li>APK 有很多的 ContentProvider 在启动时候初始化</li><li>APK 中有的三方库 ContentProvider 初始化很耗时，但是又不是必须要在启动的时候初始，可以按需初始化</li><li>应用开发者想自己控制各个库的初始化时机或者初始化顺序</li></ol></li></ol><h4 id="需要-App-开发同学验证"><a href="#需要-App-开发同学验证" class="headerlink" title="需要 App 开发同学验证"></a>需要 App 开发同学验证</h4><ol><li>检查打包出来的 apk 的配置文件里面看一下，有多少个三方库是利用 ContentProvider 初始化的（或者在 AS 的 src\main\AndroidManifest.xml 文件最下面打开 Merged Manifest 标签查看）</li><li>确认这些 ContentProvider 在启动时候的耗时</li><li>确认哪些 ContentProvider 可以延迟加载或者用时加载</li><li>如果需要的话，接入 AppStartup 库</li></ol><p><a id="idlehandler"></a></p><h1 id="6-IdleHandler-在-App-启动场景下的使用"><a href="#6-IdleHandler-在-App-启动场景下的使用" class="headerlink" title="6. IdleHandler 在 App 启动场景下的使用"></a>6. IdleHandler 在 App 启动场景下的使用</h1><p>在启动优化的过程中，idleHandler 可以在 MessageQueue 空闲的时候执行任务，如下图，可以很清晰地查看 idleHandler 的执行时机</p><p><img src="/images/XaSQsq3pfgDgCNVe.webp" alt="XaSQsq3pfgDgCNVe"></p><p>其使用场景如下：</p><ol><li><p><strong>在启动的过程中，可以借助 idleHandler 来做一些延迟加载的事情。</strong>比如在启动过程中 Activity 的 onCreate 里面 addIdleHandler，这样在 Message 空闲的时候，可以执行这个任务</p><p><img src="/images/7fLroBfAyXPaUu8W.webp" alt="7fLroBfAyXPaUu8W"></p></li><li><p><strong>进行启动时间统计</strong>：比如在页面完全加载之后，调用 activity.reportFullyDrawn 来告知系统这个 Activity 已经完全加载，用户可以使用了，比如下面的例子，在主页的 List 加载完成后，调用 activity.reportFullyDrawn </p><p><img src="/images/BN97DrcXfHiqA5DY.webp" alt="BN97DrcXfHiqA5DY"></p><p>其对应的 Systrace 如下</p><p><img src="/images/1xrN3YMzAY71ZAWi.webp" alt="1xrN3YMzAY71ZAWi"></p><p>这时候得到的应用的冷启动时间才是正常的</p><p><img src="/images/A5mH2AW4U06l43jk.webp" alt="A5mH2AW4U06l43jk"></p></li></ol><p>另外系统有些功能，也会依赖于 FullyDrawn，所以建议主动上报（即主动在 App 完全启动后调用 activity.reportFullyDrawn）</p><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列-放个链接在这里方便大家直接点过去</a></li></ol><p><a id="refs"></a></p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ol><li><a href="https://www.jianshu.com/p/37370c1d17fc">Android 应用启动全流程分析</a></li><li><a href="https://juejin.cn/post/6907493155659055111">探究 | App Startup 真的能减少启动耗时吗</a></li><li><a href="https://blog.csdn.net/guolin_blog/article/details/108026357">Jetpack 新成员，App Startup 一篇就懂</a></li><li><a href="https://developer.android.google.cn/topic/libraries/app-startup">App Startup</a></li><li><a href="https://www.androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a></li><li><a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">Android application profiling</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在讨论 Android 性能问题的时候，卡顿、响应速度、ANR 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题&lt;/p&gt;
&lt;p&gt;如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 狭义的卡顿，对应的英文描述我觉得应该是 Jank；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 响应慢，对应的英文描述我觉得应该是 Slow ；如果是发生了 ANR，那就是 &lt;strong&gt;应用无响应问题&lt;/strong&gt; 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲&lt;/p&gt;
&lt;p&gt;另外在 App 或者厂商内部，卡顿、响应速度、ANR 这几个性能指标都是有单独的标准的，比如 掉帧率、启动速度、ANR 率等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了&lt;/p&gt;
&lt;p&gt;本文是响应速度系列的第三篇，主要是讲在使用 Systrace 分析应用响应速度问题的时候，其中的一些延伸知识，包括启动速度测试、Log 输出解读、Systrace 状态解读、三方启动库等内容&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</title>
    <link href="https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/"/>
    <id>https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/</id>
    <published>2021-09-13T02:46:44.000Z</published>
    <updated>2026-02-07T05:17:47.864Z</updated>
    
    <content type="html"><![CDATA[<p>在讨论 Android 性能问题的时候，<strong>卡顿</strong>、<strong>响应速度</strong>、<strong>ANR</strong> 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题</p><p>如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 <strong>狭义的卡顿</strong>，对应的英文描述我觉得应该是 <strong>Jank</strong>；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 <strong>响应慢</strong> ，对应的英文描述我觉得应该是 <strong>Slow</strong> ；如果是发生了 ANR，那就是 <strong>应用无响应问题</strong> 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲</p><p>另外在 App 或者厂商内部，<strong>卡顿</strong>、<strong>响应速度</strong>、<strong>ANR</strong> 这几个性能指标都是有单独的标准的，比如 <strong>掉帧率</strong>、<strong>启动速度</strong>、<strong>ANR 率</strong>等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了</p><p><strong>本文是响应速度系列的第二篇，主要是以 Android App 冷启动为例，讲解如何使用 Systrace 来分析 App 冷启动</strong>  <span id="more"></span></p><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#prep">1. 准备工作</a></li><li><a href="#cold-start">2. Android App 冷启动流程分析</a></li><li><a href="#end">End</a></li><li><a href="#series">系列文章</a></li><li><a href="#refs">参考文章</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</p><p><strong>Systrace 系列文章如下</strong></p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="prep"></a></p><h1 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1. 准备工作"></a>1. 准备工作</h1><p>这个案例和对应的 Systrace 偏工程化一些，省略了很多细节，因为应用的启动流程涉及的知识非常广，如果每个都细化的话，会有很大的篇幅。推荐大家看这篇文章，非常详细：<a href="https://www.jianshu.com/p/37370c1d17fc">Android 应用启动全流程分析</a></p><p>所以这里以 Systrace 为主线，讲解应用启动的时候各个关键模块的大概工作流程。了解大概流程之后，就可以分段去深入自己感兴趣或者自己负责的部分，这里首先放一张 Systrace 和手机截图所对应的图，大家可以先看看这个图，然后再往下看（博客里面 Perfetto 和 Systrace 混合使用）</p><p><img src="/images/OIG57AudX193jD3u.webp" alt="应用启动完整图"></p><p>为了更方便分析应用冷启动，我们需要做下面的准备工作</p><ol><li>打开 Binder 调试，方便在 Trace 中显示 Binder 信息﻿（即可以在 Systrace 中看到 Binder 调用的函数）- 需要 Root<ol><li>开启 ipc debug： <code>adb shell am trace-ipc start</code></li><li>抓取结束后，可以执行下面的命令关闭﻿<code>adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt</code></li></ol></li><li>Trace 命令加入 <strong>irq</strong> tag，默认的命令不包含 irq，需要自己加 irq 的 TAG,这样打开 Trace 之后，就可以看到 irq 相关的内容，最后的抓 trace 命令如下：﻿<br>﻿   <code>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</code> ,注意这里的 com.xxx.xxx 换成自己的包名，如果不是调试特定的包名，可以去掉 -a com.xxx.xxx</li><li>推荐 ：如果要 Debug 的 App 可以进行编译（即可以使用 Gradle 编译，一般自己开发的项目都可以），可以在分析响应速度问题的时候，引入 TraceFix 库(接入方法参考 <a href="https://github.com/Gracker/TraceFix)%E3%80%82%E6%8E%A5%E5%85%A5%E4%B9%8B%E5%90%8E%EF%BC%8C%E7%BC%96%E8%AF%91%E7%9A%84%E6%97%B6%E5%80%99%E5%B0%B1%E4%BC%9A%E8%BF%9B%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%8F%92%E6%A1%A9%EF%BC%8C%E5%9C%A8">https://github.com/Gracker/TraceFix)。接入之后，编译的时候就会进行代码插桩，在</a> App 代码的每一个函数中都插入 Trace 点，这样在分析的时候可以看到更详细的 App 的信息<ol><li>使用插件前，只能看到 Framework 里面的 Trace 点﻿<br> <img src="/images/enHoD7QcxOSVg38n.webp"></li><li>使用插件后﻿，可以看到 Trace 中显示的信息多了很多（App 自身的代码逻辑，Framework 的代码没法插桩） <img src="/images/M3t5klCy3VTETYcN.webp"></li></ol></li></ol><p><a id="cold-start"></a></p><h1 id="2-Android-App-冷启动流程分析"><a href="#2-Android-App-冷启动流程分析" class="headerlink" title="2. Android App 冷启动流程分析"></a>2. Android App 冷启动流程分析</h1><p>本文以 <strong>在桌面上冷启动一个 Android App 为例</strong>，应用冷启动的整个流程包含了从用户触摸屏幕到应用完全显示的整个流程，其中涉及到</p><ol><li>触摸屏中断处理阶段</li><li>InputReader 和 InputDispatcher 处理 input 事件阶段</li><li>Launcher 处理 input 事件阶段</li><li>SystemServer 处理启动事件</li><li>启动动画</li><li>应用启动和自身逻辑阶段</li></ol><p>上一篇文章有讲到响应速度问题，需要搞清楚 <strong>起点</strong> 和 <strong>终点</strong>，对于应用冷启动来说，<strong>起点</strong>就是 input 事件，<strong>终点</strong>就是应用完全展示给用户（用户可操作）</p><p>下面将从上面几个关键流程，通过 Systrace 的来介绍整个流程</p><h2 id="2-1-触摸屏中断处理阶段"><a href="#2-1-触摸屏中断处理阶段" class="headerlink" title="2.1 触摸屏中断处理阶段"></a>2.1 触摸屏中断处理阶段</h2><p>由于我们的案例是在桌面冷启动一个 App，那么在手指触摸手机屏幕的时候，触摸屏会触发中断，这个中断我们最早能在 Systrace 中看到的地方如下：</p><p><img src="/images/qAyHzNpKxVQ7C4EY-5347769.webp"></p><p>对应的 cpu ss 区域和 中断区域（加了 irq 的 tag 才可以看到）</p><p><img src="/images/BIWDeeCYcmizchTV-5347827.webp"></p><p>一般来说，点击屏幕会触发若干个中断，这些信号经过处理之后，触摸屏驱动会把这些点更新到 EventHub 中，让 InputReader 和 InputDIspatcher 进行进一步的处理。这一步一般不会出现什么问题，厂商这边对触摸屏的调教可能会关注这里</p><h2 id="2-2-InputReader-和-InputDispatcher-处理-Input-事件阶段"><a href="#2-2-InputReader-和-InputDispatcher-处理-Input-事件阶段" class="headerlink" title="2.2 InputReader 和 InputDispatcher 处理 Input 事件阶段"></a>2.2 InputReader 和 InputDispatcher 处理 Input 事件阶段</h2><p>InputReader 和 InputDispatcher 这两个线程跑在 SystemServer 里面，专门负责处理 Input 事件，具体的流程可以参考<a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Android Systrace 基础知识 - Input 解读</a> 这篇文章</p><p><img src="/images/CVaK1fF9Ch0vT0qw.webp" alt="InputReader 和 InputDispatcher"></p><p>这里由于我们是点击桌面上的一个 App 的图标，可以看到底层上报上来的事件包括一个 Input_Down 事件 + 若干个 Input Move 事件 + 一个 Input Up 事件，组成了一个完整的点击事件</p><p>由于 Launcher 在进程创建的时候就注册了 Input 监听，且此时 Launcher 在前台且可见，所以 Launcher 进程可以收到这些 Input 事件，并根据 Input 事件的类型进行处理，input 事件在 SystemServer 和 App 的流转在 Systrace 中的具体表现可以参考 <a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Android Systrace 基础知识 - Input 解读</a> ，这里把核心的两张图放上来</p><h3 id="2-2-1-Input-事件在-SystemServer-中流转"><a href="#2-2-1-Input-事件在-SystemServer-中流转" class="headerlink" title="2.2.1 Input 事件在 SystemServer 中流转"></a>2.2.1 Input 事件在 SystemServer 中流转</h3><p>看下图即可，如果要看更详细的，可以查看 <a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Android Systrace 基础知识 - Input 解读</a></p><h4 id=""><a href="#" class="headerlink" title=""></a><img src="/images/15728723576583.jpg" alt="Input 事件在 SystemServer 中流转"></h4><h3 id="2-2-2-Input-事件在-Launcher-进程流转"><a href="#2-2-2-Input-事件在-Launcher-进程流转" class="headerlink" title="2.2.2 Input 事件在 Launcher 进程流转"></a>2.2.2 Input 事件在 Launcher 进程流转</h3><p>看下图即可，如果要看更详细的，可以查看 <a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Android Systrace 基础知识 - Input 解读</a><br><img src="/images/15728723679523.jpg"></p><h2 id="2-3-Launcher-进程处理-Input-事件阶段"><a href="#2-3-Launcher-进程处理-Input-事件阶段" class="headerlink" title="2.3 Launcher 进程处理 Input 事件阶段"></a>2.3 Launcher 进程处理 Input 事件阶段</h2><p>Launcher 处理 Input 事件也是响应时间的一个重要阶段，主要包括两个响应速度指标</p><ol><li>点击桌面到桌面第一帧响应（一般 Launcher 会在接收到 Down 事件的时候，将 App 图标置灰，以表示接收到了事件；有的定制桌面 App 图标会有一个缩小的动画，表示被按压）</li><li>桌面第一帧响应到启动 App（这段时间指的是桌面在收到 Down 对 App 图标做处理后，到收到 Up 事件判断需要启动 App 的时间）</li></ol><p>另外提一下，<strong>滑动桌面到桌面第一帧响应时间</strong>（这个指的是滑动桌面的场景，左右滑动桌面的时候，用高速相机拍摄，从手指动开始，到桌面动的第一帧的时间）也是一个很重要的<strong>响应速度指标</strong>，部分厂商也会在这方面做优化，感兴趣的可以自己试试主流厂商的桌面滑动场景（跟原生的机器对比 Systrace 即可）</p><p>在冷启动的场景里面，Launcher 在收到 up 事件后，会进行逻辑判断，然后启动对应的 App（这里主要是交给 AMS 来处理，又回到了 SystemServer 进程）</p><p><img src="/images/A9Y3FkDNSFlPuho5.webp" alt="A9Y3FkDNSFlPuho5"></p><p>这个阶段通常也是做系统优化的会比较关注，做 App 的同学还不需要关注到这里（Launcher App 的除外）；另外在最新的版本，应用启动的动画是由 Launcher 和 SystemServer 共同完成的，目的就是可以做一些复杂的动画而没有割裂感，大家可以用慢镜头拍一下启动时候和退出应用的动画，可以看到有的应用图标是分层的，甚至会动，这是之前纯粹由 SystemServer 这边来做动画所办不到的</p><h2 id="2-4-SystemServer-处理-StartActivity-阶段"><a href="#2-4-SystemServer-处理-StartActivity-阶段" class="headerlink" title="2.4 SystemServer 处理 StartActivity 阶段"></a>2.4 SystemServer 处理 StartActivity 阶段</h2><p>SystemServer 处理主要是有2部分</p><ol><li>处理启动命令</li><li>通知 Launcher 进入 Pause 状态</li><li>fork 新的进程</li></ol><h3 id="处理启动命令"><a href="#处理启动命令" class="headerlink" title="处理启动命令"></a>处理启动命令</h3><p>这个 SystemServer 进程中的 Binder 调用就是 Launcher 通过 ActivityTaskManager.getService().startActivity 调用过来的</p><p><img src="/images/SL5nD2t2w2V8EQxP.webp"></p><p>fork 新的进程，则是在判断启动的 Activity 的 App 进程没有启动后，需要首先启动进程，然后再启动 Activity，这里是冷启动和其他启动不一样的地方。fork 主要是 fork  Zygote64 这个进程（部分 App 是 fork 的 Zygote32 ）</p><p><img src="/images/c8BbgI8xrF6MRY1L.webp" alt="fork 新进程"></p><p><img src="/images/AxsjO5P7uaw9cZTj.webp" alt="fork 新进程对应的代码"></p><h3 id="Zygote-64-位进程执行-Fork-操作"><a href="#Zygote-64-位进程执行-Fork-操作" class="headerlink" title="Zygote 64 位进程执行 Fork 操作"></a>Zygote 64 位进程执行 Fork 操作</h3><p><img src="/images/uJSzBhwP4IncTGWW.webp"></p><p><img src="/images/hZBir9BCl0hpPbc8.webp"></p><h3 id="对应的-App-进程出现"><a href="#对应的-App-进程出现" class="headerlink" title="对应的 App 进程出现"></a>对应的 App 进程出现</h3><p><img src="/images/kyfMkYZfQtmkqQtI.webp"></p><p>对应的代码如下，这里就正式进入了 App 自己的进程逻辑了</p><p><img src="/images/DPeBH7yfegYmmCvp.webp"></p><p>应用启动后，SystemServer 会记录从 startActivity 被调用到应用第一帧显示的时长，在 Systrace 中的显示如下（<strong>注意结尾是应用第一帧，如果应用启动的时候是 SplashActivity -&gt; MainActivity，那么这里的结尾只是 SplashActivity，MainActivity 的完全启动需要自己查看</strong>）</p><p><img src="/images/ZsfEiexfdJn48C1k.webp"></p><h2 id="2-5-应用进程启动阶段"><a href="#2-5-应用进程启动阶段" class="headerlink" title="2.5 应用进程启动阶段"></a>2.5 应用进程启动阶段</h2><p>通常的大型应用，App 冷启动通常包括下面三个部分，每一个部分耗时都会导致应用的整体启动速度变慢，所以在优化启动速度的时候，需要明确知道应用启动结束的点（需要跟测试沟通清楚，一般是界面保持稳定的那个点）</p><ol><li>应用进程启动到 SplashActivity 第一帧显示（部分 App 没有 SplashActivity，所以可以省略这一步，直接到进程启动到 主 Activit 第一帧显示 ）</li><li>SplashActivity 第一帧显示到主 Activity 第一帧显示</li><li>主 Activity 第一帧显示到界面完全显示</li></ol><p>下面针对这三个阶段来具体分析（当然你的 App 如果简单的话，可能没有 SplashActivity ，直接进的就是主 Activity，那么忽略第二步就可以了）</p><h3 id="应用进程启动到-SplashActivity-第一帧显示"><a href="#应用进程启动到-SplashActivity-第一帧显示" class="headerlink" title="应用进程启动到 SplashActivity 第一帧显示"></a>应用进程启动到 SplashActivity 第一帧显示</h3><p>由于是冷启动，所以 App 进程在 Fork 之后，需要首先执行 bindApplication ，这个也是区分冷热启动的一个重要的点。Application 的环境创建好之后，就开始组件的启动（这里是 Activity 组件，通过 Service、Broadcast、ContentProvider 组件启动的进程则会在 bindApplication 之后先启动这些组件）</p><p>Activity 的生命周期函数会在 Activity 组件创建的时候执行，包括 onStart、onCreate、onResume 等，然后还要经过一次 Choreographer#doFrame 的执行（包括 measure、layout、draw）以及 RenderThread 的初始化和第一帧任务的绘制，再加上 SurfaceFlinger 一个 Vsync 周期的合成，应用第一帧才会真正显示（也就是下图中 finishDrawing 的位置），这部分详细的流程可以查看 <a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Android Systrace 基础知识 - MainThread 和 RenderThread 解读</a></p><p><img src="/images/Nr5HXUKyZ7qJUIBL.webp"></p><h3 id="SplashActivity-第一帧显示到主-Activity-第一帧显示"><a href="#SplashActivity-第一帧显示到主-Activity-第一帧显示" class="headerlink" title="SplashActivity 第一帧显示到主 Activity 第一帧显示"></a>SplashActivity 第一帧显示到主 Activity 第一帧显示</h3><p>大部分的 App 都有 SplashActivity 来播放广告，播放完成之后才是真正的主 Activity 的启动，同样包括 Activity 组件的创建，包括 onStart、onCreate、onResume 、自有启动逻辑的执行、WebView 的初始化等等等等，直到主 Activity 的第一帧显示</p><p><img src="/images/6ahmp1zba89yblyF.webp"></p><h3 id="主-Activity-第一帧显示到界面完全加载并显示"><a href="#主-Activity-第一帧显示到界面完全加载并显示" class="headerlink" title="主 Activity 第一帧显示到界面完全加载并显示"></a>主 Activity 第一帧显示到界面完全加载并显示</h3><p>一般来说，主 Activity 需要多帧才能显示完全，因为有很多资源（最常见的是图片）是异步加载的，第一帧可能只加载了一个显示框架、而其中的内容在准备好之后才会显示出来。这里也可以看到，通过 Systrace 不是很方便来判断应用冷启动的终点（除非你跟测试约定好，在某个 View 显示之后就算启动完成，然后你在这个 View 里面打个 Systrace 的 Tag，通过跟踪这个 Tag 就可以粗略判断具体 Systrace 里面哪一帧是启动完成的点）</p><p><img src="/images/lB2bublumIv584yu.webp"></p><p>我制作了一个 Systrace + 截图的方式，来进行演示，方便你了解 App 启动各个阶段都对应在 Systrace 的哪里（使用的是一个开源的 WanAndroid 客户端）</p><p><img src="/images/OIG57AudX193jD3u.webp" alt="OIG57AudX193jD3u"></p><p><a id="end"></a></p><h1 id="End"><a href="#End" class="headerlink" title="End"></a>End</h1><p>本文重点放在了如何在 Systrace 中展示 App 的完整冷启动流程，方便大家在做 App 的启动优化的时候，可以通过 Systrace 来快速定位到启动瓶颈，也方便进行竞品的对比和分析，</p><ol><li>至于如何分析，可以查看 <a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a> 的分析套路部分</li><li>至于如何优化，可以查看 <a href="https://www.androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a> 这篇文章，这里就不再重复了。不过随着技术的发展，有些优化手段会消失，而会有新的优化手段冒出来，我也会对这篇文章进行维护，如果大家发现新的优化技术，麻烦博客留言或者加微信（553000664）通知我，我来进行调研和更新</li></ol><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列-放个链接在这里方便大家直接点过去</a></li></ol><p><a id="refs"></a></p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ol><li><a href="https://www.jianshu.com/p/37370c1d17fc">Android 应用启动全流程分析</a></li><li><a href="https://juejin.cn/post/6907493155659055111">探究 | App Startup 真的能减少启动耗时吗</a></li><li><a href="https://blog.csdn.net/guolin_blog/article/details/108026357">Jetpack 新成员，App Startup 一篇就懂</a></li><li><a href="https://developer.android.google.cn/topic/libraries/app-startup">App Startup</a></li><li><a href="https://www.androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a></li><li><a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">Android application profiling</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在讨论 Android 性能问题的时候，&lt;strong&gt;卡顿&lt;/strong&gt;、&lt;strong&gt;响应速度&lt;/strong&gt;、&lt;strong&gt;ANR&lt;/strong&gt; 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题&lt;/p&gt;
&lt;p&gt;如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 &lt;strong&gt;狭义的卡顿&lt;/strong&gt;，对应的英文描述我觉得应该是 &lt;strong&gt;Jank&lt;/strong&gt;；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 &lt;strong&gt;响应慢&lt;/strong&gt; ，对应的英文描述我觉得应该是 &lt;strong&gt;Slow&lt;/strong&gt; ；如果是发生了 ANR，那就是 &lt;strong&gt;应用无响应问题&lt;/strong&gt; 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲&lt;/p&gt;
&lt;p&gt;另外在 App 或者厂商内部，&lt;strong&gt;卡顿&lt;/strong&gt;、&lt;strong&gt;响应速度&lt;/strong&gt;、&lt;strong&gt;ANR&lt;/strong&gt; 这几个性能指标都是有单独的标准的，比如 &lt;strong&gt;掉帧率&lt;/strong&gt;、&lt;strong&gt;启动速度&lt;/strong&gt;、&lt;strong&gt;ANR 率&lt;/strong&gt;等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文是响应速度系列的第二篇，主要是以 Android App 冷启动为例，讲解如何使用 Systrace 来分析 App 冷启动&lt;/strong&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 响应速度实战 1 ：了解响应速度原理</title>
    <link href="https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/"/>
    <id>https://androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/</id>
    <published>2021-09-13T02:46:22.000Z</published>
    <updated>2026-02-07T05:17:47.864Z</updated>
    
    <content type="html"><![CDATA[<p>在讨论 Android 性能问题的时候，<strong>卡顿</strong>、<strong>响应速度</strong>、<strong>ANR</strong> 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题</p><p>如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 <strong>狭义的卡顿</strong>，对应的英文描述我觉得应该是 <strong>Jank</strong>；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 <strong>响应慢</strong> ，对应的英文描述我觉得应该是 <strong>Slow</strong> ；如果是发生了 ANR，那就是 <strong>应用无响应问题</strong> 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲</p><p>另外在 App 或者厂商内部，<strong>卡顿</strong>、<strong>响应速度</strong>、<strong>ANR</strong> 这几个性能指标都是有单独的标准的，比如 <strong>掉帧率</strong>、<strong>启动速度</strong>、<strong>ANR 率</strong>等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了</p><p><strong>本文是响应速度系列的第一篇，主要是讲</strong>响应速度<strong>相关的理论知识，包括性能工程概述、响应速度涉及到的知识点、响应速度的分析方法和套路等</strong> <span id="more"></span></p><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#perf">性能工程</a></li><li><a href="#overview">响应速度概述</a></li><li><a href="#analysis">响应速度问题分析思路</a></li><li><a href="#series">系列文章</a></li><li><a href="#refs">参考文章</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>关于卡顿的文章可以参考这一篇 <a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a> ，ANR 的文章后续会介绍，本文主要是讲响应速度相关的基本原理</p><p>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</p><p><a id="perf"></a></p><h1 id="性能工程"><a href="#性能工程" class="headerlink" title="性能工程"></a>性能工程</h1><p>在介绍响应速度的原理之前，这里先放一段 &lt;**性能之巅**&gt; 这本书中对于性能的描述，具体来说就是方法论，非常贴合本文的主题，也强烈推荐各位搞性能优化的同学，把这本书作为手头常读的方法论书籍：</p><blockquote><h1 id="性能是充满挑战的"><a href="#性能是充满挑战的" class="headerlink" title="性能是充满挑战的"></a>性能是充满挑战的</h1><p>系统性能工程是一个充满挑战的领域，具体原因有很多，其中包括以下事实，系统性能是主观的、复杂的，而且常常是多问题并存的</p><h2 id="性能是主观的"><a href="#性能是主观的" class="headerlink" title="性能是主观的"></a>性能是主观的</h2><ol><li>技术学科往往是客观的，太多的业界人士审视问题非黑即白。在进行软件故障查找的时候，判断 bug 是否存在或 bug 是否修复就是这样。bug 的出现总是伴随着错误信息，错误信息通常容易解读，进而你就明白错误为什么会出现了</li><li>与此不同，性能常常是主观性的。开始着手性能问题的时候，对问题是否存在的判断都有可能是模糊的，在问题被修复的时候也同样，被一个用户认为是“不好”的性能，另一个用户可能认为是“好”的</li></ol><h2 id="系统是复杂的"><a href="#系统是复杂的" class="headerlink" title="系统是复杂的"></a>系统是复杂的</h2><ol><li>除了主观性之外，性能工程作为一门充满了挑战的学科，除了因为系统的复杂性，还因为对于性能，我们常常缺少一个明确的分析起点。有时我们只是从猜测开始，比如，责怪网络，而性能分析必须对这是不是一个正确的方向做出判断</li><li>性能问题可能出在子系统之间复杂的互联上，即便这些子系统隔离时表现得都很好。也可能由于连锁故障（<strong>cascading failure</strong>）出现性能问题，这指的是一个出现故障的组件会导致其他组件产生性能问题。要理解这些产生的问题，你必须理清组件之间的关系，还要了解它们是怎样协作的</li><li>瓶颈往往是复杂的，还会以意想不到的方式互相联系。修复了一个问题可能只是把瓶颈推向了系统里的其他地方，导致系统的整体性能并没有得到期望的提升。</li><li>除了系统的复杂性之外，生产环境负载的复杂特性也可能会导致性能问题。在实验室环境很难重现这类情况，或者只能间歇式地重现</li><li>解决复杂的性能问题常常需要全局性的方法。整个系统——包括自身内部和外部的交互——都可能需要被调查研究。这项工作要求有非常广泛的技能，一般不太可能集中在一人身上，这促使性能工程成为一门多变的并且充满智力挑战的工作</li></ol><h2 id="可能有多个问题并存"><a href="#可能有多个问题并存" class="headerlink" title="可能有多个问题并存"></a>可能有多个问题并存</h2><ol><li>找到一个性能问题点往往并不是问题本身，在复杂的软件中通常会有多个问题</li><li>性能分析的又一个难点：真正的任务不是寻找问题，而是辨别问题或者说是辨别哪些问题是最重要的</li><li>要做到这一点，性能分析必须量化（<strong>quantify</strong>）问题的重要程度。某些性能问题可能并不适用于你的工作负载或者只在非常小的程度上适用。理想情况下，你不仅要量化问题，还要估计每个问题修复后能带来的增速。当管理层审查工程或运维资源的开销缘由时，这类信息尤其有用。</li><li>有一个指标非常适合用来量化性能，那就是 延时（<strong>latency</strong>）</li></ol><p> – 以上几段内容摘录自 &lt;**性能之巅**&gt; </p></blockquote><p><strong>Systrace 系列文章如下</strong></p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li></ol><p><a id="overview"></a></p><h1 id="响应速度概述"><a href="#响应速度概述" class="headerlink" title="响应速度概述"></a>响应速度概述</h1><p><strong>响应速度</strong>是应用 App 性能的重要指标之一。响应慢通常表现为<strong>点击效果延迟</strong>、<strong>操作等待</strong>或<strong>白屏时间长</strong>等，主要场景包括：</p><ul><li><strong>应用启动场景，包括冷启动、热启动、温启动等</strong></li><li><strong>界面跳转场景，包括应用内页面跳转、App 之间跳转</strong></li><li><strong>其他非跳转的点击场景（开关、弹窗、长按、控件选择、单击、双击等）</strong></li><li><strong>亮灭屏、开关机、解锁、人脸识别、拍照、视频加载等场景</strong></li></ul><p>从原理上来说，响应速度场景往往是由一个 input 事件（以 Message 的形式给到需要处理的应用主线程）触发（比如点击、长按、电源键、指纹等），由一个或者多个 Message 的执行结束为结尾，而这些 Message 中一般都有关键的界面绘制相关的 Message 。衡量一个场景的响应速度，我们通常从事件触发开始计时，到应用处理完成计时结束，这一段时间就称为响应时间。</p><p>如下图所示，<strong>响应速度的问题，通常就是这些 Message 的某个执行超过预期（主观），导致最终完成的时间长于用户期待的时间</strong></p><p><img src="/images/XyGmCIpqfZxRFCs7.webp" alt="XyGmCIpqfZxRFCs7"></p><p>由于响应速度是一个比较主观的性能指标（而流畅度就是一个很精确的指标，掉一帧就是掉一帧），而且根据角色的不同，对这个性能指标的判定也不同，比如 Android 系统开发者和应用开发者以及测试同学，对 <strong>应用冷启动</strong> 的起点和终点就有不同的判定：</p><ol><li><strong>系统开发者</strong> 往往从 input 中断开始看，部分以应用第一帧为结束点（因为比较好计算），部分以应用加载完成为结束点（比较主观，除非结束点比较容易通过工具去判断），主要是以优化应用的整体性能为主，涉及到的方面就比较广，包括 input 事件传递、SystemServer、SurfaceFlinger、Kernel 、Launcher 等</li><li><strong>App 开发者</strong> 一般从 Application 的 onCreate 或者 attachContext 开始看，大部分以界面完全加载或者用户可操作为结束点，因为是自己的应用，结束点在代码里面可以主动加，主要还是以优化应用自身的启动速度为主，市面上讲启动速度优化的，大部分是讲这部分</li><li><strong>测试同学</strong> 则更多从用户的真实体验角度来看，以桌面点击应用图标且应用图标变色为第一帧，内容完全加载为结束点。测试过程一般使用 <strong>高速相机 + 自动化</strong>，通过<strong>机械手</strong>和<strong>图形识别技术</strong>，可以自动进行响应速度测试并抓取相关的测试数据</li></ol><p><a id="analysis"></a></p><h1 id="响应速度问题分析思路"><a href="#响应速度问题分析思路" class="headerlink" title="响应速度问题分析思路"></a>响应速度问题分析思路</h1><h2 id="分清起点和终点"><a href="#分清起点和终点" class="headerlink" title="分清起点和终点"></a>分清起点和终点</h2><p>分析响应速度，最重要的是要找到<strong>起点</strong>和<strong>终点</strong>，上一节讲到，不同角色的开发者，对这个性能指标的判定起点和终点都不一样；而且这个指标有很主观的成分，所以在开始的时候，就要跟各方来确定好起点和终点，具体的数值标准，下面一些手段可以帮助大家来确定</p><ol><li><strong>竞品分析</strong>。一般来说，响应速度这个指标都会有一个对标的竞品，竞品手机或者竞品 App，相同的条件下，竞品手机或者竞品 App 从点击到响应花费了多少时间，可以作为一个标准</li><li><strong>对比前一个版本</strong>。有时候系统进行大版本升级或者 App 进行版本迭代，那么上一个版本的数据就可以拿来作为标准进行对比</li></ol><p>一般来说，起点都比较好确定，无非是一个点击事件或者一个自定义的触发事件；而终点的确定就比较麻烦，比如如何确定一个复杂的 App （比如淘宝）启动完成的时间点，用 Systrace 的第一帧或者 Log 输出的 Displayed 时间或者 onWindowFocusChange 回调的时间显然是不准确的。目前市面上使用高速相机 + 图像识别来做是一个比较主流的做法</p><h2 id="响应速度常见问题"><a href="#响应速度常见问题" class="headerlink" title="响应速度常见问题"></a>响应速度常见问题</h2><h3 id="Android-系统自身原因导致响应慢"><a href="#Android-系统自身原因导致响应慢" class="headerlink" title="Android 系统自身原因导致响应慢"></a>Android 系统自身原因导致响应慢</h3><p>下面这些列举的是 Android 系统自身的原因，与 Android 机器的性能有比较大的关系，性能越差，越容易出现响应速度问题。下面就列出了 Android 系统原因导致的 App 响应速度出现问题的原因，以及这个时候 App 端在 Systrace 中的表现</p><ol><li><strong>CPU 频率不足</strong><ul><li><strong>App 端的表现</strong>：主线程处于 Running 状态，但是执行耗时变长</li></ul></li><li><strong>CPU 大小核调度：关键任务跑到了小核</strong><ul><li><strong>App 端的表现</strong>：Systrace 看主线程处于 Running 状态，但是执行耗时变长</li></ul></li><li><strong>SystemServer 繁忙，主要影响</strong><ol><li>响应 App 主线程 Binder 调用处理耗时<ul><li><strong>App 端的表现</strong>：Systrace 看主线程处于 Sleep 状态，在等待 Binder 调用返回</li></ul></li><li>应用启动过程逻辑处理耗时<ul><li><strong>App 端的表现</strong>：Systrace 看主线程处于 Sleep 状态，在等待 Binder 调用返回</li></ul></li></ol></li><li><strong>SurfaceFlinger 繁忙，主要影响应用的渲染线程的 dequeueBuffer、queueBuffer</strong><ul><li><strong>App 端的表现</strong>：Systrace 看应用渲染线程的 dequeueBuffer、queueBuffer 处于 Binder 等待状态</li></ul></li><li><strong><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">系统低内存</a>，低内存的时候，很大概率出现下面几种情况，都会对 SystemServer 和应用有影响</strong><ol><li>低内存的时候，有些应用会频繁被杀和启动，而应用启动时一个重操作，会占用 CPU 资源，导致前台 App 启动变慢<ul><li><strong>App 端的表现</strong>：Systrace 看应用主线程 Runnable 状态变多，Running 状态变少，整体函数执行耗时增加</li></ul></li><li>低内存的时候，, 很容易触发各个进程的 GC , , 用于内存回收的 HeapTaskDeamon、kswapd0 出现非常频繁<ul><li><strong>App 端的表现</strong>：Systrace 看应用主线程 Runnable 状态变多，Running 状态变少，整体函数执行耗时增加</li></ul></li><li>低内存会导致磁盘 IO 变多, 如果频繁进行磁盘 IO , 由于磁盘 IO 很慢, 那么主线程会有很多进程处于等 IO 的状态, 也就是我们经常看到的 Uninterruptible Sleep<ul><li><strong>App 端的表现</strong>：Systrace 看应用主线程 Uninterruptible Sleep 和 Uninterruptible Sleep - IO 状态变多，Running 状态变少，整体函数执行耗时增加</li></ul></li></ol></li><li><strong>系统触发温控频率被限制：由于温度过高，CPU 最高频率被限制</strong><ul><li><strong>App 端的表现</strong>：主线程处于 Running 状态，但是执行耗时变长</li></ul></li><li><strong>整机 CPU 繁忙：可能有多个高负载进程同时在运行，或者有单个进程负载过高跑满了 CPU</strong><ul><li><strong>App 端的表现</strong>：从 Systrace 来看，CPU 区域的任务非常满，所有的核心上都有任务在执行，App 的主线程和渲染线程多处于 Runnable 状态，或者频繁在 Runnable 和 Running 之间切换</li></ul></li></ol><h3 id="应用自身原因"><a href="#应用自身原因" class="headerlink" title="应用自身原因"></a>应用自身原因</h3><p>应用自身原因主要是应用启动时候的组件初始化、View 初始化、数据初始化耗时等，具体包括：</p><ol><li>Application.onCreate：应用自身的逻辑 + 三方 SDK 初始化耗时</li><li>Activity 的生命周期函数：onStart、onCreate、onResume 耗时</li><li>Services 的生命周期函数耗时</li><li>Broadcast 的 onReceive 耗时</li><li>ContentProvider 初始化耗时（注意已经被滥用）</li><li>界面布局初始化：measure、layout、draw 等耗时</li><li>渲染线程初始化：setSurface、queueBuffer、dequeueBuffer、Textureupload 等耗时</li><li>Activity 跳转：从 SplashActivity 到 MainActivity 耗时</li><li>应用向主线程 post 的耗时 Message 耗时</li><li>主线程或者渲染线程等待子线程数据更新耗时</li><li>主线程或者渲染线程等待子进程程数据更新耗时</li><li>主线程或者渲染线程等待网络数据更新耗时</li><li>主线程或者渲染线程 binder 调用耗时</li><li>WebView 初始化耗时</li><li>初次运行 JIT 耗时</li></ol><h2 id="响应速度问题分析套路（以-Systrace-为主）"><a href="#响应速度问题分析套路（以-Systrace-为主）" class="headerlink" title="响应速度问题分析套路（以 Systrace 为主）"></a>响应速度问题分析套路（以 Systrace 为主）</h2><ol><li>确认前提条件(老化,数据量、下载等)、操作步骤、问题现象，本地复现</li><li>需要明确测试标准<ol><li>启动时间的起点是哪里</li><li>启动时机的终点是哪里</li></ol></li><li>抓取所需的日志信息（Systrace、常规 log 等）</li><li>首先分析 <strong>Systrace</strong>，大概找出差异的点 <ol><li>首先查看应用耗时点，分析对比机差异，这里可以把应用启动阶段分成好几段来看，来对比分析是哪部分时间增加<ol><li>Application 创建</li><li>Activity 创建</li><li>第一个 doFrame</li><li>后续内容加载</li><li>应用自己的 Message</li></ol></li><li>分析应用耗时的点<ol><li>是否某一段方法自身执行耗时比较久（Running 状态） –&gt; <strong>应用自身问题</strong></li><li>主线程是否有大段 Running 状态，但是底下没有任何堆栈  –&gt; <strong>应用自身问题，加 TraceTag 或者使用 TraceView 来找到对应的代码逻辑</strong></li><li>是否在等 Binder 耗时比较久（Sleep 状态） –&gt; <strong>检测 Binder 服务端，一般是 SystemServer</strong> </li><li>是否在等待子线程返回数据（Sleep 状态） –&gt; <strong>应用自身问题，通过查看 wakeup 信息，来找到依赖的子线程</strong></li><li>是否在等待子进程返回数据（Sleep 状态） –&gt; <strong>应用自身问题，通过查看 wakeup 信息，来找到依赖的子进程或者其他进程(一般是 ContentProvider 所在的进程)</strong></li><li>是否有大量的 Runnable  –&gt; <strong>系统问题，查看 CPU 部分，看看是否已经跑满</strong></li><li>是否有大量的 IO 等待（Uninterruptible Sleep | WakeKill - Block I&#x2F;O） –&gt; <strong>检查系统是否已经<a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/#%E5%BD%B1%E5%93%8D%E4%B8%BB%E7%BA%BF%E7%A8%8B-IO-%E6%93%8D%E4%BD%9C">低内存</a></strong></li><li>RenderThread 是否执行 dequeueBuffer 和 queueBuffer 耗时 –&gt; <strong>查看 SurfaceFlinger</strong></li></ol></li><li>如果分析是系统的问题，则根据上面耗时的点，查看系统对应的部分，一般情况要优先查看系统是否异常，参考上面列出的的系统原因，主要看下面四个区域（Systrace）<ol><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU/">Kernel 区域</a><ol><li>查看关键任务是否跑在了小核 –&gt; <strong>一般小核是 0-3（也有特例），如果启动时候的关键任务跑到了小核，执行速度也会变慢</strong></li><li>查看频率是否没有跑满 –&gt; <strong>表现是核心频率没有达到最大值，比如最大值是 2.8Ghz，但是只跑到了 1.8Ghz，那么可能是有问题的</strong></li><li>查看 CPU 使用率，是否已经跑满了 –&gt; <strong>表现是 CPU 区域八个核心上，任务和任务之间没有空隙</strong></li><li>查看是否<a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">低内存</a><ol><li><strong>应用进程状态有大量的 Uninterruptible Sleep | WakeKill - Block I&#x2F;O</strong></li><li><strong>HeapTaskDeamon 任务执行频繁</strong></li><li><strong>kswapd0 任务执行频繁</strong></li></ol></li></ol></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">SystemServer 进程区域</a><ol><li>input 事件读取和分发是否有异常 –&gt; <strong>表现是 input 事件传递耗时，比较少见</strong></li><li>binder 执行是否耗时 –&gt; <strong>表现是 SystemServer 对应的 Binder 执行代码逻辑耗时</strong></li><li>binder 等 am、wm 锁是否耗时–&gt; <strong>表现是 SystemServer 对应的 Binder 都在等待锁，可以通过 wakeup 信息跟踪等锁情况，分析等锁是不是由于应用导致的</strong></li><li>是否有应用频繁启动或者被杀 –&gt; <strong>在 Systrace 中查看 startProcess，或者查看 Event Log</strong></li></ol></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">SurfaceFlinger 进程区域</a><ol><li>dequeueBuffer 和 queueBuffer 是否执行耗时 –&gt; <strong>表现是 SurfaceFlinger  的对应的 Binder 执行 dequeueBuffer 和 queueBuffer 耗时</strong></li><li>主线程是否执行耗时 –&gt; <strong>表现是 SurfaceFlinger 主线程耗时，可能是在执行其他的任务</strong></li></ol></li><li>Launcher 进程区域（冷热启动场景）<ol><li>Launcher 进程处理点击事件是否耗时 –&gt; <strong>表现在处理 input 事件耗时</strong></li><li>Launcher 自身 pause 是否耗时 –&gt; <strong>表现在执行 onPause 耗时</strong></li><li>Launcher 应用启动动画是否耗时或者卡顿 –&gt; <strong>表现在动画耗时或者卡顿</strong></li></ol></li></ol></li></ol></li><li>初步分析有怀疑的点之后<ol><li>如果是系统的原因，首先需要看应用自身是否能规避，如果不能规避，则转给系统来处理</li><li>如果是应用自身的原因，可以使用 TraceView（AS 自带的 CPU Profiler）、Simple Perf 等继续查看更加详细的函数调用信息，也可以使用 <a href="https://github.com/Gracker/TraceFix">TraceFix 插件</a>，插入更多的 TraceTag 之后，重新抓取 Systrace 来对比分析</li></ol></li><li>问题可能有很多个原因<ol><li>首先要把影响最大的因素找出来优化，影响比较小的因素可以先忽略</li><li>有些问题需要系统的配合才能解决，这时候需要跟系统一起进行调优（比如各大 App 厂商就会有专门跟手机厂商打交道的，手机厂商会以 SDK 的形式，暴露部分系统接口给 App 来使用，比如 Oppo 、华为、Vivo 等）</li><li>有些问题影响很小或者无解，这时候需要跟测试同学沟通清楚</li><li>有些问题是重复问题或不同平台的相同，可以在 Bug 库中搜索是否有案例</li></ol></li></ol><p>本篇文章主要是一个响应速度基础知识方面的一个普及，其中涉及到大量的系统知识，不熟悉的同学可以跟着 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a> 过一下</p><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li><li><a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列-放个链接在这里方便大家直接点过去</a></li></ol><p><a id="refs"></a></p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ol><li><a href="https://www.jianshu.com/p/37370c1d17fc">Android 应用启动全流程分析</a></li><li><a href="https://juejin.cn/post/6907493155659055111">探究 | App Startup 真的能减少启动耗时吗</a></li><li><a href="https://blog.csdn.net/guolin_blog/article/details/108026357">Jetpack 新成员，App Startup 一篇就懂</a></li><li><a href="https://developer.android.google.cn/topic/libraries/app-startup">App Startup</a></li><li><a href="https://www.androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a></li><li><a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/android_application_profiling.md">Android application profiling</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在讨论 Android 性能问题的时候，&lt;strong&gt;卡顿&lt;/strong&gt;、&lt;strong&gt;响应速度&lt;/strong&gt;、&lt;strong&gt;ANR&lt;/strong&gt; 这三个性能相关的知识点通常会放到一起来讲，因为引起卡顿、响应慢、ANR 的原因类似，只不过根据重要程度，被人为分成了卡顿、响应慢、ANR 三种，所以我们可以定义广义上的卡顿，包含了卡顿、响应慢和 ANR 三种，所以如果用户反馈说手机卡顿或者 App 卡顿，大部分情况下都是广义上的卡顿，需要搞清楚，到底出现了哪一种问题&lt;/p&gt;
&lt;p&gt;如果是动画播放卡顿、列表滑动卡顿这种，我们一般定义为 &lt;strong&gt;狭义的卡顿&lt;/strong&gt;，对应的英文描述我觉得应该是 &lt;strong&gt;Jank&lt;/strong&gt;；如果是应用启动慢、亮灭屏慢、场景切换慢，我们一般定义为 &lt;strong&gt;响应慢&lt;/strong&gt; ，对应的英文描述我觉得应该是 &lt;strong&gt;Slow&lt;/strong&gt; ；如果是发生了 ANR，那就是 &lt;strong&gt;应用无响应问题&lt;/strong&gt; 。三种情况所对应的分析方法和解决方法不太一样，所以需要分开来讲&lt;/p&gt;
&lt;p&gt;另外在 App 或者厂商内部，&lt;strong&gt;卡顿&lt;/strong&gt;、&lt;strong&gt;响应速度&lt;/strong&gt;、&lt;strong&gt;ANR&lt;/strong&gt; 这几个性能指标都是有单独的标准的，比如 &lt;strong&gt;掉帧率&lt;/strong&gt;、&lt;strong&gt;启动速度&lt;/strong&gt;、&lt;strong&gt;ANR 率&lt;/strong&gt;等，所以针对这些性能问题的分析和优化能力，对开发者来说就非常重要了&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文是响应速度系列的第一篇，主要是讲&lt;/strong&gt;响应速度&lt;strong&gt;相关的理论知识，包括性能工程概述、响应速度涉及到的知识点、响应速度的分析方法和套路等&lt;/strong&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</title>
    <link href="https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/"/>
    <id>https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/</id>
    <published>2021-04-24T11:55:12.000Z</published>
    <updated>2026-02-07T05:17:47.868Z</updated>
    
    <content type="html"><![CDATA[<p>不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明</p><ol><li>对手机用户来说，卡顿包含了很多场景，比如在 <strong>滑动列表的时候掉帧</strong>、<strong>应用启动白屏过长</strong>、<strong>点击电源键亮屏慢</strong>、<strong>界面操作没有反应然后闪退</strong>、<strong>点击图标没有响应</strong>、<strong>窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿</strong> 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意</li><li>对开发人员来说，上面的场景包括了 <strong>流畅度</strong>（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、<strong>响应速度</strong>（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、<strong>稳定性</strong>（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要</li><li>在技术上来说，<strong>流畅度、响应速度、稳定性</strong>（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制</li><li>流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍</li><li>流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题<span id="more"></span></li></ol><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#frame-colors">Systrace 的 Frame 颜色是什么意思？</a></li><li><a href="#no-red">没有红帧就没有掉帧？</a></li><li><a href="#why-wait">主线程为何要等待渲染线程？</a></li><li><a href="#why-hold">为什么一直滑动不松手，就不会卡？</a></li><li><a href="#metrics">如果不卡，怎么衡量性能好坏？</a></li><li><a href="#why-record">为什么录屏看不出来卡顿？</a></li><li><a href="#series">系列文章</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p>Systrace 系列文章如下</p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下上面列出的 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</p><p><a id="frame-colors"></a></p><h1 id="Systrace-的-Frame-颜色是什么意思？"><a href="#Systrace-的-Frame-颜色是什么意思？" class="headerlink" title="Systrace 的 Frame 颜色是什么意思？"></a>Systrace 的 Frame 颜色是什么意思？</h1><p>这里的 Frame 标记指的是应用主线程上面那个圈，共有三个颜色，每一帧的耗时不同，则标识的颜色不同</p><p>点击这个小圆圈就可以看到这一帧所对应的主线程+渲染线程（会以高亮显示，其他的则变灰显示）</p><h2 id="绿帧"><a href="#绿帧" class="headerlink" title="绿帧"></a>绿帧</h2><p>绿帧是最常见的帧，表示这一帧在一个 Vsync 周期里面完成</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2022.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2022.webp"></p><h2 id="黄帧"><a href="#黄帧" class="headerlink" title="黄帧"></a>黄帧</h2><p>黄帧表示这一帧耗时超过1个 Vsync 周期，但是小于 2 个 Vsync 周期。黄帧的出现表示这一帧可能存在性能问题，可能会导致卡顿情况出现</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2023.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2023.webp"></p><h2 id="红帧"><a href="#红帧" class="headerlink" title="红帧"></a>红帧</h2><p>红帧表示这一帧耗时超过 2 个 Vsync 周期，红帧的出现表示这一帧可能存在性能问题，大概率会导致卡顿情况出现</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2024.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2024.webp"></p><p><a id="no-red"></a></p><h1 id="没有红帧就没有掉帧？"><a href="#没有红帧就没有掉帧？" class="headerlink" title="没有红帧就没有掉帧？"></a>没有红帧就没有掉帧？</h1><p>不一定，判断是否掉帧要看 SurfaceFlinger，而不是看 App ，这部分需要有 <a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/">https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/</a> 这篇文章的基础</p><h2 id="出现黄帧但是不掉帧的情况"><a href="#出现黄帧但是不掉帧的情况" class="headerlink" title="出现黄帧但是不掉帧的情况"></a>出现黄帧但是不掉帧的情况</h2><p>如上所述，红帧和黄帧都表示这一帧存在性能问题，黄帧表示这一帧耗时超过一个 Vsync 周期，但是由于 Android Triple Buffer（现在的高帧率手机会配置更多的 Buffer）的存在，就算 App 主线程这一帧超过一个 Vsync 周期，也会由于多 Buffer 的缓冲，使得这一帧并不会出现掉帧</p><h2 id="出现黄帧且掉帧的情况"><a href="#出现黄帧且掉帧的情况" class="headerlink" title="出现黄帧且掉帧的情况"></a>出现黄帧且掉帧的情况</h2><p>这次分析的 Systrace（见附件），就是没有红帧只有黄帧，连续出现两个黄帧，第一个黄帧导致了卡顿，而第二个黄帧则没有</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2025.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2025.webp"></p><p><a id="why-wait"></a></p><h1 id="主线程为何要等待渲染线程？"><a href="#主线程为何要等待渲染线程？" class="headerlink" title="主线程为何要等待渲染线程？"></a>主线程为何要等待渲染线程？</h1><p>还是这个 Systrace（附件） 中的情况，第一个疑点处两个黄帧，可以看到第二个黄帧的主线程耗时很久，这时候不能单纯以为是主线程的问题（因为是 Sleep 状态）</p><p>如下图所示，是因为前一帧的渲染线程超时，导致这一帧的渲染线程任务在排队等待，如（<a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/</a>）这篇文章里面的流程，主线程是需要等待渲染线程执行完 syncFrameState 之后 unblockMainThread，然后才能继续。</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2026.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2026.webp"></p><p><a id="why-hold"></a></p><h1 id="为什么一直滑动不松手，就不会卡？"><a href="#为什么一直滑动不松手，就不会卡？" class="headerlink" title="为什么一直滑动不松手，就不会卡？"></a>为什么一直滑动不松手，就不会卡？</h1><p>还是这个场景（桌面左右滑动），卡顿是发生在松手之后的，如果一直不松手，那么就不会出现卡顿，这是为什么？</p><p>如下图，可以看到，如果不松手，cpu 这里会有一个持续的 Boost，且此时 RenderThread 的任务都跑在 4-6 这三个大核上面，没有跑到小核，自然也不会出现卡顿情况</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2027.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2027.webp"></p><p>这一段 Boost 的 Timeout 是 120 ms，具体的配置每个机型都不一样，熟悉 PerfLock 的应该知道，这里就不多说了</p><p><a id="metrics"></a></p><h1 id="如果不卡，怎么衡量性能好坏？"><a href="#如果不卡，怎么衡量性能好坏？" class="headerlink" title="如果不卡，怎么衡量性能好坏？"></a>如果不卡，怎么衡量性能好坏？</h1><p>如果这个场景不卡，那么我们怎么衡量两台不同的机器在这个场景下的性能呢？</p><p>可以使用 <code>adb shell dumpsys gfxinfo</code> ，使用方法如下</p><ol><li>首先确定要测试的包名，到 App 界面准备好操作</li><li>执行2-3次 <code>adb shell dumpsys gfxinfo com.miui.home framestats reset</code> ，这一步的目的是清除历数据</li><li>开始操作（比如使用命令行左右滑动，或者自己用手指滑动）</li><li>操作结束后，执行 <code>adb shell dumpsys gfxinfo com.miui.home framestats</code> 这时候会有一堆数据输出，我们只需要关注其中的一部分数据即可</li><li>重点关注<ol><li><strong>Janky frames</strong> ：超过 Vsync 周期的 Frame，不一定出现卡顿</li><li><strong>95th percentile</strong> ：95% 的值</li><li><strong>HISTOGRAM</strong> ： 原始数值</li><li><strong>PROFILEDATA</strong> ：每一帧的详细原始数据</li></ol></li></ol><p>我们拿这个场景，跟 Oppo Reno 5 来做对比，只取我们关注的一部分数据</p><h2 id="小米-90-fps"><a href="#小米-90-fps" class="headerlink" title="小米 - 90 fps"></a>小米 - 90 fps</h2><p><img src="/images/16192690407230.jpg" alt="xiaomi"></p><h2 id="Oppo-90-fps"><a href="#Oppo-90-fps" class="headerlink" title="Oppo - 90 fps"></a>Oppo - 90 fps</h2><p><img src="/images/16192690109378.jpg" alt="oppo"></p><p>下面是一些对比，可以看到小米在桌面滑动这个场景，性能是要弱于 Oppo 的</p><ol><li>Janky frames<ol><li>小米：27 (35.53%)</li><li>Oppo：1 (1.11%)</li></ol></li><li>95th percentile<ol><li>小米：18ms</li><li>Oppo：5ms</li></ol></li></ol><p>另外 GPU 的数据也比较有趣，小米的高通 865 配的 GPU 要比 Reno 5 Pro 配的 GPU 要强很多，所以 GPU 的数据小米要比 Reno 5 Pro 要好，也可以推断出这个场景的瓶颈在 CPU 而不是在 GPU</p><p><a id="why-record"></a></p><h1 id="为什么录屏看不出来卡顿？"><a href="#为什么录屏看不出来卡顿？" class="headerlink" title="为什么录屏看不出来卡顿？"></a>为什么录屏看不出来卡顿？</h1><p>可能有下面几种情况</p><ol><li>如果使用的手机是大于 60 fps 的，比如小米这个是 90 fps，而录屏的时候选择 60 fps 的录屏，则录屏文件会看不出来卡顿 （使用其他手机录像也会有这个问题）</li><li>如果录屏是以高帧率（90fps）录制的，但是播放的时候是使用低帧率（60fps）的设备观看的（小米就是这个情况），也不会看出来卡顿，比如用 90 fps 的规格录制视频，但是在手机上播放的时候，系统会自动切换到 60 fps, 导致看不出来卡顿</li></ol><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>附件已经上传到了 Github 上，可以自行下载：<a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action">https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action</a></p><ol><li>xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件，这次案例主要是分析这个 Systrace 文件</li><li>xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件</li><li>oppo_launcher_scroll.zip ：对比文件</li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对手机用户来说，卡顿包含了很多场景，比如在 &lt;strong&gt;滑动列表的时候掉帧&lt;/strong&gt;、&lt;strong&gt;应用启动白屏过长&lt;/strong&gt;、&lt;strong&gt;点击电源键亮屏慢&lt;/strong&gt;、&lt;strong&gt;界面操作没有反应然后闪退&lt;/strong&gt;、&lt;strong&gt;点击图标没有响应&lt;/strong&gt;、&lt;strong&gt;窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿&lt;/strong&gt; 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意&lt;/li&gt;
&lt;li&gt;对开发人员来说，上面的场景包括了 &lt;strong&gt;流畅度&lt;/strong&gt;（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、&lt;strong&gt;响应速度&lt;/strong&gt;（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、&lt;strong&gt;稳定性&lt;/strong&gt;（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要&lt;/li&gt;
&lt;li&gt;在技术上来说，&lt;strong&gt;流畅度、响应速度、稳定性&lt;/strong&gt;（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而 Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 流畅性实战 2 ：案例分析 - MIUI 桌面滑动卡顿分析</title>
    <link href="https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/"/>
    <id>https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/</id>
    <published>2021-04-24T11:55:07.000Z</published>
    <updated>2026-02-07T05:17:47.868Z</updated>
    
    <content type="html"><![CDATA[<p>不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明</p><ol><li>对手机用户来说，卡顿包含了很多场景，比如在 <strong>滑动列表的时候掉帧</strong>、<strong>应用启动白屏过长</strong>、<strong>点击电源键亮屏慢</strong>、<strong>界面操作没有反应然后闪退</strong>、<strong>点击图标没有响应</strong>、<strong>窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿</strong> 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意</li><li>对开发人员来说，上面的场景包括了 <strong>流畅度</strong>（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、<strong>响应速度</strong>（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、<strong>稳定性</strong>（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要</li><li>在技术上来说，<strong>流畅度、响应速度、稳定性</strong>（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制</li><li>流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍</li><li>流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而  Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题<span id="more"></span></li></ol><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#steps">Systrace 分析卡顿问题的套路</a></li><li><a href="#case">使用 Systrace 分析卡顿问题的案例</a></li><li><a href="#case-desc">案例说明</a></li><li><a href="#triple-buffer">Triple Buffer 在这个场景发挥了什么作用？</a></li><li><a href="#series">系列文章</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><strong>Systrace 系列文章如下</strong></p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下上面列出的 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</p><p><a id="steps"></a></p><h1 id="Systrace-分析卡顿问题的套路"><a href="#Systrace-分析卡顿问题的套路" class="headerlink" title="Systrace 分析卡顿问题的套路"></a>Systrace 分析卡顿问题的套路</h1><p>使用 Systrace 分析卡顿问题，我们一般的流程如下</p><ol><li><p><strong>复现卡顿的场景，抓取 Systrace，可以用 shell 或者手机自带的工具来抓取</strong></p></li><li><p><strong>双击抓出来的 trace.html 直接在 Chrome 中打开 Systrace 文件</strong></p><ol><li>如果不能直接打开，可以在 Chrome 中输入 chrome:&#x2F;&#x2F;tracing&#x2F;，然后把 Systrace 文件拖到里面就可以打开</li><li>或者使用 Perfetto View 中的 Open With Legacy UI 打开</li></ol></li><li><p><strong>分析卡顿问前，我们需要了解问题发生的背景，以提高分析 Systrace 的效率</strong></p><ol><li>用户（或者测试）的操作流程</li><li>卡顿复现概率</li><li>竞品机器是否也有同样的卡顿问题</li></ol></li><li><p><strong>分析问题之前或者分析的过程中，也可以通过检查 Systrace 来了解一些基本的信息</strong></p><ol><li>CPU 频率、架构、Boost 信息等</li><li>是否触发温控：表现为cpu 频率被压低</li><li>是否是高负载场景：表现为 cpu 区域任务非常满</li><li>是否是低内存场景：表现为 lmkd 进程繁忙，App 进程的 HeapTaskDeamon 耗时，有很多的 Block io</li></ol></li><li><p><strong>定位 App 进程在 Systrace 中的位置</strong></p><ol><li><p><strong>打开 Systrace 后，首先要首先要看的就是 App 进程，主要是 App 的主线程和渲染线程</strong>，找到 Systrace 中每一帧耗时的部分，比如下面这种，可以看到 App 的 UI Thread 的红框部分，耗时 110ms，明显是不正常的（这个案例是 Bilibili 列表滑动卡顿）<img src="/images/android-systrace-smooth-in-action-2/image-20220228231805317.webp" alt="image-20220228231805317"></p></li><li><p>事实上，所有超过一个 Vsync 周期的 doFrame 耗时（即大家看到的黄帧和红帧），我们都需要去看一下是否真的发生的掉帧，就算没有掉帧，也要看一下原因，比如下面这个</p><p><img src="/images/android-systrace-smooth-in-action-2/image-20220228231830063.webp" alt="image-20220228231830063"></p></li><li><p>Vsync 周期与刷新率的对照</p><ol><li>60fps 对应的 Vsync 周期是 16.6ms</li><li>90fps 对应的 Vsync 周期是 11.1ms</li><li>120fps 对应的 Vsync 周期是 8.3ms</li></ol></li></ol></li><li><p><strong>分析 SurfaceFlinger 进程的主线程和 Binder 线程</strong></p><ol><li><p>由于多个 Buffer 缓冲机制存在，App 主线程和渲染线程，有时候即使超过一个 Vsync 周期，也不一定会出现卡顿，所以这里我们需要看SurfaceFlinger 进程的主线程，来确认是否真的发生了卡顿 ，比如上面 3.1 部分的图，App 主线程耗时 110 ms，对应的 SurfaceFlinger 主线程如下</p><p><img src="/images/android-systrace-smooth-in-action-2/image-20220228231855706.webp" alt="image-20220228231855706"></p></li><li><p>Systrace 中的 SurfaceFlinger 进程区域，对应的 App 的 Buffer 个数也是空的</p><p><img src="/images/android-systrace-smooth-in-action-2/image-20220228231914525.webp" alt="image-20220228231914525"></p></li></ol></li><li><p><strong>从整机角度分析和 Binder 调用分析（不涉及可以不用看）</strong></p><ol><li>上面的案例，可以很容易就看到是 App 自身执行耗时，那么只需要把耗时的部分涉及到的 View 找到，进行代码或者设计方面的优化就可以了</li><li>有时候 App 进程的主线程会出现大量的 Runnable 或者 Binder 调用耗时，也会导致 App 出现卡顿，这时候就需要分析整机问题，要看具体是什么原因导致大量的 Runnable 或者 Binder 调用耗时</li></ol></li></ol><p>按照这个流程分析之后，需要再反过来看各个进程，把各个线索联系起来，推断最有可能的原因</p><p>App 掉帧的原因非常多，有 APP 本身的问题，有系统原因导致卡顿的，也有硬件层的、整机卡的，这个可以参考下面四篇文章，不同的卡顿原因在 Systrace 中会有不同的表现</p><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li><li><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">Android 中的卡顿丢帧原因概述 - 低内存篇</a></li></ol><p><a id="case"></a></p><h1 id="使用-Systrace-分析卡顿问题的案例"><a href="#使用-Systrace-分析卡顿问题的案例" class="headerlink" title="使用 Systrace 分析卡顿问题的案例"></a>使用 Systrace 分析卡顿问题的案例</h1><p>Systrace 作为分析卡顿问题的第一手工具，给开发者提供了一个从手机全局角度去看问题的方式，通过 Systrace 工具进行分析，我们可以大致确定卡顿问题的原因：是系统导致的还是应用自身的问题</p><p>当然 Systrace 作为一个工具，再进行深入的分析的时候就会有点力不从心，需要配合 TraceView + 源码来进一步定位和解决问题，最后再使用 Systrace 进行验证</p><p>所以本文更多地是讲如何发现和分析卡顿问题，至于如何解决，就需要后续自己寻找合适的解决方案了，比如对比竞品的 Systrace 表现、优化代码逻辑、优化系统调度、优化布局等</p><p><a id="case-desc"></a></p><h1 id="案例说明"><a href="#案例说明" class="headerlink" title="案例说明"></a>案例说明</h1><p>个人在使用小米 10 Pro 的时候，在桌面滑动这个最常用的场景里面，总会有一种卡顿的感觉，10 Pro 是 90Hz 的屏幕，FPS 也是 90，所以一旦出现卡顿，就会有很明显的感觉(个人对这个也比较敏感)。之前没怎么关注，在升级 12.5 之后，这个问题还是没有解决，所以我想看看到底是怎么回事</p><p>抓了 Systrace 之后分析发现，这个卡顿场景是一个非常好的案例，所以把这个例子拿出来作为流畅度的一个实战分享</p><p>建议大家下载附件中的 Systrace，对照文章食用最佳</p><blockquote><ol><li>鉴于卡顿问题的影响因素比较多，所以在开始之前，我把本次分析所涉及的硬件、软件版本沟通清楚，如果后续此场景有优化，此文章也不会进行修改，以文章附件中的 Systrace 为准</li><li>硬件：小米 10 Pro</li><li>软件：MIUI 12.5.3 稳定版</li><li>小米桌面版本：RELEASE-4.21.11.2922-03151646</li></ol></blockquote><h2 id="从-Input-事件开始"><a href="#从-Input-事件开始" class="headerlink" title="从 Input 事件开始"></a>从 Input 事件开始</h2><p>这次抓的 Systrace 我只滑动了一次，所以比较好定位，滑动的 input 事件由一个 Input Down 事件 + 若干个 Input Move 事件 + 一个 Input Up 事件组成</p><p>在 Systrace 中，SystemServer 中的 InputDispatcher 和 InputReader 线程都有体现，我们这里主要看在 App 主线程中的体现</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%208.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%208.webp"></p><p>如上图，App 主线程上的 deliverInputEvent 标识了应用处理 input 事件的过程，input up 之后，就进入了 Fling 阶段，这部分的基础知识可以查看下面这两篇文章</p><ol><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/</a></li><li><a href="https://www.androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/">https://www.androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/</a></li></ol><h2 id="分析主线程"><a href="#分析主线程" class="headerlink" title="分析主线程"></a>分析主线程</h2><p>由于这次卡顿主要是松手之后才出现的，所以我们主要看 Input Up 之后的这段</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%209.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%209.webp"></p><p>主线程上面的 Frame 有颜色进行标注，一般有绿、黄、红三种颜色，上面的 Systrace 里面，没有红色的帧，只有绿色和黄色。那么黄色就一定是卡顿么？红色就一定是卡顿么？其实不一定，单单通过主线程，我们并不能确定是否卡顿，这个在下面会讲</p><p>从主线程我们没法确定是否发生了卡顿，我们找出了三个可疑的点，接下来我们看一下 RenderThread</p><h2 id="分析渲染线程"><a href="#分析渲染线程" class="headerlink" title="分析渲染线程"></a>分析渲染线程</h2><p>放大第一个可疑点，可以看到，这一帧总耗时在 19ms， RenderThread 耗时 16ms，且 RenderThread 的 cpu 状态都是 running（绿色），那么这一帧这么耗时的原因大概率是下面两个原因导致的：</p><ol><li>RenderThread 本身耗时，任务比较繁忙</li><li>RenderThread 的任务受 CPU 影响（可能是频率低了、或者是跑到小核了）</li></ol><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2010.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2010.webp"></p><p>由于只是可疑点，所以我们先不去看 cpu 相关的，先查看 SurfaceFlinger 进程，确定这里有卡顿发生</p><h2 id="分析-SurfaceFlinger"><a href="#分析-SurfaceFlinger" class="headerlink" title="分析 SurfaceFlinger"></a>分析 SurfaceFlinger</h2><p>对于 Systrace 中 SurfaceFlinger 部分解读不熟悉的可以先预习一下这篇文章 <a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/</a></p><p>这里我们主要看两个点</p><ol><li>App 对应的 BufferQueue 的 Buffer 情况。通过这个我们可以知道在 SurfaceFlinger 端，App 是否有可用的 Buffer 提供给 SurfaceFlinger 进行合成</li><li>SurfaceFlinger 主线程的合成情况。通过查看 SurfaceFlinger 在 sf-vsync 到来的时候是否进行了合成工作，就可以判断这一帧是否出现了卡顿。</li></ol><p><strong>判断是否卡顿的标准如下</strong></p><ol><li><p>如果 SurfaceFlinger 主线程没有合成任务，而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作，<strong>但是对应的 App 的 BufferQueue 里面没有可用的 Buffer，那么说明这一帧卡了 — 卡顿出现</strong><br>这种情况如下图所示（也是上图中第一个疑点所在的位置）</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2011.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2011.webp"></p></li><li><p>如果 SurfaceFlinger 进行了合成，而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作，<strong>但是对应的 App 的 BufferQueue 里面没有可用的 Buffer，那么这一帧也是卡了</strong>，之所以 SurfaceFlinger 会正常合成，是因为有其他的 App 提供了可用来合成的 Buffer <strong>— 卡顿出现</strong><br>这种情况如下图所示（也在附件的 Systrace 里面）<br><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2012.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2012.webp"></p></li><li><p>如果 SurfaceFlinger 进行了合成，而且 App 在这一个 Vsync 周期(vsync-app)进行了正常的工作，而且对应的 App 的 BufferQueue 里面有可用的 Buffer，那么这一帧就会正常合成，此时没有卡顿出现 <strong>— 正常情况</strong><br>正常情况如下，作为对比还是贴上来方便大家对比<br><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2013.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2013.webp"></p></li></ol><p>回到本例的第一个疑点的地方，我们通过 SurfaceFlinger 端的分析，发现这一帧确实是掉了，原因是 App 没有准备好可用的 Buffer 供 SurfaceFlinger 来合成，那么接下来就需要看为什么这一帧 App 没有可用的 Buffer 给到 SurfaceFlinger</p><h2 id="回到渲染线程"><a href="#回到渲染线程" class="headerlink" title="回到渲染线程"></a>回到渲染线程</h2><p>上面我们分析这一帧所对应的 MainThread + RenderThread 耗时在 19ms，且 RenderThread 耗时就在 16ms，那么我们来看 RenderThread 的情况</p><p>出现这种情况主要是有下面两个原因</p><ol><li>RenderThread 本身耗时，任务比较繁忙</li><li>RenderThread 的任务受 CPU 影响（可能是频率低了、或者是跑到小核了）</li></ol><p>但是桌面滑动这个场景，负载并不高，且松手之后并没有多余的操作，View 更新之类的，本身耗时比前一帧多了将近 3 倍，可以推断不是自身负载加重导致的耗时</p><p>那么就需要看此时的 RenderThread 的 cpu 情况：</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2014.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2014.webp"></p><p>既然在 Running 情况，我们就去 CPU Info 区域查看这一段时间这个任务的调度情况</p><h2 id="分析-CPU-区域的信息"><a href="#分析-CPU-区域的信息" class="headerlink" title="分析 CPU 区域的信息"></a>分析 CPU 区域的信息</h2><p>查看 CPU （Kernel 区域，这部分的基础知识可以查看 <a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Android Systrace 基础知识 - CPU Info 解读</a> 和 <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Android Systrace 基础知识 – 分析 Systrace 预备知识</a>）这两篇文章</p><p>回到这个案例，我们可以看到 App 对应的 RenderThread 大部分跑在 cpu 2 和 cpu 0 上，也就是小核上（这个机型是高通骁龙 865，有四个小核+3 个大核+1 个超大核）</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2015.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2015.webp"></p><p>其此时对应的频率也已经达到了小核的最高频率（1.8Ghz）</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2016.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2016.webp"></p><p>且此时没有 cpu boost 介入</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2017.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2017.webp"></p><p>那么这里我们猜想，之所以这一帧 RenderThread 如此耗时，是因为小核就算跑满了，也没法在这么短的时间内完成任务</p><p>那么接下来要验证我们的猜想，需要进行下面两个步骤</p><ol><li>对比其他正常的帧，是否有跑在小核的。如果有且没有出现掉帧，那么说明我们的猜想是错误的</li><li>对比其他几个异常的帧，看看掉帧的原因是否也是因为 RenderThread 任务跑到了小核导致的。如果不是，那么就需要做其他的假设猜想</li></ol><p>在用同样的流程分析了后面几个掉帧之后，我们发现</p><ol><li>对比其他正常的帧，没有在小核跑的，包括掉帧后的下一帧，调度器马上把 RenderThread 摆到了大核，没有出现连续掉帧的情况</li><li>对比其他几个异常的帧，<strong>都是由于 RenderThread 跑到了小核，但是小核的性能不足导致 RenderThread 执行耗时，最终引起卡顿</strong></li></ol><p>至此，这一次的卡顿分析我们就找到了原因：<strong>RenderThread 掉到了小核</strong></p><p>至于 RenderThread 的任务为啥跑着跑着就掉到了小核，这个跟调度器是有关系的，大小核直接的调度跟任务的负载有关系，任务从大核掉到小核、或者从小核迁移到大核，调度器这边都是有参数和算法来控制的，所以后续的优化可能需要从这方面去入手</p><ol><li>调整大小核迁移的阈值参数或者修改调度器算法</li><li>参考竞品表现，看看竞品在这个场景的性能指标，调度情况等，分析竞品可能使用的策略</li></ol><p><a id="triple-buffer"></a></p><h1 id="Triple-Buffer-在这个场景发挥了什么作用？"><a href="#Triple-Buffer-在这个场景发挥了什么作用？" class="headerlink" title="Triple Buffer 在这个场景发挥了什么作用？"></a>Triple Buffer 在这个场景发挥了什么作用？</h1><p>在 <a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/#Triple-Buffer-%E7%9A%84%E4%BD%9C%E7%94%A8">Triple-Buffer-的作用</a> 这篇文章，讲到了 Triple Buffer 几个作用</p><ol><li>缓解掉帧</li><li>减少主线程和渲染线程等待时间</li><li>降低 GPU 和 SurfaceFlinger 瓶颈</li></ol><p>那么在桌面滑动卡顿这个案例里面，Triple Buffer 发挥了什么作用呢？结论是：有的场景没有发挥作用，反而有副作用，导致卡顿现象更明显，下面是分析流程</p><p>可以看文章中 Triple Buffer <strong>缓解掉帧</strong> 的原理：</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2018.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2018.webp"></p><p>在分析小米桌面滑动卡顿这个案例的时候，我发现在有一个问题，小米桌面对应的 App 的 BufferQueue，有时候会出现可用 Buffer 从 2 →0 ，这相当于直接把一个 Buffer 给抛弃掉了，如下图所示</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2019.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2019.webp"></p><p>这样的话，如果在后续的桌面 Fling 过程中，又出现了一次 RenderThread 耗时，那么就会以卡顿的形式直接体现出来，这样也就失去了 Triple Buffer 的<strong>缓解掉帧</strong>的作用了</p><p>下图可以看到，由于丢弃了一个 Buffer，导致再一次出现 RenderThread 耗时的时候，表现依然是无 Buffer 可用，出现掉帧</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2020.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2020.webp"></p><p>仔细看前面这段丢弃 Buffer 的逻辑，也很容易想到，这里本身就已经丢了一帧了，还把这个耗时帧所对应的 Buffer 给丢弃了（也可能丢弃的是第二帧），不管是哪种情况，滑动时候的每一帧的内容都是计算好的（参考 List Fling 的计算过程），如果把其中一帧丢了，再加上本身 SurfaceFlinger 卡的那一下，卡顿感会非常明显</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2021.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%2021.webp"></p><p>举个例子,以滑动为例，offset 指的是离屏幕一个左边的距离</p><ol><li>正常情况下，滑动的时候，offset 是：<code>2→4→6→8→10→12</code></li><li>掉了一帧的情况下，滑动的 Offset 是：<code>2→4→6→6→8→10→12</code> （假设 计算 8 的这一帧超时了，就会看到两个 6 ，这是掉了一帧的情况）</li><li>像上图里面，如果直接扔掉了那个耗时的帧，就会出现下面这种 Offset：<code>2→4→6→6→10→12</code> ，直接从 6 跳到了 10，相当于卡了 1 次，步子扯大了一次，感官上会觉得<strong>卡+跳跃</strong></li></ol><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>附件已经上传到了 Github 上，可以自行下载：<a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action">https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action</a></p><ol><li>xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件，这次案例主要是分析这个 Systrace 文件</li><li>xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件</li><li>oppo_launcher_scroll.zip ：对比文件</li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对手机用户来说，卡顿包含了很多场景，比如在 &lt;strong&gt;滑动列表的时候掉帧&lt;/strong&gt;、&lt;strong&gt;应用启动白屏过长&lt;/strong&gt;、&lt;strong&gt;点击电源键亮屏慢&lt;/strong&gt;、&lt;strong&gt;界面操作没有反应然后闪退&lt;/strong&gt;、&lt;strong&gt;点击图标没有响应&lt;/strong&gt;、&lt;strong&gt;窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿&lt;/strong&gt; 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意&lt;/li&gt;
&lt;li&gt;对开发人员来说，上面的场景包括了 &lt;strong&gt;流畅度&lt;/strong&gt;（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、&lt;strong&gt;响应速度&lt;/strong&gt;（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、&lt;strong&gt;稳定性&lt;/strong&gt;（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要&lt;/li&gt;
&lt;li&gt;在技术上来说，&lt;strong&gt;流畅度、响应速度、稳定性&lt;/strong&gt;（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而  Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 流畅性实战 1 ：了解卡顿原理</title>
    <link href="https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/"/>
    <id>https://androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/</id>
    <published>2021-04-24T11:55:02.000Z</published>
    <updated>2026-02-07T05:17:47.867Z</updated>
    
    <content type="html"><![CDATA[<p>不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明</p><ol><li>对手机用户来说，卡顿包含了很多场景，比如在 <strong>滑动列表的时候掉帧</strong>、<strong>应用启动白屏过长</strong>、<strong>点击电源键亮屏慢</strong>、<strong>界面操作没有反应然后闪退</strong>、<strong>点击图标没有响应</strong>、<strong>窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿</strong> 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意</li><li>对开发人员来说，上面的场景包括了 <strong>流畅度</strong>（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、<strong>响应速度</strong>（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、<strong>稳定性</strong>（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要</li><li>在技术上来说，<strong>流畅度、响应速度、稳定性</strong>（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制</li><li>流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍</li><li>流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而  Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题<span id="more"></span></li></ol><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#intro">了解卡顿原理</a></li><li><a href="#system">系统运行机制简介</a></li><li><a href="#series">系列文章</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><strong>Systrace 系列文章如下</strong></p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p>Systrace (Perfetto) 工具的基本使用如果还不是很熟悉，那么需要优先去补一下上面列出的 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 基础知识系列</a>，本文假设你已经熟悉 Systrace(Perfetto)的使用了</p><p><a id="intro"></a></p><h1 id="了解卡顿原理"><a href="#了解卡顿原理" class="headerlink" title="了解卡顿原理"></a>了解卡顿原理</h1><h2 id="卡顿现象及影响"><a href="#卡顿现象及影响" class="headerlink" title="卡顿现象及影响"></a>卡顿现象及影响</h2><p>如文章开头所述，本文主要是分析流畅度相关的问题。流畅度是一个定义，我们评价一个场景的流畅度的时候，往往会使用 fps 来表示。比如 60 fps，意思是每秒画面更新 60 次；120  fps，意思是每秒画面更新 120 次。如果 120 fps 的情况下，每秒画面只更新了 110 次(连续动画的过程)，这种情况我们就称之为<strong>掉帧</strong>，其表现就是<strong>卡顿</strong>，fps 对应的也从 120 降低到了 110 ，这些都可以被精确地监控到</p><p>同时掉帧帧的原因非常多，有 APP 本身的问题，有系统原因导致卡顿的，也有硬件层的、整机卡的，这个可以参考下面四篇文章</p><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li><li><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">Android 中的卡顿丢帧原因概述 - 低内存篇</a></li></ol><p>用户在使用手机的过程中，卡顿是最容易被感受到的</p><ol><li>偶尔出现的小卡顿会降低用户的使用体验，比如刷微博的时候卡了一下，比如返回桌面动画卡顿这种</li><li>整机出现卡顿的则会让手机无法使用</li><li>现在的高帧率时代，如果用户习惯了 120 fps ，在用户比较容易感知的场景下突然切换到 60 fps，用户也会有明显的感知，并觉得出现了卡顿</li></ol><p>所以不管是应用还是系统，都应该尽量避免出现卡顿，发现的卡顿问题最好优先进行解决</p><h2 id="卡顿定义"><a href="#卡顿定义" class="headerlink" title="卡顿定义"></a>卡顿定义</h2><h3 id="应用一帧渲染的整体流程"><a href="#应用一帧渲染的整体流程" class="headerlink" title="应用一帧渲染的整体流程"></a>应用一帧渲染的整体流程</h3><p>为了知道卡顿是如何发生的，我们需要知道应用主线程的一帧是如何工作的</p><h3 id="从执行顺序的角度来看"><a href="#从执行顺序的角度来看" class="headerlink" title="从执行顺序的角度来看"></a>从执行顺序的角度来看</h3><p>从 Choreographer 收到 Vsync 开始，到 SurfaceFlinger&#x2F;HWC 合成一帧结束（后面还包含屏幕显示部分，不过是硬件相关，这里就不列出来了）</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled.webp"></p><h3 id="从-Systrace-的角度来看"><a href="#从-Systrace-的角度来看" class="headerlink" title="从 Systrace 的角度来看"></a>从 Systrace 的角度来看</h3><p>上面的流程图从 Systrace （Perfetto）的角度来看会更加直观</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%201.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%201.webp"></p><p>具体的流程参考上面两个图以及代码就会很清楚了，上述整体流程中，任何一个步骤超时都有可能导致卡顿，所以分析卡顿问题，需要从多个层面来进行分析，比如应用主线程、渲染线程、SystemServer 进程、SurfaceFlinger 进程、Linux 区域等</p><h2 id="卡顿定义-1"><a href="#卡顿定义-1" class="headerlink" title="卡顿定义"></a>卡顿定义</h2><p>我对卡顿的定义是：<strong>稳定帧率输出的画面出现一帧或者多帧没有绘制 。</strong>对应的应用单词是 <strong>Smooth</strong> VS <strong>Jank</strong></p><p>比如下图中，App 主线程有在正常绘制的时候（通常是做动画或者列表滑动），有一帧没有绘制，那么我们认为这一帧有可能会导致卡顿（这里说的是有可能，由于 Triple Buffer 的存在，这里也有可能不掉帧）</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%202.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%202.webp"></p><p>下面从三个方面定义卡顿</p><ol><li><strong>从现象上来说，在 App 连续的动画播放或者手指滑动列表时（关键是连续），如果连续 2 帧或者 2 帧以上，应用的画面都没有变化，那么我们认为这里发生了卡顿</strong></li><li><strong>从 SurfaceFlinger 的角度来说，在 App 连续的动画播放或者手指滑动列表时（关键是连续），如果有一个 Vsync 到来的时候 ，App 没有可以用来合成的 Buffer，那么这个 Vsync 周期 SurfaceFlinger 就不会走合成的逻辑（或者是去合成其他的 Layer），那么这一帧就会显示 App 的上一帧的画面，我们认为这里发生了卡顿</strong></li><li><strong>从 App 的角度来看，如果渲染线程在一个 Vsync 周期内没有 queueBuffer 到 SurfaceFlinger 中 App 对应的 BufferQueue 中，那么我们认为这里发生了卡顿</strong></li></ol><p>这里没有提到应用主线程，是因为主线程耗时长一般会间接导致渲染线程出现延迟，加大渲染线程执行超时的风险，从而引起卡顿；而且应用导致的卡顿原因里面，大部分都是主线程耗时过长导致的</p><p>卡顿还要区分是不是<strong>逻辑卡顿</strong>，<strong>逻辑卡顿</strong>指的是一帧的渲染流程都是没有问题的，也有对应的 Buffer 给到 SurfaceFlinger 去合成，但是这个 App Buffer 的内容和上一帧 App Buffer 相同（或者基本相同，肉眼无法分辨），那么用户看来就是连续两帧显示了相同的内容。这里一般来说我们也认为是发生了卡顿（不过还要区分具体的情况）；<strong>逻辑卡顿主要是应用自身的代码逻辑造成的</strong></p><p><a id="system"></a></p><h1 id="系统运行机制简介"><a href="#系统运行机制简介" class="headerlink" title="系统运行机制简介"></a>系统运行机制简介</h1><p>由于卡顿的原因比较多，如果要分析卡顿问题，首先得对 Android 系统运行的机制有一定的了解，下面简单介绍一下分析卡顿问题需要了解的系统运行机制：</p><ol><li>App 主线程运行原理</li><li>Message、Handler、MessageQueue、Looper 机制</li><li>屏幕刷新机制和 Vsync</li><li>Choreogrepher 机制</li><li>Buffer 流程和 TripleBuffer</li><li>Input 流程</li></ol><h2 id="系统机制-App-主线程运行原理"><a href="#系统机制-App-主线程运行原理" class="headerlink" title="系统机制 - App 主线程运行原理"></a>系统机制 - App 主线程运行原理</h2><p>App 进程在创建的时候，Fork 完成后会调用 ActivityThread 的 main 方法，进行主线程的初始化工作</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">     ......</span><br><span class="line">     <span class="comment">// 创建 Looper、Handler、MessageQueue</span></span><br><span class="line">       Looper.prepareMainLooper();</span><br><span class="line">       ......</span><br><span class="line">       <span class="type">ActivityThread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ActivityThread</span>();</span><br><span class="line">       thread.attach(<span class="literal">false</span>, startSeq);</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> (sMainThreadHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">           sMainThreadHandler = thread.getHandler();</span><br><span class="line">      &#125;</span><br><span class="line">       ......</span><br><span class="line">       <span class="comment">// 开始准备接收消息</span></span><br><span class="line">       Looper.loop();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 准备主线程的 Looper</span></span><br><span class="line">frameworks/base/core/java/android/os/Looper.java</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">prepareMainLooper</span><span class="params">()</span> &#123;</span><br><span class="line">    prepare(<span class="literal">false</span>);</span><br><span class="line">    <span class="keyword">synchronized</span> (Looper.class) &#123;</span><br><span class="line">        <span class="keyword">if</span> (sMainLooper != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;The main Looper has already been prepared.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        sMainLooper = myLooper();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// prepare 方法中会创建一个 Looper 对象</span></span><br><span class="line">frameworks/base/core/java/android/os/Looper.java</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">prepare</span><span class="params">(<span class="type">boolean</span> quitAllowed)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (sThreadLocal.get() != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Only one Looper may be created per thread&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    sThreadLocal.set(<span class="keyword">new</span> <span class="title class_">Looper</span>(quitAllowed));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Looper 对象创建的时候，同时创建一个 MessageQueue</span></span><br><span class="line">frameworks/base/core/java/android/os/Looper.java</span><br><span class="line"><span class="keyword">private</span> <span class="title function_">Looper</span><span class="params">(<span class="type">boolean</span> quitAllowed)</span> &#123;</span><br><span class="line">    mQueue = <span class="keyword">new</span> <span class="title class_">MessageQueue</span>(quitAllowed);</span><br><span class="line">    mThread = Thread.currentThread()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>主线程初始化完成后，主线程就有了完整的 Looper、MessageQueue、Handler，此时 ActivityThread 的 Handler 就可以开始处理 Message，包括 Application、Activity、ContentProvider、Service、Broadcast 等组件的生命周期函数，都会以 Message 的形式，在主线程按照顺序处理，这就是 App 主线程的初始化和运行原理，部分处理的 Message 如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">H</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BIND_APPLICATION</span>        <span class="operator">=</span> <span class="number">110</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EXIT_APPLICATION</span>        <span class="operator">=</span> <span class="number">111</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">RECEIVER</span>                <span class="operator">=</span> <span class="number">113</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CREATE_SERVICE</span>          <span class="operator">=</span> <span class="number">114</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SERVICE_ARGS</span>            <span class="operator">=</span> <span class="number">115</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">STOP_SERVICE</span>            <span class="operator">=</span> <span class="number">116</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (msg.what) &#123;</span><br><span class="line">            <span class="keyword">case</span> BIND_APPLICATION:</span><br><span class="line">                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, <span class="string">&quot;bindApplication&quot;</span>);</span><br><span class="line">                <span class="type">AppBindData</span> <span class="variable">data</span> <span class="operator">=</span> (AppBindData)msg.obj;</span><br><span class="line">                handleBindApplication(data);</span><br><span class="line">                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这部分可以看 <a href="https://androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Android Systrace 基础知识 - MainThread 和 RenderThread 解读</a> 这篇文章</p><h2 id="系统机制-Message-机制"><a href="#系统机制-Message-机制" class="headerlink" title="系统机制 - Message 机制"></a>系统机制 - Message 机制</h2><p>上一节应用的主线程初始化完成后，主线程就进入阻塞状态，等待 Message，一旦有 Message 发过来，主线程就会被唤醒，处理 Message，处理完成之后，如果没有其他的 Message 需要处理，那么主线程就会进入休眠阻塞状态继续等待</p><p>从下图可以看到 ，Android Message 机制的核心就是四个：<strong>Handler</strong>、<strong>Looper</strong>、<strong>MessageQueue</strong>、<strong>Message</strong></p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%203.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%203.webp"></p><p>网上有很多关于 Message 机制代码细节的分析，所以这里只是简单介绍 Message 机制的四个核心组件的作用</p><ol><li><strong>Handler</strong> : Handler 主要是用来处理 Message，应用可以在任何线程创建 Handler，只要在创建的时候指定对应的 Looper 即可，如果不指定，默认是在当前 Thread 对应的 Looper</li><li><strong>Looper :</strong> Looper 可以看成是一个循环器，其 loop 方法开启后，不断地从 MessageQueue 中获取 Message，对 Message 进行 Delivery 和 Dispatch，最终发给对应的 Handler 去处理。由于 Looper 中应用可以在 Message 处理前后插入自己的 printer，所以很多 APM 工具都会使用这个作为性能监控的一个切入点，具体可以参考 Tencent-Matrix 和 BlockCanary</li><li><strong>MessageQueue</strong>：MessageQueue 入上图所示，就是一个 Message 管理器，队列中是 Message，在没有 Message 的时候，MessageQueue 借助 Linux 的 nativePoll 机制，阻塞等待，直到有 Message 进入队列</li><li><strong>Message</strong>：Message 是传递消息的对象，其内部包含了要传递的内容，最常用的包括 what、arg、callback 等</li></ol><p>从第一节 App 主线程运行原理可知，ActivityThread 的就是利用 Message 机制，处理 App 各个生命周期和组件各个生命周期的函数</p><h2 id="系统机制-屏幕刷新机制-和-Vsync"><a href="#系统机制-屏幕刷新机制-和-Vsync" class="headerlink" title="系统机制 - 屏幕刷新机制 和 Vsync"></a>系统机制 - 屏幕刷新机制 和 Vsync</h2><p>首先我们需要知道什么是<strong>屏幕刷新率</strong>，简单来说，屏幕刷新率是一个硬件的概念，是说屏幕这个硬件刷新画面的频率：举例来说，60Hz 刷新率意思是：这个屏幕在 1 秒内，会刷新显示内容 60 次；那么对应的，90Hz 是说在 1 秒内刷新显示内容 90 次</p><p>与屏幕刷新率对应的，<strong>FPS</strong> 是一个软件的概念，与屏幕刷新率这个硬件概念要区分开，FPS 是由软件系统决定的 ：FPS 是 Frame Per Second 的缩写，意思是每秒产生画面的个数。举例来说，60FPS 指的是每秒产生 60 个画面；90FPS 指的是每秒产生 90 个画面</p><p>VSync 是垂直同期( Vertical Synchronization )的简称。基本的思路是将你的 FPS 和显示器的刷新率同期起来。其目的是避免一种称之为”撕裂”的现象.</p><ol><li>60 fps 的系统 , 1s 内需要生成 60 个可供显示的 Frame , 也就是说绘制一帧需要 16.67ms ( 1&#x2F;60 ) , 才会不掉帧 ( FrameMiss ).</li><li>90 fps 的系统 , 1s 内生成 90 个可供显示的 Frame , 也就是说绘制一帧需要 11.11ms ( 1&#x2F;90 ) , 才不会掉帧 ( FrameMiss ).</li></ol><p>一般来说，屏幕刷新率是由屏幕控制的，FPS 则是由 Vsync 来控制的，在实际的使用场景里面，屏幕刷新率和 FPS 一般都是一一对应的，具体可以参考下面两篇文章：</p><ol><li><a href="https://androidperformance.com/2019/05/15/90hz-on-android/">Android 新的流畅体验，90Hz 漫谈</a></li><li><a href="https://androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Android Systrace 基础知识 - Vsync 解读</a></li></ol><h2 id="系统机制-Choreographer"><a href="#系统机制-Choreographer" class="headerlink" title="系统机制 - Choreographer"></a>系统机制 - Choreographer</h2><p>上一节讲到 Vsync 控制 FPS，其实 Vsync 是通过 Choreographer 来控制应用刷新的频率的</p><p>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 的主要作用</p><p>Choreographer 扮演 Android 渲染链路中承上启下的角色</p><ol><li>承上：负责接收和处理 App 的各种更新消息和回调，等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ，判断卡顿掉帧情况，记录 CallBack 耗时等</li><li>启下：负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync )；请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) .</li></ol><p>下图就是 Vsync 信号到来的时候，Choreographer 借助 Message 机制开始一帧的绘制工作流程图</p><p>这部分详细的流程可以看 <a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%204.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%204.webp"></p><h2 id="系统机制-Buffer-流程和-TripleBuffer"><a href="#系统机制-Buffer-流程和-TripleBuffer" class="headerlink" title="系统机制 - Buffer 流程和 TripleBuffer"></a>系统机制 - Buffer 流程和 TripleBuffer</h2><p>BufferQueue 是一个生产者(Producer)-消费者(Consumer)模型中的数据结构，一般来说，消费者(Consumer) 创建 BufferQueue，而生产者(Producer) 一般不和 BufferQueue 在同一个进程里面</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%205.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%205.webp"></p><p>在 Android App 的渲染流程里面，App 就是个生产者(Producer) ，而 SurfaceFlinger 是一个消费者(Consumer)，所以上面的流程就可以翻译为</p><ol><li>当 App 需要 Buffer 时，它通过调用 dequeueBuffer（）并指定 Buffer 的宽度，高度，像素格式和使用标志，从 BufferQueue 请求释放 Buffer</li><li>App 可以用 cpu 进行渲染也可以调用用 gpu 来进行渲染，渲染完成后，通过调用 queueBuffer（）将缓冲区返回到 App 对应的 BufferQueue(如果是 gpu 渲染的话，这里还有个 gpu 处理的过程，所以这个 Buffer 不会马上可用，需要等 GPU 渲染完成)</li><li>SurfaceFlinger 在收到 Vsync 信号之后，开始准备合成，使用 acquireBuffer（）获取 App 对应的 BufferQueue 中的 Buffer 并进行合成操作</li><li>合成结束后，SurfaceFlinger 将通过调用 releaseBuffer（）将 Buffer 返回到 App 对应的 BufferQueue</li></ol><p>知道了 Buffer 流转的过程，下面需要说明的是，在目前的大部分系统上，每个应用都有三个 Buffer 轮转使用，来减少由于 Buffer 在某个流程耗时过长导致应用无 Buffer 可用而出现卡顿情况</p><p>下图是双 Buffer 和 三 Buffer 的一个对比图</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%206.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%206.webp"></p><p>三 Buffer 的好处如下</p><ol><li><strong>缓解掉帧</strong> :从上图 Double Buffer 和 Triple Buffer 的对比图可以看到，在这种情况下（出现连续主线程超时），三个 Buffer 的轮转有助于缓解掉帧出现的次数（从掉帧两次 -&gt; 只掉帧一次）。，App 主线程超时不一定会导致掉帧，由于 Triple Buffer 的存在，部分 App 端的掉帧(主要是由于 GPU 导致)，到 SurfaceFlinger 这里未必是掉帧，这是看 Systrace 的时候需要注意的一个点</li><li><strong>减少主线程和渲染线程等待时间</strong> ：双 Buffer 的轮转，App 主线程有时候必须要等待 SurfaceFlinger(消费者)释放 Buffer 后，才能获取 Buffer 进行生产，这时候就有个问题，现在大部分手机 SurfaceFlinger 和 App 同时收到 Vsync 信号，如果出现 App 主线程等待 SurfaceFlinger(消费者)释放 Buffer，那么势必会让 App 主线程的执行时间延后</li><li><strong>降低 GPU 和 SurfaceFlinger 瓶颈</strong> ：这个比较好理解，双 Buffer 的时候，App 生产的 Buffer 必须要及时拿去让 GPU 进行渲染，然后 SurfaceFlinger 才能进行合成，一旦 GPU 超时，就很容易出现 SurfaceFlinger 无法及时合成而导致掉帧；在三个 Buffer 轮转的时候，App 生产的 Buffer 可以及早进入 BufferQueue，让 GPU 去进行渲染（因为不需要等待，就算这里积累了 2 个 Buffer，下下一帧才去合成，这里也会提早进行，而不是在真正使用之前去匆忙让 GPU 去渲染），另外 SurfaceFlinger 本身的负载如果比较大，三个 Buffer 轮转也会有效降低 dequeueBuffer 的等待时间</li></ol><p>坏处就是 Buffer 多了会占用内存</p><p>这部分详细的流程可以看 <a href="https://androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/">Android Systrace 基础知识 - Triple Buffer 解读</a> 这篇文章</p><h2 id="系统机制-Input-流程"><a href="#系统机制-Input-流程" class="headerlink" title="系统机制 - Input 流程"></a>系统机制 - Input 流程</h2><p>Android 系统是由事件驱动的，而 input 是最常见的事件之一，用户的点击、滑动、长按等操作，都属于 input 事件驱动，其中的核心就是 InputReader 和 InputDispatcher。InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程，负责读取和分发 Input 事件，我们分析 Systrace 的 Input 事件流，首先是找到这里。</p><ol><li>InputReader 负责从 EventHub 里面把 Input 事件读取出来，然后交给 InputDispatcher 进行事件分发</li><li>InputDispatcher 在拿到 InputReader 获取的事件之后，对事件进行包装和分发 (也就是发给对应的)</li><li>OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件</li><li>WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件</li><li>PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件，这里可以看到已经到了应用进程</li><li>deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒</li><li>InputResponse 标识 Input 事件区域，这里可以看到一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件的处理阶段都被算到了这里</li><li>App 响应 Input 事件 ： 这里是滑动然后松手，也就是我们熟悉的桌面滑动的操作，桌面随着手指的滑动更新画面，松手后触发 Fling 继续滑动，从 Systrace 就可以看到整个事件的流程</li></ol><p>上面流程对应的 Systrace 如下</p><p><img src="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%207.webp" alt="/images/Systrace-Smooth%20e5d284a979a447ad8b45ff021d6e41cf/Untitled%207.webp"></p><p>这部分详细的流程可以看 <a href="https://androidperformance.com/2019/11/04/Android-Systrace-Input/">Android Systrace 基础知识 - Input 解读</a> 这篇文章</p><p><a id="series"></a></p><h1 id="系列文章"><a href="#系列文章" class="headerlink" title="系列文章"></a>系列文章</h1><ol><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>附件已经上传到了 Github 上，可以自行下载：<a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action">https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Smooth_In_Action</a></p><ol><li>xiaomi_launcher.zip : 桌面滑动卡顿的 Systrace 文件，这次案例主要是分析这个 Systrace 文件</li><li>xiaomi_launcher_scroll_all_the_time.zip : 桌面一直按着滑动的 Systrace 文件</li><li>oppo_launcher_scroll.zip ：对比文件</li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;不同的人对流畅性(卡顿掉帧)有不同的理解，对卡顿阈值也有不同的感知，所以有必要在开始这个系列文章之前，先把涉及到的内容说清楚，防止出现不同的理解，也方便大家带着问题去看这几篇问题，下面是一些基本的说明&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对手机用户来说，卡顿包含了很多场景，比如在 &lt;strong&gt;滑动列表的时候掉帧&lt;/strong&gt;、&lt;strong&gt;应用启动白屏过长&lt;/strong&gt;、&lt;strong&gt;点击电源键亮屏慢&lt;/strong&gt;、&lt;strong&gt;界面操作没有反应然后闪退&lt;/strong&gt;、&lt;strong&gt;点击图标没有响应&lt;/strong&gt;、&lt;strong&gt;窗口动画不连贯、滑动不跟手、重启手机进入桌面卡顿&lt;/strong&gt; 等场景，这些场景跟我们开发人员所理解的卡顿还有点不一样，开发人员会更加细分去分析这些问题，这是开发人员和用户之间的一个认知差异，这一点在处理用户(或者测试人员)的问题反馈的时候尤其需要注意&lt;/li&gt;
&lt;li&gt;对开发人员来说，上面的场景包括了 &lt;strong&gt;流畅度&lt;/strong&gt;（滑动列表的时候掉帧、窗口动画不连贯、重启手机进入桌面卡顿）、&lt;strong&gt;响应速度&lt;/strong&gt;（应用启动白屏过长、点击电源键亮屏慢、滑动不跟手）、&lt;strong&gt;稳定性&lt;/strong&gt;（界面操作没有反应然后闪退、点击图标没有响应）这三个大的分类。之所以这么分类，是因为每一种分类都有不太一样的分析方法和步骤，快速分辨问题是属于哪一类很重要&lt;/li&gt;
&lt;li&gt;在技术上来说，&lt;strong&gt;流畅度、响应速度、稳定性&lt;/strong&gt;（ANR）这三类之所以用户感知都是卡顿，是因为这三类问题产生的原理是一致的，都是由于主线程的 Message 在执行任务的时候超时，根据不同的超时阈值来进行划分而已，所以要理解这些问题，需要对系统的一些基本的运行机制有一定的了解，本文会介绍一些基本的运行机制&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是分析流畅度相关的问题，响应速度和稳定性会有专门的文章介绍，在理解了流畅性相关的内容之后，再去分析响应速度和稳定性问题会事半功倍&lt;/li&gt;
&lt;li&gt;流畅性这个系列主要是讲如何使用 Systrace (Perfetto) 工具去分析，之所以 Systrace 为切入点，是因为影响流畅度的因素很多，有 App 自身的原因、也有系统的原因。而  Systrace(Perfetto)  工具可以从一个整机运行的角度来展示问题发生的过程，方便我们去初步定位问题</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="ANR" scheme="https://androidperformance.com/tags/ANR/"/>
    
  </entry>
  
  <entry>
    <title>华为手机刷微博体验更好？技术角度的一些分析和思考</title>
    <link href="https://androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/"/>
    <id>https://androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/</id>
    <published>2020-08-20T05:17:26.000Z</published>
    <updated>2026-02-07T05:17:47.882Z</updated>
    
    <content type="html"><![CDATA[<p>技术群里的小伙伴发了一条微博， <a href="https://weibo.com/1808884742/IApbpEVQr">https://weibo.com/1808884742/IApbpEVQr</a>， 博主 @王波粒 发现， Mate 30 Pro 有个很特别的现象（建议先去看一下视频） </p><p>但是这个视频描述和底下的猜测都不对，我这边总结一下这个现象： <strong>微博这个 App 在华为的手机上，在主页列表上下滑动的情况下依然可以流畅加载图片，而同一个版本的微博客户端，安装到其他手机上，在主页列表上下滑动的情况下，则必须要等到滑动停止之后才会加载图片</strong> </p><p>下面就针对这个现象,从技术的角度来深入分析产生这种现象的原因,以及我们能从里面学到什么</p><span id="more"></span><p><img src="/images/15979019506115.jpg"></p><p><strong>这个现象有什么特别呢 ？</strong> </p><ol><li>从技术上讲，滑动列表停止后再加载图片是目前列表滑动优化中一个比较常见的优化项，很多主流 App 也都是这么做的 ，做这种处理主要是因为 <strong>如果在列表滑动的时候，碰到图片视频就加载，那么会加载很多无用的图片&amp;&amp;视频，浪费资源不说，还可能会影响真正用户看到的图片的加载速度</strong> （加载一般都有并行上限和队列，队列里面无效的图片太多，后来的图片就得排队等待）。 这里比较 <strong>特别的就是同一个版本的微博 APK，在华为的机型上与在其他机型上表现不一致，作为一个系统优化工程师，这个还是值得去搞清楚的（大胆猜测是微博针对华为的机型做了优化），那么这个优化的内容是什么？</strong> </li><li>从用户体验的角度来讲，列表滑动的同时加载图片，用户可以更早地看到图片，减少图片占位白图的显示时间，可以提升滑动的体验 </li><li>第三个现象就得认真体验才会感觉到： <strong>华为手机上的微博在松手后的滑动曲线和其他手机上的微博在松手后的滑动曲线是不一样的，华为的微博列表松手后的滑动曲线速度更慢，更柔和，结束的时候也不会太突兀，与系统默认的列表滑动曲线明显不一样</strong></li></ol><p>上面三个是从现象上来说的，下面就从技术上来验证，从最后的结果来看，华为和微博的合作毫无疑问是很成功的，可以作为一个案例推广到其他头部 App，同时作为 Android 开发者，对华为这种非常细致的体验优化真的是非常敬佩 </p><h2 id="背景备注"><a href="#背景备注" class="headerlink" title="背景备注"></a>背景备注</h2><ol><li><strong>由于 “列表滑动的同时加载图片” 这个功能由微博官方服务器控制，可以随时开启或者关闭，所以文章中所说的 “同一个版本的微博客户端，安装到其他手机上，在主页列表上下滑动的情况下，则必须要等到滑动停止之后才会加载图片” 这个现象在 “列表滑动的同时加载图片” 这个功能开启后，现象就会变成 “主页列表上下滑动的时候就会加载图片”</strong> </li><li><strong>在 2020-6 月左右分析这个问题的时候，“列表滑动的同时加载图片” 这个功能还是关闭的，只有华为手机做了优化才有效果，其他手机是 “滑动停止之后才会加载图片”</strong> </li><li><strong>在 2020-8 月再看这个问题的时候，“列表滑动的同时加载图片” 这个功能在其他手机上已经开启</strong> </li><li><strong>华为的 PerfSDK 还有效果么？答案是有，具体分析可以看下文，因为有了这个 SDK，不仅对微博有好处(减少图片加载个数)，对华为也有好处(提升微博主页列表在华为手机上的滑动体验，即 Fling 曲线优化) ；而粗暴开启 “列表滑动的同时加载图片” 的其他手机，如果性能不足，开启后反而会增加卡顿出现的概率(微博官方应该有性能监控数据可以看到)</strong> </li><li><strong>反编译的微博版本：10.8.1</strong></li></ol><h2 id="结论先行"><a href="#结论先行" class="headerlink" title="结论先行"></a>结论先行</h2><p><strong>“微博这个 App 在华为的手机上，在主页列表上下滑动的情况下依然可以流畅加载图片” 这个现象是因为华为和微博做了联合优化，主要是为了优化微博列表滑动时候的用户体验，其优化点如下</strong> </p><ol><li>华为提供了一个简单的接口打包成 SDK 提供给微博，这个接口可以让微博的列表监听到列表的当前速度（Velocity），在速度高于阈值或者低于阈值的时候，都会及时通知 App </li><li>微博拿到这个速度回调之后，就可以根据列表的滑动速度来决定是否要在滑动过程中加载图片，一旦列表的滑动速度低于设定的阈值，就开启图片加载；一旦列表的滑动速度高于设定的阈值，就关闭图片加载 </li><li>华为检测到这个应用使用了 SDK，就可以将优化过后的滑动曲线应用在这个 App 的列表 Fling 阶段，提升用户体验</li></ol><p>对细节感兴趣的同学可以继续阅读，有能力的同学看完后可以修改 Framework 相关代码，编译一个 SDK，然后自己写个 Demo 接入 SDK，就可以打通我下面所说的所有内容了，我自己在 AOSP 的代码上实现了一遍，Demo 也可以正常运行，有兴趣可以跟我私下交流 </p><h1 id="微博-华为是怎么优化？"><a href="#微博-华为是怎么优化？" class="headerlink" title="微博+华为是怎么优化？"></a>微博+华为是怎么优化？</h1><h2 id="现象分析"><a href="#现象分析" class="headerlink" title="现象分析"></a>现象分析</h2><p>我们在滑动微博列表的时候，一个滑动操作主要由下面三部分组成 </p><ol><li>手指接触屏幕，上下滑动微博主页列表，但是手指 <strong>没有离开屏幕</strong> ，这个阶段我们称之为阶段一，技术术语为 SCROLL_STATE_TOUCH_SCROLL </li><li>手指上下滑动的时候 <strong>离开屏幕</strong> (必须有一个上滑或者下滑的速度)，微博列表有了一个惯性，根据惯性的方向继续滑动，这个阶段我们称之为阶段二，技术术语为 SCROLL_STATE_FLING </li><li>列表惯性滑动后停止，这 个 阶段我们称之为阶段三 ， 技术术语为  SCROLL_STATE_I DLE</li></ol><p>而华为和微博的优化主要在阶段一和阶段二 </p><h3 id="阶段一优化"><a href="#阶段一优化" class="headerlink" title="阶段一优化"></a>阶段一优化</h3><ol><li>优化前：只要手指不离开屏幕，图片加载功能关闭 </li><li>优化后：只要手指不离开屏幕，列表就不会滑动太快，这时候图片加载功能开启</li></ol><h3 id="阶段二优化"><a href="#阶段二优化" class="headerlink" title="阶段二优化"></a>阶段二优化</h3><ol><li><strong>滑动图片加载优化</strong> <ol><li>优化前：只要列表滑动不停止，图片加载功能关闭 </li><li>优化后：图片加载功能是否开启取决于当前列表滑动的速度 <ol><li>列表滑动速度太快，这时候图片加载功能关闭 </li><li>列表滑动速度掉落到一个阈值，图片加载功能开启</li></ol></li></ol></li><li><strong>列表 Fling 曲线优化</strong> <ol><li>优化前：列表滑动的曲线是默认值，滑动时间比较短，停止的时候比较突兀，不柔和 </li><li>优化后：列表滑动的曲线是华为经过优化的，滑动时间比较长，停止的时候比较柔，不突兀，比较接近 iPhone 的列表滑动曲线</li></ol></li></ol><h2 id="技术分析"><a href="#技术分析" class="headerlink" title="技术分析"></a>技术分析</h2><p>技术分析的代码主要来源于微博 apk 的反编译，微博版本 10.8.1，通过反编译的代码可以看到， <strong>微博主页在初始化的时候，会接入华为提供的 PerfSDK，从而获得监听列表滑动速度的能力</strong> </p><h3 id="阶段一优化的技术分析"><a href="#阶段一优化的技术分析" class="headerlink" title="阶段一优化的技术分析"></a>阶段一优化的技术分析</h3><p>列表的 ScrollStateChange 是标识列表状态的一个回调，微博在 ScrollStateChange 这个回调中会根据当前的状态来决定是否加载图片， 从下面的代码逻辑来看 </p><ol><li>当 <strong>滑动图片加载优化生效</strong> 的时候，如果 State !&#x3D; 2，那么就允许 ImageLoader 加载图片，State 为 2 也就是 SCROLL_STATE_FLING，熟悉列表滑动的同学应该知道，SCROLL_STATE_FLING  就是 <strong>滑动列表的时候手指松手后列表继续滑动的那一段</strong> ，叫 fling，毕竟只有 fling 的时候才有 Velocity，松手后会根据这个值的大小计算滑动曲线和滑动时长 </li><li>当 <strong>滑动图片加载优化不生效</strong> 的时候，就到了常规的列表滑动优化：即列表停止之后才开始加载图片 ：State !&#x3D;0，0 即 SCROLL_STATE_IDLE</li></ol><p><img src="/images/15979019763546.jpg"></p><h3 id="阶段二优化的技术分析"><a href="#阶段二优化的技术分析" class="headerlink" title="阶段二优化的技术分析"></a>阶段二优化的技术分析</h3><p>微博的主页在初始化的时候，会给首页的 ListView 注册一个 HwPerfVelocityCallback，从名字可以看出来，这个回调是监听 Velocity 的，也就是滑动的速度，两个回调： </p><ol><li>HwPerfonVelocityDownToThreshold : 当速度降低到阈值之后，打开 ImageLoader 的图片加载功能 </li><li>HwPerfonVelocityUpToThreshold： 当速度升高到阈值之后，关闭 ImageLoader 的图片加载功能</li></ol><p>下图为反编译后的源码 </p><p><img src="/images/15979019932037.jpg"></p><p>至于滑动曲线，则需要查看华为的 Framework 的代码，由于代码量比较大，这里只贴一下 OverScroller.java 中的 update 方法，具体感兴趣的可以自己去翻一番华为的 Framework 代码<br><img src="/images/15979020033894.jpg"></p><p>计算 Distance 的代码 </p><p><img src="/images/15979020127885.jpg"></p><p>计算 Velocity 的代码 </p><p><img src="/images/15979020218054.jpg"></p><p>关于滑动曲线的解释，大家可以看这一篇知乎回答，其中对比了 iOS 和 Android 的滑动曲线的不同 ：<a href="https://www.zhihu.com/question/291779390/answer/484881732">为什么 iOS 的过渡动画看起来很舒服？</a></p><h2 id="其他厂商处理"><a href="#其他厂商处理" class="headerlink" title="其他厂商处理"></a>其他厂商处理</h2><p>上面图中代码最后一段还有一个判断开关， 如果 boolean a &#x3D; HwPerfUtil.m14290a 这个返回的是 false，这就是说有可能华为这个优化关闭了，有可能是非华为机器，那么会 <strong>判断 Android 版本号和全局 Feature 开关</strong> </p><p><img src="/images/15979020689139.jpg"></p><p>对应的 FeedAbManager 就是一个 Feature 管理器，可以在线开关某些 Feature<br><img src="/images/15979020809489.jpg"></p><p>而 m52580k 的实现如下 </p><p><img src="/images/15979020905125.jpg"></p><p>可以看到这里还受到一个全局的 Feature 配置：feed_scroll_loadimage_enable，这个 Feature 是服务端可以配置的<br><img src="/images/15979021040883.jpg"></p><p>这里就是处理其他厂商的逻辑 </p><h1 id="最后一个问题：滑动点击"><a href="#最后一个问题：滑动点击" class="headerlink" title="最后一个问题：滑动点击"></a>最后一个问题：滑动点击</h1><p>滑动点击是个什么问题呢？列表在滑动的过程中，如果用户点击列表的某一个 Item，那么根据 Android 的事件分发机制，这时候列表的 Item 并不会被点击到，而是整个列表先收到点击事件，然后 <strong>触发列表滑动停止；列表停止的时候点击 Item 才会触发 Item 的点击</strong> </p><p>上面阶段二的优化中，在优化了滑动曲线之后，列表处于 Fling 状态的时间变长，如果用户想点击去某一个 Item 查看详情，那么需要先点击一下让列表停止，然后再点击一下才能进去，这就是这一节想说的 ：滑动点击问题 </p><p>滑动(Fling 状态)和点击其实是需要一个平衡的，这个平衡需要开发者自己去把控： </p><p>滑动(Fling 状态)的时间越短，列表越容易停下，用户点击列表越容易触发 Item 的点击，但是容易停止带来的问题就是不够柔和。想象你在粗糙的水泥地上滑出去一块石头，这石头没有滑动多久就会停止，不管是扔石头的你还是旁边看你扔石头的我，都不会觉得这有什么美感，但是没得选。这个的 <strong>代表其实就是 Android 原生的 Fling 曲线</strong> </p><p>滑动(Fling 状态)的时间越长，滑动(Fling 状态)的时间越长，列表越不容易停下，用户点击列表越不容易触发 Item 的点击，如果曲线优化的好，给人的感觉就是很柔和，符合物理规律，想象你在光滑的冰面上滑出去一块冰，冰面越滑，冰块滑动的时间就越长，越不容易停下。这其中的极端代表就是 iOS 的 Fling 曲线。说 iOS 极端是因为，iOS 的滑动曲线调的太磨叽了，时间长不说，停的异常慢，很多时候你都需要点击一下列表让他先停止，然后再进行下一步的点击动作。而小米的 MIUI12 对这个也进行了调整，效果要比 iOS 好一些，如果再和三方进行类似华为和微博的合作，体验会更上一层楼 </p><p>滑动点击问题其实也可以通过厂商和 App 合作来解决，比如，当滑动到整个滑动距离的 98%(或者 95%) 之后，用户点击列表不再是让列表停止，而是列表内的 item 响应这个点击。这个思想来源于 Launcher 的代码，Launcher 的每一页在左右滑动的时候，如果滑动还没有停止但是用户比较手速快点击了某个 icon 想启动，那么这时候不会触发 Page 停止，而是直接响应 icon 的点击启动应用操作 </p><h1 id="延伸阅读"><a href="#延伸阅读" class="headerlink" title="延伸阅读"></a>延伸阅读</h1><h2 id="列表滑动图片加载的性能考虑"><a href="#列表滑动图片加载的性能考虑" class="headerlink" title="列表滑动图片加载的性能考虑"></a>列表滑动图片加载的性能考虑</h2><p>前文有提到这个问题，滑动的时候进行图片加载主要有两个问题： </p><ol><li>如果用户滑动非常快，比如是想找昨天发的某个微博，那么今天发的所有的带图片的微博在用户滑动的时候是没必要加载的，因为用户的目标不是这些图片，而 App 去加载这些图片，而程序员是不会为用户提前加载你未看到的数据，因为加载过多的数据不仅容易发生数据复用、缓存过多、内存溢出等错误，还会对服务器造成不必要的资源请求。 </li><li>如果用户滑动非常快，那么图片加载队列势必有许多无效的资源(对这一刻的用户来说)，而用户真正想看的图片反而排在了加载队列后面，造成加载速度变慢，也会影响用户的体验</li></ol><p>滑动中加载图片最大的风险其实就是造成卡顿，因为图片加载本身就是一个比较重的操作，而高帧率的手机上，一帧的时间被压缩到很短，任何小的不确定性都有可能造成卡顿 </p><p>所以厂商+应用的这个优化： <strong>快速滑动不加载图片，慢速的时候再加载，然后优化滑动曲线</strong> ，其实对厂商和应用都是非常有益处的 </p><h2 id="列表滑动监听背景知识"><a href="#列表滑动监听背景知识" class="headerlink" title="列表滑动监听背景知识"></a>列表滑动监听背景知识</h2><p>下面的 AbsListView 的 OnScrollListener 里面标注了列表滑动的三个状态 </p><ol><li>滑动停止：SCROLL_STATE_IDLE </li><li>手指在屏幕上滑动：SCROLL_STATE_TOUCH_SCROLL </li><li>手指离开屏幕，列表靠惯性继续滑动：SCROLL_STATE_FLING</li></ol><p>两个回调 </p><ol><li>列表状态变化时的回调 ：onScrollStateChanged </li><li>列表滑动时候的回调：onScroll</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">OnScrollListener</span> &#123; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 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. </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">SCROLL_STATE_IDLE</span> <span class="operator">=</span> <span class="number">0</span>;      </span><br><span class="line">    </span><br><span class="line">    <span class="comment">//The user is scrolling using touch, and their finger is still on the screen </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">SCROLL_STATE_TOUCH_SCROLL</span> <span class="operator">=</span> <span class="number">1</span>;      </span><br><span class="line">    </span><br><span class="line">    <span class="comment">//The user had previously been scrolling using touch and had performed a fling. The animation is now coasting to a stop </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">SCROLL_STATE_FLING</span> <span class="operator">=</span> <span class="number">2</span>;      </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 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 </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onScrollStateChanged</span><span class="params">(AbsListView view, <span class="type">int</span> scrollState)</span>;      </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// Callback method to be invoked when the list or grid has been scrolled. This will be called after the scroll has completed </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onScroll</span><span class="params">(AbsListView view, <span class="type">int</span> firstVisibleItem, <span class="type">int</span> visibleItemCount, <span class="type">int</span> totalItemCount)</span>; </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h2 id="列表滑动状态的变化"><a href="#列表滑动状态的变化" class="headerlink" title="列表滑动状态的变化"></a>列表滑动状态的变化</h2><p>TOUCH_SCROLL、FLING、IDLE 三个状态对应的列表滑动操作如下 </p><ol><li>TOUCH_SCROLL： 手指滑动 List 阶段，但是手指没有离开屏幕，这时候上下滑动都是 TOUCH_SCROLL </li><li>FLING： 手指滑动 List 后抬手到 List 停止的阶段（必须有一个上滑或者下滑的速度，否则不会进入 Fling） </li><li>IDLE：List 停止阶段</li></ol><p>这三个状态的变化情况如下 </p><ol><li>手指滑动列表，停止后松手：IDLE  -&gt; TOUCH_SCROLL -&gt; IDLE </li><li>手指滑动列表，松手后列表继续滑动，然后停止：IDLE  -&gt; TOUCH_SCROLL -&gt; FLING -&gt; IDLE</li></ol><h2 id="列表的-Fling-曲线计算"><a href="#列表的-Fling-曲线计算" class="headerlink" title="列表的 Fling 曲线计算"></a>列表的 Fling 曲线计算</h2><p>Fling 触发之后，每一帧都会调用 update 函数来更新 distance  和 mCurrVelocity，所以我们只需要监听 mCurrVelocity  的值，超过一定的阈值，就可以回调给 App </p><p>frameworks&#x2F;base&#x2F;core&#x2F;java&#x2F;android&#x2F;widget&#x2F;OverScroller.java </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">update</span><span class="params">()</span> &#123; </span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">time</span> <span class="operator">=</span> AnimationUtils.currentAnimationTimeMillis(); </span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">currentTime</span> <span class="operator">=</span> time - mStartTime; </span><br><span class="line">    <span class="type">double</span> <span class="variable">distance</span> <span class="operator">=</span> <span class="number">0.0</span>; </span><br><span class="line">    <span class="keyword">switch</span> (mState) &#123; </span><br><span class="line">        <span class="keyword">case</span> SPLINE: &#123; <span class="comment">// Fling  状态 </span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t</span> <span class="operator">=</span> (<span class="type">float</span>) currentTime / mSplineDuration; </span><br><span class="line">            <span class="keyword">final</span> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> (<span class="type">int</span>) (NB_SAMPLES * t); </span><br><span class="line">            <span class="type">float</span> <span class="variable">distanceCoef</span> <span class="operator">=</span> <span class="number">1.f</span>; </span><br><span class="line">            <span class="type">float</span> <span class="variable">velocityCoef</span> <span class="operator">=</span> <span class="number">0.f</span>; </span><br><span class="line">            <span class="keyword">if</span> (index &lt; NB_SAMPLES) &#123; </span><br><span class="line">                <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t_inf</span> <span class="operator">=</span> (<span class="type">float</span>) index / NB_SAMPLES; </span><br><span class="line">                <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t_sup</span> <span class="operator">=</span> (<span class="type">float</span>) (index + <span class="number">1</span>) / NB_SAMPLES; </span><br><span class="line">                <span class="keyword">final</span> <span class="type">float</span> <span class="variable">d_inf</span> <span class="operator">=</span> SPLINE_POSITION[index]; </span><br><span class="line">                <span class="keyword">final</span> <span class="type">float</span> <span class="variable">d_sup</span> <span class="operator">=</span> SPLINE_POSITION[index + <span class="number">1</span>]; </span><br><span class="line">                velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); </span><br><span class="line">                distanceCoef = d_inf + (t - t_inf) * velocityCoef; </span><br><span class="line">            &#125; </span><br><span class="line">            distance = distanceCoef * mSplineDistance; </span><br><span class="line">            mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * <span class="number">1000.0f</span>; </span><br><span class="line">            <span class="keyword">break</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">case</span> BALLISTIC: &#123; <span class="comment">// 列表滑到 底 之后的 拉伸阶段 </span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t</span> <span class="operator">=</span> currentTime / <span class="number">1000.0f</span>; </span><br><span class="line">            mCurrVelocity = mVelocity + mDeceleration * t; </span><br><span class="line">            distance = mVelocity * t + mDeceleration * t * t / <span class="number">2.0f</span>; </span><br><span class="line">            <span class="keyword">break</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">case</span> CUBIC: &#123; <span class="comment">// 列表滑到底拉伸 之后的 回弹阶段 </span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t</span> <span class="operator">=</span> (<span class="type">float</span>) (currentTime) / mDuration; </span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">t2</span> <span class="operator">=</span> t * t; </span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">sign</span> <span class="operator">=</span> Math.signum(mVelocity); </span><br><span class="line">            distance = sign * mOver * (<span class="number">3.0f</span> * t2 - <span class="number">2.0f</span> * t * t2);  </span><br><span class="line">            mCurrVelocity = sign * mOver * <span class="number">6.0f</span> * (- t + t2);  </span><br><span class="line">            <span class="keyword">break</span>; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">    mCurrentPosition = mStart + (<span class="type">int</span>) Math.round(distance); </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>; </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h1 id="厂商应用联合优化"><a href="#厂商应用联合优化" class="headerlink" title="厂商应用联合优化"></a>厂商应用联合优化</h1><p>微博这个优化就是厂商和应用之间联合优化的一个案例，应用对用户体验的极致追求，让这种合作在未来会变得更加频繁，像微信、快手、抖音这些… </p><p>下面这个招聘是拼多多的一个 JD，看职位描述是专门对接厂商的优化，也可以看出应用对厂商的合作越来越重视。之前厂商和应用是魔高一尺道高一丈的关系，互相攻防导致最终体验受损的还是用户；而现在这种厂商和应用合作的关系，不仅提升了双方的体验，也会带动 Android 生态圈向好的方面去发展 </p><p><img src="/images/15979021330055.jpg"></p><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>微信公众号 - <a href="https://mp.weixin.qq.com/s/wJKOvU7CqP3vM0TG7rO66g">https://mp.weixin.qq.com/s/wJKOvU7CqP3vM0TG7rO66g</a><br>知乎专栏（求个赞） - <a href="https://zhuanlan.zhihu.com/p/191460094">https://zhuanlan.zhihu.com/p/191460094</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;技术群里的小伙伴发了一条微博， &lt;a href=&quot;https://weibo.com/1808884742/IApbpEVQr&quot;&gt;https://weibo.com/1808884742/IApbpEVQr&lt;/a&gt;， 博主 @王波粒 发现， Mate 30 Pro 有个很特别的现象（建议先去看一下视频） &lt;/p&gt;
&lt;p&gt;但是这个视频描述和底下的猜测都不对，我这边总结一下这个现象： &lt;strong&gt;微博这个 App 在华为的手机上，在主页列表上下滑动的情况下依然可以流畅加载图片，而同一个版本的微博客户端，安装到其他手机上，在主页列表上下滑动的情况下，则必须要等到滑动停止之后才会加载图片&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;下面就针对这个现象,从技术的角度来深入分析产生这种现象的原因,以及我们能从里面学到什么&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="Smooth" scheme="https://androidperformance.com/tags/Smooth/"/>
    
    <category term="体验优化" scheme="https://androidperformance.com/tags/%E4%BD%93%E9%AA%8C%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>一个「闰」字引发的事故 - 三星系统重启分析</title>
    <link href="https://androidperformance.com/2020/05/26/samsung_crash/"/>
    <id>https://androidperformance.com/2020/05/26/samsung_crash/</id>
    <published>2020-05-25T16:56:05.000Z</published>
    <updated>2026-02-07T05:17:47.878Z</updated>
    
    <content type="html"><![CDATA[<p>2020 年 5 月 23 号凌晨 1 点 30 左右, 大量三星手机用户的手机出现死机, 无限重启、进 Recovery 等问题, 并且操作不当会导致数据丢失, 并且上了知乎的热点, 售后点更是人满为患</p><p>知乎的部分回答中, 大家更是对三星的家属送上了亲切的问候, 甚至有的人已经将这次事故与 Note7 事件、充电门、绿屏门事件相提并论, 甚至预言三星因此会退出国内市场 ; 有的人因为这个丢了 Offer , 有的人准备了很久的资源丢失, 有的人甚至直接把手机砸了…</p><p>作为一个 Android 开发者, 我并不想对三星落井下石 , 我只想搞清楚到底是什么原因导致了这场事故 , 以及我们能从里面学到什么 . 我认为既然是 Android 系统出了问题, 我们有必要从技术的角度来分析为什么会出现这样的问题</p><span id="more"></span><p><img src="/images/15904500356668.jpg" alt="知乎热点"></p><p>甚至商场里的机器都变砖了</p><p><img src="/images/15904500558308.jpg" alt="商场"></p><h1 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h1><p>结论先行, 对于不喜欢看长文的吃瓜群众来说, 直接看结论即可：</p><p><strong>这次事故表现是一部分三星用户的手机系统中关键系统服务重复 Crash 并强制进入 Recovery 界面. 关键系统服务指的是三星的 SystemUI 进程 , SystemUI 进程在初始化 AOD 插件的时候 AOD 插件初始化出错导致 SystemUI Crash, 由于是系统服务, SystemUI Crash 到一定的次数之后, 就会强制进入 Recovery 界面, 所以 大部分用户看到的都是 Recovery 界面(下面有图)</strong></p><p><strong>AOD 全称 Always On Display, 中文翻译是息屏显示, 就是你按电源键锁屏后, 在屏幕上显示时间、天气、图案等的服务, 这个只有部分高端机型才有这个功能.</strong></p><p><strong>AOD Crash 的原因是 2020 年 5 月 23 是闰四月, AOD 显示阴历的时候, 需要显示闰四月, 所以在代码中会走到显示闰四月这个一般很难走进的分支条件, 走进这个条件之后, 需要获取 common_data_leap_month 这个字段, 但是由于代码编译出现了 Bug, 导致无法找到这个字段, 所以该进程直接报了 FATAL EXCEPETION, 进程重启, 重启之后还是要获取这个字段, 再重启, 如此反复 , 最终触发系统的自救措施, 进入 Recovery 界面</strong></p><p><strong>这也是为什么只有中国用户才会出现这个问题, 就是因为 AOD 在 5 月 23 号需要显示”闰”四月 , 但是没找到 “闰” 这个字, 所以就挂了 . 所以并不是千年虫 , 也不是服务器被黑, 更不是三星故意恶心人, 这种编译导致的 Bug , 再碰上几年一遇的闰月 , 遇到了就认了吧 , 老老实实道歉, 不丢人.</strong></p><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>吃瓜群众可以折返了, 感兴趣的 Android 开发者可以继续往下看,  内容虽然简单, 但是个人觉得还是可以看一下的</p><p>对于三星的开发人员来说, 分析这个 Crash 非常简单, 直接在监控里面捞 Log 就可以了, 从后面的分析来看, 这个问题也很快被发现, 并进行了修复(持续了半年左右)；但是对这个问题感兴趣的其他开发者来说, 需要借助其他的工具</p><p>不过分析的过程也非常简单, 这里会把自己分析的思路和用到的工具记录下来, 方便大家使用</p><h2 id="从现象入手"><a href="#从现象入手" class="headerlink" title="从现象入手"></a>从现象入手</h2><p>上面结论有说, Persist 进程频繁 Crash 会导致系统触发自救, 进入 Recovery 界面, 所以用户很多反馈截图大家看到都是 Recovery 界面 , 如下<br><img src="/images/15904500815732.jpg" alt="Recovery 界面"></p><p>不过也有用户的界面直接显示了报错信息（我猜测是三星这边自己加的功能吧, 知道的麻烦告知一下）, 这个界面对我们分析代码来说很重要</p><p><img src="/images/15904500993262.jpg" alt="报错信息界面"></p><p>开发者对于这个堆栈是最熟悉不过了, 这是在一帧的渲染流程中, AOD 的 LocalDataView 在初始化的时候, 调用 getLunarCalendarInChina 方法出错了, LunarCalendar 是阴历的意思, 报错主要是因为找不到 common_data_leap_month 这个 string 值. </p><p>那么问题就很清楚了, 我们只需要查下面两个点</p><ol><li><strong>common_data_leap_month 这个 string 字段出现的代码逻辑</strong></li><li><strong>common_data_leap_month 这个 string 字段没有找到导致运行报错的原因</strong></li></ol><h2 id="分析代码"><a href="#分析代码" class="headerlink" title="分析代码"></a>分析代码</h2><p>首先看 common_data_leap_month 字段出现的代码逻辑, 既然上面已经列出了函数堆栈, 那么我们需要直接查看代码来分析这个问题产生的逻辑, 如何拿到代码？自然是需要反编译, 推荐的反编译工具： <a href="https://github.com/tp7309/TTDeDroid">TTDeDroid</a></p><p>反编译需要三星 AOD 的代码, 可以在 ApkMirror 里面搜 Always-On-Display,  就可以找到对应的文件, 可以看到三星的 AOD 更新的频率还是很频繁的, 通过用户反馈可以知道, 并非所有的用户都有这个问题, 且更新到新版本就没有问题了, 那么我们推测问题是出在老版本上的( 从堆栈来猜测应该是 V4.0 的版本 )</p><h3 id="正常版本-V5-2-05"><a href="#正常版本-V5-2-05" class="headerlink" title="正常版本_V5.2.05"></a>正常版本_V5.2.05</h3><p>最新版本是正常的, 没有 Crash 的情况</p><p>首先我们先看一下最新版本这一段代码的逻辑</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">month</span> <span class="operator">=</span> shouldShowLeapMonth(locale) ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth] </span><br></pre></td></tr></table></figure><p>这个就是说如果需要显示闰月, 那就取  common_date_leap_month 的值, 全局搜索 common_date_leap_month 发现最新版本里面是有定义这个值的 .</p><p>这里就可以看到 common_data_leap_month 字段出现的代码逻辑 : <strong>只有需要显示闰月的时候, 才会去获取 common_date_leap_month 这个字段的值, 其他 99.9% 的时候都不会触发这个值的获取</strong> .</p><p><img src="/images/15904501249419.jpg" alt="代码逻辑"></p><p>R.java 文件里面存在的 common_date_leap_month,  说明是存在的, 查看对应的 string.xml 中也有这个字段的定义</p><p><img src="/images/15904501422407.jpg" alt="R 文件"></p><p><img src="/images/15904501665686.jpg" alt="xml 文件"></p><h3 id="出问题版本-V4-1-70"><a href="#出问题版本-V4-1-70" class="headerlink" title="出问题版本_V4.1.70"></a>出问题版本_V4.1.70</h3><p>既然新版本没有问题, 且我们也知道了 common_date_leap_month 这个字段的代码逻辑 , 那么我们从老版本来看 common_date_leap_month 这个字段没有找到的原因.</p><p>这里找的这个老版本是有问题的, 使用这个版本(这几个版本) 的用户到了 23 号会出现频繁 Crash 的现象. 之所以我认为他是有问题的 , 是因为全局搜索 common_date_leap_month 字段,  发现 R 文件里面没有对应的字段, 对应的 string.xml  里面也没有这个字段和他对应的值, 也就是说 , 这里代码只使用, 没有定义和赋值( 那怎么编译过的呢 ？？？)</p><p><img src="/images/15904501980864.jpg" alt="只有使用,没有声明和赋值"></p><p>上面对应的代码逻辑如下,  可以看到函数名和行数和报错是一致的, InChina….</p><p><img src="/images/15904502144641.jpg" alt="对应的代码和行数"></p><p>具体对应的代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String <span class="title function_">getLunarCalendarInChina</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (sSolarLunarConverter == <span class="literal">null</span>) &#123;</span><br><span class="line">        sSolarLunarConverter = SECCalendarFeatures.getInstance().getSolarLunarConverter();</span><br><span class="line">        <span class="keyword">if</span> (sSolarLunarConverter == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Time</span> <span class="variable">time</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Time</span>();</span><br><span class="line">    time.set(Calendar.getInstance().getTimeInMillis());</span><br><span class="line">    sSolarLunarConverter.convertSolarToLunar(time.getYear(), time.getMonth(), time.getMonthDay());</span><br><span class="line">    String[] months = context.getResources().getStringArray(R.array.common_LunarMonth);</span><br><span class="line">    String[] days = context.getResources().getStringArray(R.array.common_LunarDay);</span><br><span class="line">    <span class="type">int</span> <span class="variable">convertMonth</span> <span class="operator">=</span> sSolarLunarConverter.getMonth();</span><br><span class="line">    <span class="type">int</span> <span class="variable">convertDay</span> <span class="operator">=</span> sSolarLunarConverter.getDay() - <span class="number">1</span>;</span><br><span class="line">    ACLog.d(TAG, <span class="string">&quot;Lunar month and day : &quot;</span> + convertMonth + <span class="string">&quot;, &quot;</span> + convertDay);</span><br><span class="line">    <span class="keyword">if</span> (convertMonth &lt; <span class="number">0</span> || convertMonth &gt;= months.length || convertDay &lt; <span class="number">0</span> || convertDay &gt;= days.length) &#123;</span><br><span class="line">        ACLog.e(TAG, <span class="string">&quot;getLunarCalendarInChina, array out of bound month = &quot;</span> + months.length + <span class="string">&quot;, days = &quot;</span> + days.length);</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">String</span> <span class="variable">chinaLunar</span> <span class="operator">=</span> (sSolarLunarConverter.isLeapMonth() ? context.getResources().getString(R.string.common_date_leap_month) + months[convertMonth] : months[convertMonth]) + days[convertDay];</span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> chinaLunar;</span><br><span class="line">    <span class="keyword">return</span> chinaLunar;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="问题出现的时间"><a href="#问题出现的时间" class="headerlink" title="问题出现的时间"></a>问题出现的时间</h3><p>根据我这边的调查, 发现这个问题其实在 AOD 这个应用从 v3.3.18 升级到 v4.0.57 的时候就出现了, 但是中间一直都没有出问题, 没有闰四月, 用户也就不会有问题, 测试也没有测出来, 直到 2019 年 6 月 24 号发布的 v4.2.44 版本才修复了这个问题</p><p><img src="/images/15904502326041.jpg" alt="4.3.44 版本修复"></p><p>v3.3.18 版本我们可以看到, common_date_leap_month  这个字段还是存在的</p><p><img src="/images/15904502473489.jpg" alt="v3.3.18"></p><p>升级到 v4.0.57(第一个出问题的版本) 之后 , 这个字段就没有了( 那怎么编译过的呢 ？？？)</p><p><img src="/images/15904515064964.jpg"></p><p><strong>理一下</strong>：</p><ol><li>AOD 从 v3.3.18 升级到 v4.0.57 的引入了这个问题(2018 年 10 月 24 号引入)</li><li>AOD 从 v4.2.24 升级到 v4.2.44 <strong>解决</strong>了这个问题(2019 年 6 月 24 号 修复)</li></ol><p>这期间所有 AOD 版本在 v4.0.57 - v4.2.44 却从来没有升级的机型, 都会在 2020-5-23 号这一天进入 Recovery 模式.</p><h2 id="编译问题"><a href="#编译问题" class="headerlink" title="编译问题"></a>编译问题</h2><p>上面一个很重要的点就是编译问题, 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 不存在</p><p><img src="/images/15904515163672.jpg" alt="罪魁祸首"></p><p>但是通过上面的分析我们发现, 频繁 Crash 的版本就是因为找不到 common_date_leap_month  这个字段才 Crash 的, 既然找不到那也应该编译不过才对, 但是既然我们拿到了 apk,  那说明编译也是没问题的.</p><p>这种情况出现的话, 一般有下面两种情况</p><ol><li>项目中有同一个 jar 包的不同版本, 因此编译和运行时使用了不同的 jar 包</li><li>编译使用的是 Maven, 项目中的依赖由于使用了不同版本的包, 最后打包的时候使用的不是你需要的版本</li></ol><p>猜测三星这次出问题的是因为第二种情况, 主项目和子 modules 使用了不同版本的包, 导致可以编译通过, 但是最终打包进项目的并不是编译时候的包, 就出现了运行时的 FATAL EXCEPTION : NoSuchFieldError ( 如果有知道具体原因的可以留言讨论一波 )</p><p><strong>开个玩笑, 这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉 , 不丢人</strong></p><p>想必三星对这一天也会铭心刻骨</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>上面的分析过程虽然比较简单, 但是有一些比较繁琐的工作, 比如找版本, 反编译 , 看代码逻辑等. 最终也算是找到了问题的根本原因 : 编译导致的问题碰上十几年才遇到一次的闰四月. 那么从这件事我们学到了什么呢?</p><ol><li>功能测试 : 闰月是日历中的一个功能 , 不算是常用功能 , 但是相对来说比较专业 , 像这种涉及到专业的地方, 一定要谨慎 , 列出所有可能出现的情况去做测试, 必要的情况下, 交给专业的人来评估测试用例</li><li>涉及到多方依赖编译的项目, 在编译的时候要确保引用的版本和本地的版本一致 , 对于多方依赖的模块, 每次发版本之前最好跟对应的依赖的模块确认</li><li>SystemUI (锁屏\状态栏\手势\多任务\ AOD 等) 模块和桌面模块是用户直接能感受到的模块, 这些模块对稳定性的要求要非常高, 因为一旦这些模块发生 FATAL , 带来的影响是非常巨大的, 就像三星这次, 所以这几个模块的开发人员也是最辛苦的, 既要承接一些亮点功能的实现, 又要保证稳定性, 同时也位于系统开发和应用开发中间, 两边都有很大的耦合, 着实不容易 (媳妇做这一块 6 年多了, 晚上加个鸡腿…)</li><li>厂商提供的系统更新和厂商自己的应用更新(尤其是系统应用) , 一定要及时更新, 每次系统和系统应用更新一般都会修复很多 Bug , 增强稳定性和性能. 系统和系统应用没有盈利的压力, 所以更新都是以提升质量为主, 可以放心更新.</li><li>开发者对这种事情要保持好奇和敬畏 : 好奇可以帮助我学到很多东西, 透过现象看本质 ; 敬畏可以让我知道自己知识的欠缺, 在庞大的 Android 体系中, 自己知道的不过沧海一粟..</li><li>这个问题对三星来说绝对是一个大的事故, 不过也贡献了一个经典的案例, 估计以后其他 App 或者手机厂商都会把这个纳入到功能测试中. 至于三星, 国内市场本身就不行了, S20 系列刚有些回暖, 又出现这档子事, 还是那句话 : 这是命, 得认, 道歉 , 不丢人」</li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;2020 年 5 月 23 号凌晨 1 点 30 左右, 大量三星手机用户的手机出现死机, 无限重启、进 Recovery 等问题, 并且操作不当会导致数据丢失, 并且上了知乎的热点, 售后点更是人满为患&lt;/p&gt;
&lt;p&gt;知乎的部分回答中, 大家更是对三星的家属送上了亲切的问候, 甚至有的人已经将这次事故与 Note7 事件、充电门、绿屏门事件相提并论, 甚至预言三星因此会退出国内市场 ; 有的人因为这个丢了 Offer , 有的人准备了很久的资源丢失, 有的人甚至直接把手机砸了…&lt;/p&gt;
&lt;p&gt;作为一个 Android 开发者, 我并不想对三星落井下石 , 我只想搞清楚到底是什么原因导致了这场事故 , 以及我们能从里面学到什么 . 我认为既然是 Android 系统出了问题, 我们有必要从技术的角度来分析为什么会出现这样的问题&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android App 链式唤醒分析</title>
    <link href="https://androidperformance.com/2020/05/07/Android-App-Chain-Wakeup/"/>
    <id>https://androidperformance.com/2020/05/07/Android-App-Chain-Wakeup/</id>
    <published>2020-05-07T15:16:20.000Z</published>
    <updated>2026-02-07T05:17:47.823Z</updated>
    
    <content type="html"><![CDATA[<p>MIUI 12 的发布, 将之前一直是应用开发者和 Rom 开发者斗争最激烈的部分展示给了普通消费者, 让普通消费者也知道了这场斗争的细节, 正所谓 “魔高一尺道高一丈” , Rom 开发者由于有更高的代码修改权限, 始终占据着上风 ; App 开发者当然也不甘示弱, 各种保活拉起黑科技层出不穷,甚至 Google 都参与到了这部分斗争中, 居中调和, 制定各种规则来规范双方. 当然斗争对双方来说都算是好事, 毕竟任何一方完全的胜利都会导致 “狡兔死走狗烹,飞鸟尽良弓藏”</p><p>不过双方斗争的受害者无疑还是使用手机的消费者 , App 如果斗争成功, 那么手机上各种后台进程乱跑, 杀不掉, 占用 CPU 和内存 , 这不是消费者想看到的 ; 如果 Rom 开发者斗争成功 , App 的体验必定会大打折扣 , 各位 App 开发者应该深有体会. </p><p>从文章最后一段可以看到, 其实各个手机厂商对付这一套都有自己的策略, 基本上都可以搞定自启动和关联启动. 至于隐私 , 李彦宏曾经说过 “<strong>中国人对隐私问题的态度更加开放，也相对来说没那么敏感。如果他们可以用隐私换取便利、安全或者效率。在很多情况下，他们就愿意这么做</strong>“ . 大家想想在微信里面复制一段话打开到淘宝就可以自动跳转到这个物品, 方不方便? 好不好用? 还想不想用? 剪贴板再借我看一看?</p><span id="more"></span><p>希望大家在隐私问题上不要打哈哈, 技术是把双刃剑, 如果隐私落到别有用心的人手上, 后果是很严重的, 就算不是为了自己, 为了下一代. 欧盟为什么要搞《通用数据保护条例》（General Data Protection Regulation，简称 GDPR）, 就是为了隐私. 举个例子 , 国内很多厂商的产品现在要区分是否在欧盟买, 如果是在欧盟卖的话, 就得把里面那些收集用户数据的功能都关掉 , 否则抓住了就能罚你罚到吐血 . 至于中国和印度, 随便收集.</p><p>本篇文章不涉及到隐私部分, 我是对隐私保护无条件支持的 . 这里只从技术的角度 , 来讲一下 MIUI 12 爆出来的应用自启动和关联唤醒的问题.</p><p>PS: <strong>大家在自己的手机上可能看不到我列的一些例子, 是因为我是用的 Android 10 的 AOSP 代码, 大部分的国产 Rom 都已经阻断了应用的这些行为.</strong></p><h1 id="技术名词解释"><a href="#技术名词解释" class="headerlink" title="技术名词解释"></a>技术名词解释</h1><p>首先解释几个技术名词, 方便大家对号入座</p><h2 id="进程启动"><a href="#进程启动" class="headerlink" title="进程启动"></a>进程启动</h2><p>在 Android 中 , 一个 App 包含六部分, 进程(必选) + Activity (可选) + BroadcastReceiver (可选)+Service (可选)+ContentProvider (可选) + 子进程(可选)</p><p>一个必选项加五个可选项, 组成了一个 App , 其中 Activity(可选) + BroadcastReceiver(可选)+Service(可选)+ContentProvider(可选) 这四个又称为 Android 的四大组件, 之所以这四兄弟这么特殊, 是因为这四个组件都可以单独启动,</p><p>但是这四兄弟启动之前, 系统都会检查对应的进程是否存在, 如果进程不存在 , 那么就需要先启动进程, 再启动这个组件. 我们在桌面上点击一个应用图标, 其实启动的就是他的 Activity , 系统会先创建进程, 然后再启动 Activity , 我们才可以看到对应的界面</p><p>一般自启动和关联启动, 一般不会直接启动 Activity , 因为 Activity 是用户可感知的 , 你在后台莫名其妙起了一个界面到前台, 用户分分钟卸了你 . 所以一般自启动和关联启动都是在 BroadcastReceiver (可选) + Service (可选) + ContentProvider (可选) 三个上面做文章. </p><h2 id="自启动"><a href="#自启动" class="headerlink" title="自启动"></a>自启动</h2><p>自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情.</p><h2 id="关联启动"><a href="#关联启动" class="headerlink" title="关联启动"></a>关联启动</h2><p>关联启动指的是借助其他应用来启动自己, 比如大家列出来的起点读书启动作家助手\电信营业厅\百词斩这种.</p><h2 id="启动阻断"><a href="#启动阻断" class="headerlink" title="启动阻断"></a>启动阻断</h2><p>启动阻断也叫切断唤醒, Rom 开发人员在四大组件启动的地方加入逻辑判断, 符合条件的组件才能拉起自己的进程 , 不符合条件的组件直接返回 , 这样就达到了启动阻断的目的.</p><p>当然这里面还有很多工作要做, 比如工作状态判断, 拉起合理性判断 , 一旦错误的阻断必然会引起用户的使用逻辑的断裂, 比如用户在一个 App 里面要拉起支付宝进行支付 , 结果启动支付宝的支付组件的时候被你给阻断了, 可以想象用户的愤怒</p><p>有了上面几个简单的概念, 下面我们就简单说一下自启动和关联启动的技术分析 .</p><h1 id="分析手段"><a href="#分析手段" class="headerlink" title="分析手段"></a>分析手段</h1><h2 id="Monkey"><a href="#Monkey" class="headerlink" title="Monkey"></a>Monkey</h2><p>要分析应用启动，首先需要安装大量应用，然后执行 Monkey，让大部分进程都跑起来。我使用的 Monkey 命令如下，跑完就自己去睡觉了</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">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</span><br></pre></td></tr></table></figure><h2 id="EventLog"><a href="#EventLog" class="headerlink" title="EventLog"></a>EventLog</h2><p>首先可以用 EventLog 来查看进程的启动信息，EventLog 会如实记录每个进程的启动、死亡信息。我使用下面的命令来进行进程启动和死亡的过滤</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb logcat -b events | egrep &quot;am_proc_died|am_proc_start&quot;</span><br></pre></td></tr></table></figure><h2 id="Dumpsys"><a href="#Dumpsys" class="headerlink" title="Dumpsys"></a>Dumpsys</h2><p>这里主要是使用了 Dumpsys activity ，主要是用来分析进程的各个组件的信息</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell dumpsys activity</span><br></pre></td></tr></table></figure><h1 id="自启动的技术分析"><a href="#自启动的技术分析" class="headerlink" title="自启动的技术分析"></a>自启动的技术分析</h1><p>自启动指的是不借助其他的应用, 通过监听系统的一些事件, 或者文件变化, 通过系统的机制, 把自己的进程拉起来处理事情. 这些系统的事件就包括开机广播 &#x2F; 网络变化 &#x2F; 媒体库扫描等(这里只列了一部分) .</p><h2 id="开机广播"><a href="#开机广播" class="headerlink" title="开机广播"></a>开机广播</h2><p>用户重启手机后, 系统会向注册了开机广播的应用发广播, 收到广播的应用就可以把自己拉起来, 开始处理对应的逻辑(拉起更多的进程) , 对应的广播如下:</p><figure class="highlight fortran"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">android.<span class="keyword">intent</span>.<span class="keyword">action</span>.BOOT_COMPLETED</span><br></pre></td></tr></table></figure><p>应用可以监听这个广播, 在用户重启手机后, 将自己唤醒, 处理自己的逻辑 , 比如说继续图片备份&#x2F;  继续同步联系人 &#x2F; 检查是否有固件更新 &#x2F; 推送最新的新闻等操作</p><p><strong>当然监听这个广播也是应用自启动的一个手段</strong></p><h3 id="案例-腾讯新闻监听开机广播拉起后台进程"><a href="#案例-腾讯新闻监听开机广播拉起后台进程" class="headerlink" title="案例: 腾讯新闻监听开机广播拉起后台进程"></a>案例: 腾讯新闻监听开机广播拉起后台进程</h3><p>典型的广播接受处理记录 : com.tencent.news 的 com.tencent.news.system.BootBroadcastReceiver 组件接收了 android.intent.action.BOOT_COMPLETED 广播 ,处理了 7s ,至于怎么处理, 当然是先把 com.tencent.news 这个进程拉起来, 然后执行 BootBroadcastReceiver  的 onReceive 方法 . 这是一个典型的自启动的例子</p><p><img src="/images/15888647034327.jpg"></p><h2 id="网络变化"><a href="#网络变化" class="headerlink" title="网络变化"></a>网络变化</h2><p>网络变化包括网络连接 &#x2F; 断开 &#x2F; wifi 移动网络切换等操作 , 一旦发生这些事件, 系统会向对应注册了这个事件的应用发送广播 . 对应的广播如下: </p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">android<span class="selector-class">.net</span><span class="selector-class">.conn</span>.CONNECTIVITY_CHANGE</span><br></pre></td></tr></table></figure><p>应用就可以监听这个广播来执行对应的逻辑 , 比如你在看直播 ,突然 wifi 断了切换成了 4G 网络, 应用就可以提醒用户是否使用移动网络继续观看, 毕竟网络直播还是很耗流量的.</p><p><strong>当然监听这个广播也是应用自启动的一个手段</strong></p><h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><p>下图可以看到五个监听了网络变大的广播接收器 (只显示了五个 , 其实有 200 多个) , 监听到网络变化后拉起自身</p><ol><li>包名: com.alibaba.android.rimet(钉钉)</li><li>接收器 : com.xiaomi.push.service.receivers.NetworkStatusReceiver</li><li>包名: cn.xuexi.android</li><li>接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver</li><li>包名: com.sina.weibo</li><li>接收器: com.xiaomi.push.service.receivers.NetworkStatusReceiver</li><li>包名: com.sdu.didi.psnger</li><li>接收器 : com.didi.sdk.push.PushNetReceiver</li><li>包名: com.meelive.ingkee</li><li>接收器 : com.network_optimization.NetWorkStateReceiver</li></ol><p>图中 packageName 就是对于的应用的包名, name 是启动的组件</p><p><img src="/images/15888647135669.jpg"></p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">android<span class="selector-class">.net</span><span class="selector-class">.wifi</span>.STATE_CHANGE</span><br></pre></td></tr></table></figure><p><img src="/images/15888647201101.jpg"></p><h2 id="媒体库扫描"><a href="#媒体库扫描" class="headerlink" title="媒体库扫描"></a>媒体库扫描</h2><p>系统监听到文件变化或者存储盘变化也会发通知给各个应用 , 比如说增加了一个图片或者文档 , 其对于的广播如下</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">android<span class="selector-class">.intent</span><span class="selector-class">.action</span><span class="selector-class">.MEDIA_SCANNER_STARTED</span></span><br><span class="line">android<span class="selector-class">.intent</span><span class="selector-class">.action</span><span class="selector-class">.MEDIA_SCANNER_FINISHED</span></span><br><span class="line">android<span class="selector-class">.intent</span><span class="selector-class">.action</span>.MEDIA_EJECT</span><br></pre></td></tr></table></figure><p><strong>当然监听这个广播也是应用自启动的一个手段</strong></p><h3 id="案例-jd-监听-MEDIA-SCANNER-STARTED-广播自启"><a href="#案例-jd-监听-MEDIA-SCANNER-STARTED-广播自启" class="headerlink" title="案例: jd 监听 MEDIA_SCANNER_STARTED 广播自启"></a>案例: jd 监听 MEDIA_SCANNER_STARTED 广播自启</h3><p>下面是一个典型的监听媒体库扫描广播进行自启动的案例:</p><p>com.jd.jrapp 的广播接收器 com.jd.jrapp.library.longconnection.receiver.BootReceiver 监听到 android.intent.action.MEDIA_SCANNER_STARTED 广播后, 启动自己进程开始处理</p><p><img src="/images/15888647284822.jpg"></p><h2 id="三方-SDK-个推"><a href="#三方-SDK-个推" class="headerlink" title="三方 SDK - 个推"></a>三方 SDK - 个推</h2><p>个推是各个应用接入的一个三方 SDK , 用于消息推送 , 但其实个推也集成了上面说的哪几种自启动的方式 , 包括 BOOT_COMPLETED,CONNECTIVITY_CHANGE,USER_PRESENT 这些</p><p>关于个推,由于可定制型比较强, 比如 在项目源码中添加一个继承自 com.igexin.sdk.PushService 的自定义 Service 就可以 , 所以从 EventLog 和 Dumpsys 没法直接看出来哪个用了个推来保活或者相互唤醒, 不过其对于的子进程得设置为 :pushservice , 可以根据这个做判断(有可能不准)</p><p>所以我们直接看个推的配置文档</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">service</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;com.igexin.sdk.PushService&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:permission</span>=<span class="string">&quot;android.permission.BIND_JOB_SERVICE&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:exported</span>=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:label</span>=<span class="string">&quot;NotificationCenter&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:process</span>=<span class="string">&quot;:pushservice&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">receiver</span> <span class="attr">android:name</span>=<span class="string">&quot;com.igexin.sdk.PushReceiver&quot;</span> &gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.intent.action.BOOT_COMPLETED&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.net.conn.CONNECTIVITY_CHANGE&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.intent.action.USER_PRESENT&quot;</span> /&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 以下三项为可选的 action 声明，有助于提高 service 存活率 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.intent.action.MEDIA_MOUNTED&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.intent.action.ACTION_POWER_CONNECTED&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.intent.action.ACTION_POWER_DISCONNECTED&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">receiver</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">activity</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;com.igexin.sdk.PushActivity&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:excludeFromRecents</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:exported</span>=<span class="string">&quot;false&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:process</span>=<span class="string">&quot;:pushservice&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:taskAffinity</span>=<span class="string">&quot;com.igexin.sdk.PushActivityTask&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:theme</span>=<span class="string">&quot;@android:style/Theme.Translucent.NoTitleBar&quot;</span> &gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">activity</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">activity</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:name</span>=<span class="string">&quot;com.igexin.sdk.GActivity&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:excludeFromRecents</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:exported</span>=<span class="string">&quot;true&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:process</span>=<span class="string">&quot;:pushservice&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:taskAffinity</span>=<span class="string">&quot;com.igexin.sdk.PushActivityTask&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:theme</span>=<span class="string">&quot;@android:style/Theme.Translucent.NoTitleBar&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>像最前面同提到的 BootComplete , com.ss.android.ugc.aweme:pushservice 可能就是接入了个推</p><p><img src="/images/15888647377742.jpg"></p><h1 id="关联启动的技术分析"><a href="#关联启动的技术分析" class="headerlink" title="关联启动的技术分析"></a>关联启动的技术分析</h1><p>关联启动指的是借助其他应用来启动自己 , 比如说很多 App 接入了同一个 SDK , 那么一旦你启动了接入这个 SDK 的应用 ,那么这个 SDK 就可以启动同样接入了这个 SDK 的其他应用, 达到关联唤醒的目的</p><p>这个 SDK 可以是 BAT 集团内部自研的通用 SDK , 也可以是三方提供的 SDK , 根据我自己的调试来看 , 大家提到的 xxx 启动了 xxx , 大部分都是通过三方 SDK 来实现的 , 大部分是用了极光推送.</p><p>下面就以一个案例来看极光推送是怎么利用一个已经启动的 App 来启动另外一个没有启动的 App 的.</p><p>首先我们看 EventLog 可以看到进程的启动信息 , 包括进程名, 进程 pid , 启动的组件, 启动的组件类型.</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[<span class="number">0</span>,<span class="number">19428</span>,<span class="number">10195</span>,<span class="keyword">com</span>.qq.reader,service,&#123;<span class="keyword">com</span>.qq.reader/<span class="keyword">cn</span>.jpush.android.service.DaemonService&#125;] </span><br></pre></td></tr></table></figure><p>上面这条 Log 解释一下就是 </p><ol><li>启动进程 : com.qq.reader(QQ 阅读) </li><li>启动 pid :19428 </li><li>启动组件: cn.jpush.android.service.DaemonService </li><li>组件类型: service</li></ol><h3 id="案例-起点-App-通过极光推送拉起-QQ-阅读"><a href="#案例-起点-App-通过极光推送拉起-QQ-阅读" class="headerlink" title="案例:起点 App 通过极光推送拉起 QQ 阅读"></a>案例:起点 App 通过极光推送拉起 QQ 阅读</h3><p>执行 adb shell am force-stop com.qq.reader  , 强制杀掉 QQ 阅读 , 观察 EventLog, 从下面的可以看到 , QQ 阅读的进程被拉起, 拉起的是 Service 这个组件, 其具体的内容是 com.qq.reader&#x2F;cn.jpush.android.service.DaemonService</p><p><img src="/images/15888647497735.jpg"></p><p>当然从 Event Log 里面我们看不出来是谁拉起了这个 Service ,这时候就需要 dumpsys activity 的帮助了, 由于是 Service 组件被拉起, 那么我们可以看 com.qq.reader 的 ServiceRecord , 其内容如下, 可以看到其 Connections 一栏, 是被 com.qidian.QDReader:pushcore 这个进程拉起的</p><p><img src="/images/15888647596688.jpg"></p><p>那么对应的 , 在小米的 MIUI 12 关联启动界面就会显示 : 起点读书 在 8:48 分拉起了 QQ 阅读(由于没有小米手机, 所以没法截图, 大家自己看高票答案 <a href="https://www.zhihu.com/question/391494145">https://www.zhihu.com/question/391494145</a> 自己脑补一下就可以了)</p><p>极光推送的官方文档其实也说的很清楚, 提供了被拉起和拉起别人的功能, 看你自己怎么用.</p><h3 id="极光官方文档唤醒配置"><a href="#极光官方文档唤醒配置" class="headerlink" title="极光官方文档唤醒配置"></a>极光官方文档唤醒配置</h3><p><img src="/images/15888647676816.jpg"></p><h3 id="极光关联启动文档"><a href="#极光关联启动文档" class="headerlink" title="极光关联启动文档"></a>极光关联启动文档</h3><p><img src="/images/15888647754201.jpg"></p><p><img src="/images/15888647806293.jpg"></p><h1 id="手机厂商应对"><a href="#手机厂商应对" class="headerlink" title="手机厂商应对"></a>手机厂商应对</h1><p>最前面的有说到, 进程管理是应用开发者和 Rom 开发者斗争最激烈的部分 , MIUI 选择了将斗争的过程展示给了普通消费者, 让普通消费者也知道了这场斗争的细节 . 其他的厂商也做了相同的事情 , 否则整个系统基本上是没法用的 , 就像我手上现在这台测试用的 pixel , 不断有进程因为整机内存太小被 LMK 杀掉, 然后马上被各种手段重新启动 , 耗电极快, 卡的连娘都不认识了.</p><p>我们从极光和个推的官方文档就可以看到各个手机厂商的应对方法和开关的界面, 这里列出来是方便大家进去看一下, 因为各个手机厂商的白名单配置不一样, 或者有时候用户自己改过但是忘记了 , 都可以进去重新设置一下 , 对于那些你退出了就不想让他继续活动的应用 ,果断去掉白名单.</p><h2 id="极光推送白名单配置"><a href="#极光推送白名单配置" class="headerlink" title="极光推送白名单配置"></a>极光推送白名单配置</h2><p><img src="/images/15888647965330.jpg"></p><h2 id="个推白名单配置"><a href="#个推白名单配置" class="headerlink" title="个推白名单配置"></a>个推白名单配置</h2><h3 id="EMUI-OS（华为）"><a href="#EMUI-OS（华为）" class="headerlink" title="EMUI OS（华为）"></a>EMUI OS（华为）</h3><ul><li>自启动管理：需要把应用加到【自启动管理】列表，否则杀进程或重新开机后进程不会开启，只能手动开启应用</li><li>后台应用保护：需要手动把应用加到此列表，否则设备进入睡眠后会自动杀掉应用进程，只有手动开启应用才能恢复运行</li><li>通知管理：应用状态有三种：提示、允许、禁止。禁止应用则通知栏不会有任何提醒</li></ul><h3 id="Flyme-OS（魅族）"><a href="#Flyme-OS（魅族）" class="headerlink" title="Flyme OS（魅族）"></a>Flyme OS（魅族）</h3><ul><li>自启动管理：需要把应用加到【自启动管理】列表，否则杀进程或重新开机后进程无法开启</li><li>通知栏推送：关闭应用通知则收到消息不会有任何展示</li></ul><h3 id="Funtouch-OS（VIVO）"><a href="#Funtouch-OS（VIVO）" class="headerlink" title="Funtouch OS（VIVO）"></a>Funtouch OS（VIVO）</h3><ul><li>自启动管理：需要将应用加入“i 管家”中的【自启动管理】列表，否则重启手机后进程不会自启。但强制手动杀进程，即使加了这个列表中，后续进程也无法自启动。</li></ul><h3 id="Color-OS（OPPO）"><a href="#Color-OS（OPPO）" class="headerlink" title="Color OS（OPPO）"></a>Color OS（OPPO）</h3><ul><li>冻结应用管理：需要将应用加入纯净后台，否则锁屏状态下无法及时收到消息</li><li>自启动管理：将应用加入【自启动管理】列表的同时，还需要到设置-应用程序-正在运行里锁定应用进程，否则杀进程或者开机后进程不会开启，只能手动开启应用</li></ul><h3 id="MIUI-OS-小米"><a href="#MIUI-OS-小米" class="headerlink" title="MIUI OS (小米)"></a>MIUI OS (小米)</h3><ul><li>自启动管理：需要把应用加到【自启动管理】列表，否则杀进程或重新开机后进程无法开启</li><li>省电策略：需要禁用应用省电策略，否则后台几分钟后会被系统限制联网</li><li>MIUI 7 神隐模式： 允许用户设置后台联网应用，开启后应用即可在后台保持联网，否则应用进入后台时，应用无法正常接收消息。【设置】-&gt;【电量和性能】-&gt;【神隐模式】</li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;MIUI 12 的发布, 将之前一直是应用开发者和 Rom 开发者斗争最激烈的部分展示给了普通消费者, 让普通消费者也知道了这场斗争的细节, 正所谓 “魔高一尺道高一丈” , Rom 开发者由于有更高的代码修改权限, 始终占据着上风 ; App 开发者当然也不甘示弱, 各种保活拉起黑科技层出不穷,甚至 Google 都参与到了这部分斗争中, 居中调和, 制定各种规则来规范双方. 当然斗争对双方来说都算是好事, 毕竟任何一方完全的胜利都会导致 “狡兔死走狗烹,飞鸟尽良弓藏”&lt;/p&gt;
&lt;p&gt;不过双方斗争的受害者无疑还是使用手机的消费者 , App 如果斗争成功, 那么手机上各种后台进程乱跑, 杀不掉, 占用 CPU 和内存 , 这不是消费者想看到的 ; 如果 Rom 开发者斗争成功 , App 的体验必定会大打折扣 , 各位 App 开发者应该深有体会. &lt;/p&gt;
&lt;p&gt;从文章最后一段可以看到, 其实各个手机厂商对付这一套都有自己的策略, 基本上都可以搞定自启动和关联启动. 至于隐私 , 李彦宏曾经说过 “&lt;strong&gt;中国人对隐私问题的态度更加开放，也相对来说没那么敏感。如果他们可以用隐私换取便利、安全或者效率。在很多情况下，他们就愿意这么做&lt;/strong&gt;“ . 大家想想在微信里面复制一段话打开到淘宝就可以自动跳转到这个物品, 方不方便? 好不好用? 还想不想用? 剪贴板再借我看一看?&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - SurfaceFlinger 解读</title>
    <link href="https://androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/"/>
    <id>https://androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/</id>
    <published>2020-02-14T02:25:45.000Z</published>
    <updated>2026-02-07T05:17:47.846Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第五篇，主要是对 SurfaceFlinger 的工作流程进行简单介绍，介绍了 SurfaceFlinger 中几个比较重要的线程，包括 Vsync 信号的解读、应用的 Buffer 展示、卡顿判定等，由于 Vsync 这一块在 <a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a> 和 <a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这两篇文章里面已经介绍过，这里就不再做详细的讲解了。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#app">App 部分</a></li><li><a href="#bq">BufferQueue 部分</a></li><li><a href="#sf">SurfaceFlinger 部分</a></li><li><a href="#hwc">HWComposer 部分</a></li><li><a href="#refs">参考文章</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a> </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>这里直接上官方对于 <a href="https://source.android.google.cn/devices/graphics/arch-sf-hwc.html?authuser=0&hl=de">SurfaceFlinger 的定义</a></p><ol><li>大多数应用在屏幕上一次显示三个层：屏幕顶部的状态栏、底部或侧面的导航栏以及应用界面。有些应用会拥有更多或更少的层（例如，默认主屏幕应用有一个单独的壁纸层，而全屏游戏可能会隐藏状态栏）。每个层都可以单独更新。状态栏和导航栏由系统进程渲染，而应用层由应用渲染，两者之间不进行协调。</li><li>设备显示会按一定速率刷新，在手机和平板电脑上通常为 60 fps。如果显示内容在刷新期间更新，则会出现撕裂现象；因此，请务必只在周期之间更新内容。在可以安全更新内容时，系统便会收到来自显示设备的信号。由于历史原因，我们将该信号称为 VSYNC 信号。</li><li>刷新率可能会随时间而变化，例如，一些移动设备的帧率范围在 58 fps 到 62 fps 之间，具体要视当前条件而定。对于连接了 HDMI 的电视，刷新率在理论上可以下降到 24 Hz 或 48 Hz，以便与视频相匹配。由于每个刷新周期只能更新屏幕一次，因此以 200 fps 的帧率为显示设备提交缓冲区就是一种资源浪费，因为大多数帧会被舍弃掉。SurfaceFlinger 不会在应用每次提交缓冲区时都执行操作，而是在显示设备准备好接收新的缓冲区时才会唤醒。</li><li>当 VSYNC 信号到达时，SurfaceFlinger 会遍历它的层列表，以寻找新的缓冲区。如果找到新的缓冲区，它会获取该缓冲区；否则，它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容，因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区，则该层会被忽略。</li><li>SurfaceFlinger 在收集可见层的所有缓冲区之后，便会询问 Hardware Composer 应如何进行合成。」</li></ol><p>—- 引用自<a href="https://source.android.google.cn/devices/graphics/arch-sf-hwc.html?authuser=0&hl=de">SurfaceFlinger 和 Hardware Composer</a></p><p>下面是上述流程所对应的流程图， 简单地说， SurfaceFlinger 最主要的功能:<strong>SurfaceFlinger 接受来自多个来源的数据缓冲区，对它们进行合成，然后发送到显示设备。</strong></p><p><img src="/images/15816781462135.jpg"></p><p>那么 Systrace 中，我们关注的重点就是上面这幅图对应的部分</p><ol><li>App 部分</li><li>BufferQueue 部分</li><li>SurfaceFlinger 部分</li><li>HWComposer 部分</li></ol><p>这四部分，在 Systrace 中都有可以对应的地方，以时间发生的顺序排序就是 1、2、3、4，下面我们从 Systrace 的这四部分来看整个渲染的流程</p><p><a id="app"></a></p><h2 id="App-部分"><a href="#App-部分" class="headerlink" title="App 部分"></a>App 部分</h2><p>关于 App 部分，其实在<a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a>这篇文章里面已经说得比较清楚了，不清楚的可以去这篇文章里面看，其主要的流程如下图：</p><p><img src="/images/15818258189902.jpg"></p><p>从 SurfaceFlinger 的角度来看，App 部分主要负责生产 SurfaceFlinger 合成所需要的 Surface。</p><p>App 与 SurfaceFlinger 的交互主要集中在三点</p><ol><li>Vsync 信号的接收和处理</li><li>RenderThread 的 dequeueBuffer</li><li>RenderThread 的 queueBuffer</li></ol><h3 id="Vsync-信号的接收和处理"><a href="#Vsync-信号的接收和处理" class="headerlink" title="Vsync 信号的接收和处理"></a>Vsync 信号的接收和处理</h3><p>关于这部分内容可以查看 <a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章，App 和 SurfaceFlinger 的第一个交互点就是 Vsync 信号的请求和接收，如上图中第一条标识，Vsync-App 信号到达，就是指的是 SurfaceFlinger 的 Vsync-App 信号。应用收到这个信号后，开始一帧的渲染准备</p><p><img src="/images/15822547481351.jpg"></p><h3 id="RenderThread-的-dequeueBuffer"><a href="#RenderThread-的-dequeueBuffer" class="headerlink" title="RenderThread 的 dequeueBuffer"></a>RenderThread 的 dequeueBuffer</h3><p>dequeue 有出队的意思，dequeueBuffer 顾名思义，就是从队列中拿出一个 Buffer，这个队列就是 SurfaceFlinger 中的 BufferQueue。如下图，应用开始渲染前，首先需要通过 Binder 调用从 SurfaceFlinger 的 BufferQueue 中获取一个 Buffer，其流程如下：</p><p><strong>App 端的 Systrace 如下所示</strong><br><img src="/images/15822556410563.jpg" alt="-w1249"></p><p><strong>SurfaceFlinger 端的 Systrace 如下所示</strong><br><img src="/images/15822558376614.jpg" alt="-w826"></p><h3 id="RenderThread-的-queueBuffer"><a href="#RenderThread-的-queueBuffer" class="headerlink" title="RenderThread 的 queueBuffer"></a>RenderThread 的 queueBuffer</h3><p>queue 有入队的意思，queueBuffer 顾名思义就是讲 Buffer 放回到 BufferQueue，App 处理完 Buffer 后（写入具体的 drawcall），会把这个 Buffer 通过 eglSwapBuffersWithDamageKHR -&gt; queueBuffer 这个流程，将 Buffer 放回 BufferQueue，其流程如下</p><p><strong>App 端的 Systrace 如下所示</strong><br><img src="/images/15822960954718.jpg" alt="-w1165"></p><p><strong>SurfaceFlinger 端的 Systrace 如下所示</strong><br><img src="/images/15822964913781.jpg" alt="-w1295"></p><p>通过上面三部分，大家应该对下图中的流程会有一个比较直观的了解了<br><img src="/images/15822965692055.jpg" alt="-w410"></p><p><a id="bq"></a></p><h2 id="BufferQueue-部分"><a href="#BufferQueue-部分" class="headerlink" title="BufferQueue 部分"></a>BufferQueue 部分</h2><p>BufferQueue 部分其实在<a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/#BufferQueue">Systrace 基础知识 - Triple Buffer 解读</a> 这里有讲，如下图，结合上面那张图，每个有显示界面的进程对应一个 BufferQueue，使用方创建并拥有 BufferQueue 数据结构，并且可存在于与其生产方不同的进程中，BufferQueue 工作流程如下：</p><p><img src="/images/15823652509728.jpg"></p><p>上图主要是 dequeue、queue、acquire、release ，在这个例子里面，App 是<strong>生产者</strong>，负责填充显示缓冲区（Buffer）；SurfaceFlinger 是<strong>消费者</strong>，将各个进程的显示缓冲区做合成操作</p><ol><li>dequeue(生产者发起) ： 当生产者需要缓冲区时，它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区，并指定缓冲区的宽度、高度、像素格式和使用标记。</li><li>queue(生产者发起)：生产者填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。</li><li>acquire(消费者发起) ：消费者通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容</li><li>release(消费者发起) ：当消费者操作完成后，它会通过调用 releaseBuffer() 将该缓冲区返回到队列</li></ol><p><a id="sf"></a></p><h2 id="SurfaceFlinger-部分"><a href="#SurfaceFlinger-部分" class="headerlink" title="SurfaceFlinger 部分"></a>SurfaceFlinger 部分</h2><h3 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h3><p>从最前面我们知道 SurfaceFlinger 的主要工作就是合成：</p><blockquote><p>当 VSYNC 信号到达时，SurfaceFlinger 会遍历它的层列表，以寻找新的缓冲区。如果找到新的缓冲区，它会获取该缓冲区；否则，它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容，因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区，则该层会被忽略。SurfaceFlinger 在收集可见层的所有缓冲区之后，便会询问 Hardware Composer 应如何进行合成。</p></blockquote><p>其 Systrace 主线程可用看到其主要是在收到 Vsync 信号后开始工作<br><img src="/images/15822972813466.jpg" alt="-w1296"></p><p>其对应的代码如下,主要是处理两个 Message</p><ol><li>MessageQueue::INVALIDATE — 主要是执行 handleMessageTransaction 和 handleMessageInvalidate 这两个方法</li><li>MessageQueue::REFRESH — 主要是执行 handleMessageRefresh 方法</li></ol><p>frameworks&#x2F;native&#x2F;services&#x2F;surfaceflinger&#x2F;SurfaceFlinger.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">SurfaceFlinger::onMessageReceived</span><span class="params">(<span class="type">int32_t</span> what)</span> NO_THREAD_SAFETY_ANALYSIS </span>&#123;</span><br><span class="line">    <span class="built_in">ATRACE_CALL</span>();</span><br><span class="line">    <span class="keyword">switch</span> (what) &#123;</span><br><span class="line">        <span class="keyword">case</span> MessageQueue::INVALIDATE: &#123;</span><br><span class="line">            ......</span><br><span class="line">            <span class="type">bool</span> refreshNeeded = <span class="built_in">handleMessageTransaction</span>();</span><br><span class="line">            refreshNeeded |= <span class="built_in">handleMessageInvalidate</span>();</span><br><span class="line">            ......</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">case</span> MessageQueue::REFRESH: &#123;</span><br><span class="line">            <span class="built_in">handleMessageRefresh</span>();</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//handleMessageInvalidate 实现如下</span></span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">SurfaceFlinger::handleMessageInvalidate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">ATRACE_CALL</span>();</span><br><span class="line">    <span class="type">bool</span> refreshNeeded = <span class="built_in">handlePageFlip</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mVisibleRegionsDirty) &#123;</span><br><span class="line">        <span class="built_in">computeLayerBounds</span>();</span><br><span class="line">        <span class="keyword">if</span> (mTracingEnabled) &#123;</span><br><span class="line">            mTracing.<span class="built_in">notify</span>(<span class="string">&quot;visibleRegionsDirty&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; layer : mLayersPendingRefresh) &#123;</span><br><span class="line">        Region visibleReg;</span><br><span class="line">        visibleReg.<span class="built_in">set</span>(layer-&gt;<span class="built_in">getScreenBounds</span>());</span><br><span class="line">        <span class="built_in">invalidateLayerStack</span>(layer, visibleReg);</span><br><span class="line">    &#125;</span><br><span class="line">    mLayersPendingRefresh.<span class="built_in">clear</span>();</span><br><span class="line">    <span class="keyword">return</span> refreshNeeded;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//handleMessageRefresh 实现如下， SurfaceFlinger 的大部分工作都是在handleMessageRefresh 中发起的</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">SurfaceFlinger::handleMessageRefresh</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">ATRACE_CALL</span>();</span><br><span class="line"></span><br><span class="line">    mRefreshPending = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">const</span> <span class="type">bool</span> repaintEverything = mRepaintEverything.<span class="built_in">exchange</span>(<span class="literal">false</span>);</span><br><span class="line">    <span class="built_in">preComposition</span>();</span><br><span class="line">    <span class="built_in">rebuildLayerStacks</span>();</span><br><span class="line">    <span class="built_in">calculateWorkingSet</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span>&amp; [token, display] : mDisplays) &#123;</span><br><span class="line">        <span class="built_in">beginFrame</span>(display);</span><br><span class="line">        <span class="built_in">prepareFrame</span>(display);</span><br><span class="line">        <span class="built_in">doDebugFlashRegions</span>(display, repaintEverything);</span><br><span class="line">        <span class="built_in">doComposition</span>(display, repaintEverything);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">logLayerStats</span>();</span><br><span class="line"></span><br><span class="line">    <span class="built_in">postFrame</span>();</span><br><span class="line">    <span class="built_in">postComposition</span>();</span><br><span class="line"></span><br><span class="line">    mHadClientComposition = <span class="literal">false</span>;</span><br><span class="line">    mHadDeviceComposition = <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span>&amp; [token, displayDevice] : mDisplays) &#123;</span><br><span class="line">        <span class="keyword">auto</span> display = displayDevice-&gt;<span class="built_in">getCompositionDisplay</span>();</span><br><span class="line">        <span class="type">const</span> <span class="keyword">auto</span> displayId = display-&gt;<span class="built_in">getId</span>();</span><br><span class="line">        mHadClientComposition =</span><br><span class="line">                mHadClientComposition || <span class="built_in">getHwComposer</span>().<span class="built_in">hasClientComposition</span>(displayId);</span><br><span class="line">        mHadDeviceComposition =</span><br><span class="line">                mHadDeviceComposition || <span class="built_in">getHwComposer</span>().<span class="built_in">hasDeviceComposition</span>(displayId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mVsyncModulator.<span class="built_in">onRefreshed</span>(mHadClientComposition);</span><br><span class="line"></span><br><span class="line">    mLayersWithQueuedFrames.<span class="built_in">clear</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>handleMessageRefresh 中按照重要性主要有下面几个功能</p><ol><li>准备工作<ol><li>preComposition();</li><li>rebuildLayerStacks();</li><li>calculateWorkingSet();</li></ol></li><li>合成工作<ol><li>begiFrame(display);</li><li>prepareFrame(display);</li><li>doDebugFlashRegions(display, repaintEverything);</li><li>doComposition(display, repaintEverything);</li></ol></li><li>收尾工作<ol><li>logLayerStats();</li><li>postFrame();</li><li>postComposition();</li></ol></li></ol><p>由于显示系统有非常庞大的细节，这里就不一一进行讲解了，如果你的工作在这一部分，那么所有的流程都需要熟悉并掌握，如果只是想熟悉流程，那么不需要太深入，知道 SurfaceFlinger 的主要工作逻辑即可</p><h3 id="掉帧"><a href="#掉帧" class="headerlink" title="掉帧"></a>掉帧</h3><p>通常我们通过 Systrace 判断应用是否<strong>掉帧</strong>的时候，一般是直接看 SurfaceFlinger 部分，主要是下面几个步骤</p><ol><li>SurfaceFlinger 的主线程在每个 Vsync-SF 的时候是否没有合成？</li><li>如果没有合成操作，那么需要看没有合成的原因：<ol><li>因为 SurfaceFlinger 检查发现没有可用的 Buffer 而没有合成操作？</li><li>因为 SurfaceFlinger 被其他的工作占用（比如截图、HWC 等）？</li><li>因为 SurfaceFlinger 在等 presentFence ？</li><li>因为 SurfaceFlinger 在等 GPU fence？</li></ol></li><li>如果有合成操作，那么需要看 <strong>你关心的 App</strong> 的 可用 Buffer 个数是否正常：如果 App 此时可用 Buffer 为 0，那么看 App 端为何没有及时 queueBuffer（这就一般是应用自身的问题了），因为 SurfaceFlinger 合成操作触发可能是其他的进程有可用的 Buffer</li></ol><p>关于这一部分的 Systrace 怎么看，在 <a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/#%E9%80%BB%E8%BE%91%E6%8E%89%E5%B8%A7">Systrace 基础知识 - Triple Buffer 解读-掉帧检测</a> 部分已经有比较详细的解读，大家可以过去看这一段</p><p><a id="hwc"></a></p><h2 id="HWComposer-部分"><a href="#HWComposer-部分" class="headerlink" title="HWComposer 部分"></a>HWComposer 部分</h2><p>关于 HWComposer 的功能部分我们就直接看 <a href="https://source.android.google.cn/devices/graphics/arch-sf-hwc.html?authuser=0&hl=de">官方的介绍</a> 即可</p><ol><li>Hardware Composer HAL (HWC) 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL，其实现是特定于设备的，而且通常由显示设备硬件原始设备制造商 (OEM) 完成。</li><li>当您考虑使用叠加平面时，很容易发现这种方法的好处，它会在显示硬件（而不是 GPU）中合成多个缓冲区。例如，假设有一部普通 Android 手机，其屏幕方向为纵向，状态栏在顶部，导航栏在底部，其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成（后一种方法可以显著提高效率）：<ol><li>将应用内容渲染到暂存缓冲区中，然后在其上渲染状态栏，再在其上渲染导航栏，最后将暂存缓冲区传送到显示硬件。</li><li>将三个缓冲区全部传送到显示硬件，并指示它从不同的缓冲区读取屏幕不同部分的数据。</li></ol></li><li>显示处理器功能差异很大。叠加层的数量（无论层是否可以旋转或混合）以及对定位和叠加的限制很难通过 API 表达。为了适应这些选项，HWC 会执行以下计算（由于硬件供应商可以定制决策代码，因此可以在每台设备上实现最佳性能）：<ol><li>SurfaceFlinger 向 HWC 提供一个完整的层列表，并询问“您希望如何处理这些层？”</li><li>HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。</li><li>SurfaceFlinger 会处理所有 GLES 合成，将输出缓冲区传送到 HWC，并让 HWC 处理其余部分。</li></ol></li><li>当屏幕上的内容没有变化时，叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时，尤其如此。在此类情况下，HWC 可以选择为部分或全部层请求 GLES 合成，并保留合成的缓冲区。如果 SurfaceFlinger 返回来要求合成同一组缓冲区，HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。</li><li>运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成，这意味着应用使用的层数会对能耗和性能产生重大影响。</li></ol><p>——– 引用自<a href="https://source.android.google.cn/devices/graphics/arch-sf-hwc.html?authuser=0&hl=de">SurfaceFlinger 和 Hardware Composer</a></p><p>我们继续接着看 SurfaceFlinger 主线程的部分，对应上面步骤中的第三步，下图可以看到 SurfaceFlinger 与 HWC 的通信部分<br><img src="/images/15823673746926.jpg" alt="-w1149"></p><p>这也对应了最上面那张图的后面部分<br><img src="/images/15823674500263.jpg" alt="-w563"></p><p>不过这其中的细节非常多，这里就不详细说了。至于为什么要提 HWC，因为 HWC 不仅是渲染链路上重要的一环，其性能也会影响整机的性能，<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/#3-WHC-Service-%E6%89%A7%E8%A1%8C%E8%80%97%E6%97%B6">Android 中的卡顿丢帧原因概述 - 系统篇</a> 这篇文章里面就有列有 HWC 导致的卡顿问题（性能不足，中断信号慢等问题）</p><p>想了解更多 HWC 的知识，可以参考这篇文章<a href="https://www.jianshu.com/p/824a9ddf68b9">Android P 图形显示系统（一）硬件合成HWC2</a>,当然，作者的<a href="https://www.jianshu.com/nb/28304383">Android P 图形显示系</a>这个系列大家可以仔细看一下</p><p><a id="refs"></a></p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ol><li><a href="https://www.jianshu.com/p/824a9ddf68b9">Android P 图形显示系统（一）硬件合成HWC2</a></li><li><a href="https://www.jianshu.com/nb/28304383">Android P 图形显示系统</a></li><li><a href="https://source.android.google.cn/devices/graphics/arch-sf-hwc.html?authuser=0&hl=de">SurfaceFlinger 的定义</a></li><li><a href="https://github.com/openthos/display-analysis/blob/master/repo/android%E5%90%AF%E5%8A%A8%E5%9B%BE%E5%BD%A2%E7%95%8C%E9%9D%A2%E7%9B%B8%E5%85%B3log%E6%8A%A5%E5%91%8A/surface%E5%88%86%E6%9E%90%E6%96%87%E6%A1%A3.md">surfacefliner</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第五篇，主要是对 SurfaceFlinger 的工作流程进行简单介绍，介绍了 SurfaceFlinger 中几个比较重要的线程，包括 Vsync 信号的解读、应用的 Buffer 展示、卡顿判定等，由于 Vsync 这一块在 &lt;a href=&quot;https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/&quot;&gt;Systrace 基础知识 - Vsync 解读&lt;/a&gt; 和 &lt;a href=&quot;https://androidperformance.com/2019/10/22/Android-Choreographer/&quot;&gt;Android 基于 Choreographer 的渲染机制详解&lt;/a&gt; 这两篇文章里面已经介绍过，这里就不再做详细的讲解了。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 开发者学习路线(2020 版本)</title>
    <link href="https://androidperformance.com/2020/02/03/android-development-learning-path-2020-edition/"/>
    <id>https://androidperformance.com/2020/02/03/android-development-learning-path-2020-edition/</id>
    <published>2020-02-02T16:06:25.000Z</published>
    <updated>2026-02-07T05:17:47.859Z</updated>
    
    <content type="html"><![CDATA[<p>Medium 上 @MindOrks 发布了一篇 <a href="https://medium.com/mindorks/android-development-learning-path-2020-edition-3f464ac56dbf">2020 年 Android 程序员的学习线路</a>，鉴于一部分人无法阅读原文(你懂得原因)，我把这篇文章的内容结合自己的 2020 年的学习计划，一起发出来，给大家一个参考</p><p><strong>原文比较简单，并没有介绍为什么要推荐这些，只是单纯地列了一下知识点，我这边针对每个知识点做一些简单的介绍，有些知识点原文并没有提到，我会根据自己的理解加上，仅供参考</strong></p><p>这篇文章主要针对 Android 开发者，如果你是新手，那么下面的内容可以帮助你找到学习的线路；如果你是老手，这篇文章列出的内容也可以帮助你查漏补缺。如果各位有什么其他的建议，欢迎留言交流</p><span id="more"></span><h1 id="Programming"><a href="#Programming" class="headerlink" title="Programming"></a>Programming</h1><h2 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h2><p>Java 是 Android App 开发默认的语言, Android Framework 也是默认使用 Java 语言，熟练掌握 Java 语言是 Android 开发者的必备技能。</p><p>希望深入 Java 虚拟机的同学，也可以参考下面两本书：</p><ol><li>周志明的<a href="https://book.douban.com/subject/34907497/">《深入理解Java虚拟机（第3版）》</a></li><li>邓老师的 <a href="https://book.douban.com/subject/33390277/">《深入理解Android Java 虚拟机 ART》</a></li></ol><h2 id="Kotlin"><a href="#Kotlin" class="headerlink" title="Kotlin"></a>Kotlin</h2><p>Google 几年前就开始走 “Kotlin First” 的路线，目前很多官方的文档和 Demo 都是使用 Kotlin 语言作为默认，Kotlin 的重要性不言而喻。</p><p>Google 官方也出了个<a href="https://clmirror.storage.googleapis.com/codelabs/java-to-kotlin-zh/index.html#0">“Refactoring to Kotlin”</a>的教程,其介绍如下：</p><blockquote><p>此 Codelab 的适用对象为任何使用 Java 并考虑将其项目迁移到 Kotlin 的开发者。我们将从数个 Java 类入手，引导您使用 IDE 将它们转换为 Kotlin。接着，我们会审视转换后的代码，研究如何加以改善，使其更符合使用习惯，同时避免常见错误</p></blockquote><h2 id="Flutter"><a href="#Flutter" class="headerlink" title="Flutter"></a>Flutter</h2><p><a href="https://github.com/flutter/flutter">Flutter</a> 作为 Google 的亲儿子，其官方的扶持力度大家有目共睹。 Flutter 于几天前发布了<a href="https://github.com/flutter/flutter/releases/tag/v1.12.13%2Bhotfix.7">v1.12.13_hotfix.7</a> 版本，修复了几个比较严重的 Bug，如<a href="https://juejin.im/post/5e369be9f265da3e272912dc">Flutter 1.12 最新 hotfix 与 2020 路线计划</a> 这篇文章介绍所述，“v1.12.13+hotfix.7 版本主要在于解决了我比较关心的三个问题，包括： reportFullyDrawn 异常、华为手机上崩溃、光标和键盘输入异常 这几个问题。”.感兴趣也可以看一下其 1 月 30 号发布的 <a href="https://github.com/flutter/flutter/wiki/Roadmap">2020 Roadmap</a></p><p>Flutter 的发展大家可以看一下 Gityuan 的这一篇<a href="http://gityuan.com/flutter/">Flutter 跨平台演进及架构开篇</a>,目前字节跳动的多个 App 已经接入 Flutter 进行混合开发。个人对 2020 年 Flutter 不再持观望态度，读者可以根据自己的技术规划决定是否开始学习</p><h1 id="Android-Studio"><a href="#Android-Studio" class="headerlink" title="Android Studio"></a>Android Studio</h1><h2 id="Android-Studio-IDE-Overview"><a href="#Android-Studio-IDE-Overview" class="headerlink" title="Android Studio IDE Overview"></a>Android Studio IDE Overview</h2><p>Android Studio 作为 Android 默认的开发者工具，目前的版本更新已经解决了诸多之前的性能问题，虽然目前对硬件资源的要求仍然比较高，但是一旦你接受了这个设定，真香预警！</p><p>AS 主要需要熟悉下面几点</p><ol><li>AS 快捷键</li><li>AS 插件</li><li>AS Profile （内存、CPU、IO、NetWork）</li></ol><h2 id="Project-Structure-—-Java-x2F-Kotlin-x2F-Flutter-XML-gradle-files"><a href="#Project-Structure-—-Java-x2F-Kotlin-x2F-Flutter-XML-gradle-files" class="headerlink" title="Project Structure — Java&#x2F;Kotlin&#x2F;Flutter, XML, .gradle files"></a>Project Structure — Java&#x2F;Kotlin&#x2F;Flutter, XML, .gradle files</h2><p>熟悉各种项目的目录结构，资源文件、Gradle 文件</p><h1 id="Android-基础知识"><a href="#Android-基础知识" class="headerlink" title="Android 基础知识"></a>Android 基础知识</h1><h2 id="四大组件"><a href="#四大组件" class="headerlink" title="四大组件"></a>四大组件</h2><p>这部分不必做过多的解释，下面列出的就是大家熟悉的 Android 四大组件，Android 开发的基础</p><ol><li>Activity — Activity Lifecycle, Tasks &amp; Back Stack</li><li>Service</li><li>Broadcast Receiver</li><li>Content Provider</li></ol><h2 id="Intents"><a href="#Intents" class="headerlink" title="Intents"></a>Intents</h2><ol><li>Types of Intent - Implicit, Explicit</li><li>Intent Filter</li></ol><h2 id="Static-User-Interface"><a href="#Static-User-Interface" class="headerlink" title="Static User Interface"></a>Static User Interface</h2><ol><li>View — Button, ImageView, TextView, EditText, and etc :这是开发中会遇到的常用的组件，许多复杂的布局都是用简单基础的 View 组合而成</li><li>ViewGroup - LinearLayout, RelativeLayout, FrameLayout:三大传统布局，适用于不同的场合</li><li>ConstraintLayout : Google 新推的布局，目前已经取代 RelativeLayout 成为默认的 App 布局，具体使用可以参考<a href="https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout?hl=zh-cn">官方文档</a></li></ol><h2 id="Dynamic-User-Interface"><a href="#Dynamic-User-Interface" class="headerlink" title="Dynamic User Interface"></a>Dynamic User Interface</h2><ol><li>RecyclerView - 列表类的布局首选控件，性能相对 ListView 要好一些，功能也比 ListView 要多一些</li><li>ViewPager</li><li>Spinner</li></ol><h2 id="CustomView"><a href="#CustomView" class="headerlink" title="CustomView"></a>CustomView</h2><p>Android 默认的布局很多时候都没法满足设计的需求，这时候就需要自定义 View，你需要掌握下面几个知识点的使用</p><ol><li>Canvas</li><li>Bitmap</li><li>Paint</li></ol><h2 id="UI-Resources"><a href="#UI-Resources" class="headerlink" title="UI Resources"></a>UI Resources</h2><p>相比 HardCode，使用资源文件会让代码的可修改性更高</p><ol><li>Drawables</li><li>String</li><li>Styles</li></ol><h2 id="Fragments"><a href="#Fragments" class="headerlink" title="Fragments"></a>Fragments</h2><p>许多人提倡 App 使用 单 Activity + 多个 Fragment 的组合，可见 Fragment 在开发中的重要性，但是 Fragment 的管理又是一门技术，Fragment 的坑，只能在实际开发中慢慢填平了，不过下面的 Fragment 基础还是要牢固</p><ol><li>Fragment Lifecycle</li><li>Fragment Manager</li></ol><h2 id="Support-User-Interface"><a href="#Support-User-Interface" class="headerlink" title="Support User Interface"></a>Support User Interface</h2><p>这里列的同样是一些功能组件，需要知道这是什么东西，基本的用法</p><ol><li>ProgressBar - 进度条</li><li>Dialogs - 弹框</li><li>Toast &amp; Snackbar - 提示</li></ol><h2 id="Storage"><a href="#Storage" class="headerlink" title="Storage"></a>Storage</h2><p>App 开发不免要和文件打交道，文件的读写、存储都是必不可少的，下面列出了几种 Android 中存储相关的知识点</p><ol><li>Shared Preferences - 适合存储字段</li><li>File Systems - 文件存储</li><li>Database — RoomDB - 数据库存储，RoomDB 是 Google 新推出的数据库解决方案(在 AndroidX 中)，具体使用可以参考<a href="https://developer.android.google.cn/reference/androidx/room/RoomDatabase.html">官方文档</a></li></ol><h2 id="Build"><a href="#Build" class="headerlink" title="Build"></a>Build</h2><p>Android App 默认使用 Gradle 进行编译，关于 Gradle 的使用必须要熟悉，以及如何区分开发版本和 Release 版本，以及国内特有的多渠道打包技术、以及 ASM 等</p><ol><li>Gradle</li><li>Debug &#x2F; Release Configuration</li><li>多渠道打包</li><li>ASM</li></ol><h2 id="Threading"><a href="#Threading" class="headerlink" title="Threading"></a>Threading</h2><p>理解 Thread 非常重要，Android App 只有一个主线程，其余的我们称之为工作线程，我们的很多工作需要再工作线程和主线程直接切换，如何高效创建和释放线程、线程池、线程间通信、Message-Looper-Handler 模型这些知识点都要了熟于心，另外进阶的话 Binder 通信也是需要掌握的知识</p><ol><li>Threads</li><li>Handler &#x2F; Looper &#x2F; Message &#x2F; MessageQueue</li><li>AIDL &#x2F; Binder</li></ol><h1 id="Debugging"><a href="#Debugging" class="headerlink" title="Debugging"></a>Debugging</h1><p>这里列举了一些 Debug 的基本手段，实际开发中遇到具体问题的时候一般都会用到，不过有的可能入手难度要高一些，需要花时间去掌握。Debug 工具除了下面这几个还有很多</p><ol><li>Memory profiling - MAT，AS Memory Profile </li><li>Logging - Log 包含非常丰富的信息，可以帮助我们还原现场</li><li>Systrace - Systrace 工具可以查看一段时间内手机系统各个进程的运行状态，具体使用可以参考我博客的 <a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 系列教程</a></li><li>Exceptions - 各种异常，保证程序的健壮性</li><li>Error Handling - Error 是必须要解决的问题，一般会导致 App 直接闪退，需要非常重视</li></ol><h1 id="Memory-Leak"><a href="#Memory-Leak" class="headerlink" title="Memory Leak"></a>Memory Leak</h1><p>内存泄漏是一个很大的专题，包括 Java 内容泄漏和 Native 内存泄漏，涉及的知识点非常多，可以单独拿出来做一个大的知识栈。一般来说， Java 内存泄漏会比较好检测和修复，但是 Native 内存泄漏就会比较难。</p><ol><li>Detecting and Fixing Memory Leaks - 内存泄漏检测和修复，是一个比较大的工程，可以参考 LeakCanary、Matrix 等开源工具</li><li>Context - 使用不当会造成该释放的对象没有释放造成内存泄漏</li><li>Native Memory Leaks</li></ol><h1 id="3rd-Party-Library"><a href="#3rd-Party-Library" class="headerlink" title="3rd Party Library"></a>3rd Party Library</h1><p>经典的第三方类库，可以大幅节约我们的开发时间</p><ol><li>Image Loading - Glide, Picasso</li><li>Dependency Injection - Dagger</li><li>Networking - Fast Android Networking Library, Retrofit</li><li>MultiThreading - RxJava, Coroutines</li></ol><h1 id="Data-Format"><a href="#Data-Format" class="headerlink" title="Data Format"></a>Data Format</h1><p>常见的一些数据保存流格式</p><ol><li>JSON — GSON</li><li>Flat Buffer</li><li>Protocol Buffer</li></ol><h1 id="Android-Jetpack"><a href="#Android-Jetpack" class="headerlink" title="Android Jetpack"></a>Android Jetpack</h1><p><a href="https://developer.android.google.cn/jetpack?hl=zh-cn">Jetpack</a> 是 Google 推出的一套库、工具和指南，可帮助开发者更轻松地编写优质应用。这些组件可帮助您遵循最佳做法、让您摆脱编写样板代码的工作并简化复杂任务，以便您将精力集中放在所需的代码上。Jetpack 包含与平台 API 解除捆绑的 androidx.* 软件包库。这意味着，它可以提供向后兼容性，且比 Android 平台的更新频率更高，以此确保您始终可以获取最新且最好的 Jetpack 组件版本。</p><ol><li>Foundation Components — AppCompat, Android KTX, Multidex</li><li>Architecture Components — LiveData, ViewModel, DataBinding, Paging, Work Manager, Navigation</li><li>Behaviour Components - Download Manager, Media Playback, Notification, Permissions, Preference, Sharing, Slice</li><li>UI Component - Animation &amp; Transition, Android Auto, Emoji, Palette, Android TV, Android Wear</li></ol><h1 id="Architecture"><a href="#Architecture" class="headerlink" title="Architecture"></a>Architecture</h1><p>传统的开发架构，没有绝对的哪个好哪个不好，只有哪个适合哪个不适合，下面三种你都应该知道并有一定的了解</p><ol><li>MVVM - MVVM 是 Model-View-ViewModel的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化，让我们将视图 UI 和业务逻辑分开</li><li>MVI ？</li><li>MVP - MVP 从更早的 MVC 框架演变过来，与 MVC 有一定的相似性：Controller&#x2F;Presenter 负责逻辑的处理，Model 提供数据，View 负责显示</li></ol><h1 id="Unit-Testing"><a href="#Unit-Testing" class="headerlink" title="Unit Testing"></a>Unit Testing</h1><ol><li>Local Unit Testing</li><li>Instrumentation Testing</li></ol><h1 id="Firebase"><a href="#Firebase" class="headerlink" title="Firebase"></a>Firebase</h1><p>Firebase 国内很多开发者用不到，这里简单看一下即可（说不定哪天国内就可以用了呢）</p><ol><li>FCM</li><li>Crashlytics</li><li>Analytics</li><li>Remote Config</li><li>App Indexing</li><li>Dynamic Link</li></ol><h1 id="Security"><a href="#Security" class="headerlink" title="Security"></a>Security</h1><p>安全方面接触毕竟多的应该是加密、解密、混淆等，毕竟用户数据安全大于一切，不重视这个欧盟会教你做人</p><ol><li>Encrypt &#x2F; Decrypt</li><li>Proguard</li><li>R8</li></ol><h1 id="App-Release"><a href="#App-Release" class="headerlink" title="App Release"></a>App Release</h1><p>应用发布相关的知识，国内还得加上多渠道打包、插件化</p><ol><li>.keystore file</li><li>App Bundle</li><li>Playstore</li><li>多渠道打包</li><li>插件化</li></ol><h1 id="Keep-Learning-and-Improving"><a href="#Keep-Learning-and-Improving" class="headerlink" title="Keep Learning and Improving"></a>Keep Learning and Improving</h1><p>作为一个有进取心的 Android 开发者，拥有自己的技术栈和规划非常重要，技术栈确保你有足够的市场竞争力，从而形成护城河；技术规划则可以给你一个明确的学习目标。卸载抖音、微博、斗鱼、游戏吧，做好 2020 年的规划，<strong>Keep Learning and Improving</strong> ，共勉</p><p>如果你苦于没有好的时间管理方法，可以参考这个视频<a href="https://www.bilibili.com/video/av79348217">我是怎么做周计划 | 生产力提升 | 我的方法</a>,这个是我熟悉的一个大佬的工作学习方法实践，推荐给大家</p><blockquote><p>凡是预则立，不预则废，年度计划太长，日计划又太短。实践下来发现以周为单位做时间管理（时间管理）最靠谱，既考虑了短期又考虑了长期，可以使自己长期坚持做某事，也有一定的时间长度用来甄有价值的事情。</p></blockquote><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面</p><p><a href="https://zhuanlan.zhihu.com/p/104878641">知乎 - Android 开发者学习路线（2020 版)</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Medium 上 @MindOrks 发布了一篇 &lt;a href=&quot;https://medium.com/mindorks/android-development-learning-path-2020-edition-3f464ac56dbf&quot;&gt;2020 年 Android 程序员的学习线路&lt;/a&gt;，鉴于一部分人无法阅读原文(你懂得原因)，我把这篇文章的内容结合自己的 2020 年的学习计划，一起发出来，给大家一个参考&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原文比较简单，并没有介绍为什么要推荐这些，只是单纯地列了一下知识点，我这边针对每个知识点做一些简单的介绍，有些知识点原文并没有提到，我会根据自己的理解加上，仅供参考&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这篇文章主要针对 Android 开发者，如果你是新手，那么下面的内容可以帮助你找到学习的线路；如果你是老手，这篇文章列出的内容也可以帮助你查漏补缺。如果各位有什么其他的建议，欢迎留言交流&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="Kotlin" scheme="https://androidperformance.com/tags/Kotlin/"/>
    
  </entry>
  
  <entry>
    <title>我的 2020 年读书单</title>
    <link href="https://androidperformance.com/2020/01/28/2020-read/"/>
    <id>https://androidperformance.com/2020/01/28/2020-read/</id>
    <published>2020-01-28T11:06:29.000Z</published>
    <updated>2026-02-07T05:17:47.817Z</updated>
    
    <content type="html"><![CDATA[<p>下面记录了我 2020 年的读书单，所记录的，都是已经读完了的，技术书籍由于很难定义读完，所以不在此列。</p><p>我比较喜欢读历史相关的书，其中 <strong>汴京之围</strong> 这本书读起来非常难受，弱国无外交，真的是写实；<strong>坏小孩</strong> 书如其名，个人感觉是要比电视剧好很多；<strong>重来 2</strong> 和 <strong>重来 3</strong> 跟工作相关，讲的是远程工作和工作方法，跟 2020 年居家办公的大趋势一致，白领程序员推荐看一下；<strong>我的最后一本减肥书</strong> 则比较系统和专业地讲了一下减肥相关的知识，对于在减肥的我来说很有用；最后 <strong>镖人</strong> 也算是国漫经典了，等出完了之后我一定要买一套实体书典藏（巨人也是）</p><p>2020 读书不多，2021 要加大这方面的投入，<strong>读 + 记录 + 总结</strong> ，读书笔记后续都会补上</p><span id="more"></span><p>目前更新的书单</p><ol><li><input checked="" disabled="" type="checkbox"> 明朝历史的教训</li><li><input checked="" disabled="" type="checkbox"> 汴京之围:北宋末年的外交、战争和人</li><li><input checked="" disabled="" type="checkbox"> Android 高效进阶:从数据到 AI</li><li><input checked="" disabled="" type="checkbox"> 跌荡一百年</li><li><input checked="" disabled="" type="checkbox"> Remote：更为简单高效的远程工作</li><li><input checked="" disabled="" type="checkbox"> 鱼翅与花椒</li><li><input checked="" disabled="" type="checkbox"> 重来 2</li><li><input checked="" disabled="" type="checkbox"> 生来有罪</li><li><input checked="" disabled="" type="checkbox"> 低智商犯罪</li><li><input checked="" disabled="" type="checkbox"> 重来 3</li><li><input checked="" disabled="" type="checkbox"> 长夜难明</li><li><input checked="" disabled="" type="checkbox"> 坏小孩</li><li><input checked="" disabled="" type="checkbox"> 两京十五日</li><li><input checked="" disabled="" type="checkbox"> 永久记录</li><li><input checked="" disabled="" type="checkbox"> 我的最后一本减肥书</li><li><input checked="" disabled="" type="checkbox"> 镖人</li><li><input checked="" disabled="" type="checkbox"> 大中东行纪</li></ol><!-- more --><h1 id="明朝历史的教训"><a href="#明朝历史的教训" class="headerlink" title="明朝历史的教训"></a>明朝历史的教训</h1><p><img src="/images/15802103247651.jpg" alt="明朝历史的教训"></p><p>首先介绍一下作者，这样大家读书的时候，就会有一定的带入感</p><blockquote><p>吴晗，原名吴春晗，字辰伯。历史学教育家。浙江义乌人。1957 年加入中国共产党。1934 年毕业于清华大学。后任云南大学、西南联合大学教授，清华大学教授、系主任、文学院院长。1943 年参加中国民主政团同盟，积极从事民主运动。建国后，历任北京市副市长、北京市第一至四届政协副主席、中国科学院哲学社会科学部委员。1958 年当选为民盟中央副主席。是第一至三届全国人大代表，第一届全国下和协委员，第二、三届全国政协常委。生平从事中加古代史研究，对明史的厂家尤有成就。着有《朱元璋传》和历史剧《海瑞罢官》等。「文化大革命」开始后，吴晗从精神到肉体惨遭摧残，随后于 1968 年 3 月被捕入狱，1969 年 10 月 11 日被迫害致死。「文化大革命」结束后，其冤案才得以平反昭雪。</p></blockquote><p>这本明朝历史的教训是一本文章集，内容包括朱元璋的统治之术、统治阶级的内部矛盾、东西厂和锦衣卫、国民生活、党争、农民、奴隶和兵变、仕宦阶级、流寇等。如果说「明朝那些事」讲的是历朝大人物的事情，那么这本书则从另外一个角度，带我们了解明朝从统治阶级到被统治阶级的各种乱象，很多章节互相独立却又互相联系，可以说晚明时期的内忧外患，并非简单的清人入侵和流寇四起这么简单，这个朝代从顶到根就已经坏了，不是一两个人能救得来的</p><p>这本书微信读书上有，推荐。</p><h1 id="汴京之围：北宋末年的外交、战争和人"><a href="#汴京之围：北宋末年的外交、战争和人" class="headerlink" title="汴京之围：北宋末年的外交、战争和人"></a>汴京之围：北宋末年的外交、战争和人</h1><p><img src="/images/15802109582858.jpg" alt="汴京之围：北宋末年的外交、战争和人"></p><p>你肯定听过「靖康之耻」，本书就是详细描写靖康之耻产生的前因后果，说实话看之前并没有对这段历史有详细的了解，看了之后才觉得，叫靖康之耻真的是一点没错，尤其是军队一碰就没，谈判背信弃义，<strong>围城大半年对首都汴京一遍又一遍的搜刮</strong>，看着又气又无可奈何，没有实力就会被欺负，这个亘古不变的真理，历史上一次又一次的实践，从盛世到灭亡只花了三年……文中有一段话我很喜欢「<strong>它提醒我们居安思危，在任何时候，危机和盛世只差一步而已。和平并不是一种必然，它要求我们怀着谦卑的心态去看待世界，学习世界所长的同时，避免自大与狂傲。更重要的是，必须有意识地避免战争，谦卑不是错，错判了形势才是最可怕的，因为任何形势都是环环相扣的，一旦迈出了第一步，不仅无法回头，而且也无法把握未来的走向了</strong>」</p><blockquote><p>本书追溯北宋末年靖康之难的完整历史细节，讲述宋、辽、金三方的和与战，聚焦北宋历史大变局的关键时刻，以及帝国由内而外全局性危局大爆发的前因后果。北宋宣和年间，帝国上下一片繁荣景象，然而盛世之下的隐患已成暗涌。财政困难、军事痼疾、恶性党争等内部危机，北方辽、金两国的军事威胁等外部危机，使得帝国渐成风雨飘摇之势。为「收复」作为战略屏障的燕云十六州，宋徽宗决定联金灭辽。宋金联盟虽然逐渐将辽国蚕食，但金国借此窥见北宋的虚弱，加之两国复杂的利益纠纷，金国转而南下攻宋。靖康元年（1126），金军第二次围攻汴京，十一月汴京城陷。北宋轰然崩溃，从盛世到灭亡仅隔三年。作者以兼顾宋、辽、金三方立场的史料记载为基础，用通俗流畅的叙事笔法，试图复盘靖康之难历史发生的过程，探寻北宋盛衰之变背后的深层成因，以及超越时代的镜鉴意义。</p></blockquote><p>这本书微信读书上有，推荐。</p><h1 id="Android-高效进阶：从数据到-AI"><a href="#Android-高效进阶：从数据到-AI" class="headerlink" title="Android 高效进阶：从数据到 AI"></a>Android 高效进阶：从数据到 AI</h1><p><img src="/images/15802098163281.jpg" alt="Android 高效进阶：从数据到 AI"></p><p>《Android 高效进阶：从数据到 AI》是一本 Android 进阶技术与实践应用相结合的书籍，主要从 3 个方面来组织内容。第一个方面，Android 工程构建体系实践与进阶，其中不仅包含了移动数据技术、工具基建进阶、效能进阶，还包含了工具应用进阶、工程构建进阶等内容；第二个方面，对当前移动端前沿技术的探索，包含容器技术、大前端技术和 AI 技术；第三个方面，移动应用的安全攻防技术和设计模式进阶实践。本书内容全面，侧重实战经验和进阶技能，通过本书不仅能学到最新的移动端技术，以及进阶技术与实践应用相结合的知识，更重要的是能领悟到作者对技术的钻研精神和思维方式，从而帮助 Android 开发者高效进阶</p><p>《Android 高效进阶：从数据到 AI》适合移动应用开发者、Android 系统开发人员、Android 系统安全工程师，以及 Android 领域的移动技术负责人阅读</p><p>从书的内容来看，作者是一名经验丰富的开发者，很多专题都有比较详细的架构实现，读者可以根据这些架构实现一套，是一本不可多得的经验之书，这本书在微信读书可看</p><h1 id="跌荡一百年"><a href="#跌荡一百年" class="headerlink" title="跌荡一百年"></a>跌荡一百年</h1><p><img src="/images/15890825861013.jpg"></p><p>作者吴晓波从商业的角度，来展示了近代一百年来的中国的发展。作者提到发展中的三个现象：：一是意识形态争论对现代化的干扰，二是中央集权观念对国家商业主义的催生，三是传统的轻商和官商文化对新生企业家阶层的影响。让人叹息的是，在洋务运动后的多次经济变革运动中，这三个命题都幽灵般地随影而至，无法摆脱。</p><p>读完个人感受是：国家稳定对商业发展太重要的，我国近代起政局非常不稳定，商业的发展总是随着政局的变化而兴起和衰落；改革开放前的商业基本上停滞了。可以说我国的商业和科技发展是从改革开放后基本上从 0 开始发展的，与稳定发展了一百多年的美国的差距是非常巨大的，这一点是我看完这本书最大的感慨，虽然存在技术共享和技术爆炸，但是我们国家掌握的核心技术还是太少了。</p><blockquote><p>在这100多年的时间里，苦难让我们有机会凝神思索，学到不少东西。它使中国人得以细细体察所历之事，对千年历史进行更严苛的观察，若非受辱，我们对之也许根本不会留心，还沉浸在骄傲的大国幻境之中。<br>自1840年鸦片战争之后，有一个词汇覆盖了所有的主题，它成为无数热血国人的毕生理想，这就是“强国”。“强国”的急迫，让这个国家变得无比的焦虑，有时候甚至显得迫不及待，在一条道路还没有完全考察清楚的时候，便不惜铤而走险。在很多敏感关键的时刻，渐进式的思想往往被视为“反动”，颠覆式革命，甚至流血暴力，成为全民性的选择。百年春秋，闹剧、悲剧与喜剧交织上演。<br>在这个被“强国梦”激励着的100年里，中国的复兴开始于一个幽暗而绝望的梦醒时刻。商业的演进一直是国家进步和民族雪耻的重要方向，正是在这一进程中，新兴的企业家阶层崛起为一支独立的力量。而他们的曲折命运又与这个国家的政治变革和全民抉择纠缠在一起，它们时而合一，时而决裂，却在绝大多数时间里处在不和谐的状态中。百年以来，中国经济的问题，归根到底可以总结为三个利益关系的调整：一是政府利益与公众利益的调整，二是中央政府与地方政府利益的调整，三是富裕公众与贫穷公众的利益调整。作为富裕公众的代表阶层，企业家集团在与政府（包括中央政府及地方政府）、知识分子和贫穷公众的关系相处上，一直没有达成原则性和建设性的共识，这也成为中国商业进步总是被各种事件打断的重要原因之一。</p></blockquote><h1 id="Remote-更为简单高效的远程工作方式"><a href="#Remote-更为简单高效的远程工作方式" class="headerlink" title="Remote : 更为简单高效的远程工作方式"></a>Remote : 更为简单高效的远程工作方式</h1><p><img src="/images/15929815335750.jpg" alt="Remote"></p><p>这本书叫 Remote, 国内翻译成了重来2, 简直是瞎比翻译. </p><p>本书作者是 37Signals 两位创始人, 书中介绍了远程工作的方方面面, 包括远程工作的优点、远程工作中如何协作、远程工作的副作用、远程工作中的员工管理、远程工作中的自我管理等. 2020 年的一场新冠病毒, 使得大部分公司都尝试了一把远程工作, 这本书中所提到的一些事情, 在实际远程工作中也都有遇到.</p><p>摘抄其中一些, 大家可以品品</p><blockquote><p>真正重要的是把工作做好，而不是死守着上下班时间</p></blockquote><blockquote><p>新时代的奢侈就是摆脱“日后再享受生活”的思维桎梏，现在就去做你热爱的事，跟工作并行。何必要把时间浪费在那种“等我退休了，生活该有多美好”的白日梦上？在工作跟退休之间划一道界限，这其实是相当武断的。你的人生无须再遵循这样的规则。你可以把这两样混合在一起，既有趣又有钱挣——设计一种更好的、能把工作变得有趣的生活方式，因为工作不是这辈子唯一的事。那副金手铐令你没法过上你真心想过的人生，快从怨恨的情绪中解脱出来吧！</p></blockquote><blockquote><p>当你没法整天盯着某人的时候，唯一的判断标准就是工作成果。除此之外的一大堆琐碎标准全都不见了。你只需看工作成果，因此，你不必问远程工作的员工“你今天都做了些什么”，而是说一句“把你今天的成果给我看看”就行</p></blockquote><blockquote><p>当开会成为常态，成为探讨、争论的必备工具，无论解决什么问题都要用一用的时候，它就被滥用了，人人都变得麻木。会议应该像盐，小心翼翼地在菜品上洒上一点儿，用于提味，而不是一勺一勺地哗哗倒上许多。盐放得太多，菜就毁了；会开得太多，人们的士气和积极性就会降低</p></blockquote><blockquote><p>如果你没能好好地掌握生活与工作的平衡，远程工作的自由就会变成奴役。这种情况是可能的，因为当你从朝九晚五的工作中解脱出来之后，很容易又会套上全天不停工作的枷锁</p></blockquote><blockquote><p>远程工作把罩幕揭开，让人们看到一个一直存在却并不是总被人承认或被人看到的事实：优秀的远程员工就是优秀的员工，就是这么简单</p></blockquote><blockquote><p>内在动力：程序员编写开源软件，一般都是因为热爱这件事，不是为了钱。钱往往会相伴而来，但它极少是动力来源。也就是说，当你在解决一个特别感兴趣的、令你兴奋激动的问题的时候，你根本不需要有管理者经常从你背后伸脖子过来看看你是否在干活。<br>一切公开：绝大多数开源软件都是通过邮件列表和 GitHub 这样的代码追踪系统来协调运作的。只要有人想出手帮忙，就能做到，因为所有的信息都是公开的。你可以自告奋勇地参与进来，对某块内容最精通的人很容易就能切入。</p></blockquote><h1 id="鱼翅与花椒"><a href="#鱼翅与花椒" class="headerlink" title="鱼翅与花椒"></a>鱼翅与花椒</h1><p><img src="/images/15929848508400.jpg" alt="鱼翅与花椒"></p><p>作者<strong>扶霞·邓洛普</strong> Fuchsia Dunlop 在牛津长大，于剑桥大学取得英国文学学士学位，其后于伦敦亚非学院以名列前茅的优异成绩获得中国研究硕士学位。一九九四年，扶霞在获得了英国文化协会奖学金补助后，前往中国四川大学就读一年；其后又在四川烹饪高等专科学校接受了三个月的专业厨师训练，成为该校第一位外国学生</p><p>这本鱼翅与花椒，本来是怀着轻松的心情去读的，本书的前半部分气氛确实也是很轻松愉快，跟着作者在成都的大街小巷里面寻找家常菜，每一节之后还有一个家常菜的菜谱，作者对成都生活的描写也让人觉得惬意：</p><blockquote><p>这个地方本身那种慢悠悠的倦怠感也令人不知不觉地被影响。在成都这个城市，别说实现计划了，制定计划都根本不可能。从唐朝开始，这里就以生活安逸闲适著称。因为气候适宜，土壤更是传奇般地肥沃。成都人不用特别努力地工作也能吃得好、玩儿得开心。这座城市有点南方的感觉，甚至都有点像地中海沿岸了。成都人的脚步都比北京人或上海人要慢。他们在茶馆里一坐就是一下午加一晚上，打麻将、打牌、用节奏舒缓、语气甜腻的四川话开玩笑斗嘴，韵母都拖得长长的，还要加上娇俏的儿化音。他们把这叫做“摆龙门阵”，四川特有的谈天说地。而四川话里最生动的一句方言莫过于“好耍（特别有趣）”。他们说的时候总是懒洋洋的声气，咧嘴而笑，竹椅子发着嘎吱嘎吱的背景音。“沿海的那些人，”一位出租车司机跟我聊起广东人和福建人，“他们野心大得很，也肯干，所以他们就先富起来了噻。我们四川人喃，挣的钱可以吃香喝辣就够了。”</p></blockquote><p>书中描述的菜品有成都的家常菜、香港的粤菜、湖南的湘菜、扬州的江浙菜，不过由于作者四川呆的时间比较久，所以川菜和四川生活出现的更多一些，各地的美食各有特色，光看作者的描述，就能感觉到那扑鼻的香味</p><p>不过书的后半段也探讨了食品安全问题、吃野味、吃保护动物，以及中国官场的一些事情，越看越沉重，却也无能为力. 城市在变，社会在变，人也在变，事物也在变，作者再一次来到巨变后的成都，以一种旁观者的身份记录了这些变化：</p><blockquote><p>一方面，这样的拆除实在是个悲剧，是我个人的悲剧：竟然爱上了一个正如此迅速地消失着的地方。我对饮食烹饪的研究，初衷是想记录一个生机勃勃的城市。后来我才明白，从很多角度来说，我都在书写老成都的“墓志铭”。我感觉这也是成都人的悲剧，虽然他们并没有意识到。这个城市是多么迷人、多么独特啊，现在要用一个中国任何地方都存在的城市取而代之，暴殄天物、可悲可叹。</p></blockquote><blockquote><p>另一方面，九十年代的中国似乎又洋溢着满满的生机与乐观。之前那种功利主义、禁欲主义、千篇一律的呆板与单调乏味消失不见。全国上下都在动起来，十二亿人团结一心、一致向前。在英国，哪怕拆除一栋破旧的老楼，我们都会烦恼苦闷。而在四川，他们一路挥舞大锤，把整座城市都拆平了！这无所顾忌的信心让人不得不佩服。他们坚信，未来会比过去更好。</p></blockquote><blockquote><p>所以，尽管经过那些被夷为平地的街道时我的心还是会痛，但同时又被这充满活力的乐观鼓动着、躁动着。我也处在一种不稳定的状态，我的人生也在改变。我在挖掘潜在的创造力、在交很棒的朋友，像一条蛇一样慢慢蜕皮。</p></blockquote><h1 id="天生有罪"><a href="#天生有罪" class="headerlink" title="天生有罪"></a>天生有罪</h1><p><img src="/images/15946504381924.jpg" alt="天生有罪"></p><blockquote><p>出生不由自己，但人生可以</p></blockquote><p>因为 Daily Show 所以开始关注崔娃，这本书是崔娃的自传，名字起得就很让人困惑，什么叫天生有罪？之前对于非洲历史没有什么关注， 看了这本书之后才知道，天生有罪指的是非洲的种族隔离时期，黑人和白人是不能在一起的，更别说生小孩，但崔娃就是白人父亲和黑人母亲的儿子，所以说他天生就是有罪的.</p><p>贯穿文章的两个大的主题，一个是种族隔离，一个是种族认同。非洲白人统治时期实行种族隔离，这种隔离不仅仅是人的隔离，还包括语言隔离、文化隔离、思想隔离，甚至还有黑人部落之间的隔离；另外一个就是种族认同，崔娃在当地属于有色人种，属于那种既不被黑人团体容纳，也不被白人团体容纳，印度人、中国人也会自己抱团。这时候对于一个孩子来说，跟谁抱团玩（获得哪个团队的认同）是非常重要的事情，从小崔娃就被各个团体隔离，但是他在母亲和知识中找到了在各个种族和团体之间的生存之道，最终成为一位成功的喜剧演员、脱口秀表演者、电视电台节目主持人。</p><blockquote><p>种族隔离代表着一个警察国家，一个让黑人处于绝对控制下的各种法条和监视系统。若能将所有法条写下来堆到一起，那需要用掉三千多张纸，可重达五千克。但是南非种族隔离的精髓对美国人来说是非常容易理解的，在美国历史上，曾经发生过三件事：把原住民驱赶到保留地、黑人奴隶制、隔离制度。想象一下，这三件事在同一时间内发生在同一群人身上，那就是种族隔离。</p></blockquote><blockquote><p>我找到了我的位置。既然我不属于任何一个小圈子，那么我可以在不同的圈子之间游走。我还是一条变色龙，文化上的变色龙。我知道如何去融入。我可以和爱运动的小孩一起运动，和书呆子一起讨论电脑。我可以跳进人群里，和小镇男孩一起跳舞。我可以和每个人都产生短暂的交集，一起学习、聊天、讲笑话、送餐。</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;下面记录了我 2020 年的读书单，所记录的，都是已经读完了的，技术书籍由于很难定义读完，所以不在此列。&lt;/p&gt;
&lt;p&gt;我比较喜欢读历史相关的书，其中 &lt;strong&gt;汴京之围&lt;/strong&gt; 这本书读起来非常难受，弱国无外交，真的是写实；&lt;strong&gt;坏小孩&lt;/strong&gt; 书如其名，个人感觉是要比电视剧好很多；&lt;strong&gt;重来 2&lt;/strong&gt; 和 &lt;strong&gt;重来 3&lt;/strong&gt; 跟工作相关，讲的是远程工作和工作方法，跟 2020 年居家办公的大趋势一致，白领程序员推荐看一下；&lt;strong&gt;我的最后一本减肥书&lt;/strong&gt; 则比较系统和专业地讲了一下减肥相关的知识，对于在减肥的我来说很有用；最后 &lt;strong&gt;镖人&lt;/strong&gt; 也算是国漫经典了，等出完了之后我一定要买一套实体书典藏（巨人也是）&lt;/p&gt;
&lt;p&gt;2020 读书不多，2021 要加大这方面的投入，&lt;strong&gt;读 + 记录 + 总结&lt;/strong&gt; ，读书笔记后续都会补上&lt;/p&gt;</summary>
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - CPU Info 解读</title>
    <link href="https://androidperformance.com/2019/12/21/Android-Systrace-CPU/"/>
    <id>https://androidperformance.com/2019/12/21/Android-Systrace-CPU/</id>
    <published>2019-12-21T07:16:26.000Z</published>
    <updated>2026-02-07T05:17:47.844Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第十二篇，主要是对 Systrace 中的 CPU 信息区域(Kernel)进行简单介绍，简单介绍了如何在 Systrace 中查看 Kernel 模块输出的 CPU 相关的信息，了解 CPU 频率、调度、锁频、锁核相关的信息</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#cpu-fig">CPU 区域图例</a></li><li><a href="#arch">核心架构</a></li><li><a href="#pin">绑核</a></li><li><a href="#lockfreq">锁频</a></li><li><a href="#cpu-state">CPU 状态</a></li><li><a href="#details">Systrace 中的详细信息</a></li><li><a href="#refs">参考</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="cpu-fig"></a></p><h1 id="CPU-区域图例"><a href="#CPU-区域图例" class="headerlink" title="CPU 区域图例"></a>CPU 区域图例</h1><p>下面是高通骁龙 845 手机 Systrace 对应的 Kernel 中的 CPU Info 区域（底下的一些这里不讲，主要是讲 Kernel  CPU 信息）</p><p><img src="/images/15769147700353.jpg" alt="CPU 区域图例"></p><p>Systrace 中 CPU Info 一般在最上面，里面经常会用到的信息包括：</p><ol><li>CPU 频率变化情况</li><li>任务执行情况</li><li>大小核的调度情况</li><li>CPU Boost 调度情况</li></ol><p>总的来说，Systrace 中的 Kernel CPU Info 这里一般是看任务调度信息，查看是否是频率或者调度导致当前任务出现性能问题，举例如下：</p><ol><li>某个场景的任务执行比较慢，我们就可以查看是不是这个任务被调度到了小核？</li><li>某个场景的任务执行比较慢，当前执行这个任务的 CPU 频率是不是不够？</li><li>我的任务比较特殊，比如指纹解锁，能不能把我这个任务放到大核去跑？</li><li>我这个场景对 CPU 要求很高，我能不能要求在我这个场景运行的时候，限制 CPU 最低频率？</li></ol><p>与 CPU 运行信息相关的内容在 <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 – 分析 Systrace 预备知识</a> 这篇文章里面有详细的讲解，不熟悉的同学可以配合这篇文章一起食用</p><p><a id="arch"></a></p><h1 id="核心架构"><a href="#核心架构" class="headerlink" title="核心架构"></a>核心架构</h1><p>简单来说目前的手机 CPU 按照核心数和架构来说，可以分为下面三类：</p><ol><li>非大小核架构</li><li>大小核架构</li><li>大中小核架构</li></ol><p>目前的大部分 CPU 都是大小核架构，当然也有一些 CPU 是大中小核架构，比如高通骁龙 855\865，也有少部分 CPU 是非大小核架构</p><p>下面就来说说各种架构的区别，方便大家后续查看 Systrace</p><p><img src="/images/15769147794281.jpg" alt="大小核架构"></p><h2 id="非大小核架构"><a href="#非大小核架构" class="headerlink" title="非大小核架构"></a>非大小核架构</h2><p>很早的机器 CPU 只有双核心或者四核心的时候，一般只有一种核心架构，也就是说这四个核心或者两个核心是同构的，相同的频率，相同的功耗，一起开启或者关闭；有些高通的中低端处理器也会使用同构的八核心处理器，比如高通骁龙 636</p><p>现在的大部分机器已经不使用非大小核的架构了</p><h2 id="大小核架构"><a href="#大小核架构" class="headerlink" title="大小核架构"></a>大小核架构</h2><p>现在的 CPU 一般采用 8 核心，八个核心中，CPU 0-3 一般是小核心，CPU 4-7，如下图中 Systrace 中就是按照这个排列的</p><p>小核心一般来说主频低，功耗也低，使用的一般是 arm A5X 系列，比如高通骁龙 845，小核心是由四个 A55 (最高主频 1.8GHz ) 组成</p><p>大核心一般来说最高主频比较高，功耗相对来说也会比较高，使用的一般是 arm A7X 系列，比如高通骁龙 845，大核心就是由四个 A75（最高主频 2.8GHz）组成</p><p>下图就是 845 的 CPU</p><p><img src="/images/15769147884550.jpg" alt="845"></p><p>当然大小核架构中还有一些变种，比如高通骁龙 636 (4 小核 + 2 大核）或者高通骁龙 710 (6 小核 + 2 大核），宗旨还是不变，<strong>大核心用来支持高负载场景，小核心用来日常使用，至于够不够用，就看你舍不舍得花银子，毕竟一分价钱一分货，高通爸爸也不是做福利的</strong></p><p>下面这些高通的主流大小核处理器的参数如下</p><p><img src="/images/15769147957115.jpg"></p><h2 id="大中小核架构"><a href="#大中小核架构" class="headerlink" title="大中小核架构"></a>大中小核架构</h2><p>部分 CPU 比较另辟蹊径，选择了大中小核的架构，比如高通骁龙 855 8 核 (1 个 A76 的大核+3 个 A76 的中核 + 4 个 A55 的小核）和之前的的 MTK X30 10 核 (2 个 A73 的大核 + 4 个 A53 的中核 + 4 个 A35 的小核）以及麒麟 980 8 核 (2 个 A76 的大核 + 2 个 A76 的中核 + 4 个 A55 的小核）</p><p>相比大小核架构，大中小核架构中的大核可以理解为超大核(高通称之为 Gold +) ，这个超大核的个数一般比较少(1-2 个)，主频一般会比较高，功耗相对也会高很多，这个是用来处理一些比较繁重的任务</p><p>下图是 855、845 和麒麟 980 的对比</p><p><img src="/images/15769148033244.jpg"></p><p>顺带提一嘴，今年的高通骁龙 865 依然是大中小核的架构，大核和中核用的是 A77 架构,小核用的是 A55，大核和中核最高频率不一样，<strong>大核只有一个，主频到 2.8GHz</strong>，不知道 865 Plus 会不会搞到 3GHz</p><p><img src="/images/15769148095558.jpg"></p><p><a id="pin"></a></p><h1 id="绑核"><a href="#绑核" class="headerlink" title="绑核"></a>绑核</h1><p>绑核，顾名思义就是<strong>把某个任务绑定到某个或者某些核心上，来满足这个任务的性能需求</strong>：</p><ol><li>任务本身负载比较高，需要在大核心上面才能满足时间要求</li><li>任务本身不想被频繁切换，需要绑定在某一个核心上面</li><li>任务本身不重要，对时间要求不高，可以绑定或者限制在小核心上面运行</li></ol><p>上面是一些绑核的例子，目前 Android 中绑核操作一般是由系统来实现的，常用的有三种方法</p><h2 id="配置-CPUset"><a href="#配置-CPUset" class="headerlink" title="配置 CPUset"></a>配置 CPUset</h2><p>使用 CPUset 子系统可以限制某一类的任务跑在特定的 CPU 或者 CPU 组里面，比如下面，Android 中会划分一些默认的 CPU 组，厂商可以针对不同的 CPU 架构进行定制，目前默认划分</p><ol><li>system-background 一些低优先级的任务会被划分到这里，只能跑到小核心里面</li><li>foreground 前台进程</li><li>top-app 目前正在前台和用户交互的进程</li><li>background 后台进程</li><li>foreground&#x2F;boost 前台 boost 进程，通常是用来联动的，现在已经没有用到了，之前的时候是应用启动的时候，会把所有 foreground 里面的进程都迁移到这个进程组里面</li></ol><p>每个 CPU 架构对应的 CPUset 的配置都不一样，每个厂商也会有不同的策略在里面，比如下面就是一个 Google 官方默认的配置，各位也可以查看对应的节点来查看自己的 CPUset 组的配置</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//官方默认配置</span></span><br><span class="line"><span class="keyword">write</span> <span class="regexp">/dev/</span>CPUset<span class="regexp">/top-app/</span>CPUs <span class="number">0</span>-<span class="number">7</span></span><br><span class="line"><span class="keyword">write</span> <span class="regexp">/dev/</span>CPUset<span class="regexp">/foreground/</span>CPUs <span class="number">0</span>-<span class="number">7</span></span><br><span class="line"><span class="keyword">write</span> <span class="regexp">/dev/</span>CPUset<span class="regexp">/foreground/</span>boost/CPUs <span class="number">4</span>-<span class="number">7</span></span><br><span class="line"><span class="keyword">write</span> <span class="regexp">/dev/</span>CPUset<span class="regexp">/background/</span>CPUs <span class="number">0</span>-<span class="number">7</span></span><br><span class="line"><span class="keyword">write</span> <span class="regexp">/dev/</span>CPUset<span class="regexp">/system-background/</span>CPUs <span class="number">0</span>-<span class="number">3</span></span><br><span class="line"><span class="comment">// 自己查看</span></span><br><span class="line">adb shell cat <span class="regexp">/dev/</span>CPUset<span class="regexp">/top-app/</span>CPUs</span><br><span class="line"><span class="number">0</span>-<span class="number">7</span></span><br></pre></td></tr></table></figure><p>对应的，可以在每个 CPUset 组的 tasks 节点下面看有哪些进程和线程是跑在这个组里面的</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ adb shell cat <span class="regexp">/dev/</span>CPUset<span class="regexp">/top-app/</span>tasks</span><br><span class="line"><span class="number">1687</span></span><br><span class="line"><span class="number">1689</span></span><br><span class="line"><span class="number">1690</span></span><br><span class="line"><span class="number">3559</span></span><br></pre></td></tr></table></figure><p>需要注意每个任务跑在哪个组里面，是动态的，并不是一成不变的，有权限的进程就可以改</p><p>部分进程也可以在启动的时候就配置好跑到哪个进程里面，下面是 lmkd 的启动配置，writepid &#x2F;dev&#x2F;CPUset&#x2F;system-background&#x2F;tasks 这一句把自己安排到了 system-background 这个组里面</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">service lmkd <span class="regexp">/system/</span>bin/lmkd</span><br><span class="line">    <span class="keyword">class</span> core</span><br><span class="line">    user lmkd</span><br><span class="line">    <span class="keyword">group</span> lmkd system readproc</span><br><span class="line">    capabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCE BLOCK_SUSPEND</span><br><span class="line">    critical</span><br><span class="line">    socket lmkd seqpacket <span class="number">0660</span> system system</span><br><span class="line">    writepid <span class="regexp">/dev/</span>CPUset<span class="regexp">/system-background/</span>tasks</span><br></pre></td></tr></table></figure><p>大部分 App 进程是根据状态动态去变化的,在 Process 这个类中有详细的定义</p><p>android&#x2F;os&#x2F;Process.java</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Default thread group -</span></span><br><span class="line"><span class="comment"> * has meaning with setProcessGroup() only, cannot be used with setThreadGroup().</span></span><br><span class="line"><span class="comment"> * When used with setProcessGroup(), the group of each thread in the process</span></span><br><span class="line"><span class="comment"> * is conditionally changed based on that thread&#x27;s current priority, as follows:</span></span><br><span class="line"><span class="comment"> * threads with priority numerically less than THREAD_PRIORITY_BACKGROUND</span></span><br><span class="line"><span class="comment"> * are moved to foreground thread group.  All other threads are left unchanged.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_DEFAULT = -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Background thread group - All threads in</span></span><br><span class="line"><span class="comment"> * this group are scheduled with a reduced share of the CPU.</span></span><br><span class="line"><span class="comment"> * Value is same as constant SP_BACKGROUND of enum SchedPolicy.</span></span><br><span class="line"><span class="comment"> * FIXME rename to THREAD_GROUP_BACKGROUND.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_BG_NONINTERACTIVE = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Foreground thread group - All threads in</span></span><br><span class="line"><span class="comment"> * this group are scheduled with a normal share of the CPU.</span></span><br><span class="line"><span class="comment"> * Value is same as constant SP_FOREGROUND of enum SchedPolicy.</span></span><br><span class="line"><span class="comment"> * Not used at this level.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">private</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_FOREGROUND = <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * System thread group.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_SYSTEM = <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Application audio thread group.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_AUDIO_APP = <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * System audio thread group.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_AUDIO_SYS = <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread group for top foreground app.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_TOP_APP = <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread group for RT app.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_RT_APP = <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Thread group for bound foreground services that should</span></span><br><span class="line"><span class="comment"> * have additional CPU restrictions during screen off</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> **/</span></span><br><span class="line"> <span class="keyword">public</span> <span class="built_in">static</span> <span class="keyword">final</span> <span class="keyword">int</span> THREAD_GROUP_RESTRICTED = <span class="number">7</span>;</span><br></pre></td></tr></table></figure><p>在 OomAdjuster 中会动态根据进程的状态修改其对应的 CPUset 组， 详细可以自行查看 OomAdjuster 中 computeOomAdjLocked、updateOomAdjLocked、applyOomAdjLocked 的执行逻辑(Android 10)</p><h2 id="配置-affinity"><a href="#配置-affinity" class="headerlink" title="配置 affinity"></a>配置 affinity</h2><p>使用 affinity 也可以设置任务跑在哪个核心上，其系统调用的 taskset， taskset 用来查看和设定“CPU 亲和力”，其实就是查看或者配置进程和 CPU 的绑定关系，让某进程在指定的 CPU 核上运行，即是“绑核”。</p><h3 id="taskset-的用法"><a href="#taskset-的用法" class="headerlink" title="taskset 的用法"></a>taskset 的用法</h3><p><strong>显示进程运行的CPU</strong></p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">taskset -<span class="selector-tag">p</span> pid</span><br></pre></td></tr></table></figure><p>注意，此命令返回的是十六进制的，转换成二进制后，每一位对应一个逻辑 CPU，低位是 0 号CPU，依次类推。如果每个位置上是1，表示该进程绑定了该 CPU。例如，0101 就表示进程绑定在了 0 号和 3 号逻辑 CPU 上了</p><p><strong>绑核设定</strong></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">taskset</span> -pc <span class="number">3</span>  pid    表示将进程pid绑定到第<span class="number">3</span>个核上</span><br><span class="line"><span class="attribute">taskset</span> -c <span class="number">3</span> command   表示执行 command 命令，并将 command 启动的进程绑定到第<span class="number">3</span>个核上。</span><br></pre></td></tr></table></figure><p>Android 中也可以使用这个系统调用，把任务绑定到某个核心上运行。部分较老的内核里面不支持 CPUset，就会用 taskset 来设置</p><h2 id="调度算法"><a href="#调度算法" class="headerlink" title="调度算法"></a>调度算法</h2><p>在 Linux 的调度算法中修改调度逻辑，也可以让指定的 task 跑在指定的核上面，部分厂家的核调度优化就是使用的这种方法，这里就不具体来讲了</p><p><a id="lockfreq"></a></p><h1 id="锁频"><a href="#锁频" class="headerlink" title="锁频"></a>锁频</h1><p>正常情况下，CPU 的调度算法都可以满足日常的使用，但是在 Android 中的部分场景里面，单纯依靠调度器，可能会无法满足这个场景对性能的要求。比如说应用启动场景，如果让调度器去拉频率迁核，可能就会有一定的延迟，比如任务先在小核跑，发现小核频率不够，那就把小核频率往上拉，拉上去之后发现可能还是不够，经过几次一直拉到最高发现还是不够，然后把这个任务迁移到中核，频率也是一次一次拉，拉到最高发现还是不够，最好迁移到大核去做。这样一套下来，时间过去不少不说，启动速度也不是最快的</p><p>基于这种情况的考虑，系统中一般都会在这种特殊场景直接暴力拉核，将硬件资源直接拉到最高去运行，比如 CPU、GPU、IO、BUS 等；另外也会在某些场景把某些资源限制使用，比如发热太严重的时候，需要限制 CPU 的最高频率，来达到降温的目的；有时候基于功耗的考虑，也会限制一些资源在某些场景的使用</p><p>目前 Android 系统一般会在下面几个场景直接进行锁频（不同厂家也会自己定制）</p><ol><li>应用启动</li><li>应用安装</li><li>转屏</li><li>窗口动画</li><li>List Fling</li><li>Game</li></ol><p>以 高通平台为例，在 CPU Info 中我们也可以看到锁频的情况</p><p><img src="/images/15769148260464.jpg"></p><p><a id="cpu-state"></a></p><h1 id="CPU-状态"><a href="#CPU-状态" class="headerlink" title="CPU 状态"></a>CPU 状态</h1><p>CPU info 中还有标识 CPU 状态的标记，如下图所示，CPU 状态有 0 ，1，2，3 这四种<br><img src="/images/15769148355062.jpg"></p><p>之前的 CPU 支持热插拔，即不用的时候可以直接关闭，不过目前的 CPU 都不支持热插拔，而是使用 C-State</p><p>下面是摘抄的其他平台的支持 C0-C4 的处理器的状态和功耗状态，Android 中不同的平台表现不一致，大家可以做一下参考</p><ol><li>C0 状态（激活）</li><li>这是 CPU 最大工作状态，在此状态下可以接收指令和处理数据</li><li>所有现代处理器必须支持这一功耗状态</li><li>C1 状态（挂起）</li><li>可以通过执行汇编指令“ HLT （挂起）”进入这一状态</li><li>唤醒时间超快！（快到只需 10 纳秒！）</li><li>可以节省 70% 的 CPU 功耗</li><li>所有现代处理器都必须支持这一功耗状态</li><li>C2 状态（停止允许）</li><li>处理器时钟频率和 I&#x2F;O 缓冲被停止</li><li>换言之，处理器执行引擎和 I&#x2F;0 缓冲已经没有时钟频率</li><li>在 C2 状态下也可以节约 70% 的 CPU 和平台能耗</li><li>从 C2 切换到 C0 状态需要 100 纳秒以上</li><li>C3 状态（深度睡眠）</li><li>总线频率和 PLL 均被锁定</li><li>在多核心系统下，缓存无效</li><li>在单核心系统下，内存被关闭，但缓存仍有效可以节省 70% 的 CPU 功耗，但平台功耗比 C2 状态下大一些</li><li>唤醒时间需要 50 微妙</li></ol><p><a id="details"></a></p><h1 id="Systrace-中的详细信息"><a href="#Systrace-中的详细信息" class="headerlink" title="Systrace 中的详细信息"></a>Systrace 中的详细信息</h1><p>Systrace 我们一般用 Chrome 打开，转换成图形化信息之后更加方便从整体去看，但其实 Systrace 也可以以文本的方式打开，也可以看到一些详细的信息。</p><p>比如下面就是一条标识 CPU 调度的 Message，解析的时候，里面的信息会被解析到各个模块</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">appEventThread-8193  [001] d<span class="built_in">..</span>2 1638545.400415: sched_switch: <span class="attribute">prev_comm</span>=appEventThread <span class="attribute">prev_pid</span>=8193 <span class="attribute">prev_prio</span>=97 <span class="attribute">prev_state</span>=S ==&gt; <span class="attribute">next_comm</span>=swapper/1 <span class="attribute">next_pid</span>=0 <span class="attribute">next_prio</span>=120</span><br></pre></td></tr></table></figure><p>详细来看</p><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">appEventThread-<span class="number">8193</span>    <span class="comment">-- 标识 TASK-PID</span></span><br><span class="line">[<span class="number">001</span>]                  <span class="comment">-- 标识是哪个 CPU ，这里是 cpu0</span></span><br><span class="line">d..<span class="number">2</span>                   <span class="comment">-- 这是四个位，每个位分别对应 irqs-off、need-resched、hardirq/softirq、preempt-depth</span></span><br><span class="line"><span class="number">1638545.400415</span>         <span class="comment">-- 标识 delay TIMESTAMP</span></span><br><span class="line">sched_switch ...到最后  <span class="comment">-- 标识信息区，里面包含前一个任务描述，前一个任务的 pid，前一个任务的优先级 ，当前任务，当前任务 pid，当前任务优先级</span></span><br></pre></td></tr></table></figure><p>另外里面仔细看也可以看到许多有趣的输出，可以加深对调度的理解</p><ol><li>sched_waking: comm&#x3D;kworker&#x2F;u16:4 pid&#x3D;17373 prio&#x3D;120 target_cpu&#x3D;003</li><li>sched_blocked_reason: pid&#x3D;17373 iowait&#x3D;0 caller&#x3D;rpmh_write_batch+0x638&#x2F;0x7d0</li><li>cpu_idle: state&#x3D;0 cpu_id&#x3D;3</li><li>softirq_raise: vec&#x3D;6 [action&#x3D;TASKLET]</li><li>cpu_frequency_limits: min&#x3D;1555200 max&#x3D;1785600 cpu_id&#x3D;0</li><li>cpu_frequency_limits: min&#x3D;710400 max&#x3D;2419200 cpu_id&#x3D;4</li><li>cpu_frequency_limits: min&#x3D;825600 max&#x3D;2841600 cpu_id&#x3D;7</li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://blog.csdn.net/breaksoftware/article/details/79160916">绑定CPU逻辑核心的利器——taskset</a></li><li><a href="http://www.voidcn.com/article/p-kcjkqmld-bmg.html">CPU 电源状态</a></li></ol><h1 id="春笋"><a href="#春笋" class="headerlink" title="春笋"></a>春笋</h1><p>拍了张照片，觉得还不错，分享给大家<br><img src="/images/15769151861715.jpg"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第十二篇，主要是对 Systrace 中的 CPU 信息区域(Kernel)进行简单介绍，简单介绍了如何在 Systrace 中查看 Kernel 模块输出的 CPU 相关的信息，了解 CPU 频率、调度、锁频、锁核相关的信息&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - Triple Buffer 解读</title>
    <link href="https://androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/"/>
    <id>https://androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer/</id>
    <published>2019-12-15T15:31:20.000Z</published>
    <updated>2026-02-07T05:17:47.847Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第十一篇，主要是对 Systrace 中的 Triple Buffer 进行简单介绍，简单介绍了如何在 Systrace 中判断卡顿情况的发生，进行初步的定位和分析，以及介绍 Triple Buffer 的引入对性能的影响</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#define-jank">怎么定义掉帧？</a></li><li><a href="#bq-tb">BufferQueue 和 Triple Buffer</a></li><li><a href="#effects">Triple Buffer 的作用</a></li><li><a href="#debug">Debug Triple Buffer</a></li><li><a href="#refs">参考</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="define-jank"></a></p><h1 id="怎么定义掉帧？"><a href="#怎么定义掉帧？" class="headerlink" title="怎么定义掉帧？"></a>怎么定义掉帧？</h1><p>Systrace 中可以看到应用的掉帧情况，我们经常看到说主线程超过 16.6 ms 就会掉帧，其实不然，这和我们这一篇文章讲到的 Triple Buffer 和一定的关系，一般来说，Systrace 中我们从 App 端和 SurfaceFlinger 端一起来判断掉帧情况</p><h2 id="App-端判断掉帧"><a href="#App-端判断掉帧" class="headerlink" title="App 端判断掉帧"></a>App 端判断掉帧</h2><p>如果之前没有看过 Systrace 的话，仅仅从理论上来说，下面这个 Trace 中的应用是掉帧了，其主线程的绘制时间超过了 16.6ms ,但其实不一定，因为 BufferQueue 和 TripleBuffer 的存在，此时 BufferQueue 中可能还有上一帧或者上上一帧准备好的 Buffer，可以直接被 SurfaceFlinger 拿去做合成，当然也可能没有</p><p><img src="/images/15764245549172.jpg"></p><p><strong>所以从 Systrace 的 App 端我们是无法直接判断是否掉帧的，需要从 Systrace 里面的 SurfaceFlinger 端去看</strong></p><h2 id="SurfaceFlinger-端判断掉帧"><a href="#SurfaceFlinger-端判断掉帧" class="headerlink" title="SurfaceFlinger 端判断掉帧"></a>SurfaceFlinger 端判断掉帧</h2><p><img src="/images/15764245828175.jpg"></p><p>SurfaceFlinger 端可以看到 SurfaceFlinger 主线程和合成情况和应用对应的 BufferQueue 中 Buffer 的情况。如上图，就是一个掉帧的例子。App 没有及时渲染完成，且此时 BufferQueue 中也没有前几帧的 Buffer，所以这一帧 SurfaceFlinger 没有合成对应 App 的 Layer，在用户看来这里就掉了一帧</p><p>而在第一张图中我们说从 App 端无法看出是否掉帧，那张图对应的  SurfaceFlinger 的 Trace 如下, 可以看到由于有 Triple Buffer 的存在, SF 这里有之前 App 的 Buffer,所以尽管 App 测一帧超过了 16.6 ms, 但是 SF 这里依然有可用来合成的 Buffer, 所以没有掉帧</p><p><img src="/images/15764245923282.jpg" alt="SurfaceFlinger"></p><h2 id="逻辑掉帧"><a href="#逻辑掉帧" class="headerlink" title="逻辑掉帧"></a>逻辑掉帧</h2><p>上面的掉帧我们是从渲染这边来看的，这种掉帧在 Systrace 中可以很容易就发现；还存在一种掉帧情况叫<strong>逻辑掉帧</strong></p><p><strong>逻辑掉帧</strong>指的是由于应用自己的代码逻辑问题，导致画面更新的时候，不是以均匀或者物理曲线的方式，而是出现跳跃更新的情况，这种掉帧一般在 Systrace 上没法看出来，但是用户在使用的时候可以明显感觉到</p><p>举一个简单的例子，比如说列表滑动的时候，如果我们滑动松手后列表的每一帧前进步长是一个均匀变化的曲线，最后趋近于 0，这样就是完美的；但是如果出现这一帧相比上一帧走了 20，下一帧相比这一帧走了 10，下下一帧相比下一帧走了 30，这种就是跳跃更新，在 Systrace 上每一帧都是及时渲染且 SurfaceFlinger 都及时合成的，但是用户用起来就是觉得会卡. 不过我列举的这个例子中，Android 已经针对这种情况做了优化，感兴趣的可以去看一下 android&#x2F;view&#x2F;animation&#x2F;AnimationUtils.java 这个类，重点看下面三个方法的使用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">lockAnimationClock</span><span class="params">(<span class="type">long</span> vsyncMillis)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">unlockAnimationClock</span><span class="params">()</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">long</span> <span class="title function_">currentAnimationTimeMillis</span><span class="params">()</span></span><br></pre></td></tr></table></figure><p>Android 系统的动画一般不会有这个问题，但是应用开发者就保不齐会写这种代码，比如做动画的时候根据**当前的时间(而不是 Vsync 到来的时间)**来计算动画属性变化的情况，这种情况下，一旦出现掉帧，动画的变化就会变得不均匀，感兴趣的可以自己思考一下这一块</p><p>另外 Android 出现掉帧情况的原因非常多，各位可以参考下面三篇文章食用：</p><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li></ol><p><a id="bq-tb"></a></p><h1 id="BufferQueue-和-Triple-Buffer"><a href="#BufferQueue-和-Triple-Buffer" class="headerlink" title="BufferQueue 和 Triple Buffer"></a>BufferQueue 和 Triple Buffer</h1><h2 id="BufferQueue"><a href="#BufferQueue" class="headerlink" title="BufferQueue"></a>BufferQueue</h2><p>首先看一下 BufferQueue，BufferQueue 是一个生产者(Producer)-消费者(Consumer)模型中的数据结构，一般来说，消费者(Consumer) 创建 BufferQueue，而生产者(Producer) 一般不和 BufferQueue 在同一个进程里面</p><p><img src="/images/15764246330689.jpg"></p><p>其运行逻辑如下</p><ol><li>当生产者(Producer) 需要 Buffer 时，它通过调用 dequeueBuffer（）并指定 Buffer 的宽度，高度，像素格式和使用标志，从 BufferQueue 请求释放 Buffer</li><li>生产者(Producer) 将填充缓冲区，并通过调用 queueBuffer（）将缓冲区返回到队列。</li><li>消费者(Consumer) 使用 acquireBuffer（）获取 Buffer 并消费 Buffer 的内容</li><li>使用完成后，消费者(Consumer)将通过调用 releaseBuffer（）将 Buffer 返回到队列</li></ol><p>Android 通过 Vsync 机制来控制 Buffer 在 BufferQueue 中的流动时机，如果对 Vsync 机制不了解，可以参考下面这两篇文章，看完后你会有个大概的了解</p><ol><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a></li></ol><p>上面的流程比较抽象，这里举一个具体的例子，方便大家理解上面那张图，对后续了解 Systrace 中的 BufferQueue 也会有帮助。</p><p>在 Android App 的渲染流程里面，App 就是个生产者(Producer) ，而 SurfaceFlinger 是一个消费者(Consumer)，所以上面的流程就可以翻译为</p><ol><li>当 <strong>App</strong> 需要 Buffer 时，它通过调用 dequeueBuffer（）并指定 Buffer 的宽度，高度，像素格式和使用标志，从 BufferQueue 请求释放 Buffer</li><li><strong>App</strong> 可以用 cpu 进行渲染也可以调用用 gpu 来进行渲染，渲染完成后，通过调用 queueBuffer（）将缓冲区返回到 App 对应的 BufferQueue(如果是 gpu 渲染的话，这里还有个 gpu 处理的过程)</li><li><strong>SurfaceFlinger</strong> 在收到 Vsync 信号之后，开始准备合成，使用 acquireBuffer（）获取 App 对应的 BufferQueue 中的  Buffer 并进行合成操作</li><li>合成结束后，<strong>SurfaceFlinger</strong> 将通过调用 releaseBuffer（）将 Buffer 返回到 App 对应的 BufferQueue</li></ol><p>理解了 BufferQueue 的作用后，接下来来讲解一下 BufferQueue 中的 Buffer </p><p>从上面的图可以看到，BufferQueue 中的生产者和消费者通过 dequeueBuffer、queueBuffer、acquireBuffer、releaseBuffer 来申请或者释放 Buffer，那么 BufferQueue 中需要几个 Buffer 来进行运转呢？下面从单 Buffer，双 Buffer 和 Triple Buffer 的角度分析(注意这里只是从 Buffer 的角度来做分析的, 比如 App 测涉及到 Buffer 的是 RenderThread , 不过由于 RenderThread 与 MainThread 有一定的联系, 比如 unBlockUiThread 执行的时机, MainThread 也会因为 RenderThread 执行慢而被 Block 住)</p><h2 id="Single-Buffer"><a href="#Single-Buffer" class="headerlink" title="Single Buffer"></a>Single Buffer</h2><p>单 Buffer 的情况下，因为只有一个 Buffer 可用，那么这个 Buffer 既要用来做合成显示，又要被应用拿去做渲染</p><p><img src="/images/15764246600451.jpg" alt="Single Buffer"></p><p>理想情况下，单 Buffer 是可以完成任务的（有 Vsync-Offset 存在的情况下）</p><ol><li>App 收到 Vsync 信号，获取 Buffer 开始渲染</li><li>间隔  Vsync-Offset 时间后，SurfaceFlinger 收到 Vsync 信号，开始合成</li><li>屏幕刷新，我们看到合成后的画面</li></ol><p><img src="/images/15764246714403.jpg" alt="Single Buffer"></p><p>但是很不幸，理想情况我们也就想一想，这期间如果 App 渲染或者 SurfaceFlinger 合成在屏幕显示刷新之前还没有完成，那么屏幕刷新的时候，拿到的 Buffer 就是不完整的，在用户看来，就有种撕裂的感觉</p><p><img src="/images/15764246782401.jpg" alt="Single Buffer"></p><p>当然 Single Buffer 已经没有在使用，上面只是一个例子</p><h2 id="Double-Buffer"><a href="#Double-Buffer" class="headerlink" title="Double Buffer"></a>Double Buffer</h2><p>Double Buffer 相当于 BufferQueue 中有两个 Buffer 可供轮转，消费者在消费 Buffer的同时，生产者也可以拿到备用的 Buffer 进行生产操作</p><p><img src="/images/15764246889873.jpg" alt="Double Buffer"></p><p>下面我们来看理想情况下，Double Buffer 的工作流程</p><p><img src="/images/DoubleBufferPipline_NoJank.webp" alt="DoubleBufferPipline_NoJank"></p><p>但是 Double Buffer 也会存在性能上的问题，比如下面的情况，App 连续两帧生产都超过 Vsync 周期(准确的说是错过 SurfaceFlinger 的合成时机) ，就会出现掉帧情况</p><p><img src="/images/15764247063129.jpg" alt="Double Buffer"></p><h2 id="Triple-Buffer"><a href="#Triple-Buffer" class="headerlink" title="Triple Buffer"></a>Triple Buffer</h2><p>Triple Buffer 中，我们又加入了一个 BackBuffer ，这样的话 BufferQueue 里面就有三个 Buffer 可以轮转了，当 FrontBuffer 在被使用的时候，App 有两个空闲的 Buffer 可以拿去生产，就算生产过程中有 GPU 超时，CPU 任然可以拿到新的 Buffer 进行生产(<strong>即 SurfaceFling 消费 FrontBuffer，GPU 使用一个 BackBuffer，CPU使用一个 BackBuffer</strong>)</p><p><img src="/images/15764247163985.jpg" alt="Triple Buffer"></p><p>下面就是引入 Triple Buffer 之后，解决了 Double Buffer 中遇到的由于 Buffer 不足引起的掉帧问题</p><p><img src="/images/TripleBufferPipline_NoJank.webp" alt="TripleBufferPipline_NoJank"></p><p>这里把两个图放到一起来看，方便大家做对比（一个是 Double Buffer 掉帧两次，一个是使用 Triple Buffer 只掉了一帧）</p><p><img src="/images/TripleBuffer_VS_DoubleBuffer.webp" alt="TripleBuffer_VS_DoubleBuffer"></p><p><a id="effects"></a></p><h1 id="Triple-Buffer-的作用"><a href="#Triple-Buffer-的作用" class="headerlink" title="Triple Buffer 的作用"></a>Triple Buffer 的作用</h1><h2 id="缓解掉帧"><a href="#缓解掉帧" class="headerlink" title="缓解掉帧"></a>缓解掉帧</h2><p>从上一节 Double Buffer 和 Triple Buffer 的对比图可以看到，在这种情况下（出现连续主线程超时），三个 Buffer 的轮转有助于缓解掉帧出现的次数（从掉帧两次 -&gt; 只掉帧一次）</p><p>所以从第一节如何定义掉帧这里我们就知道，App 主线程超时不一定会导致掉帧，由于 Triple Buffer 的存在，部分 App 端的掉帧(主要是由于 GPU 导致)，到 SurfaceFlinger 这里未必是掉帧，这是看 Systrace 的时候需要注意的一个点</p><p><img src="/images/15764247460509.jpg" alt="缓解掉帧"></p><h2 id="减少主线程和渲染线程等待时间"><a href="#减少主线程和渲染线程等待时间" class="headerlink" title="减少主线程和渲染线程等待时间"></a>减少主线程和渲染线程等待时间</h2><p><strong>双 Buffer 的轮转</strong>， App 主线程有时候必须要等待 SurfaceFlinger(消费者)释放 Buffer 后，才能获取 Buffer 进行生产，这时候就有个问题，现在大部分手机 SurfaceFlinger 和 App 同时收到 Vsync 信号，如果出现App 主线程等待 SurfaceFlinger(消费者)释放 Buffer ，那么势必会让 App 主线程的执行时间延后，比如下面这张图，可以明显看到：<strong>Buffer B 并不是在 Vsync 信号来的时候开始被消费(因为还在使用)，而是等 Buffer A 被消费后，Buffer B 被释放，App 才能拿到 Buffer B 进行生产，这期间就有一定的延迟，会让主线程可用的时间变短</strong></p><p><img src="/images/15764247531355.jpg" alt="减少主线程和渲染线程等待时间"></p><p>我们来看一下在 Systrace 中的上面这种情况发生的时候的表现</p><p><img src="/images/15764247599570.jpg" alt="减少主线程和渲染线程等待时间"></p><p>而 三个 Buffer 轮转的情况下，则基本不会有这种情况的发生，渲染线程一般在 dequeueBuffer 的时候，都可以顺利拿到可用的 Buffer （当然如果 dequeueBuffer 本身耗时那就不是这里的讨论范围了）</p><h2 id="降低-GPU-和-SurfaceFlinger-瓶颈"><a href="#降低-GPU-和-SurfaceFlinger-瓶颈" class="headerlink" title="降低 GPU 和 SurfaceFlinger 瓶颈"></a>降低 GPU 和 SurfaceFlinger 瓶颈</h2><p>这个比较好理解，双 Buffer 的时候，App 生产的 Buffer 必须要及时拿去让 GPU 进行渲染，然后 SurfaceFlinger 才能进行合成，一旦 GPU 超时，就很容易出现 SurfaceFlinger 无法及时合成而导致掉帧</p><p>在三个 Buffer 轮转的时候，App 生产的 Buffer 可以及早进入 BufferQueue，让 GPU 去进行渲染（因为不需要等待，就算这里积累了 2 个 Buffer，下下一帧才去合成，这里也会提早进行，而不是在真正使用之前去匆忙让 GPU 去渲染），另外 SurfaceFlinger 本身的负载如果比较大，三个 Buffer 轮转也会有效降低 dequeueBuffer 的等待时间</p><p>比如下面两张图，就是对应的 SurfaceFlinger 和 App 的<strong>双 Buffer 掉帧</strong>情况，由于 SurfaceFlinger 本身就比较耗时（特定场景），而 App 的 dequeueBuffer 得不到及时的响应，导致发生了比较严重的掉帧情况。在换成 Triple Buffer 之后，这种情况就基本上没有了</p><p><img src="/images/15764247685189.jpg"></p><p><img src="/images/15764247751046.jpg"></p><p><a id="debug"></a></p><h1 id="Debug-Triple-Buffer"><a href="#Debug-Triple-Buffer" class="headerlink" title="Debug Triple Buffer"></a>Debug Triple Buffer</h1><h2 id="Dumpsys-SurfaceFlinger"><a href="#Dumpsys-SurfaceFlinger" class="headerlink" title="Dumpsys SurfaceFlinger"></a>Dumpsys SurfaceFlinger</h2><p>dumpsys SurfaceFlinger 可以查看 SurfaceFlinger 输出的众多当前的状态，比如一些性能指标、Buffer 状态、图层信息等，后续有篇幅的话可以单独拿出来讲，下面是截取的 Double Buffer 情况下和 Triple Buffer 情况下的各个 App 的 Buffer 使用情况，可以看到不同的 App，在负载不一样的情况下，对 Triple Buffer 的使用率是不一样的；Double Buffer 则完全使用的是双 Buffer</p><p><img src="/images/15764247853726.jpg"></p><h2 id="关闭-Triple-Buffer"><a href="#关闭-Triple-Buffer" class="headerlink" title="关闭 Triple Buffer"></a>关闭 Triple Buffer</h2><p>不同 Android 版本属性设置不一样(这是 Google 的一个逻辑 Bug，Android 10 上面已经修复了)</p><h3 id="Android-版本-lt-x3D-Android-P"><a href="#Android-版本-lt-x3D-Android-P" class="headerlink" title="Android 版本 &lt;&#x3D; Android P"></a>Android 版本 &lt;&#x3D; Android P</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//控制代码</span></span><br><span class="line">property<span class="constructor">_get(<span class="string">&quot;ro.sf.disable_triple_buffer&quot;</span>, <span class="params">value</span>, <span class="string">&quot;1&quot;</span>)</span>;</span><br><span class="line">mLayerTripleBufferingDisabled = atoi(value);</span><br><span class="line"><span class="constructor">ALOGI_IF(<span class="params">mLayerTripleBufferingDisabled</span>, <span class="string">&quot;Disabling Triple Buffering&quot;</span>)</span>;</span><br></pre></td></tr></table></figure><p><strong>修改对应的属性值，然后重启 Framework</strong></p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//按顺序执行下面的语句(需要 Root 权限)</span></span><br><span class="line">adb root</span><br><span class="line">adb <span class="built_in">shell</span> setprop ro.sf.disable_triple_buffer <span class="number">0</span></span><br><span class="line">adb <span class="built_in">shell</span> <span class="keyword">stop</span> &amp;&amp; adb <span class="built_in">shell</span> start</span><br></pre></td></tr></table></figure><h3 id="Android-版本-gt-Android-P"><a href="#Android-版本-gt-Android-P" class="headerlink" title="Android 版本 &gt; Android P"></a>Android 版本 &gt; Android P</h3><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//控制代码</span></span><br><span class="line">property<span class="constructor">_get(<span class="string">&quot;ro.sf.disable_triple_buffer&quot;</span>, <span class="params">value</span>, <span class="string">&quot;0&quot;</span>)</span>;</span><br><span class="line">mLayerTripleBufferingDisabled = atoi(value);</span><br><span class="line"><span class="constructor">ALOGI_IF(<span class="params">mLayerTripleBufferingDisabled</span>, <span class="string">&quot;Disabling Triple Buffering&quot;</span>)</span>;</span><br></pre></td></tr></table></figure><p><strong>修改对应的属性值，然后重启 Framework</strong></p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//按顺序执行下面的语句(需要 Root 权限)</span></span><br><span class="line">adb root</span><br><span class="line">adb <span class="built_in">shell</span> setprop ro.sf.disable_triple_buffer <span class="number">1</span></span><br><span class="line">adb <span class="built_in">shell</span> <span class="keyword">stop</span> &amp;&amp; adb <span class="built_in">shell</span> start</span><br></pre></td></tr></table></figure><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://source.android.google.cn/devices/graphics">https://source.android.google.cn/devices/graphics</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>本文涉及到的附件也上传了，各位下载后解压，使用 <strong>Chrome</strong> 浏览器打开即可<br><a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_TripleBuffer">点此链接下载文章所涉及到的 Systrace 附件</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第十一篇，主要是对 Systrace 中的 Triple Buffer 进行简单介绍，简单介绍了如何在 Systrace 中判断卡顿情况的发生，进行初步的定位和分析，以及介绍 Triple Buffer 的引入对性能的影响&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - Binder 和锁竞争解读</title>
    <link href="https://androidperformance.com/2019/12/06/Android-Systrace-Binder/"/>
    <id>https://androidperformance.com/2019/12/06/Android-Systrace-Binder/</id>
    <published>2019-12-06T11:12:34.000Z</published>
    <updated>2026-02-07T05:17:47.843Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第十篇，主要是对 Systrace 中的 Binder 和锁信息进行简单介绍，简单介绍了 Binder 的情况，介绍了 Systrace 中 Binder 通信的表现形式，以及 Binder 信息查看，SystemServer 锁竞争分析等</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#overview">Binder 概述</a></li><li><a href="#diagram">Binder 调用图例</a></li><li><a href="#locks">Systrace 显示的锁的信息</a></li><li><a href="#waiting">等锁分析</a></li><li><a href="#code">相关代码</a></li><li><a href="#refs">参考</a></li><li><a href="#attachments">附件</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a> </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="overview"></a></p><h1 id="Binder-概述"><a href="#Binder-概述" class="headerlink" title="Binder 概述"></a>Binder 概述</h1><p>Android 的大部分进程间通信都使用 Binder，这里对 Binder 不做过多的解释，想对 Binder 的实现有一个比较深入的了解的话，推荐你阅读下面三篇文章</p><ol><li><a href="https://paul.pub/android-binder-driver/">理解Android Binder机制1&#x2F;3：驱动篇</a></li><li><a href="https://paul.pub/android-binder-cpp/">理解Android Binder机制2&#x2F;3：C++层</a></li><li><a href="https://paul.pub/android-binder-java/">理解Android Binder机制3&#x2F;3：Java层</a></li></ol><p><strong>之所以要单独讲 Systrace 中的 Binder 和锁，是因为很多卡顿问题和响应速度的问题，是因为跨进程 binder 通信的时候，锁竞争导致 binder 通信事件变长，影响了调用端。最常见的就是应用渲染线程 dequeueBuffer 的时候 SurfaceFlinger 主线程阻塞导致 dequeueBuffer 耗时，从而导致应用渲染出现卡顿; 或者 SystemServer 中的 AMS 或者 WMS 持锁方法等待太多, 导致应用调用的时候等待时间比较长导致主线程卡顿</strong></p><p>这里放一张文章里面的 Binder 架构图 ， 本文主要是以 Systrace 为主，所以会讲 Systrace 中的 Binder 表现，不涉及 Binder 的实现</p><p><img src="/images/15756309069397.jpg"></p><p><a id="diagram"></a></p><h1 id="Binder-调用图例"><a href="#Binder-调用图例" class="headerlink" title="Binder 调用图例"></a>Binder 调用图例</h1><p>Binder 主要是用来跨进程进行通信，可以看下面这张图，简单显示了在 Systrace 中 ，Binder 通信是如何显示的</p><p><img src="/images/15756309176435.jpg"></p><p>图中主要是 SystemServer 进程和 高通的 perf 进程通信，Systrace 中右上角 ViewOption 里面勾选 Flow Events  就可以看到 Binder 的信息</p><p><img src="/images/15756309267278.jpg"></p><p>点击 Binder 可以查看其详细信息，其中有的信息在分析问题的时候可以用到，这里不做过多的描述</p><p><img src="/images/15756309336019.jpg"></p><p>对于 Binder，这里主要介绍如何在 Systrace 中查看 Binder <strong>锁信息</strong>和<strong>锁等待</strong>这两个部分，很多卡顿和响应问题的分析，都离不开这两部分信息的解读，不过最后还是要回归代码，找到问题后，要读源码来理顺其代码逻辑，以方便做相应的优化工作</p><p><a id="locks"></a></p><h1 id="Systrace-显示的锁的信息"><a href="#Systrace-显示的锁的信息" class="headerlink" title="Systrace 显示的锁的信息"></a>Systrace 显示的锁的信息</h1><p><img src="/images/15756309429683.jpg"></p><p><strong>monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters&#x3D;2 blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)</strong></p><p>上面的话分两段来看，以 <strong>blocking</strong> 为分界线　</p><h2 id="第一段信息解读"><a href="#第一段信息解读" class="headerlink" title="第一段信息解读"></a>第一段信息解读</h2><p><strong>monitor contention with owner Binder:1605_B (4667) at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733) waiters&#x3D;2</strong></p><p><strong>Monitor</strong> 指的是当前锁对象的池，在 Java 中，每个对象都有两个池，锁(monitor)池和等待池：</p><p><strong>锁池</strong>（同步队列 SynchronizedQueue ）：假设线程 A 已经拥有了某个对象(注意:不是类 )的锁，而其它的线程想要调用这个对象的某个 synchronized 方法(或者 synchronized 块)，由于这些线程在进入对象的 synchronized 方法之前必须先获得该对象的锁的拥有权，但是该对象的锁目前正被线程 A 拥有，所以这些线程就进入了该对象的锁池中。</p><p>这里用了争夺(contention)这个词，意思是这里由于在和目前对象的锁正被其他对象（Owner）所持有，所以没法得到该对象的锁的拥有权，所以进入该对象的锁池</p><p><strong>Owner</strong> : 指的是当前<strong>拥有</strong>这个对象的锁的对象。这里是 Binder:1605_B，4667 是其线程 ID。</p><p><strong>at</strong> 后面跟的是<strong>拥有</strong>这个对象的锁的对象正在做什么。这里是在执行 void com.android.server.wm.ActivityTaskManagerService.activityPaused 这个方法，其代码位置是 ：ActivityTaskManagerService.java:1733 其对应的代码如下：</p><p>com&#x2F;android&#x2F;server&#x2F;wm&#x2F;ActivityTaskManagerService.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">activityPaused</span><span class="params">(IBinder token)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">origId</span> <span class="operator">=</span> Binder.clearCallingIdentity();</span><br><span class="line">    <span class="keyword">synchronized</span> (mGlobalLock) &#123; <span class="comment">// 1733 是这一行</span></span><br><span class="line">        <span class="type">ActivityStack</span> <span class="variable">stack</span> <span class="operator">=</span> ActivityRecord.getStackLocked(token);</span><br><span class="line">        <span class="keyword">if</span> (stack != <span class="literal">null</span>) &#123;</span><br><span class="line">            stack.activityPausedLocked(token, <span class="literal">false</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    Binder.restoreCallingIdentity(origId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到这里 synchronized (mGlobalLock) ，获取了 mGlobalLock 锁的拥有权，在他释放这个对象的锁之前，任何其他的调用 synchronized (mGlobalLock) 的地方都得在锁池中等待</p><p><strong>waiters</strong> 值得是锁池里面正在等待锁的操作的个数；这里 waiters&#x3D;2 表示目前锁池里面已经有一个操作在等待这个对象的锁释放了，加上这个的话就是 3 个了</p><h2 id="第二段信息解读"><a href="#第二段信息解读" class="headerlink" title="第二段信息解读"></a>第二段信息解读</h2><p><strong>blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)</strong></p><p>第二段信息相对来说简单一些，就是标识了当前被阻塞等锁的方法 ， 这里是 ActivityManager 的 getFocusedStackInfo 被阻塞，其对应的代码</p><p>com&#x2F;android&#x2F;server&#x2F;wm&#x2F;ActivityTaskManagerService.java</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> ActivityManager.<span class="function">StackInfo <span class="title">getFocusedStackInfo</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException </span>&#123;</span><br><span class="line">    enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, <span class="string">&quot;getStackInfo()&quot;</span>);</span><br><span class="line">    <span class="keyword">long</span> ident = Binder.clearCallingIdentity();</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (mGlobalLock) &#123; <span class="comment">// 2064 是这一行 </span></span><br><span class="line">            ActivityStack focusedStack = getTopDisplayFocusedStack();</span><br><span class="line">            <span class="keyword">if</span> (focusedStack != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="function"><span class="keyword">return</span> mRootActivityContainer.<span class="title">getStackInfo</span><span class="params">(focusedStack.mStackId)</span></span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        Binder.restoreCallingIdentity(ident);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到这里也是调用了 synchronized (ActivityManagerService.this) ，从而需要等待获取 ams 对象的锁拥有权</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>上面这段话翻译过来就是</p><p><strong>ActivityTaskManagerService 的 getFocusedStackInfo 方法在执行过程中被阻塞，原因是因为执行同步方法块的时候，没有拿到同步对象的锁的拥有权；需要等待拥有同步对象的锁拥有权的另外一个方法 ActivityTaskManagerService.activityPaused 执行完成后，才能拿到同步对象的锁的拥有权，然后继续执行</strong></p><p>可以对照原文看上面的翻译</p><p><strong>monitor contention with owner Binder:1605_B (4667)<br>at void com.android.server.wm.ActivityTaskManagerService.activityPaused(android.os.IBinder)(ActivityTaskManagerService.java:1733)<br>waiters&#x3D;2<br>blocking from android.app.ActivityManager$StackInfo com.android.server.wm.ActivityTaskManagerService.getFocusedStackInfo()(ActivityTaskManagerService.java:2064)</strong></p><p><a id="waiting"></a></p><h1 id="等锁分析"><a href="#等锁分析" class="headerlink" title="等锁分析"></a>等锁分析</h1><p>还是上面那个 Systrace，Binder 信息里面显示 waiters&#x3D;2，意味着前面还有两个操作在等锁释放，也就是说总共有三个操作都在等待 Binder:1605_B (4667) 释放锁，我们来看一下 Binder:1605_B 的执行情况</p><p><img src="/images/15756309846544.jpg"></p><p>从上图可以看到，Binder:1605_B 正在执行 activityPaused，中间也有一些其他的 Binder 操作，最终 activityPaused 执行完成后，释放锁</p><p>下面我们就把这个逻辑里面的执行顺序理顺，包括两个 <strong>waiters</strong></p><h2 id="锁等待"><a href="#锁等待" class="headerlink" title="锁等待"></a>锁等待</h2><p><img src="/images/15756309922668.jpg"></p><p>上图中可以看到 mGlobalLock 这个对象锁的争夺情况</p><ol><li>Binder_1605_B 首先开始执行 <strong>activityPaused</strong>，这个方法中是要获取 mGlobalLock 对象锁的，由于此时 mGlobalLock 没有竞争，所以 activityPaused 获取对象锁之后开始执行</li><li>android.display 线程开始执行 <strong>checkVisibility</strong> 方法，这个方法也是要获取 mGlobalLock 对象锁的，但是此时 Binder_1605_B 的 activityPaused 持有 mGlobalLock 对象锁 ，所以这里 android.display 的 checkVisibility 开始等待，进入 sleep 状态</li><li>android.anim 线程开始执行 <strong>relayoutWindow</strong> 方法，这个方法也是要获取 mGlobalLock 对象锁的，但是此时 Binder_1605_B 的 activityPaused 持有 mGlobalLock 对象锁 ，所以这里 android.display 的 checkVisibility 开始等待，进入 sleep 状态</li><li>android.bg 线程开始执行 <strong>getFocusedStackInfo</strong> 方法，这个方法也是要获取 mGlobalLock 对象锁的，但是此时 Binder_1605_B 的 activityPaused 持有 mGlobalLock 对象锁 ，所以这里 android.display 的 checkVisibility 开始等待，进入 sleep 状态</li></ol><p>经过上面四步，就形成了 Binder_1605_B 线程在运行，其他三个争夺 mGlobalLock 对象锁失败的线程分别进入 sleep 状态，等待 Binder_1605_B 执行结束后释放 mGlobalLock 对象锁</p><h2 id="锁释放"><a href="#锁释放" class="headerlink" title="锁释放"></a>锁释放</h2><p><img src="/images/15756310021037.jpg"></p><p>上图可以看到 mGlobalLock 锁的释放和后续的流程</p><ol><li>Binder_1605_B 线程的 <strong>activityPaused</strong> 执行结束，mGlobalLock 对象锁释放</li><li>第一个进入等待的 android.display 线程开始执行 <strong>checkVisibility</strong> 方法 ，这里从  android.display 线程的唤醒信息可以看到，是被 Binder_1605_B(4667) 唤醒的</li><li>android.display 线程的 <strong>checkVisibility</strong> 执行结束，mGlobalLock 对象锁释放</li><li>第二个进入等待的 android.anim 线程开始执行 <strong>relayoutWindow</strong> 方法 ，这里从  android.anim 线程的唤醒信息可以看到，是被 android.display(1683) 唤醒的</li><li>android.anim 线程的 <strong>relayoutWindow</strong> 执行结束，mGlobalLock 对象锁释放</li><li>第三个进入等待的 android.bg 线程开始执行 <strong>getFocusedStackInfo</strong> 方法 ，这里从  android.bg 线程的唤醒信息可以看到，是被 android.anim(1684) 唤醒的</li></ol><p>经过上面 6 步，这一轮由于 mGlobalLock 对象锁引起的等锁现象结束。这里只是一个简单的例子，在实际情况下，SystemServer 中的 BInder 等锁情况会非常严重，经常 waiter 会到达 7 - 10 个，非常恐怖，比如下面这种：</p><p><img src="/images/15756310119592.jpg"></p><p>这也就可以解释为什么 Android 手机 App 安装多了、用的久了之后，系统就会卡的一个原因；另外重启后也会有短暂的时候出现这种情况</p><p>如果不知道怎么查看唤醒信息，可以查看： <a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/#%E8%BF%9B%E7%A8%8B%E5%94%A4%E9%86%92%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90">Systrace中查看进程信息唤醒</a> 这篇文章</p><p><a id="code"></a></p><h1 id="相关代码"><a href="#相关代码" class="headerlink" title="相关代码"></a>相关代码</h1><h3 id="Monitor-信息"><a href="#Monitor-信息" class="headerlink" title="Monitor 信息"></a>Monitor 信息</h3><p>art&#x2F;runtime&#x2F;monitor.cc</p><figure class="highlight cc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">Monitor::PrettyContentionInfo</span><span class="params">(<span class="type">const</span> std::string&amp; owner_name,</span></span></span><br><span class="line"><span class="params"><span class="function">                                          <span class="type">pid_t</span> owner_tid,</span></span></span><br><span class="line"><span class="params"><span class="function">                                          ArtMethod* owners_method,</span></span></span><br><span class="line"><span class="params"><span class="function">                                          <span class="type">uint32_t</span> owners_dex_pc,</span></span></span><br><span class="line"><span class="params"><span class="function">                                          <span class="type">size_t</span> num_waiters)</span> </span>&#123;</span><br><span class="line">  Locks::mutator_lock_-&gt;<span class="built_in">AssertSharedHeld</span>(Thread::<span class="built_in">Current</span>());</span><br><span class="line">  <span class="type">const</span> <span class="type">char</span>* owners_filename;</span><br><span class="line">  <span class="type">int32_t</span> owners_line_number = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">if</span> (owners_method != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">    <span class="built_in">TranslateLocation</span>(owners_method, owners_dex_pc, &amp;owners_filename, &amp;owners_line_number);</span><br><span class="line">  &#125;</span><br><span class="line">  std::ostringstream oss;</span><br><span class="line">  oss &lt;&lt; <span class="string">&quot;monitor contention with owner &quot;</span> &lt;&lt; owner_name &lt;&lt; <span class="string">&quot; (&quot;</span> &lt;&lt; owner_tid &lt;&lt; <span class="string">&quot;)&quot;</span>;</span><br><span class="line">  <span class="keyword">if</span> (owners_method != <span class="literal">nullptr</span>) &#123;</span><br><span class="line">    oss &lt;&lt; <span class="string">&quot; at &quot;</span> &lt;&lt; owners_method-&gt;<span class="built_in">PrettyMethod</span>();</span><br><span class="line">    oss &lt;&lt; <span class="string">&quot;(&quot;</span> &lt;&lt; owners_filename &lt;&lt; <span class="string">&quot;:&quot;</span> &lt;&lt; owners_line_number &lt;&lt; <span class="string">&quot;)&quot;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  oss &lt;&lt; <span class="string">&quot; waiters=&quot;</span> &lt;&lt; num_waiters;</span><br><span class="line">  <span class="keyword">return</span> oss.<span class="built_in">str</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Block-信息"><a href="#Block-信息" class="headerlink" title="Block 信息"></a>Block 信息</h3><p>art&#x2F;runtime&#x2F;monitor.cc</p><figure class="highlight cc"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="built_in">ATRACE_ENABLED</span>()) &#123;</span><br><span class="line">  <span class="keyword">if</span> (owner_ != <span class="literal">nullptr</span>) &#123;  <span class="comment">// Did the owner_ give the lock up?</span></span><br><span class="line">    std::ostringstream oss;</span><br><span class="line">    std::string name;</span><br><span class="line">    owner_-&gt;<span class="built_in">GetThreadName</span>(name);</span><br><span class="line">    oss &lt;&lt; <span class="built_in">PrettyContentionInfo</span>(name,</span><br><span class="line">                                owner_-&gt;<span class="built_in">GetTid</span>(),</span><br><span class="line">                                owners_method,</span><br><span class="line">                                owners_dex_pc,</span><br><span class="line">                                num_waiters);</span><br><span class="line">    <span class="comment">// Add info for contending thread.</span></span><br><span class="line">    <span class="type">uint32_t</span> pc;</span><br><span class="line">    ArtMethod* m = self-&gt;<span class="built_in">GetCurrentMethod</span>(&amp;pc);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>* filename;</span><br><span class="line">    <span class="type">int32_t</span> line_number;</span><br><span class="line">    <span class="built_in">TranslateLocation</span>(m, pc, &amp;filename, &amp;line_number);</span><br><span class="line">    oss &lt;&lt; <span class="string">&quot; blocking from &quot;</span></span><br><span class="line">        &lt;&lt; ArtMethod::<span class="built_in">PrettyMethod</span>(m) &lt;&lt; <span class="string">&quot;(&quot;</span> &lt;&lt; (filename != <span class="literal">nullptr</span> ? filename : <span class="string">&quot;null&quot;</span>)</span><br><span class="line">        &lt;&lt; <span class="string">&quot;:&quot;</span> &lt;&lt; line_number &lt;&lt; <span class="string">&quot;)&quot;</span>;</span><br><span class="line">    <span class="built_in">ATRACE_BEGIN</span>(oss.<span class="built_in">str</span>().<span class="built_in">c_str</span>());</span><br><span class="line">    started_trace = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://paul.pub/android-binder-driver/">理解Android Binder机制1&#x2F;3：驱动篇</a></li><li><a href="https://paul.pub/android-binder-cpp/">理解Android Binder机制2&#x2F;3：C++层</a></li><li><a href="https://paul.pub/android-binder-java/">理解Android Binder机制3&#x2F;3：Java层</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>本文涉及到的附件也上传了，各位下载后解压，使用 <strong>Chrome</strong> 浏览器打开即可<br><a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Binder">点此链接下载文章所涉及到的 Systrace 附件</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第十篇，主要是对 Systrace 中的 Binder 和锁信息进行简单介绍，简单介绍了 Binder 的情况，介绍了 Systrace 中 Binder 通信的表现形式，以及 Binder 信息查看，SystemServer 锁竞争分析等&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>「置顶」博客文章目录</title>
    <link href="https://androidperformance.com/2019/12/01/BlogMap/"/>
    <id>https://androidperformance.com/2019/12/01/BlogMap/</id>
    <published>2019-11-30T23:38:21.000Z</published>
    <updated>2026-02-07T05:17:47.853Z</updated>
    
    <content type="html"><![CDATA[<p>本博客内容主要集中在 Android 开发和优化相关的话题，包括一些性能工具的使用、Android App 优化知识、Android Framework 知识讲解，性能理论知识讲解等，这里整理了一份目录供大家参考，大家可以挑感兴趣的部分来看。这里不仅仅包含博客中的内容，一些我在 <a href="https://www.zhihu.com/people/gracker">知乎</a> 或者 <a href="https://t.zsxq.com/mIimiey">知识星球 - The Performance</a> 的回答也会放到这里，不过这个目录里面放的都是我原创的博客，另外还收集了一些优秀文章，我也会不定期更新 <a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">Android 性能优化必知必会</a>。</p><span id="more"></span><p>博客的每次更新都会更新这篇目录，方便大家查阅。我会尽量保证每周一更，学无止境，与大家共勉，有什么想了解的或者博客中不足的地方，请大家在博客或者知乎、微博、微信留言给我，我会积极改正。</p><h1 id="理论知识"><a href="#理论知识" class="headerlink" title="理论知识"></a>理论知识</h1><ol><li><a href="https://www.androidperformance.com/2022/01/07/The-Performace-1-Performance-Tools/">Android 性能优化的术、道、器</a></li><li><a href="https://www.androidperformance.com/2022/03/13/the-performance">The Performance 知识星球简介</a> </li><li><a href="https://www.androidperformance.com/2022/03/27/the-performance-tea-part-01/">The Performance 星球茶话会 - 第一期</a></li><li><a href="https://androidperformance.com/2023/08/21/the-performance-design-of-os/">OS 设计之性能设计</a></li></ol><h1 id="Perfetto-系列"><a href="#Perfetto-系列" class="headerlink" title="Perfetto 系列"></a>Perfetto 系列</h1><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><blockquote><p>备注：Perfetto 和 Systrace 可以互相转换，所以哪个趁手用哪个：</p><ol><li>Perfetto Trace 和 Systrace 都可以在 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 中打开</li><li>Perfetto Trace 在 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 中打开后，可以点击工具栏：Convert trace -&gt; Switch to legacy UI ，在 legacy systrace 界面打开</li></ol></blockquote><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><h1 id="Systrace-系列"><a href="#Systrace-系列" class="headerlink" title="Systrace 系列"></a>Systrace 系列</h1><p>Systrace 工具是分析 Android 性能问题的利器，它可以从一个图形的角度，来展现整机的运行情况。Systrace 工具不仅可以分析性能问题，用它来进行 Framework 的学习也是很好的，这也是我写本系列文章的一个原因。</p><blockquote><p>备注：Perfetto 和 Systrace 可以互相转换，所以哪个趁手用哪个：</p><ol><li>Perfetto Trace 和 Systrace 都可以在 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 中打开</li><li>Perfetto Trace 在 <a href="https://ui.perfetto.dev/">https://ui.perfetto.dev/</a> 中打开后，可以点击工具栏：Convert trace -&gt; Switch to legacy UI ，在 legacy systrace 界面打开</li></ol></blockquote><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 产生与工作机制解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>  </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><h1 id="流畅性"><a href="#流畅性" class="headerlink" title="流畅性"></a>流畅性</h1><p>流畅性主要指的是卡顿、掉帧，对应的英文是 Smooth vs Jank</p><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li><li><a href="https://androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">Android 中的卡顿丢帧原因概述 - 低内存篇</a></li><li><a href="https://androidperformance.com/2018/08/13/Some-Thoughts-on-the-Fluency-of-Android/">关于 Android 系统流畅性的一些思考</a></li><li><a href="https://androidperformance.com/2019/05/15/90hz-on-android/">新的流畅体验，90Hz 漫谈</a></li><li><a href="https://androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/">Android性能优化之过渡绘制(一)</a></li><li><a href="https://androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/">Android性能优化之过渡绘制( 二)</a></li><li><a href="https://androidperformance.com/2015/03/31/android-performance-case-study-follow-up/">Android性能优化后续</a></li><li><a href="https://www.androidperformance.com/2020/08/20/weibo-imageload-opt-on-huawei/">华为手机刷微博体验更好？技术角度的一些分析和思考</a></li></ol><h1 id="响应速度"><a href="#响应速度" class="headerlink" title="响应速度"></a>响应速度</h1><p>响应速度主要指的是 App 冷热启动、界面跳转速度、亮灭屏速度等，对应的英文是 Fast vs Slow</p><ol><li><a href="https://androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a></li><li><a href="https://androidperformance.com/2018/05/20/zhihu-startingwindow/">知乎 救救你的 StartingWindow</a></li><li><a href="https://androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/">Android 中如何计算 App 的启动时间？</a></li><li><a href="https://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/">Android 应用启动优化:一种 DelayLoad 的实现和原理(上篇)</a></li><li><a href="https://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87/">Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇)</a></li></ol><h1 id="ANR"><a href="#ANR" class="headerlink" title="ANR"></a>ANR</h1><p>主要记录 Android ANR 相关的文章</p><ol><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-01-ANR-Design/">Android App ANR 系列 1 ：深入理解 Android ANR 机制</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-02-How-to-analysis-ANR/">Android App ANR 系列 2 ：ANR 分析套路和关键 Log 介绍</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-ANR-03-ANR-Case-Share/">Android App ANR 系列 3 ：ANR 案例分享</a></li></ol><h1 id="内存"><a href="#内存" class="headerlink" title="内存"></a>内存</h1><p>主要记录 Android 内存优化相关的知识和工具，以及对系统的影响</p><ol><li><a href="https://androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">Android 中低内存对性能的影响</a></li><li><a href="https://androidperformance.com/2018/09/13/android-memory/">Android 系统不释放内存吗？</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android 代码内存优化建议-Android 资源篇</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android 代码内存优化建议-Android 官方篇</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android 代码内存优化建议-Java 官方篇</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android 内存优化之一：MAT 使用入门</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android内存优化之二：MAT使用进阶</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android内存优化之三：打开MAT中的Bitmap原图</a></li></ol><h1 id="Framework-知识"><a href="#Framework-知识" class="headerlink" title="Framework 知识"></a>Framework 知识</h1><p>博客中 Framework 相关的内容会集中在这里，包括一些 Framework 的运行原理、Framework 问题的解题思路、Framework 优化方法等</p><ol><li><a href="https://www.androidperformance.com/2023/05/14/bad-android-app-with-system-permissions/#/6-5-WriteSettingsExecutor">当 App 有了系统权限，真的可以为所欲为？</a></li><li><a href="https://androidperformance.com/2019/10/24/Android-Background-Animation/">Android 中的“后台无效动画“行为分析</a></li><li><a href="https://androidperformance.com/2019/09/17/Android-Kill-Background-App-Debug/">Android 框架问题分析案例 - 谁杀了桌面?</a></li><li><a href="https://androidperformance.com/2019/09/01/Android-Activity-Lunch-Mode/">Android 中的 Activity Launch Mode 详解</a></li><li><a href="https://androidperformance.com/2019/07/27/Android-Hardware-Layer/">Android 中的 Hardware Layer 详解</a></li><li><a href="https://androidperformance.com/2019/01/21/android-performance-case-jank-accessbility/">Android 平台应用宝和讯飞输入法无障碍服务导致的全局卡顿分析</a></li><li><a href="https://androidperformance.com/2018/10/24/android-process-review-with-user-side/">从用户角度来理解 Android 应用的状态</a></li><li><a href="https://androidperformance.com/2015/08/12/AndroidL-hwui-RenderThread-workflow/">Android hwui 中 RenderThread 工作流程</a></li><li><a href="https://androidperformance.com/2015/08/05/HashMap/">HashMap 源码分析</a></li><li><a href="https://androidperformance.com/2015/05/06/Java-Singleton/">细说Java单例模式</a></li><li><a href="https://androidperformance.com/2018/11/01/android-system-develop-0/">Android 系统开发源码环境搭建</a></li><li><a href="https://www.androidperformance.com/2020/05/07/Android-App-Chain-Wakeup/">Android App 链式唤醒分析</a></li><li><a href="https://www.androidperformance.com/2020/05/26/samsung_crash/">一个「闰」字引发的事故 - 三星系统重启分析</a></li><li><a href="https://www.androidperformance.com/2021/10/26/build-android-12/">Android 系统开发系列（1）：Android 12 源代码下载、编译和刷机</a></li></ol><h1 id="App-开发"><a href="#App-开发" class="headerlink" title="App 开发"></a>App 开发</h1><p>这里主要记录一些 App 开发相关的博文，由于写的比较早，大家随便看一下就可以了</p><ol><li><a href="https://androidperformance.com/2016/04/05/android-bottom-bar-1/">Android Bottom navigation 规范一：使用方法</a></li><li><a href="https://androidperformance.com/2016/04/05/android-bottom-bar-2/">Android Bottom navigation 规范二：样式、行为与规格</a></li><li><a href="https://androidperformance.com/2014/03/17/android-build-your-own-android-notification-service-app/">Android Service：开发自己的通知中心(1):辅助性服务介绍</a></li><li><a href="https://androidperformance.com/2014/04/01/android-service-build-your-own-notification-servers-app/">Android Service：开发自己的通知中心(2):辅助性服务实战</a></li><li><a href="https://androidperformance.com/2014/05/02/android_log_to_file/">Android开发:Log2File工具类</a></li><li><a href="https://androidperformance.com/2014/03/25/ubuntu-adb-can-not-find-devices/">Android:Ubuntu下执行Adb命令找不到设备</a></li><li><a href="https://androidperformance.com/2014/06/03/android-edittext-do-not-auto-get-focus/">Android小技巧:如何让EditText不自动获取焦点</a></li></ol><h1 id="个人总结和好物推荐"><a href="#个人总结和好物推荐" class="headerlink" title="个人总结和好物推荐"></a>个人总结和好物推荐</h1><p>与技术无关，但是可以提高幸福感和工作效率</p><ol><li><a href="https://androidperformance.com/2024/01/01/2023-review/">2023 年的方方面面</a></li><li><a href="https://www.androidperformance.com/2022/01/03/2021-Review/">回顾 2021</a></li><li><a href="https://androidperformance.com/2019/04/07/liqi/">我是 Gracker，这是我的利器</a></li><li><a href="https://androidperformance.com/2019/01/12/recommend-of-2018/">Gracker 的 2018 年度最推荐 - 给辛勤工作的自己一点奖励</a></li><li><a href="https://androidperformance.com/2018/10/25/How-do-engineers-count-well/">陆奇：除了好代码，工程师怎样才算优秀？</a></li><li><a href="https://androidperformance.com/2018/01/06/2017%E5%B9%B4%E5%BA%A6%E6%9C%80%E6%8E%A8%E8%8D%90/">2017 年度最推荐 - 给辛勤工作的自己一点奖励</a></li><li><a href="https://androidperformance.com/2017/04/23/About-work/">关于 2017</a></li><li><a href="https://www.androidperformance.com/2020/02/03/android-development-learning-path-2020-edition/">Android 开发者学习路线(2020 版本)</a></li><li><a href="https://www.androidperformance.com/2020/01/28/2020-read/">我的 2020 年读书单</a></li></ol><h1 id="读书笔记"><a href="#读书笔记" class="headerlink" title="读书笔记"></a>读书笔记</h1><ol><li><a href="https://www.androidperformance.com/2021/10/27/if-i-write-a-book-about-performance/">一本讲 Android 流畅性的书，应该有什么内容？</a></li><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><h1 id="性能优化典范和-Tips"><a href="#性能优化典范和-Tips" class="headerlink" title="性能优化典范和 Tips"></a>性能优化典范和 Tips</h1><p>性能优化典范是 Google 出品的一系列性能相关的短视频，总共出了 6 季，之前想的是每一集都来一个文章配合，后面发现不是很现实；Android Tips 则是翻译的另外一个博主的文章</p><ol><li><a href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li><li><a href="https://androidperformance.com/2014/05/28/android-tips-round-up-1/">Android Tips 1</a> </li><li><a href="https://androidperformance.com/2014/05/31/android-tips-round-up-2/">Android Tips 2</a> </li><li><a href="https://androidperformance.com/2015/03/15/android-tips-round-up-3/">Android Tips 3</a> </li><li><a href="https://androidperformance.com/2015/03/15/android-tips-round-up-4/">Android Tips 4</a></li><li><a href="https://androidperformance.com/2015/03/15/android-tips-round-up-5/">Android Tips 5</a></li></ol><h1 id="知乎问答"><a href="#知乎问答" class="headerlink" title="知乎问答"></a>知乎问答</h1><p>知乎专栏会搬运一部分文章，这里只贴一些高赞的回答</p><ol><li><a href="https://www.zhihu.com/people/gracker">个人知乎主页</a> ，欢迎大家点赞关注</li><li><a href="https://www.zhihu.com/question/67627009/answer/255199992">如何看待小米部分机型运行《王者荣耀》时两个大核被锁</a></li><li><a href="https://www.zhihu.com/question/38533041/answer/77512815">Flyme 5 相对于 Flyme 4 流畅得脱胎换骨，其中根本的变化是什么？</a></li><li><a href="https://www.zhihu.com/question/24976909/answer/49711238">Android 系统不释放内存吗？</a></li><li><a href="https://www.zhihu.com/question/350047125">了解Android的Framework层对工作有什么帮助吗？</a></li><li><a href="https://www.zhihu.com/question/396666758/answer/1245994988">怎么看待三星大量手机在今天（5.23）凌晨系统崩溃并数据丢失？</a></li></ol><h1 id="Android-Weekly"><a href="#Android-Weekly" class="headerlink" title="Android Weekly"></a>Android Weekly</h1><ol><li><strong>掘金专栏地址</strong> ：<a href="https://juejin.cn/column/7456829086335107091">掘金专栏 - Android Weekly</a> </li><li><strong>知乎专栏地址</strong> ：<a href="https://www.zhihu.com/column/c_1278963991947780096">Android Weekly</a> , 欢迎大家点赞收藏</li></ol><h3 id="Android-Weekly-文章："><a href="#Android-Weekly-文章：" class="headerlink" title="Android Weekly 文章："></a>Android Weekly 文章：</h3><ol><li><a href="https://androidperformance.com/2025/01/06/Android-Weekly-2025-01/">Android Weekly 2025-01 期</a></li><li><a href="https://androidperformance.com/2025/01/12/Android-Weekly-2025-02/">Android Weekly 2025-02 期</a></li><li><a href="https://androidperformance.com/2025/01/19/Android-Weekly-2025-03/">Android Weekly 2025-03 期</a></li><li><a href="https://androidperformance.com/2025/01/27/Android-Weekly-2025-04/">Android Weekly 2025-04 期</a></li><li><a href="https://androidperformance.com/2025/02/03/Android-Weekly-2025-05/">Android Weekly 2025-05 期</a></li><li><a href="https://androidperformance.com/2025/02/09/Android-Weekly-2025-06/">Android Weekly 2025-06 期</a></li><li><a href="https://androidperformance.com/2025/02/16/Android-Weekly-2025-07/">Android Weekly 2025-07 期</a></li><li><a href="https://androidperformance.com/2025/02/23/Android-Weekly-2025-08/">Android Weekly 2025-08 期</a></li><li><a href="https://androidperformance.com/2025/03/02/Android-Weekly-2025-09/">Android Weekly 2025-09 期</a></li><li><a href="https://androidperformance.com/2025/03/10/Android-Weekly-2025-10/">Android Weekly 2025-10 期</a></li><li><a href="https://androidperformance.com/2025/03/16/Android-Weekly-2025-11/">Android Weekly 2025-11 期</a></li><li><a href="https://androidperformance.com/2025/03/31/Android-Weekly-2025-12/">Android Weekly 2025-12 期</a></li><li><a href="https://androidperformance.com/2025/04/06/Android-Weekly-2025-13/">Android Weekly 2025-13 期</a></li><li><a href="https://androidperformance.com/2025/04/13/Android-Weekly-2025-14/">Android Weekly 2025-14 期</a></li><li><a href="https://androidperformance.com/2025/04/20/Android-Weekly-2025-15/">Android Weekly 2025-15 期</a></li><li><a href="https://androidperformance.com/2025/05/19/Android-Weekly-2025-16/">Android Weekly 2025-16 期</a></li><li><a href="https://androidperformance.com/2025/05/25/Android-Weekly-2025-17/">Android Weekly 2025-17 期</a></li><li><a href="https://androidperformance.com/2025/07/20/Android-Weekly-2025-18/">Android Weekly 2025-18 期</a></li><li><a href="https://androidperformance.com/2025/07/28/Android-Weekly-2025-19/">Android Weekly 2025-19 期</a></li></ol><h1 id="个人页"><a href="#个人页" class="headerlink" title="个人页"></a>个人页</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/">个人博客</a>: 写东西的地方</li><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章</a> - Android 性能优化必知必会 ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li><li><strong>微信公众号</strong><br><img src="/images/BlogMap/46c63e7f-e15e-4538-9aa1-72ccc6245942.webp"></li><li><a href="https://www.zhihu.com/people/gracker">知乎</a>：<a href="https://www.zhihu.com/people/gracker">https://www.zhihu.com/people/gracker</a></li><li><a href="https://androidweekly.zhubai.love/">邮箱订阅</a>：<a href="https://androidweekly.zhubai.love/">https://androidweekly.zhubai.love/</a></li><li><a href="https://juejin.cn/user/1816846860560749">掘金</a>:<a href="https://juejin.cn/user/1816846860560749">https://juejin.cn/user/1816846860560749</a></li></ol><h1 id="个人演讲-amp-amp-培训-PPT"><a href="#个人演讲-amp-amp-培训-PPT" class="headerlink" title="个人演讲 &amp;&amp; 培训 PPT"></a>个人演讲 &amp;&amp; 培训 PPT</h1><p>这一部分整理之后会放出来，不过大家都知道，演讲或者培训的时候，PPT 里面不会有太多的东西，多数只是一个大纲</p><ol><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><h1 id="知识星球"><a href="#知识星球" class="headerlink" title="知识星球"></a>知识星球</h1><p>知识星球名为 <strong>The Performance</strong>，一个分享 Android 开发领域性能优化相关的圈子，主理人是博主自己，国内一线手机厂商性能优化方面的一线开发者，有多年性能相关领域的知识积累和案例分析经验，可以提供性能、功耗分析知识的一站式服务，涵盖了基础、方法论、工具使用和最宝贵的案例分析。<br><img src="/images/17039900286551.jpg" alt="付费知识星球"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本博客内容主要集中在 Android 开发和优化相关的话题，包括一些性能工具的使用、Android App 优化知识、Android Framework 知识讲解，性能理论知识讲解等，这里整理了一份目录供大家参考，大家可以挑感兴趣的部分来看。这里不仅仅包含博客中的内容，一些我在 &lt;a href=&quot;https://www.zhihu.com/people/gracker&quot;&gt;知乎&lt;/a&gt; 或者 &lt;a href=&quot;https://t.zsxq.com/mIimiey&quot;&gt;知识星球 - The Performance&lt;/a&gt; 的回答也会放到这里，不过这个目录里面放的都是我原创的博客，另外还收集了一些优秀文章，我也会不定期更新 &lt;a href=&quot;https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/&quot;&gt;Android 性能优化必知必会&lt;/a&gt;。&lt;/p&gt;</summary>
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - Vsync 解读</title>
    <link href="https://androidperformance.com/2019/12/01/Android-Systrace-Vsync/"/>
    <id>https://androidperformance.com/2019/12/01/Android-Systrace-Vsync/</id>
    <published>2019-11-30T22:38:21.000Z</published>
    <updated>2026-02-07T05:17:47.848Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第七篇，主要是是介绍 Android 中的 Vsync 机制。文章会从 Systrace 的角度来看 Android 系统如何基于 Vsync 每一帧的展示。Vsync 是 Systrace 中一个非常关键的机制，虽然我们在操作手机的时候看不见，摸不着，但是在 Systrace 中我们可以看到，Android 系统在 Vsync 信号的指引下，有条不紊地进行者每一帧的渲染、合成操作，使我们可以享受稳定帧率的画面。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#gfx-flow">Android 图形数据流向</a></li><li><a href="#systrace-flow">Systrace 中的图像数据流</a></li><li><a href="#offset">Vsync Offset</a></li><li><a href="#hw-vsync">HW_Vsync</a></li><li><a href="#others">本文其他地址</a></li><li><a href="#refs">参考</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>Vsync 信号可以由硬件产生，也可以用软件模拟，不过现在基本上都是硬件产生，负责产生硬件 Vsync 的是 HWC,HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinge , DispSync 将 Vsync 生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC_APP 和 VSYNC_SF 信号</p><p><img src="/images/15751536260871.jpg"></p><p>在 <a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章里面，我们有提到 ：Choreographer 的引入，主要是配合 Vsync，给上层 App 的渲染提供一个稳定的 Message 处理的时机，也就是 Vsync 到来的时候 ，系统通过对 Vsync 信号周期的调整，来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率，也就是 16.6ms 刷新一次，系统为了配合屏幕的刷新频率，将 Vsync 的周期也设置为 16.6 ms，每个 16.6 ms，Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ，这就是引入 Choreographer 的主要作用</p><p>渲染层(App)与 Vsync 打交道的是 Choreographer，而合成层与 Vsync 打交道的，则是 SurfaceFlinger。SurfaceFlinger 也会在 Vsync 到来的时候，将所有已经准备好的 Surface 进行合成操作</p><p>下图显示在 Systrace 中，SurfaceFlinger 进程中的 VSYNC_APP 和 VSYNC_SF 的情况</p><p><img src="/images/15751536450451.jpg"></p><p><a id="gfx-flow"></a></p><h1 id="Android-图形数据流向"><a href="#Android-图形数据流向" class="headerlink" title="Android 图形数据流向"></a>Android 图形数据流向</h1><p>首先我们要大概了解 Android 中的图形数据流的方向，从下面这张图，结合 Android 的图像流，我们大概把从 App 绘制到屏幕显示，分为下面几个阶段：</p><p><img src="/images/15751536542613.jpg"></p><ol><li>第一阶段：App 在收到 Vsync-App 的时候，在主线程进行 measure、layout、draw(构建 DisplayList , 里面包含 OpenGL 渲染需要的命令及数据) 。这里对应的 Systrace 中的主线程 <strong>doFrame</strong> 操作</li><li>第二阶段：CPU 将数据上传（共享或者拷贝）给 GPU,　这里 ARM 设备 内存一般是 GPU 和 CPU 共享内存。这里对应的 Systrace 中的渲染线程的 <strong>flush drawing commands</strong> 操作</li><li>第三阶段：通知 GPU 渲染，真机一般不会阻塞等待 GPU 渲染结束，CPU 通知结束后就返回继续执行其他任务，使用 Fence 机制辅助 GPU CPU 进行同步操作</li><li>第四 阶段：swapBuffers，并通知 SurfaceFlinger 图层合成。这里对应的 Systrace 中的渲染线程的 <strong>eglSwapBuffersWithDamageKHR</strong> 操作</li><li>第五阶段：SurfaceFlinger 开始合成图层，如果之前提交的 GPU 渲染任务没结束，则等待 GPU 渲染完成，再合成（Fence 机制），合成依然是依赖 GPU，不过这就是下一个任务了.这里对应的 Systrace 中的 SurfaceFlinger 主线程的 onMessageReceived 操作（包括 handleTransaction、handleMessageInvalidate、handleMessageRefresh）SurfaceFlinger 在合成的时候，会将一些合成工作委托给 Hardware Composer,从而降低来自 OpenGL 和 GPU 的负载，只有  Hardware Composer 无法处理的图层，或者指定用 OpenGL 处理的图层，其他的 图层偶会使用  Hardware Composer 进行合成</li><li>第六阶段 ：最终合成好的数据放到屏幕对应的 Frame Buffer 中，固定刷新的时候就可以看到了</li></ol><p>下面这张图也是官方的一张图，结合上面的阶段，从左到右看，可以看到一帧的数据是如何在各个进程之间流动的</p><p><img src="/images/15751536775887.jpg"></p><p><a id="systrace-flow"></a></p><h1 id="Systrace-中的图像数据流"><a href="#Systrace-中的图像数据流" class="headerlink" title="Systrace 中的图像数据流"></a>Systrace 中的图像数据流</h1><p>了解了 Android 中的图形数据流的方向，我们就可以把上面这个比较抽象的数据流图，在 Systrace 上进行映射展示</p><p><img src="/images/15751536946754.jpg"></p><p>上图中主要包含 SurfaceFlinger、App 和 hwc 三个进程，下面就来结合图中的标号，来进一步说明数据的流向</p><ol><li>第一个 Vsync 信号到来, SurfaceFlinger 和 App 同时收到 Vsync 信号</li><li>SurfaceFlinger 收到 Vsync-sf 信号，开始进行 App 上一帧的 Buffer 的合成</li><li>App 收到 Vsycn-app 信号，开始进行这一帧的 Buffer 的渲染(对应上面的第一、二、三、四阶段)</li><li>第二个 Vsync 信号到来 ，SurfaceFlinger 和 App 同时收到 Vsync 信号，SurfaceFlinger 获取 App 在第二步里面渲染的 Buffer，开始合成（对应上面的第五阶段），App 收到 Vsycn-app 信号，开始新一帧的 Buffer 的渲染(对应上面的第一、二、三、四阶段)</li></ol><p><a id="offset"></a></p><h1 id="Vsync-Offset"><a href="#Vsync-Offset" class="headerlink" title="Vsync Offset"></a>Vsync Offset</h1><p>文章最开始有提到，Vsync 信号可以由硬件产生，也可以用软件模拟，不过现在基本上都是硬件产生，负责产生硬件 Vsync 的是 HWC,HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinge , DispSync 将 Vsync 生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC_APP 和 VSYNC_SF 信号.</p><p><img src="/images/disp_sync_arch.webp" alt="disp_sync_arch"></p><p>其中 app 和 sf 相对 hw_vsync_0 都有一个偏移,即 phase-app 和 phase-sf，如下图</p><p><img src="/images/15751537168911.jpg"></p><p><strong>Vsync Offset 我们指的是 VSYNC_APP 和 VSYNC_SF 之间有一个 Offset，即上图中 phase-sf - phase-app 的值</strong>，这个 Offset 是厂商可以配置的。如果 Offset 不为 0，那么意味着 <strong>App 和 SurfaceFlinger 主进程不是同时收到 Vsync 信号，而是间隔 Offset (通常在 0 - 16.6ms 之间)</strong></p><p>目前大部分厂商都没有配置这个 Offset，所以 App 和 SurfaceFlinger 是同时收到 Vsync 信号的.</p><p>可以通过 Dumpsys SurfaceFlinger 来查看对应的值</p><p><strong>Offset 为 0</strong>：（sf phase - app phase &#x3D; 0)</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Sync</span> configuration:<span class="meta"> [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]</span></span><br><span class="line"><span class="attribute">DispSync</span> configuration: </span><br><span class="line">          <span class="attribute">app</span> phase <span class="number">1000000</span> ns,              sf phase <span class="number">1000000</span> ns </span><br><span class="line">    <span class="attribute">early</span> app phase <span class="number">1000000</span> ns,        early sf phase <span class="number">1000000</span> ns </span><br><span class="line"> <span class="attribute">early</span> app gl phase <span class="number">1000000</span> ns,     early sf gl phase <span class="number">1000000</span> ns </span><br><span class="line">     <span class="attribute">present</span> offset <span class="number">0</span> ns                      refresh <span class="number">16666666</span> ns</span><br></pre></td></tr></table></figure><p><strong>Offset 不为 0</strong> (SF phase - app phase &#x3D; 4 ms)</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Sync</span> configuration:<span class="meta"> [using: EGL_ANDROID_native_fence_sync EGL_KHR_wait_sync]</span></span><br><span class="line"></span><br><span class="line"><span class="attribute">VSYNC</span> configuration:</span><br><span class="line">         <span class="attribute">app</span> phase:   <span class="number">2000000</span> ns         SF phase:   <span class="number">6000000</span> ns</span><br><span class="line">   <span class="attribute">early</span> app phase:   <span class="number">2000000</span> ns   early SF phase:   <span class="number">6000000</span> ns</span><br><span class="line"><span class="attribute">GL</span> early app phase:   <span class="number">2000000</span> nsGL early SF phase:   <span class="number">6000000</span> ns</span><br><span class="line">    <span class="attribute">present</span> offset:         <span class="number">0</span> ns     VSYNC period:  <span class="number">16666666</span> ns</span><br></pre></td></tr></table></figure><p>下面以 Systrace 为例，来看 Offset 在 Systrace 中的表现</p><h2 id="Offset-为-0"><a href="#Offset-为-0" class="headerlink" title="Offset 为 0"></a>Offset 为 0</h2><p>首先说 Offset 为 0 的情况， 此时 App 和 SurfaceFlinger 是同时收到 Vsync 信号 ， 其对应的 Systrace 图如下：</p><p><img src="/images/15751537800460.jpg"></p><p>这个图上面也有讲解，这里就不再详细说明，大家只需要看到，App 渲染好的 Buffer，要等到下一个 Vsync-SF 来的时候才会被 SurfaceFlinger 拿去做合成，这个时间大概在 16.6 ms。这时候大家可能会想，<strong>如果 App 的 Buffer 渲染结束，Swap 到 BufferQueue 中 ，就触发 SurfaceFlinger 去做合成，那岂不是省了一些时间(0-16.6ms )</strong>? </p><p>答案是可行的，这也就引入了 Offset 机制，在这种情况下，App 先收到 Vsync 信号，进行一帧的渲染工作，然后过了 Offset 时间后，SurfaceFlinger 才收到 Vsync 信号开始合成，这时候如果 App 的 Buffer 已经 Ready 了，那么 SurfaceFlinger 这一次合成就可以包含 App 这一帧，用户也会早一点看到。</p><h2 id="Offset-不为-0"><a href="#Offset-不为-0" class="headerlink" title="Offset 不为 0"></a>Offset 不为 0</h2><p>下图中，就是一个 Offset 为 4ms 的案例，App 收到 Vsync 4 ms 之后，SurfaceFlinger 才收到 Vsync 信号</p><p><img src="/images/15751537928994.jpg"></p><h2 id="Offset-的优缺点"><a href="#Offset-的优缺点" class="headerlink" title="Offset 的优缺点"></a>Offset 的优缺点</h2><p>Offset 的一个比较难以确定的点就在于 Offset 的时间该如何设置，这也是众多厂商默认都不进行配置 Offset 的一个原因，其优缺点是动态的，与机型的性能和使用场景有很大的关系</p><ol><li>如果 Offset 配置过短，那么可能 App 收到 Vsync-App 后还没有渲染完成，SurfaceFlinger 就收到 Vsync-SF 开始合成，那么此时如果 App 的 BufferQueue 中没有之前累积的 Buffer，那么 SurfaceFlinger 这次合成就不会有 App 的东西在里面，需要等到下一个 Vsync-SF 才能合成这次 App 的内容，时间相当于变成了 Vsync 周期+Offset，而不是我们期待的 Offset</li><li>如果 Offset 配置过长，就起不到作用了</li></ol><p><a id="hw-vsync"></a></p><h1 id="HW-Vsync"><a href="#HW-Vsync" class="headerlink" title="HW_Vsync"></a>HW_Vsync</h1><p>这里需要说明的是，不是每次申请 Vsync 都会由硬件产生 Vsync，只有此次请求 vsync 的时间距离上次合成时间大于 500ms，才会通知 hwc，请求 HW_VSYNC</p><p>以桌面滑动为例，看 SurfaceFlinger 的进程 Trace 可以看到 HW_VSYNC 的状态</p><p><img src="/images/15751538069738.jpg"></p><p>后续 App 申请 Vsync 时候，会有两种情况，一种是有 HW_VSYNC 的情况，一种是没有有 HW_VSYNC 的情况</p><h2 id="不使用HW-VSYNC"><a href="#不使用HW-VSYNC" class="headerlink" title="不使用HW_VSYNC"></a>不使用HW_VSYNC</h2><p><img src="/images/15751538170844.jpg"></p><h2 id="使用-HW-VSYNC"><a href="#使用-HW-VSYNC" class="headerlink" title="使用 HW_VSYNC"></a>使用 HW_VSYNC</h2><p><img src="/images/15751538247774.jpg"></p><p>HW_VSYNC 主要是利用最近的硬件 VSYNC 来做预测,最少要 3 个,最多是 32 个,实际上要用几个则不一定, DispSync 拿到 6 个 VSYNC 后就会计算出 SW_VSYNC,只要收到的 Present Fence 没有超过误差,硬件 VSYNC 就会关掉,不然会继续接收硬件 VSYNC 计算 SW_VSYNC 的值,直到误差小于 threshold.关于这一块的计算具体过程，可以参考这篇文章： <a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20">S</a>  <a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20">W-VS</a>  <a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20">YN</a>  <a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20">C</a><a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20"> 的生成与传递</a> ，关于这一块的流程大家也可以参考这篇文章，里面有更细节的内容，这里摘录了他的结论</p><blockquote><p>SurfaceFlinger 通过实现了 HWC2::ComposerCallback 接口，当 HW-VSYNC 到来的时候，SurfaceFlinger 将会收到回调并且发给 DispSync。DispSync 将会把这些 HW-VSYNC 的时间戳记录下来，当累计了足够的 HW-VSYNC 以后（目前是大于等于 6 个），就开始计算 SW-VSYNC 的偏移 mPeriod。计算出来的 mPeriod 将会用于 DispSyncThread 用来模拟 HW-VSYNC 的周期性起来并且通知对 VSYNC 感兴趣的 Listener，这些 Listener 包括 SurfaceFlinger 和所有需要渲染画面的 app。这些 Listener 通过 EventThread 以 Connection 的抽象形式注册到 EventThread。DispSyncThread 与 EventThread 通过 DispSyncSource 作为中间人进行连接。EventThread 在收到 SW-VSYNC 以后将会把通知所有感兴趣的 Connection，然后 SurfaceFlinger 开始合成，app 开始画帧。在收到足够多的 HW-VSYNC 并且在误差允许的范围内，将会关闭通过 EventControlThread 关闭 HW-VSYNC。</p></blockquote><p><a id="others"></a></p><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>待更新</p><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://source.android.google.cn/devices/graphics/implement-vsync">VSYNC</a> </li><li><a href="https://juejin.im/post/5b6948086fb9a04fb87771fb">https://juejin.im/post/5b6948086fb9a04fb87771fb</a></li><li><a href="http://gityuan.com/2017/02/05/graphic_arch/">http://gityuan.com/2017/02/05/graphic_arch&#x2F;</a></li><li><a href="https://juejin.im/post/5dbe658be51d452a45800e76#heading-20">SW-VSYNC 的生成与传递</a></li><li><a href="http://echuang54.blogspot.com/2015/01/dispsync.html">http://echuang54.blogspot.com/2015/01/dispsync.html</a></li></ol><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第七篇，主要是是介绍 Android 中的 Vsync 机制。文章会从 Systrace 的角度来看 Android 系统如何基于 Vsync 每一帧的展示。Vsync 是 Systrace 中一个非常关键的机制，虽然我们在操作手机的时候看不见，摸不着，但是在 Systrace 中我们可以看到，Android 系统在 Vsync 信号的指引下，有条不紊地进行者每一帧的渲染、合成操作，使我们可以享受稳定帧率的画面。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android App 启动优化全记录</title>
    <link href="https://androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/"/>
    <id>https://androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/</id>
    <published>2019-11-18T15:02:46.000Z</published>
    <updated>2026-02-07T05:17:47.823Z</updated>
    
    <content type="html"><![CDATA[<p>本文参考了目前大部分 Android 应用启动优化的方案，将大家的方案做一个汇总，如果你有这方面的需求，只需要对照这篇文章，看看其他人的方案，查漏补缺。很多方案是要根据具体的业务去做优化的，所以这里也没有对每一种方案进行详细的介绍，要用到哪一个方案的时候，可以具体去网上查找对应方案的具体实现方法，这里只是做一个汇总</p><p>另外我还加上了部分系统厂商所做的启动相关的优化，不过只写了一些我知道的，还有一些厂商有黑科技，就不在这里的讨论范围了。知道厂商做的事情，可能也会帮助到你，比如联系厂商做白名单、接入厂商 SDK 等</p><span id="more"></span><h1 id="应用启动概述"><a href="#应用启动概述" class="headerlink" title="应用启动概述"></a>应用启动概述</h1><h2 id="应用启动的一般流程"><a href="#应用启动的一般流程" class="headerlink" title="应用启动的一般流程"></a>应用启动的一般流程</h2><p>应用的启动，从桌面点击应用图标到主界面用户可操作，大致遵循下面的流程：</p><p><img src="/images/15740895170994.jpg"></p><p>可以看到应用启动过程中，最重要的两个进程就是 SystemServer 和 App Process . 其职责划分如下：</p><ul><li><strong>SystemServer 负责应用的启动流程调度、进程的创建和管理、窗口的创建和管理(StartingWindow 和 AppWindow) 等</strong></li><li><strong>应用进程被 SystemServer 创建后，进行一系列的进程初始化、组件初始化(Activity、Service、ContentProvider、Broadcast)、主界面的构建、内容填充等</strong></li></ul><h2 id="冷启动和热启动"><a href="#冷启动和热启动" class="headerlink" title="冷启动和热启动"></a>冷启动和热启动</h2><p>这里还需要引入冷启动和热启动的概念，这也是我们经常会碰到的两个概念</p><ul><li><strong>冷启动</strong>：当启动应用时，后台没有该应用的进程，这时系统会重新创建一个新的进程分配给该应用，然后再根据启动的参数，启动对应的进程组件，这个启动方式就是冷启动</li><li><strong>热启动</strong>：当启动应用时，后台已有该应用的进程（例：按back键、home键，应用虽然会退出，但是该应用的进程是依然会保留在后台，可进入任务列表查看），所以在已有进程的情况下，这种启动会从已有的进程中来启动对应的进程组件，这个方式叫热启动</li></ul><h2 id="启动速度的测量"><a href="#启动速度的测量" class="headerlink" title="启动速度的测量"></a>启动速度的测量</h2><p>各家应该都有自己的方案，关键在于如何定义启动结束的点，这个也是一直困扰我的一个地方，有的应用很好定义，有的应用则因为比较复杂，无法直接衡量启动速度。像 adb 这种方法自己玩玩可以，生产环境没啥用；录屏本身就有性能损耗..</p><p>这里我建议大家学习<a href="https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&mid=2650403370&idx=1&sn=b4297b138eb7f73c95a6279c3458f025&chksm=83953a32b4e2b3247fc18cbee08a2682d8b09720a1c5fef0c36257ae92b1e201cb1ad3125455&mpshare=1&scene=1&srcid=#rd">历时1年，上百万行代码！首次揭秘手淘全链路性能优化（上）</a>中提到的测量方法：自动化、稳定、持续集成</p><blockquote><p>通过OCR提取图片中的文字信息作为关键特征。该算法的优势：1. 在于应用页面上基本都是有文字的， OCR也可以识别到图片上的文字， 文字出现则图片加载完成， 和用户体感是一致的；2. 文字作为特征，过滤掉了很多图片特征可能带来的噪声， 减少了算法调试的工作量；另外阿里集团内有非常成熟和优秀的OCR服务——读光，文档识别率超过99.7%， 使用水滴平台封装的OCR服务，可以快速接入和使用。最终的识别方案就是基于OCR识别来进行的</p></blockquote><h1 id="App-优化"><a href="#App-优化" class="headerlink" title="App 优化"></a>App 优化</h1><h2 id="启动窗口优化"><a href="#启动窗口优化" class="headerlink" title="启动窗口优化"></a>启动窗口优化</h2><p>启动窗口，也叫启动页、SplashWindow、StartingWindow 等，指的是应用启动时候的预览窗口。iOS App 强制有一个启动页，用户点击桌面 App 图标之后，系统会立即显示这个启动窗口，等 App 主页加载好之后再显示主页面。Android 也有类似的机制 (启动窗口这个是 Android 系统提供的)，但是也提供了一个接口，让应用开发者设置是否显示这个启动窗口(默认是显示)，部分开发者会把这个系统提供的启动窗口禁掉，启动自己的窗口。</p><p>但是启动自己的窗口需要的时间要比直接显示系统的启动窗口所花的时间要长，这就会导致用户在使用的时候，点击图标启动 App 的时候，有一定的延迟，表现在点击图标过了一段时间才进行窗口动画进入 App，我们要尽量避免这种情况</p><ul><li><strong>不要禁止系统默认的启动窗口：即不要在主题里面设置 android:windowDisablePreview 为 true</strong></li><li>自己定制启动窗口的内容，比如将启动页主题背景设置成闪屏页图片，或者尽量使闪屏页面的主题和主页一致。可以参考知乎、抖音的做法</li><li>合并闪屏和主页面的 Activity ：微信的做法，不过由于微信设置了 android:windowDisablePreview ， 且他在各个厂商的白名单里面，一般不会被杀，冷启动的机会比较少。不过也是一个可以思考的地方</li></ul><h2 id="线程优化"><a href="#线程优化" class="headerlink" title="线程优化"></a>线程优化</h2><p>线程优化主要是减少 CPU 调度带来的波动，让启动时间更稳定。如果启动过程中有太多的线程一起启动，会给 CPU 带来非常大的压力，尤其是比较低端的机器。过多的线程同时跑会让主线程的 Sleep 和 Runnable 状态变多， 增加了应用的启动速度，优化的过程中要注意：</p><ul><li>控制线程数量 – 线程池</li><li>检查线程间的锁 ，防止依赖等待</li><li>使用合理的启动架构<ul><li>微信内部使用的 mmkernel</li><li>阿里 Alpha</li></ul></li></ul><h2 id="系统调度优化"><a href="#系统调度优化" class="headerlink" title="系统调度优化"></a>系统调度优化</h2><p>应用启动的时候，如果主线程的工作过多，也会造成主线程过于繁忙，下面几个系统调度相关的点需要注意：</p><ul><li><strong>启动过程中减少系统调用</strong>，避免与 AMS、WMS 竞争锁。启动过程中本身 AMS 和 WMS 的工作就很多，且 AMS 和 WMS 很多操作都是带锁的，如果此时 App 再有过多的 Binder 调用与 AMS、WMS 通信，SystemServer 就会出现大量的锁等待，阻塞关键操作</li><li><strong>启动过程中不要启动子进程</strong>，如果好几个进程同时启动，系统负担则会加倍，SystemServer 也会更繁忙</li><li><strong>启动过程中除了 Activity 之外的组件启动要谨慎</strong>，因为四大组件的启动都是在主线程的，如果组件启动慢，占用了 Message 通道，也会影响应用的启动速度</li><li>Application 和主 Activity 的 onCreate 中异步初始化某些代码</li></ul><p>启动过程中繁忙的 cpu<br><img src="/images/15740895432157.jpg"></p><p>启动过程中繁忙的 SystemServer<br><img src="/images/15740895514226.jpg"></p><h2 id="GC-优化"><a href="#GC-优化" class="headerlink" title="GC 优化"></a>GC 优化</h2><p>启动过程中减少 GC 的次数</p><ul><li>避免进行大量的字符串操作，特别是序列化和反序列化</li><li>频繁创建的对象需要考虑复用</li><li>转移到 Native 实现</li></ul><p>可以参考下面这篇文章 <a href="https://juejin.im/post/5be1077d518825171140dbfa">支付宝客户端架构解析：Android 客户端启动速度优化之「垃圾回收」</a>)</p><h2 id="IO-优化"><a href="#IO-优化" class="headerlink" title="IO 优化"></a>IO 优化</h2><p>启动过程中负载比较高，有许多系统 IO 都在此时发生，这时候 IO 的性能下降会比较快，此时 App 中的 IO 操作会比平时更慢一些，尤其是在性能比较差的机器上。</p><p>IO 分网络 IO 和磁盘 IO ，启动过程中不建议进行网络 IO ，对于磁盘 IO 则要细扣，邵文在高手课里面有讲到：</p><ol><li>我们要清楚启动过程中读了什么文件、多少个字节、 Buffer 是多大，使用了多长时间、在什么线程等一系列信息</li><li>进行启动过程中的 IO 监控，微信在监控 IO 时发现有用户的 db 文件达到了 500MB</li></ol><p><img src="/images/15740895712642.jpg"></p><p>下面图中可以看到低内存的时候，启动应用主线程有较多的 IO 等待（UI Thread 这一栏，橘红色代表 IO 等待 ）</p><p><img src="/images/15740895799020.jpg"></p><p><img src="/images/15740895863900.jpg"></p><h2 id="资源重排"><a href="#资源重排" class="headerlink" title="资源重排"></a>资源重排</h2><p>利用 Linux 的 IO 读取策略，PageCache 和 ReadAhead 机制，按照读取顺序重新排列，减少磁盘 IO 次数 。具体操作可以参考<a href="https://juejin.im/post/5be400c3f265da61476fb63c">支付宝 App 构建优化解析：通过安装包重排布优化 Android 端启动性能</a> 这篇文章</p><p>Linux 底层文件系统中 VFS 上次 App 进程之间，存在一层 pagecache，pagecache 由内存中的物理 page 组成，其内容对应磁盘上的 block。Pagecache 的大小是动态变化的，可以扩大，也可以在内存不足时缩小。Cache 缓存的存储设备被称为后备存储（backing store），一个 page 通常包含多个 block，这些 block 不一定是连续的</p><p><img src="/images/15740895943096.jpg"></p><p>利用文件重布局结合Pagecache 机制可以减少启动过程中的真正 IO 的次数，简单的说，通过文件重布局的目的，就是将启动阶段需要用到的文件在 APK 文件中排布在一起，尽可能的利用 pagecache 机制，用最少的磁盘 IO 次数，读取尽可能多的启动阶段需要的文件，减少 IO 开销，从而达到提升启动性能的目的</p><h2 id="类重排"><a href="#类重排" class="headerlink" title="类重排"></a>类重排</h2><p>类重排的实现通过 ReDex 的 Interdex 调整类在 Dex 中的排列顺序。Interdex 优化不需要去分析类引用，它只需要调整 Dex 中类的顺序，把启动时需要加载的类按顺序放到主 dex 里，这个工作我们完全可以在编译过程中实现，而且这个优化可以提升启动速度，优化效果从 facebook 公布的数据来看也比较可观，性价比高。具体实现可以参考 <a href="https://mp.weixin.qq.com/s/Bf41Kez_OLZTyty4EondHA">Redex 初探与 Interdex：Andorid 冷启动优化</a></p><h2 id="主页面布局优化"><a href="#主页面布局优化" class="headerlink" title="主页面布局优化"></a>主页面布局优化</h2><p>应用主界面布局优化是老生常谈了，综合起来无非就是下面两点，这个需要结合具体的界面布局去做优化，网上也有比较多的资料可以查阅</p><ul><li>通过减少冗余或者嵌套布局来降低视图层次结构</li><li>用 ViewStub 替代在启动过程中不需要显示的 UI 控件</li><li>使用自定义 View 替代复杂的 View 叠加</li></ul><h2 id="闲时调用"><a href="#闲时调用" class="headerlink" title="闲时调用"></a>闲时调用</h2><p>IdleHandler：当 Handler 空闲的时候才会被调用，如果返回 true, 则会一直执行，如果返回 false，执行完一次后就会被移除消息队列。比如，我们可以将从服务器获取推送 Token 的任务放在延迟 IdleHandler 中执行，或者把一些不重要的 View 的加载放到 IdleHandler 中执行</p><h2 id="类加载优化"><a href="#类加载优化" class="headerlink" title="类加载优化"></a>类加载优化</h2><p>可以在 systrace 生成的文件中看到 verifyClass 过程，因为需要校验方法的每一个指令，所以是一个比较耗时的操作。</p><p><img src="/images/15740896151484.jpg"></p><h2 id="App-瘦身"><a href="#App-瘦身" class="headerlink" title="App 瘦身"></a>App 瘦身</h2><p>App 瘦身包括代码瘦身和资源瘦身，通常的做法如下：</p><ul><li>Inspect  Code ：Android Studio 提供的代码审查工具，实际上是内嵌了 Lint </li><li>代码混淆</li><li>图片格式的选择：如果对图片的要求不高，可以换成 565</li><li>接入资源混淆</li><li>减少 Dex 数量</li></ul><h2 id="选择合适的启动框架"><a href="#选择合适的启动框架" class="headerlink" title="选择合适的启动框架"></a>选择合适的启动框架</h2><p>启动优化整个流程的梳理，流程的梳理，我们这里引入了一个有向无环图的概念，我们会把整个的概念梳理成有向无环图的结构，然后会去挨个加载。右边的部分，可以看到我们其实在启动的时候，首先会去加载一些必要的启动项，必要的启动项是左边流程，会用一个多进程的方式加载，以来有向无环图进行控制，比如说我是在非必须的时候启动加载我可以放在后面再去加载。当然在整个有向无环图的顺序加载，其实还是会做一些进程的判断，要判断某些项目是不是要在主进程里加载，某些要在初始进程里面加载</p><p>从 Spark 的 DAGScheduler 中领悟到它的核心思想，面向阶段调度（Stage-Oriented Scheduler）：把应用划分成一个个的阶段（Stage），再把任务（Task）安排到各个阶段中去，任务的编排则是通过构建 有向无环图（DAG），把任务依赖通过图的方式梳理得 井井有条。因为它分阶段执行，先集中资源把阶段一搞定，再齐心协力去执行阶段二，这样即能控制拥塞，又能保证时序，还能并发执行，让设备性能尽可能得到发挥</p><p><img src="/images/15740896407148.jpg"></p><p><img src="/images/15740896485396.jpg"></p><p><img src="/images/15740896590892.jpg"></p><p>大家可以参考淘宝的全链路优化的案例：<a href="https://yq.aliyun.com/articles/710466">历时1年，上百万行代码！首次揭秘手淘全链路性能优化（上）</a></p><h2 id="启动网络链路优化"><a href="#启动网络链路优化" class="headerlink" title="启动网络链路优化"></a>启动网络链路优化</h2><h3 id="问题和优化点"><a href="#问题和优化点" class="headerlink" title="问题和优化点"></a>问题和优化点</h3><ul><li>发送处理阶段：网络库bindService影响前x个请求，图片并发限制图片库线程排队</li><li>网络耗时：部分请求响应size大，包括 SO文件，Cache资源，图片原图大尺寸等</li><li>返回处理：个别数据网关请求json串复杂解析严重耗时（3s）,且历史线程排队设计不合适</li><li>上屏阻塞：回调UI线程被阻，反映主线程卡顿严重。高端机达1s，低端机恶化达3s以上</li><li>回调阻塞：部分业务回调执行耗时，阻塞主线程或回调线程</li></ul><h3 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h3><ul><li>多次重复的请求，业务方务必收敛请求次数，减少非必须请求。</li><li>数据大的请求如资源文件、so文件，非启动必须统一延后或取消。</li><li>业务方回调执行阻塞主线程耗时过长整改。我们知道，肉眼可见流畅运行，需要运行60帧&#x2F;秒， 意味着每帧的处理时间不超过16ms。针对主线程执行回调超过16ms的业务方，推动主线程执行优化。</li><li>协议json串过于复杂导致解析耗时严重，网络并发线程数有限，解析耗时过长意味着请求长时间占用MTOP线程影响其他关键请求执行。推动业务方handler注入使用自己的线程解析或简化json串。</li></ul><h2 id="预加载"><a href="#预加载" class="headerlink" title="预加载"></a>预加载</h2><p>Activity 打开之前就预加载数据，在 Activity 的 UI 布局初始化完成后显示预加载的数据，大大缩短启动时间。 可以参考 ：<a href="https://github.com/luckybilly/PreLoader/blob/master/README-zh-CN.md">https://github.com/luckybilly/PreLoader/blob/master/README-zh-CN.md</a></p><h2 id="保活"><a href="#保活" class="headerlink" title="保活"></a>保活</h2><p>保活，是各个应用开发者的噩梦，也是 Android 厂商关注和打击的重点。不过从启动的角度来看，如果应用进程不被杀，那么启动自然就快了，所以保活对应用启动速度也是有极大的帮助。</p><p>当然这里说的保活，并不是建议大家用各种黑科技、相互唤醒、通知轰炸这种保活手段，而是提供真正的功能，能让用户觉得你在后台是合理的、可以接收的。比如在后台的时候，资源能释放的都释放掉，不要一直在后台做耗电操作，该停的服务停掉，该关的动画关掉。</p><p>当然对于应用开发者来说，上面说的都太多理想化了，而且目前的手机厂商也会很暴力，应用到了后台就会处理掉，不过这毕竟是一个方向，Google 也在规范应用后台行为和规范厂商处理应用这两方面都在做努力，Android 系统的生态，还是需要应用开发者和 Android 厂商一起取改善。</p><p>当然保活还有一条路就是走跟厂商的合作，优化后台内存、去掉重复拉起、去掉流氓逻辑、积极响应低内存警告，做好这些话后可以跟系统厂商联系，<strong>谈放到查杀白名单和自启动白名单的可行性</strong></p><h2 id="业务梳理"><a href="#业务梳理" class="headerlink" title="业务梳理"></a>业务梳理</h2><p>这里涉及到具体的业务，每个 App 都不一样，但是所要做的事情都是一样的，下面是邵文在高手课里面提到的：</p><ul><li>梳理清楚启动过程中的每一个模块，哪些是一定需要的，那些是可以砍掉，那些是可以懒加载的</li><li>根据不同的业务场景决定不同的启动模式</li><li>懒加载防止集中化</li></ul><p>可以把具体的业务分为下面四个维度（此处图文来自<a href="https://juejin.im/post/5c21ea325188254eaa5c45b1#heading-5">https://juejin.im/post/5c21ea325188254eaa5c45b1#heading-5</a>）</p><ul><li>必要且耗时：启动初始化，考虑用线程来初始化</li><li>必要不耗时：首页绘制</li><li>非必要但耗时：数据上报、插件初始化</li><li>非必要不耗时：不用想，这块直接去掉，在需要用的时再加载</li></ul><p>然后按需进行加载优化</p><p><img src="/images/15740896948509.jpg"></p><h2 id="业务优化"><a href="#业务优化" class="headerlink" title="业务优化"></a>业务优化</h2><ol><li>优化业务中的代码效率，抓大放小，先从比较明显的瓶颈处下手，逐步进行优化</li><li>历史债务要偿还，历史的代码要重构，不能一直拖着</li></ol><p>具体的业务会有具体的优化场景，大家可以参考这篇文章中的优化流程和优化项<a href="https://www.jianshu.com/p/f5514b1a826c">https://www.jianshu.com/p/f5514b1a826c</a></p><blockquote><ol><li>数据库及IO操作都移到工作线程，并且设置线程优先级为THREAD_PRIORITY_BACKGROUND，这样工作线程最多能获取到10%的时间片，优先保证主线程执行</li><li>流程梳理，延后执行；实际上，这一步对项目启动加速最有效果。通过流程梳理发现部分流程调用时机偏失等，  例如</li><li>更新等操作无需在首屏尚未展示就调用，造成资源竞争</li><li>调用了IOS为了规避审核而做的开关，造成网络请求密集</li><li>自有统计在Application的调用里创建数量固定为5的线程池，造成资源竞争</li><li>修改广告闪屏逻辑为下次生效</li><li>去掉用无但被执行的老代码</li><li>去掉开发阶段使用但线上被执行的代码</li><li>去掉重复逻辑执行代码</li><li>去掉调用三方SDK里或者Demo里的多余代码</li><li>信息缓存，常用信息只在第一次获取，之后从缓存中取</li><li>项目是多进程架构，只在主进程执行Application的onCreate()</li></ol></blockquote><h2 id="减少Activity的跳转层次"><a href="#减少Activity的跳转层次" class="headerlink" title="减少Activity的跳转层次"></a>减少Activity的跳转层次</h2><p>StartingWindow 会在用户点击 App 后立即创建并显示(前提是 App 没有禁止 StartingWindow)，在 AppWindow 创建好之后，StartingWindow 消失，AppWindow 显示</p><h3 id="默认-App-的启动窗口流程"><a href="#默认-App-的启动窗口流程" class="headerlink" title="默认 App 的启动窗口流程"></a>默认 App 的启动窗口流程</h3><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">StartingWindow</span>(SystemWindow) </span><br><span class="line">  -&gt;<span class="built_in">MainActivity</span>(AppWindow)</span><br></pre></td></tr></table></figure><h3 id="大部分三方-App-启动流程"><a href="#大部分三方-App-启动流程" class="headerlink" title="大部分三方 App 启动流程"></a>大部分三方 App 启动流程</h3><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">StartingWindow<span class="function"><span class="params">(SystemWindow)</span> </span></span><br><span class="line"><span class="function">  -&gt;</span> SplashActivity<span class="function"><span class="params">(AppWindow)</span></span></span><br><span class="line"><span class="function">    -&gt;</span> MainActivity(AppWindow)</span><br></pre></td></tr></table></figure><h3 id="糟糕一点的启动流程是这样的"><a href="#糟糕一点的启动流程是这样的" class="headerlink" title="糟糕一点的启动流程是这样的"></a>糟糕一点的启动流程是这样的</h3><figure class="highlight livescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">StartingWindow<span class="function"><span class="params">(SystemWindow)</span> </span></span><br><span class="line"><span class="function">  -&gt;</span> MainActivity<span class="function"><span class="params">(AppWindow)</span> </span></span><br><span class="line"><span class="function">    -&gt;</span> SplashActivity<span class="function"><span class="params">(AppWindow)</span></span></span><br><span class="line"><span class="function">      -&gt;</span> MainActivity(AppWindow)</span><br></pre></td></tr></table></figure><h3 id="更糟糕一点的启动流程：去掉了-StartingWindow"><a href="#更糟糕一点的启动流程：去掉了-StartingWindow" class="headerlink" title="更糟糕一点的启动流程：去掉了 StartingWindow"></a>更糟糕一点的启动流程：去掉了 StartingWindow</h3><figure class="highlight isbl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">SplashActivity</span>(<span class="variable">AppWindow</span>)</span></span><br><span class="line">   -&gt; <span class="function"><span class="title">MainActivity</span>(<span class="variable">AppWindow</span>)</span></span><br></pre></td></tr></table></figure><p>其实对用户来说，第一种启动流程是最好的，只涉及到一次窗口的切换；但是部分 App 由于广告页的需求，会使用第二种流程 ；但是尽量不要使用第三种和第四种启动流程，体验非常不好</p><h1 id="厂商优化"><a href="#厂商优化" class="headerlink" title="厂商优化"></a>厂商优化</h1><p>除了 App 自身的优化之外，Android 框架对应用启动也是非常关注的，做了比较多的优化，下面简单说一下思路，各个厂商的实现也不太一样，但是基本上都会有，有些是硬核代码优化，有的是利用系统策略做优化。</p><p>厂商的策略各不相同，这里只是简单的提一下思路</p><h2 id="启动加速"><a href="#启动加速" class="headerlink" title="启动加速"></a>启动加速</h2><p>App 启动的时候，系统会对要启动的应用做绝对的资源倾斜，比如 CPU、IO、GPU 等，这一点大家抓个 Systrace 看一下即可，不管是频率还是调度算法，正在启动的 App 绝对是当时的系统 VIP 客户</p><p><img src="/images/15740897496735.jpg"></p><p>部分厂商也提供了资源调度的 SDK ，应用可以接入这些 SDK，在需要资源的时候直接调用 SDK 获取</p><h2 id="PreFork"><a href="#PreFork" class="headerlink" title="PreFork"></a>PreFork</h2><p>Android Q 加入了 PreFork 机制，会先 fork 几个空进程，当 App 启动的时候，可以直接复用这几个空进程，而不用重新去 fork</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">2</span>,<span class="number">348</span>K: usap32 (pid <span class="number">18731</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">346</span>K: usap32 (pid <span class="number">18702</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">343</span>K: usap32 (pid <span class="number">18707</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">342</span>K: usap32 (pid <span class="number">18729</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">341</span>K: usap32 (pid <span class="number">18711</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">335</span>K: usap32 (pid <span class="number">20322</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">335</span>K: usap32 (pid <span class="number">20325</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">333</span>K: usap32 (pid <span class="number">20319</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">333</span>K: usap32 (pid <span class="number">20320</span>)</span><br><span class="line"><span class="attribute">2</span>,<span class="number">333</span>K: usap32 (pid <span class="number">20321</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="attribute">1</span>,<span class="number">509</span>K: usap64 (pid <span class="number">21169</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">509</span>K: usap64 (pid <span class="number">21180</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">507</span>K: usap64 (pid <span class="number">21171</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">452</span>K: usap64 (pid <span class="number">21513</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">450</span>K: usap64 (pid <span class="number">21506</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">449</span>K: usap64 (pid <span class="number">21512</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">447</span>K: usap64 (pid <span class="number">21511</span>)</span><br><span class="line"><span class="attribute">1</span>,<span class="number">445</span>K: usap64 (pid <span class="number">21514</span>）</span><br></pre></td></tr></table></figure><h2 id="启动消息重排"><a href="#启动消息重排" class="headerlink" title="启动消息重排"></a>启动消息重排</h2><p>启动的时候，对启动过程中的 Message 进行重新排列</p><h2 id="主线程、渲染线程加速"><a href="#主线程、渲染线程加速" class="headerlink" title="主线程、渲染线程加速"></a>主线程、渲染线程加速</h2><p>部分厂家会对启动过程 App 的主线程和渲染线程做特殊对待，比如让他们直接跑到大核上，将其他不重要的线程移到小核</p><h2 id="启动预测"><a href="#启动预测" class="headerlink" title="启动预测"></a>启动预测</h2><p>部分场景会针对用户的使用习惯进行学习，比如在什么时间、什么场合、什么交通工具打开手机，系统会预测你要启动的 App，并在后台进行启动，这样你点击这个 App 的时候，就已经是热启动了</p><h2 id="后台保活"><a href="#后台保活" class="headerlink" title="后台保活"></a>后台保活</h2><p>系统也会对一些应用进行特殊处理，以提升用户体验：包括但不限于 <strong>进程\线程优先级调整、查杀白名单、用户常用应用记录</strong>等，进行适当的后台保活，下次启动的时候就是热启动了</p><h2 id="后台重启"><a href="#后台重启" class="headerlink" title="后台重启"></a>后台重启</h2><p>系统会对一些应用进行特殊处理，比如这个 App 比较重要但是不能杀掉，那么有的厂商会在这种应用退到后台之后，进行无感重启：比如说某个应用内存超标或者持续 Crash ，后台重启可以很好地解决这个问题，这样重启后的 App 是用户点击启动的时候就是热启动</p><h2 id="内存优化"><a href="#内存优化" class="headerlink" title="内存优化"></a>内存优化</h2><p>部分应用启动的时候，需要大量的内存，比如现在的相机启动，这时候如果没有足够的内存，那么系统必须要通过杀掉很多应用、释放 Cache 等操作来给这个 App 让路，这个过程会使得这些大内存的 App 在启动的时候频繁进行内存操作，导致启动速度变慢</p><p>部分厂商会在监测到这种大内存 App 启动的时候，提前做内存的回收操作，这样在启动的时候，就有了足够的内存给这个 App 使用</p><h2 id="优化启动逻辑"><a href="#优化启动逻辑" class="headerlink" title="优化启动逻辑"></a>优化启动逻辑</h2><p>Android 系统更新也会对应用启动速度进行优化，比如上面提到的 Pre-Fork，又比如这里的简化 doFrame 个数</p><p><img src="/images/15740897674007.jpg"></p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650829097&idx=2&sn=e59841d4b1ed7e12a30e29ec51072d70&chksm=80b7a5b7b7c02ca184e0c06289d90823d589e738c55712318875f51e4aeb8646294b8d426299&mpshare=1&scene=1&srcid=&sharer_sharetime=1571275213308&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">都9102年了，Android 冷启动优化除了老三样还有哪些新招？</a></li><li><a href="https://developer.android.google.cn/topic/performance/vitals/launch-time">App startup time</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&mid=2650403370&idx=1&sn=b4297b138eb7f73c95a6279c3458f025&chksm=83953a32b4e2b3247fc18cbee08a2682d8b09720a1c5fef0c36257ae92b1e201cb1ad3125455&mpshare=1&scene=1&srcid=#rd">历时1年，上百万行代码！首次揭秘手淘全链路性能优化（上）</a></li><li><a href="https://time.geekbang.org/column/intro/142">极客时间 ： Android 高手开发课</a></li><li><a href="https://github.com/facebook/redex">Facebook-Redex</a></li><li><a href="http://yummylau.com/2019/03/15/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%B9%8B%20alpha/">关于 Android 异步启动框架 alpha 的思考</a></li><li><a href="https://mp.weixin.qq.com/s/79tAFx6zi3JRG-ewoapIVQ">支付宝 App 构建优化解析：通过安装包重排布优化 Android 端启动性能</a></li><li><a href="https://github.com/facebook/redex">Redex 官网</a></li><li><a href="https://mp.weixin.qq.com/s/Bf41Kez_OLZTyty4EondHA">Redex 初探与 Interdex：Andorid 冷启动优化</a></li><li><a href="https://juejin.im/post/5c21ea325188254eaa5c45b1#heading-5">Android性能优化笔记（一）——启动优化</a></li></ol><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面</p><p><a href="https://zhuanlan.zhihu.com/p/92497570">知乎 - Android App 启动优化全记录</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文参考了目前大部分 Android 应用启动优化的方案，将大家的方案做一个汇总，如果你有这方面的需求，只需要对照这篇文章，看看其他人的方案，查漏补缺。很多方案是要根据具体的业务去做优化的，所以这里也没有对每一种方案进行详细的介绍，要用到哪一个方案的时候，可以具体去网上查找对应方案的具体实现方法，这里只是做一个汇总&lt;/p&gt;
&lt;p&gt;另外我还加上了部分系统厂商所做的启动相关的优化，不过只写了一些我知道的，还有一些厂商有黑科技，就不在这里的讨论范围了。知道厂商做的事情，可能也会帮助到你，比如联系厂商做白名单、接入厂商 SDK 等&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - MainThread 和 RenderThread 解读</title>
    <link href="https://androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/"/>
    <id>https://androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/</id>
    <published>2019-11-06T09:11:45.000Z</published>
    <updated>2026-02-07T05:17:47.845Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第九篇，主要是是介绍 Android App 中的 MainThread 和 RenderThread，也就是大家熟悉的<strong>主线程</strong>和<strong>渲染线程</strong>。文章会从 Systrace 的角度来看 MainThread 和 RenderThread 的工作流程，以及涉及到的相关知识：卡顿、软件渲染、掉帧计算等</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#main-thread">主线程的创建</a></li><li><a href="#render-thread">渲染线程的创建和发展</a></li><li><a href="#division">主线程和渲染线程的分工</a></li><li><a href="#game">游戏的主线程与渲染线程</a></li><li><a href="#flutter">Flutter 的主线程和渲染线程</a></li><li><a href="#performance">性能</a></li><li><a href="#refs">参考</a></li><li><a href="#attachments">附件</a></li><li><a href="#others">本文其他地址</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>这里以滑动列表为例 ，我们截取主线程和渲染线程<strong>一帧</strong>的工作流程(每一帧都会遵循这个流程，不过有的帧需要处理的事情多，有的帧需要处理的事情少) ，重点看 “UI Thread ” 和 RenderThread 这两行</p><p><img src="/images/15732904872967.jpg"></p><p><strong>这张图对应的工作流程如下</strong></p><ol><li>主线程处于 Sleep 状态，等待 Vsync 信号</li><li>Vsync 信号到来，主线程被唤醒，Choreographer 回调 FrameDisplayEventReceiver.onVsync 开始一帧的绘制</li><li>处理 App 这一帧的 Input 事件(如果有的话)</li><li>处理 App 这一帧的 Animation 事件(如果有的话)</li><li>处理 App 这一帧的 Traversal 事件(如果有的话)</li><li>主线程与渲染线程同步渲染数据，同步结束后，主线程结束一帧的绘制，可以继续处理下一个 Message(如果有的话，IdleHandler 如果不为空，这时候也会触发处理)，或者进入 Sleep 状态等待下一个 Vsync</li><li>渲染线程首先需要从 BufferQueue 里面取一个 Buffer(dequeueBuffer) , 进行数据处理之后，调用 OpenGL 相关的函数，真正地进行渲染操作，然后将这个渲染好的 Buffer 还给 BufferQueue (queueBuffer) , SurfaceFlinger 在 Vsync-SF 到了之后，将所有准备好的 Buffer 取出进行合成(这个流程在讲 SurfaceFlinger 的时候会提到)</li></ol><p>上面这个流程在 <a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章里面已经介绍的很详细了，包括每一帧的 doFrame 都在做什么、卡顿计算的原理、APM 相关. 没有看过这篇文章的同学，建议先去扫一眼</p><p>那么这篇文章我们主要从 <a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章没有讲到的几个点来入手，帮你更好地理解主线程和渲染线程</p><ol><li>主线程的发展</li><li>主线程的创建</li><li>渲染线程的创建</li><li>主线程和渲染线程的分工</li><li>游戏的主线程与渲染线程</li><li>Flutter 的主线程和渲染线程</li></ol><p><a id="main-thread"></a></p><h1 id="主线程的创建"><a href="#主线程的创建" class="headerlink" title="主线程的创建"></a>主线程的创建</h1><p>Android App 的进程是基于 Linux 的，其管理也是基于 Linux 的进程管理机制，所以其创建也是调用了 fork 函数</p><p>frameworks&#x2F;base&#x2F;core&#x2F;jni&#x2F;com_android_internal_os_Zygote.cpp</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pid_t pid <span class="operator">=</span> fork()<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>Fork 出来的进程，我们这里可以把他看做主线程，但是这个线程还没有和 Android 进行连接，所以无法处理 Android App 的 Message ；由于 Android App 线程运行<strong>基于消息机制</strong> ，那么这个 Fork 出来的主线程需要和 Android 的 Message 消息绑定，才能处理 Android App 的各种 Message </p><p>这里就引入了 <strong>ActivityThread</strong> ，确切的说，ActivityThread 应该起名叫 ProcessThread 更贴切一些。ActivityThread 连接了 Fork 出来的进程和 App 的 Message ，他们的通力配合组成了我们熟知的 Android App 主线程。所以说 ActivityThread 其实并不是一个 Thread，而是他初始化了 Message 机制所需要的 MessageQueue、Looper、Handler ，而且其 Handler 负责处理大部分 Message 消息，所以我们习惯上觉得 ActivityThread 是主线程，其实他只是主线程的一个逻辑处理单元。</p><h2 id="ActivityThread-的创建"><a href="#ActivityThread-的创建" class="headerlink" title="ActivityThread 的创建"></a>ActivityThread 的创建</h2><p>App 进程 fork 出来之后，回到 App 进程，查找 ActivityThread 的 Main函数</p><p>com&#x2F;android&#x2F;internal&#x2F;os&#x2F;ZygoteInit.java</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="keyword">final</span> Runnable <span class="title">childZygoteInit</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="type">int</span> targetSdkVersion, <span class="type">String</span>[] argv, ClassLoader classLoader)</span> </span>&#123;</span><br><span class="line">    RuntimeInit.Arguments args = <span class="keyword">new</span> RuntimeInit.<span class="built_in">Arguments</span>(argv);</span><br><span class="line">    <span class="keyword">return</span> RuntimeInit.<span class="built_in">findStaticMain</span>(args.startClass, args.startArgs, classLoader);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的 startClass 就是 ActivityThread，找到之后调用，逻辑就到了 ActivityThread的main函数</p><p>android&#x2F;app&#x2F;ActivityThread.java</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="type">static</span> <span class="type">void</span> <span class="title">main</span><span class="params">(<span class="type">String</span>[] args)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//1. 初始化 Looper、MessageQueue</span></span><br><span class="line">    Looper.<span class="built_in">prepareMainLooper</span>();</span><br><span class="line">    <span class="comment">// 2. 初始化 ActivityThread</span></span><br><span class="line">    ActivityThread thread = <span class="keyword">new</span> <span class="built_in">ActivityThread</span>();</span><br><span class="line">    <span class="comment">// 3. 主要是调用 AMS.attachApplicationLocked，同步进程信息，做一些初始化工作</span></span><br><span class="line">    thread.<span class="built_in">attach</span>(<span class="literal">false</span>, startSeq);</span><br><span class="line">    <span class="comment">// 4. 获取主线程的 Handler，这里是 H ，基本上 App 的 Message 都会在这个 Handler 里面进行处理 </span></span><br><span class="line">    <span class="keyword">if</span> (sMainThreadHandler == null) &#123;</span><br><span class="line">        sMainThreadHandler = thread.<span class="built_in">getHandler</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 5. 初始化完成，Looper 开始工作</span></span><br><span class="line">    Looper.<span class="built_in">loop</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注释里面都很清楚，这里就不详细说了，main 函数处理完成之后，主线程就算是正式上线开始工作，其 Systrace 流程如下：</p><p><img src="/images/15732905074966.jpg"></p><h2 id="ActivityThread-的功能"><a href="#ActivityThread-的功能" class="headerlink" title="ActivityThread 的功能"></a>ActivityThread 的功能</h2><p>另外我们经常说的，Android 四大组件都是运行在主线程上的，其实这里也很好理解，看一下 ActivityThread 的 Handler 的 Message 就知道了</p><figure class="highlight smali"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">class H extends Handler &#123; //摘抄了部分</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>BIND_APP<span class="class">LICATION        = 110;</span></span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>EXIT_APP<span class="class">LICATION        = 111;</span></span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>RECEIVER                = 113;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>CREATE_SERVICE          = 114;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>STOP_SERVICE            = 116;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>BIND_SERVICE            = 121;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>UNBIND_SERVICE          = 122;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>DUMP_SERVICE            = 123;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>REMOVE_PROVIDER         = 131;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>DISPATCH_PACKAGE_BROADCAST = 133;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>DUMP_PROVIDER           = 141;</span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>UNSTAB<span class="class">LE_PROVIDER_DIED  = 142;</span></span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>INSTA<span class="class">LL_PROVIDER        = 145;</span></span><br><span class="line">   <span class="keyword"> public</span><span class="keyword"> static</span><span class="keyword"> final</span><span class="built_in"> int </span>ON_NEW_ACTIVITY_OPTIONS = 146;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，进程创建、Activity 启动、Service 的管理、Receiver 的管理、Provider 的管理这些都会在这里处理，然后进到具体的 handleXXX </p><p><img src="/images/15732905186985.jpg"></p><p><a id="render-thread"></a></p><h1 id="渲染线程的创建和发展"><a href="#渲染线程的创建和发展" class="headerlink" title="渲染线程的创建和发展"></a>渲染线程的创建和发展</h1><p>主线程讲完了我们来讲渲染线程，渲染线程也就是 RenderThread ，最初的 Android 版本里面是没有渲染线程的，渲染工作都是在主线程完成，使用的也都是 CPU ，调用的是 libSkia 这个库，RenderThread 是在 Android Lollipop 中新加入的组件，负责承担一部分之前主线程的渲染工作，减轻主线程的负担</p><h2 id="软件绘制"><a href="#软件绘制" class="headerlink" title="软件绘制"></a>软件绘制</h2><p>我们一般提到的硬件加速，指的就是 GPU 加速，这里可以理解为用 RenderThread 调用 GPU 来进行渲染加速 。 硬件加速在目前的 Android 中是默认开启的， 所以如果我们什么都不设置，那么我们的进程默认都会有主线程和渲染线程(有可见的内容)。我们如果在 App 的 AndroidManifest 里面，在 Application 标签里面加一个 </p><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>hardwareAccelerated=<span class="string">&quot;false&quot;</span></span><br></pre></td></tr></table></figure><p>我们就可以关闭硬件加速，系统检测到你这个 App 关闭了硬件加速，就不会初始化 RenderThread ，直接 cpu 调用 libSkia 来进行渲染。其 Systrace 的表现如下</p><p><img src="/images/15732905305035.jpg"></p><p>与这篇文章开头的开了硬件加速的那个图对比，可以看到主线程由于要进行渲染工作，所以执行的时间变长了，也更容易出现卡顿，同时帧与帧直接的空闲间隔也变短了，使得其他 Message 的执行时间被压缩</p><h2 id="硬件加速绘制"><a href="#硬件加速绘制" class="headerlink" title="硬件加速绘制"></a>硬件加速绘制</h2><p>正常情况下，硬件加速是开启的，主线程的 draw 函数并没有真正的执行 drawCall ，而是把要 draw 的内容记录到 DIsplayList 里面，同步到 RenderThread 中，一旦同步完成，主线程就可以被释放出来做其他的事情，RenderThread 则继续进行渲染工作</p><p><img src="/images/15732905407683.jpg"></p><h2 id="渲染线程初始化"><a href="#渲染线程初始化" class="headerlink" title="渲染线程初始化"></a>渲染线程初始化</h2><p>渲染线程初始化在真正需要 draw 内容的时候，一般我们启动一个 Activity ，在第一个 draw 执行的时候，会去检测渲染线程是否初始化，如果没有则去进行初始化</p><p>android&#x2F;view&#x2F;ViewRootImpl.java</p><figure class="highlight autohotkey"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mAttachInfo.mThreadedRenderer.initializeIfNeeded(</span><br><span class="line"><span class="built_in">        mWidth,</span> mHeight, mAttachInfo, mSurface, surfaceInsets)<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>后续直接调用 draw </p><p>android&#x2F;graphics&#x2F;HardwareRenderer.java</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, <span class="keyword">this</span>);</span><br><span class="line"><span class="keyword">void</span> draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) &#123;</span><br><span class="line">    <span class="keyword">final</span> Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;</span><br><span class="line">    choreographer.mFrameInfo.markDrawStart();</span><br><span class="line"></span><br><span class="line">    updateRootDisplayList(view, callbacks);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (attachInfo.mPendingAnimatingRenderNodes != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">int</span> <span class="keyword">count</span> = attachInfo.mPendingAnimatingRenderNodes.<span class="keyword">size</span>();</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="keyword">count</span>; i++) &#123;</span><br><span class="line">            registerAnimatingRenderNode(</span><br><span class="line">                    attachInfo.mPendingAnimatingRenderNodes.get(i));</span><br><span class="line">        &#125;</span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes.clear();</span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes = <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> syncResult = syncAndDrawFrame(choreographer.mFrameInfo);</span><br><span class="line">    <span class="keyword">if</span> ((syncResult &amp; SYNC_LOST_SURFACE_REWARD_IF_FOUND) != <span class="number">0</span>) &#123;</span><br><span class="line">        setEnabled(<span class="keyword">false</span>);</span><br><span class="line">        attachInfo.mViewRootImpl.mSurface.release();</span><br><span class="line">        attachInfo.mViewRootImpl.invalidate();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> ((syncResult &amp; SYNC_REDRAW_REQUESTED) != <span class="number">0</span>) &#123;</span><br><span class="line">        attachInfo.mViewRootImpl.invalidate();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的 draw 只是更新 DIsplayList ，更新结束后，调用 syncAndDrawFrame ，通知渲染线程开始工作，主线程释放。渲染线程的核心实现在 libhwui 库里面，其代码位于 frameworks&#x2F;base&#x2F;libs&#x2F;hwui</p><p>frameworks&#x2F;base&#x2F;libs&#x2F;hwui&#x2F;renderthread&#x2F;RenderProxy.cpp</p><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">RenderProxy::syncAndDrawFrame</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> mDrawFrameTask.<span class="built_in">drawFrame</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关于 RenderThread 的工作流程这里就不细说了，后续会有专门的篇幅来讲解这个，目前 hwui 这一块的流程也有很多优秀的文章，大家可以对照文章和源码来看，其核心流程在 Systrace 上的表现如下:</p><p><img src="/images/15732905545821.jpg"></p><p><a id="division"></a></p><h2 id="主线程和渲染线程的分工"><a href="#主线程和渲染线程的分工" class="headerlink" title="主线程和渲染线程的分工"></a>主线程和渲染线程的分工</h2><p>主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ，更新 DIsplayList ，但是不涉及 SurfaceFlinger 打交道；渲染线程负责渲染渲染相关的工作，一部分工作也是 CPU 来完成的，一部分操作是调用 OpenGL 函数来完成的</p><p>当启动硬件加速后，在 Measure、Layout、Draw 的 Draw 这个环节，Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操作的记录，抽象为 RenderNode 类，这样间接的进行绘制操作的优点如下</p><ol><li>DisplayList 可以按需多次绘制而无须同业务逻辑交互</li><li>特定的绘制操作（如 translation， scale 等）可以作用于整个 DisplayList 而无须重新分发绘制操作</li><li>当知晓了所有绘制操作后，可以针对其进行优化：例如，所有的文本可以一起进行绘制一次</li><li>可以将对 DisplayList 的处理转移至另一个线程（也就是 RenderThread）</li><li>主线程在 sync 结束后可以处理其他的 Message，而不用等待 RenderThread 结束</li></ol><p>RenderThread 的具体流程大家可以看这篇文章 ： <a href="http://www.cocoachina.com/articles/35302">http://www.cocoachina.com/articles/35302</a></p><p><a id="game"></a></p><h1 id="游戏的主线程与渲染线程"><a href="#游戏的主线程与渲染线程" class="headerlink" title="游戏的主线程与渲染线程"></a>游戏的主线程与渲染线程</h1><p>游戏大多使用单独的渲染线程，有单独的 Surface ，直接跟 SurfaceFlinger 进行交互，其主线程的存在感比较低，绝大部分的逻辑都是自己在自己的渲染线程里面实现的。</p><p>大家可以看一下王者荣耀对应的 Systrace ，重点看应用进程和 SurfaceFlinger 进程（30fps）</p><p><img src="/images/15732905635210.jpg"></p><p>可以看到王者荣耀主线程的主要工作，就是把 Input 事件传给 Unity 的渲染线程，渲染线程收到 Input 事件之后，进行逻辑处理，画面更新等。</p><p><img src="/images/15732905704149.jpg"></p><p><a id="flutter"></a></p><h1 id="Flutter-的主线程和渲染线程"><a href="#Flutter-的主线程和渲染线程" class="headerlink" title="Flutter 的主线程和渲染线程"></a>Flutter 的主线程和渲染线程</h1><p>这里提一下 Flutter App 在 Systrace 上的表现，由于 Flutter 的渲染是基于 libSkia 的，所以它也没有 RenderThread ，而是他自建的 RenderEngine ， Flutter 比较重要的两个线程是 ui 线程和 gpu 线程，对应到下面提到的  Framework 和 Engine 两层</p><p><img src="/images/15732905786714.jpg"></p><p>Flutter 中也会监听 Vsync 信号 ，其 VsyncView 中会以 postFrameCallback 的形式，监听 doFrame 回调，然后调用 nativeOnVsync ，将 Vsync  到来的信息传给 Flutter UI 线程，开始一帧的绘制。</p><p><img src="/images/15732905861662.jpg"></p><p>可以看到 Flutter 的思路跟游戏开发的思路差不多，不依赖具体的平台，自建渲染管道，更新快，跨平台优势明显。</p><p>Flutter SDK 自带 Skia 库，不用等系统升级就可以用到最新的 Skia 库，而且 Google 团队在 Skia 上做了很多优化，所以官方号称性能可以媲美原生应用</p><p><img src="/images/15732905929654.jpg"></p><p>Flutter 的框架分为 Framework 和 Engine 两层，应用是基于 Framework 层开发的，Framework 负责渲染中的 Build，Layout，Paint，生成 Layer 等环节。Engine 层是 C++实现的渲染引擎，负责把 Framework 生成的 Layer 组合，生成纹理，然后通过 Open GL 接口向 GPU 提交渲染数据。</p><p><img src="/images/15732906008462.jpg"></p><p>当需要更新 UI 的时候，Framework 通知 Engine，Engine 会等到下个 Vsync 信号到达的时候，会通知 Framework，然后 Framework 会进行 animations, build，layout，compositing，paint，最后生成 layer 提交给 Engine。Engine 会把 layer 进行组合，生成纹理，最后通过 Open Gl 接口提交数据给 GPU，GPU 经过处理后在显示器上面显示。整个流程如下图：</p><p><img src="/images/15732906073036.jpg"></p><p><a id="performance"></a></p><h1 id="性能"><a href="#性能" class="headerlink" title="性能"></a>性能</h1><p>如果主线程需要处理所有任务，则执行耗时较长的操作（例如，网络访问或数据库查询）将会阻塞整个界面线程。一旦被阻塞，线程将无法分派任何事件，包括绘图事件。主线程执行超时通常会带来两个问题</p><ol><li>卡顿：如果主线程 + 渲染线程每一帧的执行都超过 16.6ms(60fps 的情况下)，那么就可能会出现掉帧。</li><li>卡死：如果界面线程被阻塞超过几秒钟时间（根据组件不同 , 这里的阈值也不同），用户会看到 “<a href="http://developer.android.google.cn/guide/practices/responsiveness.html">应用无响应</a>” (ANR) 对话框(部分厂商屏蔽了这个弹框,会直接 Crash 到桌面)</li></ol><p>对于用户来说，这两个情况都是用户不愿意看到的，所以对于 App 开发者来说，两个问题是发版本之前必须要解决的，ANR 这个由于有详细的调用栈，所以相对来说比较好定位；但是间歇性卡顿这个，可能就需要使用工具来进行分析了：Systrace + TraceView，所以理解主线程和渲染线程的关系和他们的工作原理是非常重要的，这也是本系列的一个初衷</p><p>另外关于卡顿，可以参考下面三篇文章，你的 App 卡顿不一定是你 App 的问题，也有可能是系统的问题，不过不管怎么说，首先要会分析卡顿问题。</p><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li></ol><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ol><li><a href="https://juejin.im/post/5a9e01c3f265da239d48ce32">https://juejin.im/post/5a9e01c3f265da239d48ce32</a></li><li><a href="http://www.cocoachina.com/articles/35302">http://www.cocoachina.com/articles/35302</a></li><li><a href="https://juejin.im/post/5b7767fef265da43803bdc65">https://juejin.im/post/5b7767fef265da43803bdc65</a></li><li><a href="http://gityuan.com/2019/06/15/flutter_ui_draw/">http://gityuan.com/2019/06/15/flutter_ui_draw&#x2F;</a></li><li><a href="https://developer.android.google.cn/guide/components/processes-and-threads">https://developer.android.google.cn/guide/components/processes-and-threads</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>本文涉及到的附件也上传了，各位下载后解压，使用 <strong>Chrome</strong> 浏览器打开即可</p><p><a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace-MainThread-RenderThread">点此链接下载文章所涉及到的 Systrace 附件</a></p><p><a id="others"></a></p><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面<br><a href="https://juejin.im/post/5dc68556f265da4cff702742">掘金 - Systrace 基础知识 - MainThread 和 RenderThread 解读</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第九篇，主要是是介绍 Android App 中的 MainThread 和 RenderThread，也就是大家熟悉的&lt;strong&gt;主线程&lt;/strong&gt;和&lt;strong&gt;渲染线程&lt;/strong&gt;。文章会从 Systrace 的角度来看 MainThread 和 RenderThread 的工作流程，以及涉及到的相关知识：卡顿、软件渲染、掉帧计算等&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - Input 解读</title>
    <link href="https://androidperformance.com/2019/11/04/Android-Systrace-Input/"/>
    <id>https://androidperformance.com/2019/11/04/Android-Systrace-Input/</id>
    <published>2019-11-04T01:38:29.000Z</published>
    <updated>2026-02-07T05:17:47.845Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第六篇，主要是对 Systrace 中的 Input 进行简单介绍，介绍其 Input 的流程； Systrace 中 Input 信息的体现 ，以及如何结合 Input 信息，分析与 Input 相关的问题</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#input-overview">Input in Systrace</a></li><li><a href="#inputdown-ss">InputDown 事件在 SystemServer 的工作流</a></li><li><a href="#inputdown-app">InputDown 事件在 App 的工作流</a></li><li><a href="#key-points">关键知识点和流程</a></li><li><a href="#input-vsync">Input 刷新与 Vsync</a></li><li><a href="#input-debug">Input 调试信息</a></li><li><a href="#refs">参考</a></li><li><a href="#attachments">附件</a></li><li><a href="#others">本文其他地址</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>在<a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Android 基于 Choreographer 的渲染机制详解</a> 这篇文章中，我有讲到，Android App 的主线程运行的本质是靠 Message 驱动的，这个 Message 可以是循环动画、可以是定时任务、可以是其他线程唤醒，不过我们最常见的还是 Input Message ，这里的 Input 是以 InputReader 这里的分类，不仅包含触摸事件(Down、Up、Move) ， 可包含 Key 事件(Home Key  、 Back Key) . 这里我们着重讲的是<strong>触摸事件</strong></p><p>由于 Android 系统在 Input 链上加了一些 Trace 点，且这些 Trace 点也比较完善，部分厂家可能会自己加一些，不过我们这里以标准的 Trace 点来讲解，这样不至于你换了个手机抓的 Trace 就不一样了</p><p>Input 在 Android 中的地位是很高的，我们在玩手机的时候，大部分应用的滑动、跳转这些都依靠 Input 事件来驱动，后续我会专门写一篇文章，来介绍 Android 中基于 Input 的运行机制。这里是从 Systrace 的角度来看 Input 。看下面的流程之前，脑子里先有个关于 Input 的大概处理流程，这样看的时候，就可以代入：</p><ol><li><strong>触摸屏每隔几毫秒扫描一次，如果有触摸事件，那么把事件上报到对应的驱动</strong></li><li><strong>InputReader 读取触摸事件交给 InputDispatcher 进行事件派发</strong></li><li><strong>InputDispatcher 将触摸事件发给注册了 Input 事件的 App</strong></li><li><strong>App 拿到事件之后，进行 Input 事件分发，如果此事件分发的过程中，App 的 UI 发生了变化，那么会请求 Vsync，则进行一帧的绘制</strong></li></ol><p><strong>另外在看 Systrace 的时候，要牢记 Systrace 中时间是从左到右流逝的，也就是说如果你在 Systrace 上画一条竖直线，那么竖直线左边的事件永远比右边的事件先发生，这也是我们分析源码流程的一个基石。我希望大家在看基于  Systrace 的源码流程分析之后，脑子里有一个图形化的、立体的流程图，你跟的代码走到哪一步了在图形你在脑中可以快速定位出来</strong></p><p><a id="input-overview"></a></p><h1 id="Input-in-Systrace"><a href="#Input-in-Systrace" class="headerlink" title="Input in Systrace"></a>Input in Systrace</h1><p>下面这张图是一个概览图，以滑动桌面为例 (<strong>滑动桌面包括一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件，这些事件和事件流都会在 Systrace 上有所体现，这也是我们分析 Systrace 的一个重要的切入点</strong>)，主要牵扯到的模块是 SystemServer 和 App 模块，其中用蓝色标识的是事件的流动信息，红色的是辅助信息。</p><p><img src="/images/15728723423427.jpg"></p><p><strong>InputReader</strong> 和 <strong>InputDispatcher</strong> 是跑在 SystemServer 里面的两个 Native 线程，负责读取和分发 Input 事件，我们分析 Systrace 的 Input 事件流，首先是找到这里。下面针对上图中标号进行简单说明</p><ol><li><strong>InputReader</strong> 负责从 EventHub 里面把 Input 事件读取出来，然后交给 InputDispatcher 进行事件分发</li><li><strong>InputDispatcher</strong> 在拿到 InputReader 获取的事件之后，对事件进行包装和分发 (也就是发给对应的)</li><li><strong>OutboundQueue</strong> 里面放的是即将要被派发给对应 AppConnection 的事件</li><li><strong>WaitQueue</strong> 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件</li><li><strong>PendingInputEventQueue</strong> 里面记录的是 App 需要处理的 Input 事件，这里可以看到已经到了应用进程</li><li><strong>deliverInputEvent</strong> 标识 App UI Thread 被 Input 事件唤醒</li><li><strong>InputResponse</strong> 标识 Input 事件区域，这里可以看到一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件的处理阶段都被算到了这里</li><li><strong>App 响应 Input 事件</strong> ： 这里是滑动然后松手，也就是我们熟悉的桌面滑动的操作，桌面随着手指的滑动更新画面，松手后触发 Fling 继续滑动，从 Systrace 就可以看到整个事件的流程</li></ol><p>下面以第一个 Input_Down 事件的处理流程来进行详细的工作流说明，其他的 Move 事件和 Up 事件的处理是一样的（部分不一样，不过影响不大）</p><p><a id="inputdown-ss"></a></p><h2 id="InputDown-事件在-SystemServer-的工作流"><a href="#InputDown-事件在-SystemServer-的工作流" class="headerlink" title="InputDown 事件在 SystemServer 的工作流"></a>InputDown 事件在 SystemServer 的工作流</h2><p>放大 SystemServer 的部分，可以看到其工作流(蓝色)，<strong>滑动桌面包括 Input_Down + 若干个 Input_Move + Input_Up ，我们这里看的是 Input_Down 这个事件</strong></p><p><img src="/images/15728723576583.jpg"></p><p><a id="inputdown-app"></a></p><h2 id="InputDown-事件在-App-的工作流"><a href="#InputDown-事件在-App-的工作流" class="headerlink" title="InputDown 事件在 App 的工作流"></a>InputDown 事件在 App 的工作流</h2><p>应用在收到 Input 事件后，有时候会马上去处理 (没有 Vsync 的情况下)，有时候会等 Vsync 信号来了之后去处理，这里 Input_Down 事件就是直接去唤醒主线程做处理，其 Systrace 比较简单，最上面有个 Input 事件队列，主线程则是简单的处理</p><p><img src="/images/15728723679523.jpg"></p><h3 id="App-的-Pending-队列"><a href="#App-的-Pending-队列" class="headerlink" title="App 的 Pending 队列"></a>App 的 Pending 队列</h3><p><img src="/images/15728723758398.jpg"></p><h3 id="主线程处理-Input-事件"><a href="#主线程处理-Input-事件" class="headerlink" title="主线程处理 Input 事件"></a>主线程处理 Input 事件</h3><p>主线程处理 Input 事件这个大家比较熟悉，从下面的调用栈可以看到，Input 事件传到了 ViewRootImpl，最终到了 DecorView ，然后就是大家熟悉的 Input 事件分发机制</p><p><img src="/images/15728723834004.jpg"></p><p><a id="key-points"></a></p><h1 id="关键知识点和流程"><a href="#关键知识点和流程" class="headerlink" title="关键知识点和流程"></a>关键知识点和流程</h1><p>从上面的 Systrace 来看，Input 事件的基本流向如下：</p><ol><li><strong>InputReader 读取 Input 事件</strong></li><li><strong>InputReader 将读取的 Input 事件放到 InboundQueue 中</strong></li><li><strong>InputDispatcher 从 InboundQueue 中取出 Input 事件派发到各个 App(连接) 的 OutBoundQueue</strong></li><li><strong>同时将事件记录到各个 App(连接) 的  WaitQueue</strong></li><li><strong>App 接收到 Input 事件，同时记录到 PendingInputEventQueue ，然后对事件进行分发处理</strong></li><li><strong>App 处理完成后，回调 InputManagerService 将负责监听的 WaitQueue 中对应的 Input 移除</strong></li></ol><p>通过上面的流程，一次 Input 事件就被消耗掉了(当然这只是正常情况，还有很多异常情况、细节处理，这里就不细说了，自己看相关流程的时候可以深挖一下) ， 那么本节就从上面的关键流中取几个重要的知识点讲解（部分流程和图参考和拷贝了 Gityuan 的博客的图，链接在最下面<strong>参考</strong>那一节）</p><h2 id="InputReader"><a href="#InputReader" class="headerlink" title="InputReader"></a>InputReader</h2><p>InputReader 是一个 Native 线程，跑在 SystemServer 进程里面，其核心功能是从 EventHub 读取事件、进行加工、将加工好的事件发送到 InputDispatcher</p><p>InputReader Loop 流程如下</p><ol><li>getEvents：通过 EventHub (监听目录 &#x2F;dev&#x2F;input )读取事件放入 mEventBuffer ,而mEventBuffer 是一个大小为256的数组, 再将事件 input_event 转换为 RawEvent </li><li>processEventsLocked: 对事件进行加工, 转换 RawEvent -&gt; NotifyKeyArgs(NotifyArgs) </li><li>QueuedListener-&gt;flush：将事件发送到 InputDispatcher 线程, 转换 NotifyKeyArgs -&gt; KeyEntry(EventEntry)</li></ol><p>核心代码 loopOnce 处理流程如下：<br><img src="/images/15728723980792.jpg"></p><p>InputReader 核心 Loop 函数 loopOnce 逻辑如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">InputReader::loopOnce</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">int32_t</span> oldGeneration;</span><br><span class="line">    <span class="type">int32_t</span> timeoutMillis;</span><br><span class="line">    <span class="type">bool</span> inputDevicesChanged = <span class="literal">false</span>;</span><br><span class="line">    std::vector&lt;InputDeviceInfo&gt; inputDevices;</span><br><span class="line">    &#123; <span class="comment">// acquire lock</span></span><br><span class="line">    ......</span><br><span class="line">    <span class="comment">//获取输入事件、设备增删事件，count 为事件数量</span></span><br><span class="line">    <span class="type">size_t</span> count = mEventHub -&gt;<span class="built_in">getEvents</span>(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);</span><br><span class="line">    &#123;</span><br><span class="line">    ......</span><br><span class="line">        <span class="keyword">if</span> (count) &#123;<span class="comment">//处理事件</span></span><br><span class="line">            <span class="built_in">processEventsLocked</span>(mEventBuffer, count);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">    mQueuedListener-&gt;<span class="built_in">flush</span>();<span class="comment">//将事件传到 InputDispatcher，这里getListener 得到的就是 InputDispatcher</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="InputDispatcher"><a href="#InputDispatcher" class="headerlink" title="InputDispatcher"></a>InputDispatcher</h2><p>上面的 InputReader 调用 mQueuedListener-&gt;flush 之后 ，将 Input 事件加入到InputDispatcher 的 mInboundQueue ，然后唤醒 InputDispatcher ， 从 Systrace 的唤醒信息那里也可以看到 InputDispatch 线程是被 InputReader 唤醒的</p><p><img src="/images/15728724564781.jpg"></p><p>InputDispatcher 的核心逻辑如下：</p><ol><li>dispatchOnceInnerLocked(): 从 InputDispatcher 的 mInboundQueue 队列，取出事件 EventEntry。另外该方法开始执行的时间点 (currentTime) 便是后续事件 dispatchEntry 的分发时间 (deliveryTime）</li><li>dispatchKeyLocked()：满足一定条件时会添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible；</li><li>enqueueDispatchEntryLocked()：生成事件 DispatchEntry 并加入 connection 的 outbound 队列</li><li>startDispatchCycleLocked()：从 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 队列；</li><li>InputChannel.sendMessage 通过 socket 方式将消息发送给远程进程；</li><li>runCommandsLockedInterruptible()：通过循环遍历的方式，依次处理 mCommandQueue 队列中的所有命令。而 mCommandQueue 队列中的命令是通过 postCommandLocked() 方式向该队列添加的。</li></ol><p><img src="/images/15728724685263.jpg"></p><p>其核心处理逻辑在 dispatchOnceInnerLocked 这里</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">InputDispatcher::dispatchOnceInnerLocked</span><span class="params">(<span class="type">nsecs_t</span>* nextWakeupTime)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Ready to start a new event.</span></span><br><span class="line">    <span class="comment">// If we don&#x27;t already have a pending event, go grab one.</span></span><br><span class="line">    <span class="keyword">if</span> (! mPendingEvent) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mInboundQueue.<span class="built_in">isEmpty</span>()) &#123;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Inbound queue has at least one entry.</span></span><br><span class="line">            mPendingEvent = mInboundQueue.<span class="built_in">dequeueAtHead</span>();</span><br><span class="line">            <span class="built_in">traceInboundQueueLengthLocked</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Poke user activity for this event.</span></span><br><span class="line">        <span class="keyword">if</span> (mPendingEvent-&gt;policyFlags &amp; POLICY_FLAG_PASS_TO_USER) &#123;</span><br><span class="line">            <span class="built_in">pokeUserActivityLocked</span>(mPendingEvent);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Get ready to dispatch the event.</span></span><br><span class="line">        <span class="built_in">resetANRTimeoutsLocked</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> EventEntry::TYPE_MOTION: &#123;</span><br><span class="line">        done = <span class="built_in">dispatchMotionLocked</span>(currentTime, typedEntry,</span><br><span class="line">                &amp;dropReason, nextWakeupTime);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (done) &#123;</span><br><span class="line">        <span class="keyword">if</span> (dropReason != DROP_REASON_NOT_DROPPED) &#123;</span><br><span class="line">            <span class="built_in">dropInboundEventLocked</span>(mPendingEvent, dropReason);</span><br><span class="line">        &#125;</span><br><span class="line">        mLastDropReason = dropReason;</span><br><span class="line">        <span class="built_in">releasePendingEventLocked</span>();</span><br><span class="line">        *nextWakeupTime = LONG_LONG_MIN;  <span class="comment">// force next poll to wake up immediately</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="InboundQueue"><a href="#InboundQueue" class="headerlink" title="InboundQueue"></a>InboundQueue</h2><p>InputDispatcher 执行 notifyKey 的时候，会将 Input 事件封装后放到  InboundQueue 中，后续 InputDispatcher 循环处理 Input 事件的时候，就是从 InboundQueue 取出事件然后做处理</p><p><img src="/images/15728724936139.jpg"></p><h2 id="OutboundQueue"><a href="#OutboundQueue" class="headerlink" title="OutboundQueue"></a>OutboundQueue</h2><p>Outbound 意思是出站，这里的 OutboundQueue 指的是要被 App 拿去处理的事件队列，每一个 App(Connection) 都对应有一个 OutboundQueue ，从 InboundQueue 那一节的图来看，事件会先进入 InboundQueue ，然后被 InputDIspatcher 派发到各个 App 的 OutboundQueue </p><p><img src="/images/15728725023006.jpg"></p><h2 id="WaitQueue"><a href="#WaitQueue" class="headerlink" title="WaitQueue"></a>WaitQueue</h2><p>当 InputDispatcher 将 Input 事件分发出去之后，将 DispatchEntry 从 outboundQueue 中取出来放到 WaitQueue 中，当 publish 出去的事件被处理完成（finished），InputManagerService 就会从应用中得到一个回复，此时就会取出 WaitQueue 中的事件，从 Systrace 中看就是对应 App 的 WaitQueue 减少</p><p>如果主线程发生卡顿，那么 Input 事件没有及时被消耗，也会在 WaitQueue 这里体现出来，如下图：</p><p><img src="/images/15728725102137.jpg"></p><h2 id="整体逻辑"><a href="#整体逻辑" class="headerlink" title="整体逻辑"></a>整体逻辑</h2><p>图来自 <a href="http://gityuan.com/">Gityuan 博客</a><br><img src="/images/15728725219191.jpg"></p><p><a id="input-vsync"></a></p><h1 id="Input-刷新与-Vsync"><a href="#Input-刷新与-Vsync" class="headerlink" title="Input 刷新与 Vsync"></a>Input 刷新与 Vsync</h1><p>Input 的刷新取决于触摸屏的采样，目前比较多的屏幕采样率是 120Hz 和 160Hz ，对应就是 8ms 采样一次或者 6.25ms 采样一次，我们来看一下其在 Systrace 上的展示</p><p><img src="/images/15728725296687.jpg"></p><p>可以看到上图中， InputReader 每隔 6.25ms 就可以读上来一个数据，交给 InputDispatcher 去分发给 App ，那么是不是屏幕采样率越高越好呢？也不一定，比如上面那张图，虽然 InputReader 每隔 6.25ms 就可以读上来一个数据给 InputDispatcher 去分发给 App ，但是从 WaitQueue 的表现来看，应用并没有消耗这个 Input 事件，这是为什么呢？</p><p>原因在于应用消耗 Input 事件的时机是 Vsync 信号来了之后，刷新率为 60Hz 的屏幕，一般系统也是 60 fps ，也就是说两个 Vsync 的间隔在 16.6ms ，这期间如果有两个或者三个 Input 事件，那么必然有一个或者两个要被抛弃掉，只拿最新的那个。也就是说：</p><ol><li><strong>在屏幕刷新率和系统 FPS 都是 60 的时候，盲目提高触摸屏的采样率，是没有太大的效果的，反而有可能出现上面图中那样，有的 Vsync 周期中有两个 Input 事件，而有的 Vsync 周期中有三个 Input 事件，这样造成事件不均匀，可能会使 UI 产生抖动</strong></li><li><strong>在屏幕刷新率和系统 FPS 都是 60 的时候，使用 120Hz 采样率的触摸屏就可以了</strong></li><li><strong>如果在屏幕刷新率和系统 FPS 都是 90 的时候 ，那么 120Hz 采样率的触摸屏显然不够用了，这时候应该采用 180Hz 采样率的屏幕</strong></li></ol><p><a id="input-debug"></a></p><h1 id="Input-调试信息"><a href="#Input-调试信息" class="headerlink" title="Input 调试信息"></a>Input 调试信息</h1><p>Dumpsys Input 主要是 Debug 用，我们也可以来看一下其中的一些关键信息，到时候遇到了问题也可以从这里面找 ， 其命令如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell dumpsys input</span><br></pre></td></tr></table></figure><p>其中的输出比较多，我们终点截取 Device 信息、InputReader、InputDispatcher 三段来看就可以了</p><h2 id="Device-信息"><a href="#Device-信息" class="headerlink" title="Device 信息"></a>Device 信息</h2><p>主要是目前连接上的 Device 信息，下面摘取的是 touch 相关的</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">    3: main_touch</span><br><span class="line">      Classes: 0x00000015</span><br><span class="line">      Path: /dev/input/event6</span><br><span class="line">      Enabled: true</span><br><span class="line">      Descriptor: 4055b8a032ccf50ef66dbe2ff99f3b2474e9eab5</span><br><span class="line">      Location: main_touch/input0</span><br><span class="line">      ControllerNumber: 0</span><br><span class="line">      UniqueId: </span><br><span class="line">      Identifier: bus=0x0000, vendor=0xbeef, product=0xdead, version=0x28bb</span><br><span class="line">      KeyLayoutFile: /system/usr/keylayout/main_touch.kl</span><br><span class="line">      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm</span><br><span class="line">      ConfigurationFile: </span><br><span class="line">      HaveKeyboardLayoutOverlay: false</span><br></pre></td></tr></table></figure><h2 id="Input-Reader-状态"><a href="#Input-Reader-状态" class="headerlink" title="Input Reader 状态"></a>Input Reader 状态</h2><p>InputReader 这里就是当前 Input 事件的一些展示</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line">  Device 3: main_touch</span><br><span class="line">    Generation: 24</span><br><span class="line">    IsExternal: false</span><br><span class="line">    HasMic:     false</span><br><span class="line">    Sources: 0x00005103</span><br><span class="line">    KeyboardType: 1</span><br><span class="line">    Motion Ranges:</span><br><span class="line">      X: source=0x00005002, min=0.000, max=1079.000, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      Y: source=0x00005002, min=0.000, max=2231.000, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      PRESSURE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      SIZE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      TOUCH_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      TOUCH_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      TOOL_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">      TOOL_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000</span><br><span class="line">    Keyboard Input Mapper:</span><br><span class="line">      Parameters:</span><br><span class="line">        HasAssociatedDisplay: false</span><br><span class="line">        OrientationAware: false</span><br><span class="line">        HandlesKeyRepeat: false</span><br><span class="line">      KeyboardType: 1</span><br><span class="line">      Orientation: 0</span><br><span class="line">      KeyDowns: 0 keys currently down</span><br><span class="line">      MetaState: 0x0</span><br><span class="line">      DownTime: 521271703875000</span><br><span class="line">    Touch Input Mapper (mode - direct):</span><br><span class="line">      Parameters:</span><br><span class="line">        GestureMode: multi-touch</span><br><span class="line">        DeviceType: touchScreen</span><br><span class="line">        AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=&#x27;&#x27;</span><br><span class="line">        OrientationAware: true</span><br><span class="line">      Raw Touch Axes:</span><br><span class="line">        X: min=0, max=1080, flat=0, fuzz=0, resolution=0</span><br><span class="line">        Y: min=0, max=2232, flat=0, fuzz=0, resolution=0</span><br><span class="line">        Pressure: min=0, max=127, flat=0, fuzz=0, resolution=0</span><br><span class="line">        TouchMajor: min=0, max=512, flat=0, fuzz=0, resolution=0</span><br><span class="line">        TouchMinor: unknown range</span><br><span class="line">        ToolMajor: unknown range</span><br><span class="line">        ToolMinor: unknown range</span><br><span class="line">        Orientation: unknown range</span><br><span class="line">        Distance: unknown range</span><br><span class="line">        TiltX: unknown range</span><br><span class="line">        TiltY: unknown range</span><br><span class="line">        TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0</span><br><span class="line">        Slot: min=0, max=20, flat=0, fuzz=0, resolution=0</span><br><span class="line">      Calibration:</span><br><span class="line">        touch.size.calibration: geometric</span><br><span class="line">        touch.pressure.calibration: physical</span><br><span class="line">        touch.orientation.calibration: none</span><br><span class="line">        touch.distance.calibration: none</span><br><span class="line">        touch.coverage.calibration: none</span><br><span class="line">      Affine Transformation:</span><br><span class="line">        X scale: 1.000</span><br><span class="line">        X ymix: 0.000</span><br><span class="line">        X offset: 0.000</span><br><span class="line">        Y xmix: 0.000</span><br><span class="line">        Y scale: 1.000</span><br><span class="line">        Y offset: 0.000</span><br><span class="line">      Viewport: displayId=0, orientation=0, logicalFrame=[0, 0, 1080, 2232], physicalFrame=[0, 0, 1080, 2232], deviceSize=[1080, 2232]</span><br><span class="line">      SurfaceWidth: 1080px</span><br><span class="line">      SurfaceHeight: 2232px</span><br><span class="line">      SurfaceLeft: 0</span><br><span class="line">      SurfaceTop: 0</span><br><span class="line">      PhysicalWidth: 1080px</span><br><span class="line">      PhysicalHeight: 2232px</span><br><span class="line">      PhysicalLeft: 0</span><br><span class="line">      PhysicalTop: 0</span><br><span class="line">      SurfaceOrientation: 0</span><br><span class="line">      Translation and Scaling Factors:</span><br><span class="line">        XTranslate: 0.000</span><br><span class="line">        YTranslate: 0.000</span><br><span class="line">        XScale: 0.999</span><br><span class="line">        YScale: 1.000</span><br><span class="line">        XPrecision: 1.001</span><br><span class="line">        YPrecision: 1.000</span><br><span class="line">        GeometricScale: 0.999</span><br><span class="line">        PressureScale: 0.008</span><br><span class="line">        SizeScale: 0.002</span><br><span class="line">        OrientationScale: 0.000</span><br><span class="line">        DistanceScale: 0.000</span><br><span class="line">        HaveTilt: false</span><br><span class="line">        TiltXCenter: 0.000</span><br><span class="line">        TiltXScale: 0.000</span><br><span class="line">        TiltYCenter: 0.000</span><br><span class="line">        TiltYScale: 0.000</span><br><span class="line">      Last Raw Button State: 0x00000000</span><br><span class="line">      Last Raw Touch: pointerCount=1</span><br><span class="line">        [0]: id=0, x=660, y=1338, pressure=44, touchMajor=44, touchMinor=44, toolMajor=0, toolMinor=0, orientation=0, tiltX=0, tiltY=0, distance=0, toolType=1, isHovering=false</span><br><span class="line">      Last Cooked Button State: 0x00000000</span><br><span class="line">      Last Cooked Touch: pointerCount=1</span><br><span class="line">        [0]: id=0, x=659.389, y=1337.401, pressure=0.346, touchMajor=43.970, touchMinor=43.970, toolMajor=43.970, toolMinor=43.970, orientation=0.000, tilt=0.000, distance=0.000, toolType=1, isHovering=false</span><br><span class="line">      Stylus Fusion:</span><br><span class="line">        ExternalStylusConnected: false</span><br><span class="line">        External Stylus ID: -1</span><br><span class="line">        External Stylus Data Timeout: 9223372036854775807</span><br><span class="line">      External Stylus State:</span><br><span class="line">        When: 9223372036854775807</span><br><span class="line">        Pressure: 0.000000</span><br><span class="line">        Button State: 0x00000000</span><br><span class="line">        Tool Type: 0</span><br></pre></td></tr></table></figure><h2 id="InputDispatcher-状态"><a href="#InputDispatcher-状态" class="headerlink" title="InputDispatcher 状态"></a>InputDispatcher 状态</h2><p>InputDispatch 这里的重要信息主要包括</p><ol><li>FocusedApplication ：当前获取焦点的应用</li><li>FocusedWindow ： 当前获取焦点的窗口</li><li>TouchStatesByDisplay</li><li>Windows ：所有的 Window</li><li>MonitoringChannels ：Window 对应的 Channel</li><li>Connections ：所有的连接</li><li>AppSwitch: not pending</li><li>Configuration</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">Input Dispatcher State:</span><br><span class="line">  DispatchEnabled: 1</span><br><span class="line">  DispatchFrozen: 0</span><br><span class="line">  FocusedApplication: name=&#x27;AppWindowToken&#123;ac6ec28 token=Token&#123;a38a4b ActivityRecord&#123;7230f1a u0 com.meizu.flyme.launcher/.Launcher t13&#125;&#125;&#125;&#x27;, dispatchingTimeout=5000.000ms</span><br><span class="line">  FocusedWindow: name=&#x27;Window&#123;3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher&#125;&#x27;</span><br><span class="line">  TouchStatesByDisplay:</span><br><span class="line">    0: down=true, split=true, deviceId=3, source=0x00005002</span><br><span class="line">      Windows:</span><br><span class="line">        0: name=&#x27;Window&#123;3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher&#125;&#x27;, pointerIds=0x80000000, targetFlags=0x105</span><br><span class="line">        1: name=&#x27;Window&#123;8cb8f7 u0 com.android.systemui.ImageWallpaper&#125;&#x27;, pointerIds=0x0, targetFlags=0x4102</span><br><span class="line">  Windows:</span><br><span class="line">    2: name=&#x27;Window&#123;ba2fc6b u0 NavigationBar&#125;&#x27;, displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, layer=0, frame=[0,2136][1080,2232], scale=1.000000, touchableRegion=[0,2136][1080,2232], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms</span><br><span class="line">    3: name=&#x27;Window&#123;72b7776 u0 StatusBar&#125;&#x27;, displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x81840048, type=0x000007d0, layer=0, frame=[0,0][1080,84], scale=1.000000, touchableRegion=[0,0][1080,84], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms</span><br><span class="line">    9: name=&#x27;Window&#123;3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher&#125;&#x27;, displayId=0, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, layer=0, frame=[0,0][1080,2232], scale=1.000000, touchableRegion=[0,0][1080,2232], inputFeatures=0x00000000, ownerPid=27619, ownerUid=10021, dispatchingTimeout=5000.000ms</span><br><span class="line">  MonitoringChannels:</span><br><span class="line">    0: &#x27;WindowManager (server)&#x27;</span><br><span class="line">  RecentQueue: length=10</span><br><span class="line">    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (524.5, 1306.4)]), policyFlags=0x62000000, age=61.2ms</span><br><span class="line">    MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (543.5, 1309.4)]), policyFlags=0x62000000, age=54.7ms</span><br><span class="line">  PendingEvent: &lt;none&gt;</span><br><span class="line">  InboundQueue: &lt;empty&gt;</span><br><span class="line">  ReplacedKeys: &lt;empty&gt;</span><br><span class="line">  Connections:</span><br><span class="line">    0: channelName=&#x27;WindowManager (server)&#x27;, windowName=&#x27;monitor&#x27;, status=NORMAL, monitor=true, inputPublisherBlocked=false</span><br><span class="line">      OutboundQueue: &lt;empty&gt;</span><br><span class="line">      WaitQueue: &lt;empty&gt;</span><br><span class="line">    5: channelName=&#x27;72b7776 StatusBar (server)&#x27;, windowName=&#x27;Window&#123;72b7776 u0 StatusBar&#125;&#x27;, status=NORMAL, monitor=false, inputPublisherBlocked=false</span><br><span class="line">      OutboundQueue: &lt;empty&gt;</span><br><span class="line">      WaitQueue: &lt;empty&gt;</span><br><span class="line">    6: channelName=&#x27;ba2fc6b NavigationBar (server)&#x27;, windowName=&#x27;Window&#123;ba2fc6b u0 NavigationBar&#125;&#x27;, status=NORMAL, monitor=false, inputPublisherBlocked=false</span><br><span class="line">      OutboundQueue: &lt;empty&gt;</span><br><span class="line">      WaitQueue: &lt;empty&gt;</span><br><span class="line">    12: channelName=&#x27;3c007ad com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher (server)&#x27;, windowName=&#x27;Window&#123;3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher&#125;&#x27;, status=NORMAL, monitor=false, inputPublisherBlocked=false</span><br><span class="line">      OutboundQueue: &lt;empty&gt;</span><br><span class="line">      WaitQueue: length=3</span><br><span class="line">        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (634.4, 1329.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=17.4ms, wait=16.8ms</span><br><span class="line">        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (647.4, 1333.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=11.1ms, wait=10.4ms</span><br><span class="line">        MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (659.4, 1337.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=5.2ms, wait=4.6ms</span><br><span class="line">  AppSwitch: not pending</span><br><span class="line">  Configuration:</span><br><span class="line">    KeyRepeatDelay: 50.0ms</span><br><span class="line">    KeyRepeatTimeout: 500.0ms</span><br></pre></td></tr></table></figure><p><a id="refs"></a></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p>本文部分图文参考和拷贝自下面几篇文章，同时下面几篇文章讲解了 Input 流程的细节部分，推荐大家在看完这篇文章后，如果对代码细节感兴趣，可以仔细研读下面这几篇非常棒的文章。</p><ol><li><a href="http://gityuan.com/2016/12/11/input-reader/">http://gityuan.com/2016/12/11/input-reader/</a></li><li><a href="http://gityuan.com/2016/12/10/input-manager/">http://gityuan.com/2016/12/10/input-manager/</a></li><li><a href="http://gityuan.com/2016/12/17/input-dispatcher/">http://gityuan.com/2016/12/17/input-dispatcher/</a></li><li><a href="https://zhuanlan.zhihu.com/p/29386642">https://zhuanlan.zhihu.com/p/29386642</a></li></ol><p><a id="attachments"></a></p><h1 id="附件"><a href="#附件" class="headerlink" title="附件"></a>附件</h1><p>本文涉及到的附件也上传了，各位下载后解压，使用 <strong>Chrome</strong> 浏览器打开即可<br><a href="https://github.com/Gracker/SystraceForBlog/tree/master/Android_Systrace_Input">点此链接下载文章所涉及到的 Systrace 附件</a></p><p><a id="others"></a></p><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面<br><a href="https://juejin.im/post/5dc1838ef265da4d02626ae0">掘金 - Systrace 基础知识 - Input 解读</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第六篇，主要是对 Systrace 中的 Input 进行简单介绍，介绍其 Input 的流程； Systrace 中 Input 信息的体现 ，以及如何结合 Input 信息，分析与 Input 相关的问题&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的“后台无效动画“行为分析</title>
    <link href="https://androidperformance.com/2019/10/24/Android-Background-Animation/"/>
    <id>https://androidperformance.com/2019/10/24/Android-Background-Animation/</id>
    <published>2019-10-24T00:27:34.000Z</published>
    <updated>2026-02-07T05:17:47.824Z</updated>
    
    <content type="html"><![CDATA[<p>当一个 Android App 退到后台之后，只要他没有被杀死，那么他做什么事情大家都不要奇怪，因为这就是 Android。但是当用户知道一个你一个 App 退到后台之后还在持续做无效的动画，而这个动画完全是无意义的，而且用户还不知道他在做动画，消耗用户那可怜的电量的时候，轻则被多任务杀掉，禁止后台运行，重则直接卸载。</p><p>一般的开发者很难发现这个问题，但是如果你经常使用 <a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace</a> ，多开几十个应用然后退回到桌面，左右滑动抓取 <a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace</a> ，就可以很容易发现，总有那么几个后台的应用，还在频繁地做无效的动画。</p><p>这里说的后台做动画，指的是由于某种原因，应用在退到后台之后，用户看不到任何这个 App 界面的时候，他仍然在后台不断地更新，耗费 CPU。引起这个问题的原因可能有好多个，毕竟 往 <a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Choreographer</a> 扔 CALLBACK_ANIMATION 的地方太多了，而且每个应用可能都不一样，但最终还是需要各个应用去做修复</p><span id="more"></span><p>下面我们就以两个实例，从技术的角度来看一下事件发生时候的情况和原因，<strong>希望看到这篇文章的开发者，检查一下自己的应用是否有这个问题，有则改之，无则恭喜</strong></p><h1 id="实例-网易新闻"><a href="#实例-网易新闻" class="headerlink" title="实例 - 网易新闻"></a>实例 - 网易新闻</h1><p>我们在使用网易新闻后，将网易新闻退到后台，然后左右滑动桌面，抓 Systrace 来看：</p><p>网易新闻到后台之后还在持续做 Animation 的回调（红框内），每一帧都还是在 doFrame 操作</p><p><img src="/images/15718769486548.jpg"></p><p>放大每一个 doFrame 来看，<a href="https://www.androidperformance.com/2019/10/22/Android-Choreographer/">Choreographer</a>  中的 input 和 traversal 都没有触发，只有 animation 的回调一直在执行</p><p><img src="/images/15718769576130.jpg"></p><p>我们把这份 Trace 上的 cpu 部分全选，然后下面按照 Wall Duration 排序，可以发现网易新闻后台动画执行时间最长。应用已经在后台且不可见的时候，还在这么频繁地工作，占用 CPU 资源，消耗电量，实在是不应该</p><p><img src="/images/15718769674934.jpg"></p><p>抓对应的 MethodTrace 来看，就是在做动画，没有进行关闭 ，动画依旧在每一帧进行 onAnimationUpdate 的回调 ，可以看到这里是因为使用了 Airbnb 的 <a href="https://airbnb.design/lottie/">Lottie</a> 库导致的，动画没有关闭，所以还是一直在做触发</p><p><img src="/images/15718769752471.jpg"></p><h1 id="实例-QQ音乐"><a href="#实例-QQ音乐" class="headerlink" title="实例 - QQ音乐"></a>实例 - QQ音乐</h1><p>启动 QQ 音乐，然后回到桌面， 左右滑动桌面并抓取 Systrace 和 MethodTrace ，可以看到跟上面的网易新闻的表现一致</p><p><img src="/images/15718769826711.jpg"></p><p>抓取了 QQ 音乐的后台动画时候的 MethodTrace 发现，也是由于退到后台之后，没有暂停动画导致的，也是 Airbnb 的 <a href="https://airbnb.design/lottie/">Lottie</a> 的锅， 而且 QQ 音乐有三个动画没有停止，比网易新闻还要严重一些</p><p><img src="/images/15718769926973.jpg"></p><p>放大后可以看到</p><p><img src="/images/15718770037520.jpg"></p><p>当然也不是每一个都是 Airbnb 的 <a href="https://airbnb.design/lottie/">Lottie</a> 动画库引起的，比如下面这个，就是普通的动画没有结束</p><p><img src="/images/15718770123948.jpg"></p><h1 id="根本原因"><a href="#根本原因" class="headerlink" title="根本原因"></a>根本原因</h1><p>根本原因是应用在不可见之后，没有将动画暂停，导致应用切换到后台之后，依然在刷新动画的回调，但此时由于是不可见的，不会触发 Input Callback 和 draw Callback ，所以也不会有任何的绘制操作，也就是说这个 Animation 的刷新完全是没有意义的（当然也有可能是业务需求？）</p><p>上面两个例子里面，网易新闻和 QQ 音乐都是因为使用了 <a href="https://airbnb.design/lottie/">Lottie</a> 来实现动画，但是没有正确的关闭导致的。</p><h1 id="开发建议"><a href="#开发建议" class="headerlink" title="开发建议"></a>开发建议</h1><p><a href="https://airbnb.design/lottie/">Lottie</a> 库的 issue 列表里面有人提到了这个情况：</p><p>提出问题：</p><blockquote><p>I recently did some benchmarking on an app which uses lottie to do some animations (autoplay and looping). I noticed that there is quite some CPU usage <strong>when the app is in the background</strong> and tried to investigate.</p></blockquote><blockquote><p>It seems to me looping animations do not pause&#x2F;stop when the containing LottieAnimationView is off screen, and&#x2F;or the Activity is paused.</p></blockquote><blockquote><p>I believe this is due to the cleanup code being only in onDetachedFromWindow() which is not necessarily being called once the Activity goes into a paused state and most definitely not, when the view is simply not visible (GONE, INVISIBLE ) anymore.</p></blockquote><p>解决方法：</p><blockquote><p>Overriding LottieAnimationView and doing the following solves the visibility issue for me and Lottie is paused when not visible.</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onVisibilityChanged</span><span class="params">(<span class="meta">@NonNull</span> View changedView, <span class="type">int</span> visibility)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onVisibilityChanged(changedView, visibility);</span><br><span class="line">    <span class="keyword">if</span> (visibility == VISIBLE &amp;&amp; wasAnimatingWhenVisibilityChanged) &#123;</span><br><span class="line">        resumeAnimation();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isAnimating()) &#123;</span><br><span class="line">            wasAnimatingWhenVisibilityChanged = <span class="literal">true</span>;</span><br><span class="line">            pauseAnimation();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            wasAnimatingWhenVisibilityChanged = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>总之就是 ： 当 App 不可见的时候，停止所有的动画：<strong>pauseAnimation</strong>！！！</p><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面<br><a href="https://zhuanlan.zhihu.com/p/88221952">知乎 - Android 中的“后台无效动画“行为分析</a><br><a href="https://juejin.im/post/5db0ef7ef265da4cfd2951fd">掘金 - Android 中的“后台无效动画“行为分析</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;当一个 Android App 退到后台之后，只要他没有被杀死，那么他做什么事情大家都不要奇怪，因为这就是 Android。但是当用户知道一个你一个 App 退到后台之后还在持续做无效的动画，而这个动画完全是无意义的，而且用户还不知道他在做动画，消耗用户那可怜的电量的时候，轻则被多任务杀掉，禁止后台运行，重则直接卸载。&lt;/p&gt;
&lt;p&gt;一般的开发者很难发现这个问题，但是如果你经常使用 &lt;a href=&quot;https://www.androidperformance.com/2019/05/28/Android-Systrace-About/&quot;&gt;Systrace&lt;/a&gt; ，多开几十个应用然后退回到桌面，左右滑动抓取 &lt;a href=&quot;https://www.androidperformance.com/2019/05/28/Android-Systrace-About/&quot;&gt;Systrace&lt;/a&gt; ，就可以很容易发现，总有那么几个后台的应用，还在频繁地做无效的动画。&lt;/p&gt;
&lt;p&gt;这里说的后台做动画，指的是由于某种原因，应用在退到后台之后，用户看不到任何这个 App 界面的时候，他仍然在后台不断地更新，耗费 CPU。引起这个问题的原因可能有好多个，毕竟 往 &lt;a href=&quot;https://www.androidperformance.com/2019/10/22/Android-Choreographer/&quot;&gt;Choreographer&lt;/a&gt; 扔 CALLBACK_ANIMATION 的地方太多了，而且每个应用可能都不一样，但最终还是需要各个应用去做修复&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Framework" scheme="https://androidperformance.com/tags/Framework/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 基于 Choreographer 的渲染机制详解</title>
    <link href="https://androidperformance.com/2019/10/22/Android-Choreographer/"/>
    <id>https://androidperformance.com/2019/10/22/Android-Choreographer/</id>
    <published>2019-10-22T10:55:11.000Z</published>
    <updated>2026-02-07T05:17:47.825Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍了 App 开发者不经常接触到但是在 Android Framework 渲染链路中非常重要的一个类 Choreographer。包括 Choreographer 的引入背景、Choreographer 的简介、部分源码解析、Choreographer 与 MessageQueue、Choreographer 和 APM，以及手机厂商基于 Choreographer 的一些优化思路</p><p>Choreographer 的引入，主要是配合 Vsync ，给上层 App 的渲染提供一个稳定的 Message 处理的时机，也就是 Vsync 到来的时候 ，系统通过对  Vsync 信号周期的调整，来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率，也就是 16.6ms 刷新一次，系统为了配合屏幕的刷新频率，将 Vsync 的周期也设置为 16.6 ms，每个 16.6 ms ， Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ，这就是引入 Choreographer 的主要作用. 了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理，也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解</p><span id="more"></span><p>本文是 Systrace 系列文章的第八篇，主要是对 Systrace 中的 Choreographer 进行简单介绍</p><p>本系列的<strong>目的</strong>是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><h1 id="主线程运行机制的本质"><a href="#主线程运行机制的本质" class="headerlink" title="主线程运行机制的本质"></a>主线程运行机制的本质</h1><p>在讲 Choreographer 之前，我们先理一下 Android 主线程运行的本质，其实就是 Message 的处理过程，我们的各种操作，包括每一帧的渲染操作 ，都是通过 Message 的形式发给主线程的 MessageQueue ，MessageQueue 处理完消息继续等下一个消息，如下图所示</p><p><strong>MethodTrace 图示</strong></p><p><img src="/images/15717420275540.jpg"></p><p><strong>Systrace 图示</strong></p><p><img src="/images/15717420373518.jpg"></p><h2 id="演进"><a href="#演进" class="headerlink" title="演进"></a>演进</h2><p>引入 Vsync 之前的 Android 版本，渲染一帧相关的 Message ，中间是没有间隔的，上一帧绘制完，下一帧的 Message 紧接着就开始被处理。这样的问题就是，帧率不稳定，可能高也可能低，不稳定，如下图</p><p><strong>MethodTrace 图示</strong></p><p><img src="/images/15717420453069.jpg"></p><p><strong>Systrace 图示</strong></p><p><img src="/images/15717420572997.jpg"></p><p>可以看到这时候的瓶颈是在 dequeueBuffer, 因为屏幕是有刷新周期的, FB 消耗 Front Buffer 的速度是一定的, 所以 SF 消耗 App Buffer 的速度也是一定的, 所以 App 会卡在 dequeueBuffer 这里,这就会导致 App Buffer 获取不稳定, 很容易就会出现卡顿掉帧的情况.</p><p>对于用户来说，稳定的帧率才是好的体验，比如你玩王者荣耀，相比 fps 在 60 和 40 之间频繁变化，用户感觉更好的是稳定在 50 fps 的情况.</p><p>所以 Android 的演进中，引入了 <strong>Vsync + TripleBuffer + Choreographer</strong> 的机制，其主要目的就是提供一个稳定的帧率输出机制，让软件层和硬件层可以以共同的频率一起工作。</p><h2 id="引入-Choreographer"><a href="#引入-Choreographer" class="headerlink" title="引入 Choreographer"></a>引入 Choreographer</h2><p>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 的主要作用</p><p><img src="/images/15722752299458.jpg"></p><p>当然目前使用 90Hz 刷新率屏幕的手机越来越多，Vsync 周期从 16.6ms 到了 11.1ms，上图中的操作要在更短的时间内完成，对性能的要求也越来越高，具体可以看<a href="https://www.androidperformance.com/2019/05/15/90hz-on-android/">新的流畅体验，90Hz 漫谈</a> 这篇文章</p><h1 id="Choreographer-简介"><a href="#Choreographer-简介" class="headerlink" title="Choreographer 简介"></a>Choreographer 简介</h1><p>Choreographer 扮演 Android 渲染链路中承上启下的角色</p><ol><li><strong>承上</strong>：负责接收和处理 App 的各种更新消息和回调，等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ，判断卡顿掉帧情况，记录 CallBack 耗时等</li><li><strong>启下</strong>：负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync )；请求 Vsync(FrameDisplayEventReceiver.scheduleVsync)</li></ol><p>从上面可以看出来， Choreographer 担任的是一个工具人的角色，他之所以重要，是因为通过 <strong>Choreographer + SurfaceFlinger + Vsync + TripleBuffer</strong> 这一套从上到下的机制，保证了 Android App 可以以一个稳定的帧率运行(20fps、90fps 或者 60fps)，减少帧率波动带来的不适感。</p><p>了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理，也可以加深对 <strong>Message、Handler、Looper、MessageQueue、Input、Animation、Measure、Layout、Draw</strong> 的理解 , 很多 <strong>APM</strong> 工具也用到了 <strong>Choreographer( 利用 FrameCallback + FrameInfo )</strong> + <strong>MessageQueue ( 利用 IdleHandler )</strong> + <strong>Looper ( 设置自定义 MessageLogging)</strong> 这些组合拳，深入了解了这些之后，再去做优化，脑子里的思路会更清晰。</p><p>另外虽然画图是一个比较好的解释流程的好路子，但是我个人不是很喜欢画图，因为平时 Systrace 和 MethodTrace 用的比较多，Systrace 是按从左到右展示整个系统的运行情况的一个工具(包括 cpu、SurfaceFlinger、SystemServer、App 等关键进程)，使用 <strong>Systrace</strong> 和 <strong>MethodTrace</strong> 也可以很方便地展示关键流程。当你对系统代码比较熟悉的时候，看 Systrace 就可以和手机运行的实际情况对应起来。所以下面的文章除了一些网图之外，其他的我会多以 Systrace 来展示。</p><h2 id="从-Systrace-的角度来看-Choreogrepher-的工作流程"><a href="#从-Systrace-的角度来看-Choreogrepher-的工作流程" class="headerlink" title="从 Systrace 的角度来看 Choreogrepher 的工作流程"></a>从 Systrace 的角度来看 Choreogrepher 的工作流程</h2><p>下图以滑动桌面为例子，我们先看一下从左到右滑动桌面的一个完整的预览图（App 进程），可以看到 Systrace 中从左到右，每一个绿色的帧都表示一帧，表示最终我们可以手机上看到的画面</p><ol><li>图中每一个灰色的条和白色的条宽度是一个 Vsync 的时间，也就是 16.6ms</li><li>每一帧处理的流程：接收到 Vsync 信号回调-&gt; UI Thread –&gt; RenderThread –&gt; SurfaceFlinger(图中未显示)</li><li>UI Thread 和 RenderThread 就可以完成 App 一帧的渲染，渲染完的 Buffer 抛给 SurfaceFlinger 去合成，然后我们就可以在屏幕上看到这一帧了</li><li>可以看到桌面滑动的每一帧耗时都很短（Ui Thread 耗时 + RenderThread 耗时），但是由于 Vsync 的存在，每一帧都会等到 Vsync 才会去做处理</li></ol><p><img src="/images/15717420793673.jpg"></p><p>有了上面这个整体的概念，我们将 UI Thread 的每一帧放大来看，看看 Choreogrepher 的位置以及 Choreogrepher 是怎么组织每一帧的</p><p><img src="/images/15717420863795.jpg"></p><h2 id="Choreographer-的工作流程"><a href="#Choreographer-的工作流程" class="headerlink" title="Choreographer 的工作流程"></a>Choreographer 的工作流程</h2><ol><li>Choreographer 初始化</li><li>初始化 FrameHandler ，绑定 Looper</li><li>初始化 FrameDisplayEventReceiver ，与 SurfaceFlinger 建立通信用于接收和请求 Vsync</li><li>初始化 CallBackQueues</li><li>SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ，Choreographer 回调 FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数  doFrame</li><li>Choreographer.doFrame 计算掉帧逻辑</li><li>Choreographer.doFrame 处理 Choreographer 的第一个 callback ： input </li><li>Choreographer.doFrame 处理 Choreographer 的第二个 callback ： animation </li><li>Choreographer.doFrame 处理 Choreographer 的第三个 callback ： insets animation </li><li>Choreographer.doFrame 处理 Choreographer 的第四个 callback ： traversal </li><li>traversal-draw 中 UIThread 与 RenderThread 同步数据</li><li>Choreographer.doFrame 处理 Choreographer 的第五个 callback ： commit ?</li><li>RenderThread 处理绘制命令，将处理好的绘制命令发给 GPU 处理</li><li>调用 swapBuffer 提交给 SurfaceFlinger 进行合成（此时 Buffer 并没有真正完成，需要等 CPU 完成后 SurfaceFlinger 才能真正使用，新版本的 Systrace 中有 gpu 的 fence 来标识这个时间）</li></ol><p><strong>第一步初始化完成后，后续就会在步骤 2-9 之间循环</strong></p><p>同时也附上这一帧所对应的 MethodTrace（这里预览一下即可，下面会有详细的大图）</p><p><img src="/images/15717420948412.jpg"></p><p>下面我们就从源码的角度，来看一下具体的实现</p><h1 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h1><p>下面从源码的角度来简单看一下，源码只摘抄了部分重要的逻辑，其他的逻辑则被剔除，另外 Native 部分与 SurfaceFlinger 交互的部分也没有列入，不是本文的重点，有兴趣的可以自己去跟一下。</p><h2 id="Choreographer-的初始化"><a href="#Choreographer-的初始化" class="headerlink" title="Choreographer 的初始化"></a>Choreographer 的初始化</h2><h3 id="Choreographer-的单例初始化"><a href="#Choreographer-的单例初始化" class="headerlink" title="Choreographer 的单例初始化"></a>Choreographer 的单例初始化</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Thread local storage for the choreographer.</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Choreographer&gt; sThreadInstance =</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;Choreographer&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Choreographer <span class="title function_">initialValue</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 获取当前线程的 Looper</span></span><br><span class="line">        <span class="type">Looper</span> <span class="variable">looper</span> <span class="operator">=</span> Looper.myLooper();</span><br><span class="line">        ......</span><br><span class="line">        <span class="comment">// 构造 Choreographer 对象</span></span><br><span class="line">        <span class="type">Choreographer</span> <span class="variable">choreographer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Choreographer</span>(looper, VSYNC_SOURCE_APP);</span><br><span class="line">        <span class="keyword">if</span> (looper == Looper.getMainLooper()) &#123;</span><br><span class="line">            mMainInstance = choreographer;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> choreographer;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="Choreographer-的构造函数"><a href="#Choreographer-的构造函数" class="headerlink" title="Choreographer 的构造函数"></a>Choreographer 的构造函数</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Choreographer</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    mLooper = looper;</span><br><span class="line">    <span class="comment">// 1. 初始化 FrameHandler</span></span><br><span class="line">    mHandler = <span class="keyword">new</span> <span class="title class_">FrameHandler</span>(looper);</span><br><span class="line">    <span class="comment">// 2. 初始化 DisplayEventReceiver</span></span><br><span class="line">    mDisplayEventReceiver = USE_VSYNC</span><br><span class="line">            ? <span class="keyword">new</span> <span class="title class_">FrameDisplayEventReceiver</span>(looper, vsyncSource)</span><br><span class="line">            : <span class="literal">null</span>;</span><br><span class="line">    mLastFrameTimeNanos = Long.MIN_VALUE;</span><br><span class="line">    mFrameIntervalNanos = (<span class="type">long</span>)(<span class="number">1000000000</span> / getRefreshRate());</span><br><span class="line">    <span class="comment">//3. 初始化 CallbacksQueues</span></span><br><span class="line">    mCallbackQueues = <span class="keyword">new</span> <span class="title class_">CallbackQueue</span>[CALLBACK_LAST + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt;= CALLBACK_LAST; i++) &#123;</span><br><span class="line">        mCallbackQueues[i] = <span class="keyword">new</span> <span class="title class_">CallbackQueue</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="FrameHandler"><a href="#FrameHandler" class="headerlink" title="FrameHandler"></a>FrameHandler</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FrameHandler</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (msg.what) &#123;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_FRAME:<span class="comment">//开始渲染下一帧的操作</span></span><br><span class="line">                doFrame(System.nanoTime(), <span class="number">0</span>);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_SCHEDULE_VSYNC:<span class="comment">//请求 Vsync </span></span><br><span class="line">                doScheduleVsync();</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MSG_DO_SCHEDULE_CALLBACK:<span class="comment">//处理 Callback</span></span><br><span class="line">                doScheduleCallback(msg.arg1);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Choreographer-初始化链"><a href="#Choreographer-初始化链" class="headerlink" title="Choreographer 初始化链"></a>Choreographer 初始化链</h3><p>在 Activity 启动过程，执行完 onResume 后，会调用 Activity.makeVisible()，然后再调用到 addView()， 层层调用会进入如下方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">ActivityThread.handleResumeActivity(IBinder, <span class="type">boolean</span>, <span class="type">boolean</span>, String) (android.app) </span><br><span class="line">--&gt;WindowManagerImpl.addView(View, LayoutParams) (android.view) </span><br><span class="line">  --&gt;WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) </span><br><span class="line">    --&gt;ViewRootImpl.ViewRootImpl(Context, Display) (android.view) </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ViewRootImpl</span><span class="params">(Context context, Display display)</span> &#123;</span><br><span class="line">        ......</span><br><span class="line">        mChoreographer = Choreographer.getInstance();</span><br><span class="line">        ......</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h2 id="FrameDisplayEventReceiver-简介"><a href="#FrameDisplayEventReceiver-简介" class="headerlink" title="FrameDisplayEventReceiver 简介"></a>FrameDisplayEventReceiver 简介</h2><p>Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类，所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver ， 有三个比较重要的方法</p><ol><li>onVsync – Vsync 信号回调</li><li>run – 执行 doFrame</li><li>scheduleVsync  – 请求 Vsync 信号</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FrameDisplayEventReceiver</span> <span class="keyword">extends</span> <span class="title class_">DisplayEventReceiver</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame)</span> &#123;</span><br><span class="line">        ......</span><br><span class="line">        mTimestampNanos = timestampNanos;</span><br><span class="line">        mFrame = frame;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">        msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        mHavePendingVsync = <span class="literal">false</span>;</span><br><span class="line">        doFrame(mTimestampNanos, mFrame);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scheduleVsync</span><span class="params">()</span> &#123;</span><br><span class="line">        ......  </span><br><span class="line">        nativeScheduleVsync(mReceiverPtr);</span><br><span class="line">        ......</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Choreographer-中-Vsync-的注册"><a href="#Choreographer-中-Vsync-的注册" class="headerlink" title="Choreographer 中 Vsync 的注册"></a>Choreographer 中 Vsync 的注册</h2><p>从下面的函数调用栈可以看到，Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调，通知 UIThread 进行数据处理。</p><p>那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢？答案是 FrameDisplayEventReceiver 的初始化的时候，最终通过监听文件句柄的形式，其对应的初始化流程如下</p><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Choreographer</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    mLooper = looper;</span><br><span class="line">    mDisplayEventReceiver = USE_VSYNC</span><br><span class="line">            ? <span class="keyword">new</span> <span class="title class_">FrameDisplayEventReceiver</span>(looper, vsyncSource)</span><br><span class="line">            : <span class="literal">null</span>;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">FrameDisplayEventReceiver</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(looper, vsyncSource);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;DisplayEventReceiver.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">DisplayEventReceiver</span><span class="params">(Looper looper, <span class="type">int</span> vsyncSource)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mMessageQueue = looper.getQueue();</span><br><span class="line">    mReceiverPtr = nativeInit(<span class="keyword">new</span> <span class="title class_">WeakReference</span>&lt;DisplayEventReceiver&gt;(<span class="built_in">this</span>), mMessageQueue,</span><br><span class="line">            vsyncSource);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>nativeInit 后续的代码可以自己跟一下，可以对照这篇文章和源码，由于篇幅比较多，这里就不细写了(<a href="https://www.jianshu.com/p/304f56f5d486">https://www.jianshu.com/p/304f56f5d486</a>) ， 后续梳理好这一块的逻辑后，会在另外的文章更新。</p><p>简单来说，FrameDisplayEventReceiver 的初始化过程中，通过 BitTube(本质是一个 socket pair)，来传递和请求 Vsync 事件，当 SurfaceFlinger 收到 Vsync 事件之后，通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher ，DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后，回调 Choreographer.FrameDisplayEventReceiver.onVsync ，触发开始一帧的绘制，如下图</p><p><img src="/images/15717421215251.jpg"></p><h2 id="Choreographer-处理一帧的逻辑"><a href="#Choreographer-处理一帧的逻辑" class="headerlink" title="Choreographer 处理一帧的逻辑"></a>Choreographer 处理一帧的逻辑</h2><p>Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中，从下图可以看到，FrameDisplayEventReceiver.onVsync post 了自己，其 run 方法直接调用了 doFrame 开始一帧的逻辑处理</p><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onVsync</span><span class="params">(<span class="type">long</span> timestampNanos, <span class="type">long</span> physicalDisplayId, <span class="type">int</span> frame)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mTimestampNanos = timestampNanos;</span><br><span class="line">    mFrame = frame;</span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> Message.obtain(mHandler, <span class="built_in">this</span>);</span><br><span class="line">    msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    mHavePendingVsync = <span class="literal">false</span>;</span><br><span class="line">    doFrame(mTimestampNanos, mFrame);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>doFrame 函数主要做下面几件事</p><ol><li>计算掉帧逻辑</li><li>记录帧绘制信息</li><li>执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT</li></ol><h3 id="计算掉帧逻辑"><a href="#计算掉帧逻辑" class="headerlink" title="计算掉帧逻辑"></a>计算掉帧逻辑</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos, <span class="type">int</span> frame)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> startNanos;</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        ......</span><br><span class="line">        <span class="type">long</span> <span class="variable">intendedFrameTimeNanos</span> <span class="operator">=</span> frameTimeNanos;</span><br><span class="line">        startNanos = System.nanoTime();</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">jitterNanos</span> <span class="operator">=</span> startNanos - frameTimeNanos;</span><br><span class="line">        <span class="keyword">if</span> (jitterNanos &gt;= mFrameIntervalNanos) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">long</span> <span class="variable">skippedFrames</span> <span class="operator">=</span> jitterNanos / mFrameIntervalNanos;</span><br><span class="line">            <span class="keyword">if</span> (skippedFrames &gt;= SKIPPED_FRAME_WARNING_LIMIT) &#123;</span><br><span class="line">                Log.i(TAG, <span class="string">&quot;Skipped &quot;</span> + skippedFrames + <span class="string">&quot; frames!  &quot;</span></span><br><span class="line">                        + <span class="string">&quot;The application may be doing too much work on its main thread.&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ......</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Choreographer.doFrame 的掉帧检测比较简单，从下图可以看到，Vsync 信号到来的时候会标记一个 start_time ，执行 doFrame 的时候标记一个 end_time ，这两个时间差就是 Vsync 处理时延，也就是掉帧</p><p><img src="/images/15717421364722.jpg"></p><p>我们以 Systrace 的掉帧的实际情况来看掉帧的计算逻辑</p><p><img src="/images/15717421441350.jpg"></p><p>这里需要注意的是，这种方法计算的掉帧，是前一帧的掉帧情况，而不是这一帧的掉帧情况，这个计算方法是有缺陷的，会导致有的掉帧没有被计算到</p><h3 id="记录帧绘制信息"><a href="#记录帧绘制信息" class="headerlink" title="记录帧绘制信息"></a>记录帧绘制信息</h3><p>Choreographer 中 FrameInfo 来负责记录帧的绘制信息，doFrame 执行的时候，会把每一个关键节点的绘制时间记录下来，我们使用 dumpsys gfxinfo 就可以看到。当然 Choreographer 只是记录了一部分，剩余的部分在 hwui 那边来记录。</p><p>从 FrameInfo 这些标志就可以看出记录的内容，后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Various flags set to provide extra metadata about the current frame</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">FLAGS</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Is this the first-draw following a window layout?</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">FLAG_WINDOW_LAYOUT_CHANGED</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// A renderer associated with just a Surface, not with a ViewRootImpl instance.</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">FLAG_SURFACE_CANVAS</span> <span class="operator">=</span> <span class="number">1</span> &lt;&lt; <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">@LongDef(flag = true, value = &#123;</span></span><br><span class="line"><span class="meta">        FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS &#125;)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.SOURCE)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> FrameInfoFlags &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The intended vsync time, unadjusted by jitter</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">INTENDED_VSYNC</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Jitter-adjusted vsync time, this is what was used as input into the</span></span><br><span class="line"><span class="comment">// animation &amp; drawing system</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">VSYNC</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The time of the oldest input event</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OLDEST_INPUT_EVENT</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The time of the newest input event</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NEWEST_INPUT_EVENT</span> <span class="operator">=</span> <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// When input event handling started</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">HANDLE_INPUT_START</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// When animation evaluations started</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">ANIMATION_START</span> <span class="operator">=</span> <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// When ViewRootImpl#performTraversals() started</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">PERFORM_TRAVERSALS_START</span> <span class="operator">=</span> <span class="number">7</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// When View:draw() started</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DRAW_START</span> <span class="operator">=</span> <span class="number">8</span>;</span><br></pre></td></tr></table></figure><p>doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos, <span class="type">int</span> frame)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INPUT Callbacks </span></span><br><span class="line">    mFrameInfo.markInputHandlingStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_ANIMATION Callbacks</span></span><br><span class="line">    mFrameInfo.markAnimationsStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INSETS_ANIMATION Callbacks</span></span><br><span class="line">    <span class="comment">// 处理 CALLBACK_TRAVERSAL Callbacks</span></span><br><span class="line">    mFrameInfo.markPerformTraversalsStart();</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_COMMIT Callbacks</span></span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="执行-Callbacks"><a href="#执行-Callbacks" class="headerlink" title="执行 Callbacks"></a>执行 Callbacks</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos, <span class="type">int</span> frame)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INPUT Callbacks </span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_ANIMATION Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_INSETS_ANIMATION Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_TRAVERSAL Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);</span><br><span class="line">    <span class="comment">// 处理 CALLBACK_COMMIT Callbacks</span></span><br><span class="line">    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Input 回调调用栈</strong></p><p><strong>input callback</strong> 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable</p><p>android&#x2F;view&#x2F;ViewRootImpl.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ConsumeBatchedInputRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">doConsumeBatchedInput</span><span class="params">(<span class="type">long</span> frameTimeNanos)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mConsumeBatchedInputScheduled) &#123;</span><br><span class="line">        mConsumeBatchedInputScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">if</span> (mInputEventReceiver != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)</span><br><span class="line">                    &amp;&amp; frameTimeNanos != -<span class="number">1</span>) &#123;</span><br><span class="line">                scheduleConsumeBatchedInput();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        doProcessInputEvents();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Input 时间经过处理，最终会传给 DecorView 的 dispatchTouchEvent，这就到了我们熟悉的 Input 事件分发</p><p><img src="/images/15717421837064.jpg"></p><p><strong>Animation 回调调用栈</strong></p><p>一般我们接触的多的是调用 View.postOnAnimation 的时候，会使用到 CALLBACK_ANIMATION </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postOnAnimation</span><span class="params">(Runnable action)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AttachInfo</span> <span class="variable">attachInfo</span> <span class="operator">=</span> mAttachInfo;</span><br><span class="line">    <span class="keyword">if</span> (attachInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">        attachInfo.mViewRootImpl.mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_ANIMATION, action, <span class="literal">null</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// Postpone the runnable until we know</span></span><br><span class="line">        <span class="comment">// on which thread it needs to run.</span></span><br><span class="line">        getRunQueue().post(action);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那么一般是什么时候回调用到 View.postOnAnimation 呢，我截取了一张图，大家可以自己去看一下，接触最多的应该是 startScroll，Fling 这种操作</p><p><img src="/images/15717421963577.jpg"></p><p>其调用栈根据其 post 的内容，下面是桌面滑动松手之后的 fling 动画。</p><p><img src="/images/15717422041938.jpg"></p><p>另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postFrameCallbackDelayed</span><span class="params">(FrameCallback callback, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (callback == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;callback must not be null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    postCallbackDelayedInternal(CALLBACK_ANIMATION,</span><br><span class="line">            callback, FRAME_CALLBACK_TOKEN, delayMillis);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Traversal 调用栈</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//为了提高优先级，先 postSyncBarrier</span></span><br><span class="line">        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</span><br><span class="line">        mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">TraversalRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 真正开始执行 measure、layout、draw</span></span><br><span class="line">        doTraversal();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">doTraversal</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 这里把 SyncBarrier remove</span></span><br><span class="line">mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);</span><br><span class="line">        <span class="comment">// 真正开始</span></span><br><span class="line">        performTraversals();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">performTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="comment">// measure 操作</span></span><br><span class="line">      <span class="keyword">if</span> (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) &#123;</span><br><span class="line">            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// layout 操作</span></span><br><span class="line">      <span class="keyword">if</span> (didLayout) &#123;</span><br><span class="line">          performLayout(lp, mWidth, mHeight);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// draw 操作</span></span><br><span class="line">      <span class="keyword">if</span> (!cancelDraw &amp;&amp; !newSurface) &#123;</span><br><span class="line">          performDraw();</span><br><span class="line">      &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>doTraversal 的 TraceView 示例</strong></p><p><img src="/images/15717422180571.jpg"></p><h2 id="下一帧的-Vsync-请求"><a href="#下一帧的-Vsync-请求" class="headerlink" title="下一帧的 Vsync 请求"></a>下一帧的 Vsync 请求</h2><p>由于动画、滑动、Fling 这些操作的存在，我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑，在连续的操作，比如动画、滑动、Fling 这些情况下，每一帧的 doFrame 的时候，都会根据情况触发下一个 Vsync 的申请，这样我们就可以获得连续的 Vsync 信号。</p><p>看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求)<br><img src="/images/15724225347501.jpg"><br>我们比较熟悉的 invalidate 和 requestLayout 都会触发 Vsync 信号请求</p><p>我们下面以 Animation 为例，看看 Animation 是如何驱动下一个 Vsync ，来持续更新画面的</p><h3 id="ObjectAnimator-动画驱动逻辑"><a href="#ObjectAnimator-动画驱动逻辑" class="headerlink" title="ObjectAnimator 动画驱动逻辑"></a>ObjectAnimator 动画驱动逻辑</h3><p>android&#x2F;animation&#x2F;ObjectAnimator.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;animation&#x2F;ValueAnimator.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">boolean</span> playBackwards)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    addAnimationCallback(<span class="number">0</span>); <span class="comment">// 动画 start 的时候添加 Animation Callback </span></span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">addAnimationCallback</span><span class="params">(<span class="type">long</span> delay)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    getAnimationHandler().addAnimationFrameCallback(<span class="built_in">this</span>, delay);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;animation&#x2F;AnimationHandler.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addAnimationFrameCallback</span><span class="params">(<span class="keyword">final</span> AnimationFrameCallback callback, <span class="type">long</span> delay)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mAnimationCallbacks.size() == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// post FrameCallback</span></span><br><span class="line">        getProvider().postFrameCallback(mFrameCallback);</span><br><span class="line">    &#125;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里的 mFrameCallback 回调 doFrame，里面 post了自己</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Choreographer.<span class="type">FrameCallback</span> <span class="variable">mFrameCallback</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Choreographer</span>.FrameCallback() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos)</span> &#123;</span><br><span class="line">        doAnimationFrame(getProvider().getFrameTime());</span><br><span class="line">        <span class="keyword">if</span> (mAnimationCallbacks.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// post 自己</span></span><br><span class="line">            getProvider().postFrameCallback(<span class="built_in">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ，这里就会触发 Choreographer 的 Vsync 请求逻辑</p><p>android&#x2F;animation&#x2F;AnimationHandler.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postFrameCallback</span><span class="params">(Choreographer.FrameCallback callback)</span> &#123;</span><br><span class="line">    mChoreographer.postFrameCallback(callback);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>android&#x2F;view&#x2F;Choreographer.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">postCallbackDelayedInternal</span><span class="params">(<span class="type">int</span> callbackType,</span></span><br><span class="line"><span class="params">        Object action, Object token, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">dueTime</span> <span class="operator">=</span> now + delayMillis;</span><br><span class="line">        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (dueTime &lt;= now) &#123;</span><br><span class="line">            <span class="comment">// 请求 Vsync scheduleFrameLocked -&gt;scheduleVsyncLocked-&gt; mDisplayEventReceiver.scheduleVsync -&gt;nativeScheduleVsync</span></span><br><span class="line">            scheduleFrameLocked(now);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);</span><br><span class="line">            msg.arg1 = callbackType;</span><br><span class="line">            msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">            mHandler.sendMessageAtTime(msg, dueTime);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上面的 Animation.start 设置，利用了 Choreographer.FrameCallback 接口，每一帧都去请求下一个 Vsync<br><strong>动画过程中一帧的 TraceView 示例</strong></p><p><img src="/images/15717422327935.jpg"></p><h2 id="源码小结"><a href="#源码小结" class="headerlink" title="源码小结"></a>源码小结</h2><ol><li><p><strong>Choreographer</strong> 是线程单例的，而且必须要和一个 Looper 绑定，因为其内部有一个 Handler 需要和 Looper 绑定，一般是 App 主线程的 Looper 绑定</p></li><li><p><strong>DisplayEventReceiver</strong> 是一个 abstract class，其 JNI 的代码部分会创建一个IDisplayEventConnection 的 Vsync 监听者对象。这样，来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时，DisplayEventReceiver 的 onVsync 函数将被调用。</p></li><li><p><strong>DisplayEventReceiver</strong> 还有一个 scheduleVsync 函数。当应用需要绘制UI时，将首先申请一次 Vsync 中断，然后再在中断处理的 onVsync 函数去进行绘制。</p></li><li><p><strong>Choreographer</strong> 定义了一个 <strong>FrameCallback</strong> <strong>interface</strong>，每当 Vsync 到来时，其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间，现在终于有了固定的时间中断。</p></li><li><p><strong>Choreographer</strong> 的主要功能是，当收到 Vsync 信号时，去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调，它们分别是：</p><ol><li><strong>CALLBACK_INPUT</strong>  : 处理输入事件处理有关</li><li><strong>CALLBACK_ANIMATION</strong> ： 处理 Animation 的处理有关</li><li><strong>CALLBACK_INSETS_ANIMATION</strong> ： 处理 Insets Animation 的相关回调</li><li><strong>CALLBACK_TRAVERSAL</strong>  : 处理和 UI 等控件绘制有关</li><li><strong>CALLBACK_COMMIT</strong> ： 处理 Commit 相关回调，主要是是用于执行组件 Application&#x2F;Activity&#x2F;Service 的 onTrimMemory，在 ApplicationThread 的 scheduleTrimMemory 方法中向 Choreographer 插入的；另外这个 Callback 也提供了一个监测一帧耗时的时机</li></ol></li><li><p><strong>ListView</strong> 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面，这取决于</p></li><li><p><strong>CALLBACK_INPUT</strong> 、<strong>CALLBACK_ANIMATION</strong> 会修改 view 的属性，所以要先与 CALLBACK_TRAVERSAL 执行</p></li></ol><h1 id="APM-与-Choreographer"><a href="#APM-与-Choreographer" class="headerlink" title="APM 与 Choreographer"></a>APM 与 Choreographer</h1><p>由于 Choreographer 的位置，许多性能监控的手段都是利用 Choreographer 来做的，除了自带的掉帧计算，Choreographer 提供的 FrameCallback 和 FrameInfo 都给 App 暴露了接口，让 App 开发者可以通过这些方法监控自身 App 的性能，其中常用的方法如下：</p><ol><li>利用 FrameCallback 的 doFrame 回调</li><li>利用 FrameInfo 进行监控</li><li>使用 ：adb shell dumpsys gfxinfo <packagename> framestats</li><li>示例 ：adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats</li><li>利用 SurfaceFlinger 进行监控</li><li>使用 ：adb shell dumpsys SurfaceFlinger –latency</li><li>示例 ：adb shell dumpsys SurfaceFlinger –latency com.meizu.flyme.launcher&#x2F;com.meizu.flyme.launcher.Launcher#0</li><li>利用 SurfaceFlinger PageFlip 机制进行监控</li><li>使用 ： adb service call SurfaceFlinger 1013</li><li>备注：需要系统权限</li><li>Choreographer 自身的掉帧计算逻辑</li><li>BlockCanary 基于 Looper 的性能监控</li></ol><h2 id="利用-FrameCallback-的-doFrame-回调"><a href="#利用-FrameCallback-的-doFrame-回调" class="headerlink" title="利用 FrameCallback 的 doFrame 回调"></a>利用 FrameCallback 的 doFrame 回调</h2><h3 id="FrameCallback-接口"><a href="#FrameCallback-接口" class="headerlink" title="FrameCallback 接口"></a>FrameCallback 接口</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">FrameCallback</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFrame</span><span class="params">(<span class="type">long</span> frameTimeNanos)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="接口使用"><a href="#接口使用" class="headerlink" title="接口使用"></a>接口使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );</span><br></pre></td></tr></table></figure><h3 id="接口处理"><a href="#接口处理" class="headerlink" title="接口处理"></a>接口处理</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postFrameCallbackDelayed</span><span class="params">(FrameCallback callback, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    postCallbackDelayedInternal(CALLBACK_ANIMATION,</span><br><span class="line">            callback, FRAME_CALLBACK_TOKEN, delayMillis);</span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>TinyDancer 就是使用了这个方法来计算 FPS (<a href="https://github.com/friendlyrobotnyc/TinyDancer">https://github.com/friendlyrobotnyc/TinyDancer</a>)</p><h2 id="利用-FrameInfo-进行监控"><a href="#利用-FrameInfo-进行监控" class="headerlink" title="利用 FrameInfo 进行监控"></a>利用 FrameInfo 进行监控</h2><p>adb shell dumpsys gfxinfo <packagename> framestats</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Window: StatusBar</span><br><span class="line">Stats since: 17990256398ns</span><br><span class="line">Total frames rendered: 1562</span><br><span class="line">Janky frames: 361 (23.11%)</span><br><span class="line">50th percentile: 6ms</span><br><span class="line">90th percentile: 23ms</span><br><span class="line">95th percentile: 36ms</span><br><span class="line">99th percentile: 101ms</span><br><span class="line">Number Missed Vsync: 33</span><br><span class="line">Number High input latency: 683</span><br><span class="line">Number Slow UI thread: 273</span><br><span class="line">Number Slow bitmap uploads: 8</span><br><span class="line">Number Slow issue draw commands: 18</span><br><span class="line">Number Frame deadline missed: 287</span><br><span class="line">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 </span><br><span class="line"></span><br><span class="line">---PROFILEDATA---</span><br><span class="line">Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,</span><br><span class="line">0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,</span><br><span class="line">0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,</span><br><span class="line">0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,</span><br><span class="line">0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,</span><br></pre></td></tr></table></figure><h2 id="利用-SurfaceFlinger-进行监控"><a href="#利用-SurfaceFlinger-进行监控" class="headerlink" title="利用 SurfaceFlinger 进行监控"></a>利用 SurfaceFlinger 进行监控</h2><p>命令解释：</p><ol><li>数据的单位是纳秒，时间是以开机时间为起始点</li><li>每一次的命令都会得到128行的帧相关的数据</li></ol><p>数据：</p><ol><li>第一行数据，表示刷新的时间间隔refresh_period</li><li>第1列：这一部分的数据表示应用程序绘制图像的时间点</li><li>第2列：在SF(软件)将帧提交给H&#x2F;W(硬件)绘制之前的垂直同步时间，也就是每帧绘制完提交到硬件的时间戳，该列就是垂直同步的时间戳</li><li>第3列：在SF将帧提交给H&#x2F;W的时间点，算是H&#x2F;W接受完SF发来数据的时间点，绘制完成的时间点。</li></ol><p><strong>掉帧 jank 计算</strong></p><p>每一行都可以通过下面的公式得到一个值，该值是一个标准，我们称为jankflag，如果当前行的jankflag与上一行的jankflag发生改变，那么就叫掉帧</p><p>ceil((C - A) &#x2F; refresh-period)</p><h2 id="利用-SurfaceFlinger-PageFlip-机制进行监控"><a href="#利用-SurfaceFlinger-PageFlip-机制进行监控" class="headerlink" title="利用 SurfaceFlinger PageFlip 机制进行监控"></a>利用 SurfaceFlinger PageFlip 机制进行监控</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Parcel</span> <span class="variable">data</span> <span class="operator">=</span> Parcel.obtain();</span><br><span class="line"><span class="type">Parcel</span> <span class="variable">reply</span> <span class="operator">=</span> Parcel.obtain();</span><br><span class="line">                data.writeInterfaceToken(<span class="string">&quot;android.ui.ISurfaceComposer&quot;</span>);</span><br><span class="line">mFlinger.transact(<span class="number">1013</span>, data, reply, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">pageFlipCount</span> <span class="operator">=</span> reply.readInt();</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">frames</span> <span class="operator">=</span> pageFlipCount - mLastPageFlipCount;</span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">duration</span> <span class="operator">=</span> now - mLastUpdateTime;</span><br><span class="line">mFps = (<span class="type">float</span>) (frames * <span class="number">1e9</span> / duration);</span><br><span class="line">mLastPageFlipCount = pageFlipCount;</span><br><span class="line">mLastUpdateTime = now;</span><br><span class="line">reply.recycle();</span><br><span class="line">data.recycle();</span><br></pre></td></tr></table></figure><h2 id="Choreographer-自身的掉帧计算逻辑"><a href="#Choreographer-自身的掉帧计算逻辑" class="headerlink" title="Choreographer 自身的掉帧计算逻辑"></a>Choreographer 自身的掉帧计算逻辑</h2><p>SKIPPED_FRAME_WARNING_LIMIT 默认为30 , 由 debug.choreographer.skipwarning 这个属性控制</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (jitterNanos &gt;= mFrameIntervalNanos) &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">skippedFrames</span> <span class="operator">=</span> jitterNanos / mFrameIntervalNanos;</span><br><span class="line">    <span class="keyword">if</span> (skippedFrames &gt;= SKIPPED_FRAME_WARNING_LIMIT) &#123;</span><br><span class="line">        Log.i(TAG, <span class="string">&quot;Skipped &quot;</span> + skippedFrames + <span class="string">&quot; frames!  &quot;</span></span><br><span class="line">                + <span class="string">&quot;The application may be doing too much work on its main thread.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="BlockCanary"><a href="#BlockCanary" class="headerlink" title="BlockCanary"></a>BlockCanary</h2><p>Blockcanary 做性能监控使用的是 Looper 的消息机制，通过对 MessageQueue 中每一个 Message 的前后进行记录，打到监控性能的目的</p><p>android&#x2F;os&#x2F;Looper.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="comment">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">        <span class="type">Printer</span> <span class="variable">logging</span> <span class="operator">=</span> me.mLogging;</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&gt;&gt;&gt;&gt;&gt; Dispatching to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> +</span><br><span class="line">                    msg.callback + <span class="string">&quot;: &quot;</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line">        msg.target.dispatchMessage(msg);</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="literal">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">&quot;&lt;&lt;&lt;&lt;&lt; Finished to &quot;</span> + msg.target + <span class="string">&quot; &quot;</span> + msg.callback);</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="MessageQueue-与-Choreographer"><a href="#MessageQueue-与-Choreographer" class="headerlink" title="MessageQueue 与 Choreographer"></a>MessageQueue 与 Choreographer</h1><p>所谓的异步消息其实就是这样的，我们可以通过 enqueueBarrier 往消息队列中插入一个 Barrier，那么队列中执行时间在这个 Barrier 以后的同步消息都会被这个 Barrier 拦截住无法执行，直到我们调用 removeBarrier 移除了这个 Barrier，而异步消息则没有影响，消息默认就是同步消息，除非我们调用了 Message 的 setAsynchronous，这个方法是隐藏的。只有在初始化 Handler 时通过参数指定往这个 Handler 发送的消息都是异步的，这样在 Handler 的 enqueueMessage 中就会调用 Message 的 setAsynchronous 设置消息是异步的，从上面 Handler.enqueueMessage 的代码中可以看到。</p><p>所谓异步消息，其实只有一个作用，就是在设置 Barrier 时仍可以不受 Barrier 的影响被正常处理，如果没有设置 Barrier，异步消息就与同步消息没有区别，可以通过 removeSyncBarrier 移除 Barrier</p><h2 id="SyncBarrier-在-Choreographer-中使用的一个示例"><a href="#SyncBarrier-在-Choreographer-中使用的一个示例" class="headerlink" title="SyncBarrier 在 Choreographer 中使用的一个示例"></a>SyncBarrier 在 Choreographer 中使用的一个示例</h2><p>scheduleTraversals 的时候 postSyncBarrier</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">scheduleTraversals</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//为了提高优先级，先 postSyncBarrier</span></span><br><span class="line">        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();</span><br><span class="line">        mChoreographer.postCallback(</span><br><span class="line">                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>doTraversal 的时候 removeSyncBarrier</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">doTraversal</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mTraversalScheduled) &#123;</span><br><span class="line">        mTraversalScheduled = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 这里把 SyncBarrier remove</span></span><br><span class="line">mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);</span><br><span class="line">        <span class="comment">// 真正开始</span></span><br><span class="line">        performTraversals();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Choreographer post Message 的时候，会把这些消息设为 Asynchronous ，这样 Choreographer 中的这些 Message 的优先级就会比较高，</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);</span><br><span class="line">msg.arg1 = callbackType;</span><br><span class="line">msg.setAsynchronous(<span class="literal">true</span>);</span><br><span class="line">mHandler.sendMessageAtTime(msg, dueTime);</span><br></pre></td></tr></table></figure><h1 id="厂商优化"><a href="#厂商优化" class="headerlink" title="厂商优化"></a>厂商优化</h1><p>系统厂商由于可以直接修改源码，也利用这方面的便利，做一些功能和优化，不过由于保密的问题，代码就不直接放上来了，我可以大概说一下思路，感兴趣的可以私下讨论</p><h2 id="移动事件优化"><a href="#移动事件优化" class="headerlink" title="移动事件优化"></a>移动事件优化</h2><p>Choreographer 本身是没有 input 消息的， 不过修改源码之后，input 消息可以直接给到 Choreographer 这里， 有了这些 Input 消息，Choreographer 就可以做一些事情，比如说提前响应，不去等 Vsync</p><h2 id="后台动画优化"><a href="#后台动画优化" class="headerlink" title="后台动画优化"></a>后台动画优化</h2><p>当一个 Android App 退到后台之后，只要他没有被杀死，那么他做什么事情大家都不要奇怪，因为这就是 Android。有的 App 退到后台之后还在持续调用 Choreographer 中的 Animation Callback，而这个 Callback 的执行完全是无意义的，而且用户还不知道，但是对 cpu 的占用是比较高的。</p><p>所以在 Choreographer 中会针对这种情况做优化，禁止不符合条件的 App 在后台继续无用的操作</p><p><img src="/images/15717422623134.jpg"></p><h2 id="帧绘制优化"><a href="#帧绘制优化" class="headerlink" title="帧绘制优化"></a>帧绘制优化</h2><p>和移动事件优化一样，由于有了 Input 事件的信息，在某些场景下我们可以通知 SurfaceFlinger 不用去等待 Vsync 直接做合成操作</p><h2 id="应用启动优化"><a href="#应用启动优化" class="headerlink" title="应用启动优化"></a>应用启动优化</h2><p>我们前面说，主线程的所有操作都是给予 Message 的 ，如果某个操作，非重要的 Message 被排列到了队列后面，那么对这个操作产生影响；而通过重新排列 MessageQueue，在应用启动的时候，把启动相关的重要的启动 Message 放到队列前面，来起到加快启动速度的作用</p><h2 id="高帧率优化"><a href="#高帧率优化" class="headerlink" title="高帧率优化"></a>高帧率优化</h2><p>90&#x2F;120 fps 的手机上 ， Vsync 间隔从 16.6ms 变成了 11.1ms&#x2F;8.8ms ，这带来了巨大的性能和功耗挑战，如何在一帧内完成渲染的必要操作，是手机厂商必须要思考和优化的地方：</p><ol><li>超级 App 的性能表现以及优化</li><li>游戏高帧率合作</li><li>120fps、90 fps 和 60 fps 相互切换的逻辑</li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://www.jianshu.com/p/304f56f5d486">https://www.jianshu.com/p/304f56f5d486</a></li><li><a href="http://gityuan.com/2017/02/25/choreographer/">http://gityuan.com/2017/02/25/choreographer/</a></li><li><a href="https://developer.android.com/reference/android/view/Choreographer">https://developer.android.com/reference/android/view/Choreographer</a></li><li><a href="https://www.jishuwen.com/d/2Vcc">https://www.jishuwen.com/d/2Vcc</a></li><li><a href="https://juejin.im/entry/5c8772eee51d456cda2e8099">https://juejin.im/entry/5c8772eee51d456cda2e8099</a></li><li><a href="https://time.geekbang.org/column/intro/142">Android 开发高手课</a></li></ol><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/87954949">知乎 - Android 基于 Choreographer 的渲染机制详解</a><br><a href="https://juejin.im/post/5daedc65e51d457834735c65">掘金 - Android 基于 Choreographer 的渲染机制详解</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍了 App 开发者不经常接触到但是在 Android Framework 渲染链路中非常重要的一个类 Choreographer。包括 Choreographer 的引入背景、Choreographer 的简介、部分源码解析、Choreographer 与 MessageQueue、Choreographer 和 APM，以及手机厂商基于 Choreographer 的一些优化思路&lt;/p&gt;
&lt;p&gt;Choreographer 的引入，主要是配合 Vsync ，给上层 App 的渲染提供一个稳定的 Message 处理的时机，也就是 Vsync 到来的时候 ，系统通过对  Vsync 信号周期的调整，来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率，也就是 16.6ms 刷新一次，系统为了配合屏幕的刷新频率，将 Vsync 的周期也设置为 16.6 ms，每个 16.6 ms ， Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ，这就是引入 Choreographer 的主要作用. 了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理，也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的卡顿丢帧原因概述 - 低内存篇</title>
    <link href="https://androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/"/>
    <id>https://androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/</id>
    <published>2019-09-18T15:21:26.000Z</published>
    <updated>2026-02-07T05:17:47.826Z</updated>
    
    <content type="html"><![CDATA[<p>在<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a> 这篇文章中 , 实际案例这里我们有列举一些由于系统低内存导致的卡顿 , 由于 Android 低内存对整机性能影响比较大 , 所以单独写一篇文章 , 来概述系统低内存对整机性能的影响 .</p><p>随着 Android 系统版本的更迭 , 以及 App 的代码膨胀 , Android 系统对内存的需求越来越大 , 但是目前市面上还存在着大量的 4G 内存以下的机器 , 这部分用户就很容易遇到整机低内存的情况 , 尤其是在系统大版本更新和 App 越装越多的情况下 . </p><p>Android 低内存会导致性能问题 , 具体表现就是响应慢和卡顿 . 比如启动一个应用要花比平时更长的时间 ; 滑动列表会掉更多帧 ; 后台的进程减少导致冷启动变多 ; 手机很容易发热发烫等 , 下面我会概述发生这些性能问题的原因 . Debug 的方法 , 以及可能的优化措施 . </p><span id="more"></span><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a><br><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">3. Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><h1 id="低内存的数据特征和行为特征"><a href="#低内存的数据特征和行为特征" class="headerlink" title="低内存的数据特征和行为特征"></a>低内存的数据特征和行为特征</h1><h2 id="Meminfo-信息"><a href="#Meminfo-信息" class="headerlink" title="Meminfo 信息"></a>Meminfo 信息</h2><p>最简单的方法是使用 Android 系统自带的 Dumpsys meminfo 工具</p><figure class="highlight mathematica"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">adb</span> <span class="variable">shell</span> <span class="variable">dumpsys</span> <span class="variable">meminfo</span></span><br><span class="line"><span class="operator">......</span></span><br><span class="line"><span class="built_in">Total</span> <span class="variable">RAM</span><span class="operator">:</span> <span class="number">7</span><span class="operator">,</span><span class="number">658</span><span class="operator">,</span><span class="number">060</span><span class="built_in">K</span> <span class="punctuation">(</span><span class="variable">status</span> <span class="variable">moderate</span><span class="punctuation">)</span></span><br><span class="line"> <span class="variable">Free</span> <span class="variable">RAM</span><span class="operator">:</span>   <span class="number">550</span><span class="operator">,</span><span class="number">200</span><span class="built_in">K</span> <span class="punctuation">(</span>   <span class="number">78</span><span class="operator">,</span><span class="number">760</span><span class="built_in">K</span> <span class="variable">cached</span> <span class="variable">pss</span> <span class="operator">+</span>   <span class="number">156</span><span class="operator">,</span><span class="variable">ba480K</span> <span class="variable">cached</span> <span class="variable">kernel</span> <span class="operator">+</span>   <span class="number">314</span><span class="operator">,</span><span class="number">960</span><span class="built_in">K</span> <span class="variable">free</span><span class="punctuation">)</span></span><br><span class="line"> <span class="variable">Used</span> <span class="variable">RAM</span><span class="operator">:</span> <span class="number">7</span><span class="operator">,</span><span class="number">718</span><span class="operator">,</span><span class="number">091</span><span class="built_in">K</span> <span class="punctuation">(</span><span class="number">6</span><span class="operator">,</span><span class="number">118</span><span class="operator">,</span><span class="number">703</span><span class="built_in">K</span> <span class="variable">used</span> <span class="variable">pss</span> <span class="operator">+</span> <span class="number">1</span><span class="operator">,</span><span class="number">599</span><span class="operator">,</span><span class="number">388</span><span class="built_in">K</span> <span class="variable">kernel</span><span class="punctuation">)</span></span><br><span class="line"> <span class="variable">Lost</span> <span class="variable">RAM</span><span class="operator">:</span>  <span class="operator">-</span><span class="number">319</span><span class="operator">,</span><span class="number">863</span><span class="built_in">K</span></span><br><span class="line">     <span class="variable">ZRAM</span><span class="operator">:</span>     <span class="number">2</span><span class="operator">,</span><span class="number">608</span><span class="built_in">K</span> <span class="variable">physical</span> <span class="variable">used</span> <span class="variable">for</span>   <span class="number">301</span><span class="operator">,</span><span class="number">256</span><span class="built_in">K</span> <span class="variable">in</span> <span class="variable">swap</span> <span class="punctuation">(</span><span class="number">4</span><span class="operator">,</span><span class="number">247</span><span class="operator">,</span><span class="number">544</span><span class="built_in">K</span> <span class="variable">total</span> <span class="variable">swap</span><span class="punctuation">)</span></span><br><span class="line">   <span class="variable">Tuning</span><span class="operator">:</span> <span class="number">256</span> <span class="punctuation">(</span><span class="variable">large</span> <span class="number">512</span><span class="punctuation">)</span><span class="operator">,</span> <span class="variable">oom</span>   <span class="number">322</span><span class="operator">,</span><span class="number">560</span><span class="built_in">K</span><span class="operator">,</span> <span class="variable">restore</span> <span class="variable">limit</span>   <span class="number">107</span><span class="operator">,</span><span class="number">520</span><span class="built_in">K</span> <span class="punctuation">(</span><span class="variable">high</span><span class="operator">-</span><span class="variable">end</span><span class="operator">-</span><span class="variable">gfx</span><span class="punctuation">)</span></span><br></pre></td></tr></table></figure><p>如果系统处于低内存的话 , 会有如下特征:</p><ol><li>FreeRam 的值非常少 , Used RAM 的值非常大</li><li>ZRAM 使用率非常高（如果开了 Zram 的话）</li></ol><h2 id="LMK-amp-amp-kswapd-线程活跃"><a href="#LMK-amp-amp-kswapd-线程活跃" class="headerlink" title="LMK &amp;&amp; kswapd 线程活跃"></a>LMK &amp;&amp; kswapd 线程活跃</h2><p>低内存的时候， LKMD 会非常活跃， 在 Kernel Log 里面可以看到 LMK 杀进程的信息:</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[kswapd0] lowmemorykiller: Killing <span class="string">&#x27;u.mzsyncservice&#x27;</span> (<span class="number">15609</span>) (tgid <span class="number">15609</span>), adj <span class="number">906</span>,</span><br><span class="line"><span class="keyword">to</span> free <span class="number">28864</span>kB <span class="keyword">on</span> behalf <span class="keyword">of</span> <span class="string">&#x27;kswapd0&#x27;</span> (<span class="number">91</span>) because</span><br><span class="line"><span class="keyword">cache</span> <span class="number">258652</span>kB <span class="keyword">is</span> below <span class="keyword">limit</span> <span class="number">261272</span>kB <span class="keyword">for</span> oom score <span class="number">906</span></span><br><span class="line">Free memory <span class="keyword">is</span> <span class="number">-5540</span>kB above reserved.</span><br><span class="line">Free CMA <span class="keyword">is</span> <span class="number">3172</span>kB</span><br><span class="line">Total reserve <span class="keyword">is</span> <span class="number">227288</span>kB</span><br><span class="line">Total free pages <span class="keyword">is</span> <span class="number">271748</span>kB</span><br><span class="line">Total file <span class="keyword">cache</span> is <span class="number">345384</span>kB</span><br><span class="line">GFP mask <span class="keyword">is</span> <span class="number">0x14000c0</span></span><br></pre></td></tr></table></figure><p>上面这段 Log 的意思是说， 由于 mem 低于我们设定的 900 的水位线 （261272kB），所以把 pid 为 15609 的 mzsyncservice 这个进程杀掉（这个进程的 adj 是 906 ）</p><h2 id="proc-x2F-meminfo"><a href="#proc-x2F-meminfo" class="headerlink" title="proc&#x2F;meminfo"></a>proc&#x2F;meminfo</h2><p>这里是 Linux Kernel 展示 meminfo 的地方 , 关于 meminfo 的解读，可以参考这篇文章：<a href="http://linuxperf.com/?p=142">&#x2F;PROC&#x2F;MEMINFO之谜</a></p><p>从结果来 , 当系统处于低内存的情况时候 , MemFree 和 MemAvailable 的值都很小</p><figure class="highlight plaintext"><figcaption><span>shell cat proc/meminfo</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">MemTotal:        5630104 kB</span><br><span class="line">MemFree:          148928 kB</span><br><span class="line">MemAvailable:     864172 kB</span><br><span class="line">Buffers:           28464 kB</span><br><span class="line">Cached:          1003144 kB</span><br><span class="line">SwapCached:        19844 kB</span><br><span class="line">Active:          1607512 kB</span><br><span class="line">Inactive:         969208 kB</span><br><span class="line">Active(anon):    1187828 kB</span><br><span class="line">Inactive(anon):   426192 kB</span><br><span class="line">Active(file):     419684 kB</span><br><span class="line">Inactive(file):   543016 kB</span><br><span class="line">Unevictable:       62152 kB</span><br><span class="line">Mlocked:           62152 kB</span><br><span class="line">SwapTotal:       2097148 kB</span><br><span class="line">SwapFree:          42576 kB</span><br><span class="line">Dirty:              3604 kB</span><br><span class="line">Writeback:             0 kB</span><br><span class="line">AnonPages:       1602928 kB</span><br><span class="line">Mapped:           996768 kB</span><br><span class="line">Shmem:              7284 kB</span><br><span class="line">Slab:             306440 kB</span><br><span class="line">SReclaimable:      72320 kB</span><br><span class="line">SUnreclaim:       234120 kB</span><br><span class="line">KernelStack:       89776 kB</span><br><span class="line">PageTables:       107572 kB</span><br><span class="line">NFS_Unstable:          0 kB</span><br><span class="line">Bounce:                0 kB</span><br><span class="line">WritebackTmp:          0 kB</span><br><span class="line">CommitLimit:     4912200 kB</span><br><span class="line">Committed_AS:   118487976 kB</span><br><span class="line">VmallocTotal:   263061440 kB</span><br><span class="line">VmallocUsed:           0 kB</span><br><span class="line">VmallocChunk:          0 kB</span><br><span class="line">CmaTotal:         303104 kB</span><br><span class="line">CmaFree:            3924 kB</span><br></pre></td></tr></table></figure><h2 id="整机卡顿-amp-amp-响应慢"><a href="#整机卡顿-amp-amp-响应慢" class="headerlink" title="整机卡顿 &amp;&amp; 响应慢"></a>整机卡顿 &amp;&amp; 响应慢</h2><p>低内存的时候，整机使用的时候要比非低内存的时候要卡很多，点击应用或者启动 App 都会有不顺畅或者响应慢的感觉</p><h1 id="低内存对性能的具体影响"><a href="#低内存对性能的具体影响" class="headerlink" title="低内存对性能的具体影响"></a>低内存对性能的具体影响</h1><h2 id="影响主线程-IO-操作"><a href="#影响主线程-IO-操作" class="headerlink" title="影响主线程 IO 操作"></a>影响主线程 IO 操作</h2><p>主线程出现大量的 IO 相关的问题 ， </p><ol><li>反馈到 Trace 上就是有大量的黄色 Trace State 出现 , 例如 : Uninterruptible Sleep | WakeKill - Block I&#x2F;O .</li><li>查看其 Block 信息 （kernel callsite when blocked:: “wait_on_page_bit_killable+0x78&#x2F;0x88）</li></ol><p><strong>Linux 系统的 page cache 链表中有时会出现一些还没准备好的 page ( 即还没把磁盘中的内容完全地读出来 ) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.</strong></p><p><strong>当出现大量的 IO 操作的时候，应用主线程的 Uninterruptible Sleep 也会变多，此时涉及到 io 操作（比如  view ，读文件，读配置文件、读 odex 文件），都会触发 Uninterruptible Sleep ， 导致整个操作的时间变长</strong></p><p><img src="/images/15688217911224.jpg"></p><p><img src="/images/15688217985878.jpg"></p><h2 id="出现-CPU-竞争"><a href="#出现-CPU-竞争" class="headerlink" title="出现 CPU 竞争"></a>出现 CPU 竞争</h2><p>低内存会触发 Low Memory Killer  进程频繁进行扫描和杀进程，kswapd0 是一个内核工作线程，内存不足时会被唤醒，做内存回收的工作。 当内存频繁在低水位的时候，kswapd0 会被频繁唤醒，占用 cpu ，造成卡顿和耗电。</p><p>比如下面这个情况， kswapd0 占用了 855 的超大核 cpu7 ，而且是满频在跑，耗电可想而知，如果此时前台应用的主线程跑到了 cpu7 上，很大可能会出现 cpu 竞争，导致调度不到而丢帧。 </p><p>HeapTaskDaemon 通常也会在低内存的时候跑的很高 </p><p><img src="/images/15688218269079.jpg">, 来做内存相关的操作</p><h2 id="进程频繁查杀和重启"><a href="#进程频繁查杀和重启" class="headerlink" title="进程频繁查杀和重启"></a>进程频繁查杀和重启</h2><p>对 AMS 的影响主要集中在进程的查杀上面 , 由于 LMK 的介入 , 处于 Cache 状态的进程很容易被杀掉 , 然后又被他们的父进程或者其他的应用所拉起来 , 导致陷入了一种死循环 . 对系统 CPU \ Memory \ IO 等资源的影响非常大.</p><p>比如下面就是一次 Monkey 之后的结果 , QQ 在短时间内频繁被杀和重启 .</p><figure class="highlight plaintext"><figcaption><span>14:32:16.932  1435  1510 I am_proc_start: [0,30387,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">07-23 14:32:16.969  1435  3420 I am_proc_bound: [0,30387,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:16.979  1435  3420 I am_kill : [0,30387,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:16.996  1435  3420 I am_proc_died: [0,30387,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.028  1435  1510 I am_proc_start: [0,30400,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.054  1435  3420 I am_proc_bound: [0,30400,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.064  1435  3420 I am_kill : [0,30400,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.082  1435  3420 I am_proc_died: [0,30400,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.114  1435  1510 I am_proc_start: [0,30413,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.139  1435  3420 I am_proc_bound: [0,30413,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.149  1435  3420 I am_kill : [0,30413,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.166  1435  3420 I am_proc_died: [0,30413,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.202  1435  1510 I am_proc_start: [0,30427,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.216  1435  3420 I am_proc_bound: [0,30427,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.226  1435  3420 I am_kill : [0,30427,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.249  1435  3420 I am_proc_died: [0,30427,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.278  1435  1510 I am_proc_start: [0,30440,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.299  1435  3420 I am_proc_bound: [0,30440,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.309  1435  3420 I am_kill : [0,30440,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.329  1435  2116 I am_proc_died: [0,30440,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.362  1435  1510 I am_proc_start: [0,30453,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.387  1435  2116 I am_proc_bound: [0,30453,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.398  1435  2116 I am_kill : [0,30453,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.420  1435  2116 I am_proc_died: [0,30453,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.447  1435  1510 I am_proc_start: [0,30466,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.474  1435  2116 I am_proc_bound: [0,30466,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.484  1435  2116 I am_kill : [0,30466,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.507  1435  2116 I am_proc_died: [0,30466,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.533  1435  1510 I am_proc_start: [0,30479,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.556  1435  2116 I am_proc_bound: [0,30479,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.566  1435  2116 I am_kill : [0,30479,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.587  1435  2116 I am_proc_died: [0,30479,com.tencent.mobileqq,901,18]</span><br><span class="line">07-23 14:32:17.613  1435  1510 I am_proc_start: [0,30492,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.636  1435  2116 I am_proc_bound: [0,30492,com.tencent.mobileqq]</span><br><span class="line">07-23 14:32:17.646  1435  2116 I am_kill : [0,30492,com.tencent.mobileqq,901,empty #3]</span><br><span class="line">07-23 14:32:17.667  1435  2116 I am_proc_died: [0,30492,com.tencent.mobileqq,901,18]</span><br></pre></td></tr></table></figure><p>其对应的 Systrace - SystemServer 中可以看到 AM 在频繁杀 QQ 和起 QQ<br><img src="/images/15688220198874.jpg"></p><p>此 Trace 对应的 Kernel 部分也可以看到繁忙的 cpu<br><img src="/images/15688220915505.jpg"></p><h2 id="影响内存分配和触发-IO"><a href="#影响内存分配和触发-IO" class="headerlink" title="影响内存分配和触发 IO"></a>影响内存分配和触发 IO</h2><p>手机经过长时间老化使用整机卡顿一下 , 或者整体比刚刚开机的时候操作要慢 , 可能是因为触发了内存回收或者 block io , 而这两者又经常有关联 . 内存回收可能触发了 fast path  回收 \ kswapd 回收 \ direct reclaim 回收 \ LMK杀进程回收等。（fast path 回收不进行回写）</p><p>回收的内容是匿名页 swapout 或者 file-backed 页写回和清空。（假设手机都是 swap file 都是内存，不是 disk）, 涉及到 file 的，都可能操作 io，增加 block io 的概率。</p><p>还有更常见的是打开之前打开过的应用，没有第一次打开的快，需要加载或者卡一段时间 . 可能发生了 do_page_fault，这条路径经常见到 block io 在 wait_on_page_bit_killable()，如果是 swapout 内存，就要 swapin 了。如果是普通文件，就要 read out in pagecache&#x2F;disk.</p><p>do_page_fault —&gt; lock_page_or_retry -&gt; wait_on_page_bit_killable 里面会判断 page 是否置位 PG_locked, 如果置位就一直阻塞, 直到 PG_locked 被清除  , 而 PG_locked 标志位是在回写开始时和 I&#x2F;O 读完成时才会被清除，而 readahead 到 pagecache 功能也对 block io 产生影响，太大了增加阻塞概率。</p><h1 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h1><p>下面这个 Trace 是低内存情况下 ， 抓取的一个 App 的冷启动 ， 我们只取应用启动到第一帧显示的部分 ，总耗时为2s 。<br>可以看到其 Running 的总时间是 682 ms ，</p><h2 id="低内存的启动情况"><a href="#低内存的启动情况" class="headerlink" title="低内存的启动情况"></a>低内存的启动情况</h2><p>低内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 共花费了 2s . 从下面的 Thread 信息那里可以看到 </p><ol><li>Uninterruptible Sleep | WakeKill - Block I&#x2F;O 和 Uninterruptible Sleep 这两栏总共花费 750 ms 左右(对比下面正常情况才 130 ms)</li><li>Running 的时间在 600 ms (对比下面正常情况才 624 ms , 相差不大)</li></ol><p><img src="/images/15688227815756.jpg"></p><p>从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDeamon 跑的比较多之外 , 其他的内存和 io 相关的进程也非常多 , 比如若干个 kworker 和 kswapd0.</p><p><img src="/images/15688227887581.jpg"></p><h2 id="正常内存情况下"><a href="#正常内存情况下" class="headerlink" title="正常内存情况下"></a>正常内存情况下</h2><p>正常内存情况下 , 这个 App 从 bindApplication 到第一帧显示 , 只需要 1.22s . 从下面的 Thread 信息那里可以看到 </p><ol><li>Uninterruptible Sleep | WakeKill - Block I&#x2F;O 和 Uninterruptible Sleep 这两栏总共才 130 ms.</li><li>Running 的时间是 624 ms<br><img src="/images/15688228638217.jpg"></li></ol><p>从这段时间内的 CPU 使用情况来看 , 除了 HeapTaskDeamon 跑的比较多之外 , 其他的内存和 io 相关的进程非常少.<br><img src="/images/15688228721814.jpg"></p><h1 id="可能的优化方案-来自实际的经验和大佬分享的经验"><a href="#可能的优化方案-来自实际的经验和大佬分享的经验" class="headerlink" title="可能的优化方案 (来自实际的经验和大佬分享的经验)"></a>可能的优化方案 (来自实际的经验和大佬分享的经验)</h1><p>下面列举的只是一些经验之谈 , 具体问题还是得具体分析 , 在 Android 平台上 , 对三方应用的管控是非常重要的 , 很多小白用户 , 一大堆常驻通知和后台服务 , 导致这些 App 的优先级非常高 , 很难被杀掉 . 导致整机的内存长时间比较低 . 所以做系统的必要的优化之后 , 就要着重考虑对三方应用的查杀和管控逻辑 , 尽量减少后台进程的个数 , 在必要的时候 , 清理掉无用的进程来释放内存个前台应用使用.</p><ol><li>提高 extra_free_kbytes 值</li><li>提高 disk I&#x2F;O 读写速率，如用 UFS3.0，用固态硬盘</li><li>避免设置太大的 read_ahead_kb 值</li><li>使用 cgroup 的 blkio 来限制后台进程的 io 读操作，缩短前台 io 响应时间</li><li>提前做内存回收的操作，避免在用户使用应用时碰到而感受到稍微卡顿</li><li>增加 LMK 效率，避免无效的 kill</li><li>kswapd 周期性回收更多的 high 水位</li><li>调整 swappiness 来平衡 pagecache 和 swap </li><li>策略 : 针对低内存机器做特殊的策略 , 比如杀进程更加激进 (这会带来用户体验的降低 , 所以这个度需要兼顾性能和用户体验)</li><li>策略 : 在内存不足的时候提醒用户(或者不提醒用户) , 杀掉不必要的后台进程 .</li><li>策略 : 在内存严重不足且无法恢复的情况下 , 可以提示用户重启手机.</li></ol><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://blog.csdn.net/qkhhyga2016/article/details/79540119">https://blog.csdn.net/qkhhyga2016/article/details/79540119</a></li><li><a href="https://blog.csdn.net/zsj100213/article/details/82427527">https://blog.csdn.net/zsj100213/article/details/82427527</a></li></ol><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/84687400">知乎 - Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/&quot;&gt;Android 中的卡顿丢帧原因概述 - 系统篇&lt;/a&gt; 这篇文章中 , 实际案例这里我们有列举一些由于系统低内存导致的卡顿 , 由于 Android 低内存对整机性能影响比较大 , 所以单独写一篇文章 , 来概述系统低内存对整机性能的影响 .&lt;/p&gt;
&lt;p&gt;随着 Android 系统版本的更迭 , 以及 App 的代码膨胀 , Android 系统对内存的需求越来越大 , 但是目前市面上还存在着大量的 4G 内存以下的机器 , 这部分用户就很容易遇到整机低内存的情况 , 尤其是在系统大版本更新和 App 越装越多的情况下 . &lt;/p&gt;
&lt;p&gt;Android 低内存会导致性能问题 , 具体表现就是响应慢和卡顿 . 比如启动一个应用要花比平时更长的时间 ; 滑动列表会掉更多帧 ; 后台的进程减少导致冷启动变多 ; 手机很容易发热发烫等 , 下面我会概述发生这些性能问题的原因 . Debug 的方法 , 以及可能的优化措施 . &lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Android 桌面被杀问题分析案例</title>
    <link href="https://androidperformance.com/2019/09/17/Android-Kill-Background-App-Debug/"/>
    <id>https://androidperformance.com/2019/09/17/Android-Kill-Background-App-Debug/</id>
    <published>2019-09-17T10:58:11.000Z</published>
    <updated>2026-02-07T05:17:47.830Z</updated>
    
    <content type="html"><![CDATA[<p>写这篇文章的契机是因为一个实际遇到的问题 , 这个问题其实不难 , 不过在分析了这个问题然后写日记的时候 , 我突然觉得这个问题分析的过程有必要记录一下 , 分享给大家 . 分析过程中有用到一些工具 , 一些方法 , 也从另外一个聪明的小伙伴梅明那里学到了一些分析技巧和工具的使用技巧 .</p><p>这篇文章中分析过程包括我之前在<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a> 里面提到的一些工具 , 包括 : 复现视频 \ Event Log \ Android Studio 源码和 App Debug  \ Android Studio Profile \ Systrace \ Dumpsys \ PS 等 . 大多数工具大家都在开发过程中使用过 , 这次分析正是使用了这些工具相互配合 , 最终找到的问题的原因.</p><p>大家看下来可能会觉得 , 这么简单一个问题还需要写一篇文章 ? 我写这篇文章的目的一是为了记录给自己 , 二是觉得分析过程比较有普遍性 , 包括分析思路和工具的使用 , 如果可以帮助到大家 , 那么最好不过了 , 如果你也有好的思路或者独家调试技巧 , 欢迎大家扫描<a href="https://www.androidperformance.com/about/">关于我</a> 里面的讨论群二维码加入群聊 , 共同进步!</p><span id="more"></span><h1 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h1><p>这个问题是测试直接报过来的 , Bug 描述是典型的按现象描述 : “<strong>从应用返回桌面 , 桌面图标加载慢</strong>“. 测试这边提供了录制的视频和抓取的 Log , 以及对应的 Systrace 等. 既然现象和 Log 都在 , 那么就开始分析吧.</p><h1 id="分析过程"><a href="#分析过程" class="headerlink" title="分析过程"></a>分析过程</h1><h2 id="确定问题发生的时间点"><a href="#确定问题发生的时间点" class="headerlink" title="确定问题发生的时间点"></a>确定问题发生的时间点</h2><ol><li>由于测试提供的复现视频 , 首先看复现视频 , 确定时间发生的时间</li><li>根据视频里面的大概时间(精确到分) , 查看对应的 EventLog ，跟视频比对，确定发生的确切时间点 (精确到秒)</li><li>查看 EventLog 和 MainLog ， 还原发生时候的用户操作 ，这个例子里面就发现启动和我信这个 App 之后，Launcher 被杀了</li></ol><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">EventLog</span></span><br><span class="line"><span class="comment">// 启动 com.jx.cmcc.ict.ibelieve 这个 App</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.877</span>  <span class="number">1456</span>  <span class="number">2269</span> <span class="selector-tag">I</span> <span class="selector-tag">am_set_resumed_activity</span>: <span class="selector-attr">[0,com.jx.cmcc.ict.ibelieve/.ui.MainTabActivity,resumeTopActivityInnerLocked]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.886</span>  <span class="number">1456</span>  <span class="number">2269</span> <span class="selector-tag">I</span> <span class="selector-tag">am_resume_activity</span>: <span class="selector-attr">[0,80317506,54938,com.jx.cmcc.ict.ibelieve/.ui.MainTabActivity]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.891</span>  <span class="number">1456</span>  <span class="number">1485</span> <span class="selector-tag">I</span> <span class="selector-tag">sysui_count</span>: <span class="selector-attr">[window_time_0,0]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.891</span>  <span class="number">1456</span>  <span class="number">1485</span> <span class="selector-tag">I</span> <span class="selector-tag">sysui_multi_action</span>: <span class="selector-attr">[757,803,799,window_time_0,802,0]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.902</span>  <span class="number">1456</span>  <span class="number">2269</span> <span class="selector-tag">I</span> <span class="selector-tag">am_uid_stopped</span>: <span class="number">10021</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里桌面被杀</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">48.903</span>  <span class="number">1456</span>  <span class="number">2269</span> <span class="selector-tag">I</span> <span class="selector-tag">am_kill</span> : <span class="selector-attr">[0,13509,com.meizu.flyme.launcher,600,kill background]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这里开始从 App 返回桌面</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">51.990</span>  <span class="number">1456</span>  <span class="number">1791</span> <span class="selector-tag">I</span> <span class="selector-tag">am_pause_activity</span>: <span class="selector-attr">[0,80317506,com.jx.cmcc.ict.ibelieve/.ui.MainTabActivity]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">51.994</span>  <span class="number">1456</span>  <span class="number">1791</span> <span class="selector-tag">I</span> <span class="selector-tag">am_task_to_front</span>: <span class="selector-attr">[0,54923]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">51.996</span> <span class="number">13674</span> <span class="number">13674</span> <span class="selector-tag">I</span> <span class="selector-tag">am_on_paused_called</span>: <span class="selector-attr">[0,com.jx.cmcc.ict.ibelieve.ui.MainTabActivity,handlePauseActivity]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.013</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_uid_running</span>: <span class="number">10021</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 重新创建桌面进程</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.025</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_proc_start</span>: <span class="selector-attr">[0,14013,10021,com.meizu.flyme.launcher,activity,com.meizu.flyme.launcher/.Launcher]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.045</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_proc_bound</span>: <span class="selector-attr">[0,14013,com.meizu.flyme.launcher]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.069</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_uid_active</span>: <span class="number">10021</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.069</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_restart_activity</span>: <span class="selector-attr">[0,238217861,54923,com.meizu.flyme.launcher/.Launcher]</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 桌面显示</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.071</span>  <span class="number">1456</span>  <span class="number">2270</span> <span class="selector-tag">I</span> <span class="selector-tag">am_set_resumed_activity</span>: <span class="selector-attr">[0,com.meizu.flyme.launcher/.Launcher,minimalResumeActivityLocked]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.335</span> <span class="number">14013</span> <span class="number">14013</span> <span class="selector-tag">I</span> <span class="selector-tag">am_on_resume_called</span>: <span class="selector-attr">[0,com.meizu.flyme.launcher.Launcher,LAUNCH_ACTIVITY]</span></span><br><span class="line"><span class="number">09</span><span class="selector-tag">-10</span> <span class="number">10</span>:<span class="number">14</span>:<span class="number">52.437</span>  <span class="number">1456</span>  <span class="number">1504</span> <span class="selector-tag">I</span> <span class="selector-tag">am_activity_launch_time</span>: <span class="selector-attr">[0,238217861,com.meizu.flyme.launcher/.Launcher,413,413]</span></span><br></pre></td></tr></table></figure><p>那么这里就可以简单还原问题了 , 测试报的是<strong>从应用返回桌面 , 桌面图标加载慢</strong> , 从 Event Log 来看 , 桌面显示慢 , 是因为<strong>桌面被杀了</strong> , 所以从 App 返回的时候 , 桌面需要重新加载 , 从桌面进程创建到桌面完全显示 , 花费了 413ms(实际到桌面完全显示,花费了至少 2s 左右,因为 Launcher 冷启动还要重新加载内容).</p><h2 id="分析被杀原因"><a href="#分析被杀原因" class="headerlink" title="分析被杀原因"></a>分析被杀原因</h2><p>从上面的分析来看 , 我们需要找到 Launcher 被杀的原因 , 从现象上来看 , 似乎是和 com.jx.cmcc.ict.ibelieve 这个进程有关系 , 但是我们目前是没有办法确认的 .</p><p>这里我们重点看这个这个 Event Log</p><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">am_kill : [<span class="number">0</span>,<span class="number">13509</span>,com.meizu.flyme.launcher,<span class="number">600</span>,<span class="built_in">kill</span> <span class="built_in">background</span>]</span><br></pre></td></tr></table></figure><p>这里可以看到 Launcher 被杀的原因是 kill background , 查看对应的源码可知，reason &#x3D;  kill background 是 AMS.killBackgroundProcesses 这里发出的.</p><p>ActivityManagerService.killBackgroundProcesses</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">killBackgroundProcesses</span><span class="params">(<span class="keyword">final</span> String packageName, <span class="type">int</span> userId)</span> &#123;</span><br><span class="line">......</span><br><span class="line">            <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">                killPackageProcessesLocked(packageName, appId, targetUserId,</span><br><span class="line">                        ProcessList.SERVICE_ADJ, <span class="literal">false</span>, <span class="literal">true</span>, <span class="literal">true</span>, <span class="literal">false</span>, <span class="string">&quot;kill background&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对源码比较熟悉的同学可以很快知道 , AMS.killBackgroundProcesses 这个接口会提供给三方应用去调用 , 其 Binder 的客户端在 ActivityManager.killBackgroundProcesses 这里</p><p>ActivityManager.killBackgroundProcesses</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Have the system immediately kill all background processes associated</span></span><br><span class="line"><span class="comment"> * with the given package.  This is the same as the kernel killing those</span></span><br><span class="line"><span class="comment"> * processes to reclaim memory; the system will take care of restarting</span></span><br><span class="line"><span class="comment"> * these processes in the future as needed.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> packageName The name of the package whose processes are to</span></span><br><span class="line"><span class="comment"> * be killed.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@RequiresPermission</span>(<span class="title class_">Manifest</span>.<span class="property">permission</span>.<span class="property">KILL_BACKGROUND_PROCESSES</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">killBackgroundProcesses</span>(<span class="params"><span class="built_in">String</span> packageName</span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="title function_">getService</span>().<span class="title function_">killBackgroundProcesses</span>(packageName,</span><br><span class="line">                mContext.<span class="title function_">getUserId</span>());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (<span class="title class_">RemoteException</span> e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e.<span class="title function_">rethrowFromSystemServer</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="对-SystemServer-进程进行断点-Debug"><a href="#对-SystemServer-进程进行断点-Debug" class="headerlink" title="对 SystemServer 进程进行断点 Debug"></a>对 SystemServer 进程进行断点 Debug</h2><p>知道了上面的代码逻辑 , 我们需要做的就是找到在这个场景下 , 是哪个应用调用 ActivityManager.killBackgroundProcesses 杀掉了桌面. 由于不知道具体是哪个应用(这里虽然我们怀疑是 com.jx.cmcc.ict.ibelieve , 但是没有证据) , 我们先对 SystemServer 进程进行 Debug .</p><p>1.首先对源码进行 debug , 首先在 Android 中点击 debug 按钮 , 选择 system_process 这个进程(就是我们所说的 SystemServer) , 然后点击 OK . 代码的断点我们打在上面列出的 ActivityManagerService.killBackgroundProcesses 方法里面.<br><img src="/images/15687205494479.jpg"><br>2.点击启动怀疑的 App ( 可以从 EventLog 和视频里面倒推，找到比较可疑的 App , 安装后进行本地测试复现 , 这里选择了视频中出现的几个应用,包括我们之前怀疑的 com.jx.cmcc.ict.ibelieve- <strong>和我信</strong> ) , 点击其他的应用都不会进入到这个断点, 而在点击 <strong>和我信</strong> 这个 App 启动后走到的断点<br><img src="/images/15687209172754.jpg"></p><p>3.这里我们可以看到调用栈是一个 Binder 调用 , 我们需要找到这个 Binder 调用的客户端. 在 AS 里面继续操作 , 点击如下图的计算器按钮 , 输入 getRealCallingPid() 点击下面的 Evaluate , 就可以看到结果. result &#x3D; 29771<br><img src="/images/15687209855094.jpg"></p><p>4.通过 PS 命令 , 查看这个 pid 对应的 app<br><img src="/images/15687210593143.jpg"></p><p>可以看到就是这个应用调用的 killBackgroundProcesses</p><h2 id="对-App-进程进行断点-Debug"><a href="#对-App-进程进行断点-Debug" class="headerlink" title="对 App 进程进行断点 Debug"></a>对 App 进程进行断点 Debug</h2><p>为了进一步调查，我们对这个 app 进行 debug ， 由于没有源码，我们直接把断点打到 android&#x2F;app&#x2F;ActivityManager.killBackgroundProcesses 这里(因为这里是客户端代码 , 所以调试 App 进程的时候 , 可以直接打断点 )</p><p><img src="/images/15687211973509.jpg"></p><p>本地安装这个应用进行调试， 发现登录后，再次启动， 桌面必会被杀 ，确定就是这个 App 的问题<br><img src="/images/15687212469647.jpg"></p><p>到了这一步我们已经基本上确定问题就是这个 App 引起的了 , 不过如果我们想看比较详细的调用情况 , 可以使用 Android Studio Profile</p><h2 id="使用-Android-Studio-Profiler-工具"><a href="#使用-Android-Studio-Profiler-工具" class="headerlink" title="使用 Android Studio Profiler 工具"></a>使用 Android Studio Profiler 工具</h2><p>打开 Android Studio , 点击 Profiler 按钮 , 点击 + 号 , 选择 com.jx.cmcc.ict.ibelieve 这个进程 , 然后点击 CPU 这一栏<br><img src="/images/15687213642261.jpg"></p><p>这里选择了 Trace Java Methods , 然后点击旁边的 Record , 就可以开始进行操作 , 操作结束后 , 点击 Stop , AS 会自动开始解析.<br><img src="/images/15687214604910.jpg"></p><p>解析结果我们可以看这里<br><img src="/images/15687215343839.jpg"></p><p>最下面就是刚刚操作所对应的详细函数调用栈 , 以真正运行的顺序展示在我们面前(我经常会用这个工具来查看源码逻辑和三方应用的代码逻辑 , 不管是学习还是解决问题 , 都是一个非常好的方法)</p><p>我们使用 ctrl+f 进行搜索 killBackgroundProcesses , 如果有的话 , 会以高亮显示, 我们只需要用鼠标放大就可以看到详细的调用栈<br><img src="/images/15687217316253.jpg"></p><p>可以看到这个 App 在 loadComplete 回调里面执行了 killBackground 方法.(到了这里，应用开发者就已经知道是哪里的问题了，修复起来飞快)</p><h2 id="权限问题分析"><a href="#权限问题分析" class="headerlink" title="权限问题分析"></a>权限问题分析</h2><p>如上面所示 , 调用 killBackgroundProcesses 是需要Manifest.permission.KILL_BACKGROUND_PROCESSES 这个权限的 .  </p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequiresPermission</span>(<span class="title class_">Manifest</span>.<span class="property">permission</span>.<span class="property">KILL_BACKGROUND_PROCESSES</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">killBackgroundProcesses</span>(<span class="params"><span class="built_in">String</span> packageName</span>) &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行 adb shell dumpsys package com.jx.cmcc.ict.ibelieve 查看 com.jx.cmcc.ict.ibelieve 这个进程所申请的权限 , 发现这个应用在安装的时候就申请了KILL_BACKGROUND_PROCESSES 这个权限 , 且默认是授予的.</p><figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">install permissions:</span><br><span class="line">  <span class="params">...</span><span class="params">...</span></span><br><span class="line">  android.permission.ACCESS_NETWORK_STATE: granted=<span class="literal">true</span></span><br><span class="line">  android.permission.KILL_BACKGROUND_PROCESSES: granted=<span class="literal">true</span></span><br><span class="line">  android.permission.WRITE_USER_DICTIONARY: granted=<span class="literal">true</span></span><br><span class="line">  <span class="params">...</span><span class="params">...</span></span><br></pre></td></tr></table></figure><p>对应的权限级别为 normal<br><img src="/images/15687220008286.jpg"><br>也就是说 , 所有的第三方应用都可以默认有这个权限 , 只要你申请 . 这个案例里面 , 就是因为这个 App 申请了这个权限 , 且执行了错误的行为 , 导致把桌面杀掉 , 严重影响用户体验. Sad !</p><h2 id="Systrace-工具可以找出来-Kill-桌面的元凶么"><a href="#Systrace-工具可以找出来-Kill-桌面的元凶么" class="headerlink" title="Systrace 工具可以找出来 Kill 桌面的元凶么?"></a>Systrace 工具可以找出来 Kill 桌面的元凶么?</h2><p>由于经常使用 Systrace , 那么 Systrace 是否可以找到元凶呢? 答案是可以 (这里如果对如何在 Systrace 上查看唤醒信息不了解 , 可以看一下这篇文章<a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">分析 Systrace 预备知识</a>). 我们录制一段 Systrace , 安装下面的顺序去看</p><p>1.首先看 system_server 进程对应的 trace ,找到 killProcessGroup 对应的点 , 查看其唤醒情况 , 可以看到是 19688 这个线程唤醒执行 AMS 的 killProcessGroup<br><img src="/images/15687222368417.jpg"></p><p>在 Systrace 中搜索 19688 , 可以看到是 Binder:1295_1E , 1295 就是 SystemServer<br><img src="/images/15687224005673.jpg"></p><p>查看对应的 Binder:1295_1E , 看看是哪个线程唤醒这个线程<br><img src="/images/15687224679939.jpg"></p><p>搜索 7289这个线程 ， 可以看到这个线程就是和我信这个 App 的主线程。<br><img src="/images/15687225307555.jpg"></p><p>查看 7289 , 确定就是 com.jx.cmcc.ict.ibelieve 这个进程 . 也就是<strong>和我信</strong>这个 App(瘤子).<br><img src="/images/15687225541312.jpg"></p><p>这里也可以反推出来这个 Kill 是 <strong>和我信</strong> 这个 App 发起的 ， 进一步确认可以使用上面 AS 的 MethodTrace </p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>从上面的分析来看 , 这个问题是由于应用申请了不恰当的权限并错误使用对应的函数导致的一个严重影响用户使用的问题. 一般分析到这一步 , 我们的工作就基本上结束了 , 后续只需要和商店沟通 , 跟 App 开发者联系进行修改即可.</p><p>不过令我感到意外的是 android.permission.KILL_BACKGROUND_PROCESSES 这个权限 Google 居然放的这么松 , 我一直以为这个权限是要专门申请以防止 App 滥用或者卵用的(毕竟涉及到其他 App 的生死存亡).</p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/82904693">知乎 - Android 框架问题分析案例 - 谁杀了桌面?</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;写这篇文章的契机是因为一个实际遇到的问题 , 这个问题其实不难 , 不过在分析了这个问题然后写日记的时候 , 我突然觉得这个问题分析的过程有必要记录一下 , 分享给大家 . 分析过程中有用到一些工具 , 一些方法 , 也从另外一个聪明的小伙伴梅明那里学到了一些分析技巧和工具的使用技巧 .&lt;/p&gt;
&lt;p&gt;这篇文章中分析过程包括我之前在&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/&quot;&gt;Android 中的卡顿丢帧原因概述 - 方法论&lt;/a&gt; 里面提到的一些工具 , 包括 : 复现视频 &#92; Event Log &#92; Android Studio 源码和 App Debug  &#92; Android Studio Profile &#92; Systrace &#92; Dumpsys &#92; PS 等 . 大多数工具大家都在开发过程中使用过 , 这次分析正是使用了这些工具相互配合 , 最终找到的问题的原因.&lt;/p&gt;
&lt;p&gt;大家看下来可能会觉得 , 这么简单一个问题还需要写一篇文章 ? 我写这篇文章的目的一是为了记录给自己 , 二是觉得分析过程比较有普遍性 , 包括分析思路和工具的使用 , 如果可以帮助到大家 , 那么最好不过了 , 如果你也有好的思路或者独家调试技巧 , 欢迎大家扫描&lt;a href=&quot;https://www.androidperformance.com/about/&quot;&gt;关于我&lt;/a&gt; 里面的讨论群二维码加入群聊 , 共同进步!&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的卡顿丢帧原因概述 - 应用篇</title>
    <link href="https://androidperformance.com/2019/09/05/Android-Jank-Due-To-App/"/>
    <id>https://androidperformance.com/2019/09/05/Android-Jank-Due-To-App/</id>
    <published>2019-09-05T12:35:42.000Z</published>
    <updated>2026-02-07T05:17:47.826Z</updated>
    
    <content type="html"><![CDATA[<p>在<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a> 这篇文章中我们列举了系统自身原因导致的手机卡顿问题 , 这一篇文章我们主要列举一些由于 App 自身原因导致的卡顿问题. 各位用户在使用 App 的时候 , 如果遇见卡顿现象 , 先别第一时间骂手机厂商优化烂 , 先想想是不是这个 App 自己的问题.</p><p>Android 手机使用中的卡顿问题 , 一般来说手机厂商和 App 开发商都会非常重视 , 所以不管是手机厂商还是 App 开发者 , 都会对卡顿问题非常重视 , 内部一般也会有专门的基础组或者优化组来进行优化 . 目前市面上有一些非常棒的第三方性能监控工具 , 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 , 由于可以修改源码和避免权限问题 , 所以手机厂商可以拿到更多的数据 , 分析起来也会更方便一些.</p><p>说回流畅度 , 其实就是操作过程中的丢帧 , 本来一秒中画面需要更新 60 帧,但是如果这期间只更新了 55 帧 , 那么在用户看来就是丢帧了 , 主观感觉就是卡了 , 尤其是帧率波动 , 用户的感知会更明显. 引起丢帧的原因非常多, 有硬件层面的 , 有软件层面的 , 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 , 会简单讲一下哪些原因会用户觉得卡顿丢帧 :</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a><br><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">3. Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><span id="more"></span><h1 id="Android-App-自身导致的性能问题"><a href="#Android-App-自身导致的性能问题" class="headerlink" title="Android App 自身导致的性能问题"></a>Android App 自身导致的性能问题</h1><p>在<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a> 这篇文章中我们列举了系统自身原因导致的手机卡顿问题 , 这一篇文章我们主要列举一些由于 App 自身原因导致的卡顿问题. 各位用户在使用 App 的时候 , 如果遇见卡顿现象 , 先别第一时间骂手机厂商优化烂 , 先想想是不是这个 App 自己的问题.</p><p>这些实际的案例 , 很多都可以在 Systrace 上看出来 , 所以我的很多贴图都是 Systrace 上实际被发现的问题 , 如果你对 Systrace 不了解 , 可以查看这个 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 系列</a> , 这里你只需要知道 , Systrace 从系统全局的角度 , 来展示当前系统的运行状况 , 通常被用来 Debug Android 性能问题 .</p><h2 id="1-App-主线程执行时间长"><a href="#1-App-主线程执行时间长" class="headerlink" title="1.App 主线程执行时间长"></a>1.App 主线程执行时间长</h2><p>主线程执行 Input \ Animation \ Measure \ Layout \ Draw \ decodeBitmap 等操作超时都会导致卡顿 , 下面就是一些真实的案例 </p><h3 id="Measure-Layout-耗时-超时-或者没有调度到"><a href="#Measure-Layout-耗时-超时-或者没有调度到" class="headerlink" title="Measure \ Layout 耗时\超时 (或者没有调度到)"></a>Measure \ Layout 耗时\超时 (或者没有调度到)</h3><p><img src="/images/15683672071047.jpg"></p><h3 id="Draw-耗时"><a href="#Draw-耗时" class="headerlink" title="Draw 耗时"></a>Draw 耗时</h3><p><img src="/images/15683674644517.jpg"></p><p><img src="/images/15683674720923.jpg"></p><h3 id="Animation回调耗时"><a href="#Animation回调耗时" class="headerlink" title="Animation回调耗时"></a>Animation回调耗时</h3><p><img src="/images/15683674907408.jpg"></p><h3 id="View-初始化耗时-PlayStore"><a href="#View-初始化耗时-PlayStore" class="headerlink" title="View 初始化耗时 (PlayStore)"></a>View 初始化耗时 (PlayStore)</h3><p><img src="/images/15683675067851.jpg"></p><h3 id="List-Item-初始化耗时（WeChat）"><a href="#List-Item-初始化耗时（WeChat）" class="headerlink" title="List Item 初始化耗时（WeChat）"></a>List Item 初始化耗时（WeChat）</h3><p><img src="/images/15683675156787.jpg"></p><h3 id="decodeBitmap-耗时-或者没有调度到"><a href="#decodeBitmap-耗时-或者没有调度到" class="headerlink" title="decodeBitmap 耗时 (或者没有调度到)"></a>decodeBitmap 耗时 (或者没有调度到)</h3><p><img src="/images/15683675235793.jpg"></p><h2 id="2-uploadBitmap-耗时"><a href="#2-uploadBitmap-耗时" class="headerlink" title="2.uploadBitmap 耗时"></a>2.uploadBitmap 耗时</h2><p>这里的 uploadBitmap 主要是 upload bitmap to gpu 的操作 , 如果 bitmap 过大 , 或者每一帧内容都在变化 , 那么就需要频繁 upload , 导致渲染线程耗时.</p><h2 id="3-BuildDrawingCache-耗时"><a href="#3-BuildDrawingCache-耗时" class="headerlink" title="3.BuildDrawingCache 耗时"></a>3.BuildDrawingCache 耗时</h2><p>应用本身频繁调用 buildDrawingCache 会导致主线程执行耗时从而导致卡顿 , 从下图来看, 主线程每一帧明显超过了 Vsync 周期<br><img src="/images/15683676076418.jpg"></p><p>微信对话框有多个动态表情的时候, 也会出现这种情况导致的卡顿<br><img src="/images/15683676141497.jpg"></p><h2 id="4-使用-CPU-渲染而不是-GPU-渲染"><a href="#4-使用-CPU-渲染而不是-GPU-渲染" class="headerlink" title="4.使用 CPU 渲染而不是 GPU 渲染"></a>4.使用 CPU 渲染而不是 GPU 渲染</h2><p>如果应用在 Activity 中设置了软件渲染, 那么就不会走 hwui , 直接走 skia, 纯 cpu 进程渲染, 由于这么做会加重 UI Thread 的负载, 所以大部分情况下这种写法都会导致卡顿 , 详细技术分析可以看这篇文章 <a href="https://www.androidperformance.com/2019/07/27/Android-Hardware-Layer/">Android 中的 Hardware Layer 详解</a><br><img src="/images/15683676219380.jpg"></p><h2 id="5-主线程-Binder-耗时"><a href="#5-主线程-Binder-耗时" class="headerlink" title="5.主线程 Binder 耗时"></a>5.主线程 Binder 耗时</h2><p>Activity resume 的时候, 与 AMS 通信要持有 AMS 锁, 这时候如果碰到后台比较繁忙的时候, 等锁操作就会比较耗时, 导致部分场景因为这个卡顿, 比如多任务手势操作<br><img src="/images/15683676787549.jpg"></p><h2 id="6-游戏-SurfaceView-内容绘制不均匀"><a href="#6-游戏-SurfaceView-内容绘制不均匀" class="headerlink" title="6.游戏 SurfaceView 内容绘制不均匀"></a>6.游戏 SurfaceView 内容绘制不均匀</h2><p>这一项指的是游戏自身的绘制问题, 会导致总是不能满帧去跑, 如下图, 红框部分是SurfaceFlinger 显示掉帧, 原因是底下的游戏在绘制的时候, 刚好这一帧超过了 Vsync SF 的信号.这种一般是游戏自身的问题.<br><img src="/images/15683676909566.jpg"></p><h2 id="7-WebView-性能不足"><a href="#7-WebView-性能不足" class="headerlink" title="7.WebView 性能不足"></a>7.WebView 性能不足</h2><p>应用里面涉及到 WebView 的时候, 如果页面比较复杂, WebView 的性能就会比较差, 从而造成卡顿<br><img src="/images/15683677032582.jpg"></p><h2 id="8-帧率与刷新率不匹配"><a href="#8-帧率与刷新率不匹配" class="headerlink" title="8.帧率与刷新率不匹配"></a>8.帧率与刷新率不匹配</h2><p>如果屏幕帧率和系统的 fps 不相符 , 那么有可能会导致画面不是那么顺畅. 比如使用 90 Hz 的屏幕搭配 60 fps 的动画</p><p><img src="/images/15683677178211.jpg"></p><h2 id="9-应用性能跟不上高帧率屏幕和系统"><a href="#9-应用性能跟不上高帧率屏幕和系统" class="headerlink" title="9.应用性能跟不上高帧率屏幕和系统"></a>9.应用性能跟不上高帧率屏幕和系统</h2><p>部分应用由于设计比较复杂, 每一帧绘制的耗时都比较长 , 这么做的话在 60 fps 的机器上可能没有问题 , 但是在 90 fps 的机器上就会很卡, 因为从 60 -&gt; 90 , 每帧留给应用的绘制时间从 16.6 ms 变成了 11.1 ms , 如果没有在 11.1 ms 内完成, 就会出现掉帧的情况.</p><p>如下图, 这个 App 的性能比较差, 每一帧耗时都很长<br><img src="/images/15683677971989.jpg"></p><h2 id="10-主线程-IO-操作"><a href="#10-主线程-IO-操作" class="headerlink" title="10.主线程 IO 操作"></a>10.主线程 IO 操作</h2><p>主线程操作数据库<br>使用 SharedPerforence 的 Commit 而不是 Apply</p><h2 id="11-WebView-与主线程交互"><a href="#11-WebView-与主线程交互" class="headerlink" title="11.WebView 与主线程交互"></a>11.WebView 与主线程交互</h2><p>与 WebView 进行交互的时候, 如果 WebView 出现问题, 那么也会出现卡顿<br><img src="/images/15683678262079.jpg"></p><p><img src="/images/15683678319540.jpg"></p><p>微信文章页卡顿<br><img src="/images/15683678397112.jpg"><br><img src="/images/15683678453850.jpg"></p><h2 id="12-RenderThread-耗时"><a href="#12-RenderThread-耗时" class="headerlink" title="12.RenderThread 耗时"></a>12.RenderThread 耗时</h2><p>RenderThread 自身比较耗时, 导致一帧的时长超过 Vsync 间隔.<br><img src="/images/15683678598642.jpg"></p><p>渲染线程耗时过长阻塞了主线程的下一次 sync<br><img src="/images/15683678667847.jpg"></p><p><img src="/images/15683678744405.jpg"></p><h2 id="13-多个-RenderThread-同步导致主线程卡顿"><a href="#13-多个-RenderThread-同步导致主线程卡顿" class="headerlink" title="13.多个 RenderThread 同步导致主线程卡顿"></a>13.多个 RenderThread 同步导致主线程卡顿</h2><p>有的 App 会产生多个 RenderThread ，在某些场景下 RenderThread 在 sync 的时候花费比较多的时间，导致主线程卡顿</p><p><img src="/images/15683678855008.jpg"></p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">adb</span> shell ps -AT | grep <span class="number">10300</span> | grep RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">16228</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> SyS_epoll_wait      <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17394</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17395</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17396</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17397</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17399</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17400</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17401</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br><span class="line"><span class="attribute">u0_a170</span>      <span class="number">10300</span> <span class="number">17402</span>  <span class="number">6709</span> <span class="number">2693260</span> <span class="number">305172</span> futex_wait_queue_me <span class="number">0</span> S RenderThread</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>Android 原生系统是一个不断进化的过程 , 目前已经进化到了 Android Q , 每个版本都会解决非常多的性能问题 , 同时也会引进一些问题 ; 到了手机厂商这里 , 由于硬件差异和软件定制 , 会在系统中加入大量的自己的代码 , 这无疑也会影响系统的性能 . 同样由于 Android 的开放 , App 的质量和行为也影响着整机的用户体验.</p><p>本篇主要列出了 App 自身的实现问题导致的流畅性问题 , Android App 最大的问题就是质量良莠不齐 , 不同于 App Store 这样的强力管理市场 , Android App 不仅可以在 Google Play 上面进行安装 , 也可以在其他的软件市场上面安装 , 甚至可以下载安装包自行安装 , 可以说上架的门槛非常低 , 那么质量就只能由 App 开发者自己来把握了.</p><p>许多大厂的 App 质量自然不必多说 , 他们对性能和用户体验都是非常关注的 , 但也会有需求和功能过多导致的性能问题 , 比如微信就非常占内存 ; 新版本的 QQ 要比之前版本的使用起来流畅性差好多 . 中小厂的 App 就更不用说了. 再加上 Android 平台的开放性 , 需要 App 玩起来黑科技 , 什么保活 \ 相互唤醒 \ 热更新 \ 跑后台任务等 . 站在 App 开发者的角度来说这无可厚非 , 但是系统开发者则希望系统能在用户使用的时候 , 前后台 App 都能有正常的行为 , 来保证前台 App 的用户体验 . 也希望 App 开发者能重视自己 App 的性能体验 , 给用户一个好印象.</p><p>系统这边发现 App 自身的性能问题 , 且在其他厂商的手机上也是一样的表现的时候 , 通常会与 App 开发者进行联系 , 沟通一起解决 . </p><p>大家可以看看这个问题 : <a href="https://www.zhihu.com/question/335226118/answer/751587534">当手机厂商说安卓手机性能优化的时候，他们到底在做什么</a></p><p>这也是流畅性的一个系列文章中的一篇 , 可以点击下面的链接查看本系列的其他文章.</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a></p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/82520887">知乎 - Android 中的卡顿丢帧原因概述 - 应用篇</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/&quot;&gt;Android 中的卡顿丢帧原因概述 - 系统篇&lt;/a&gt; 这篇文章中我们列举了系统自身原因导致的手机卡顿问题 , 这一篇文章我们主要列举一些由于 App 自身原因导致的卡顿问题. 各位用户在使用 App 的时候 , 如果遇见卡顿现象 , 先别第一时间骂手机厂商优化烂 , 先想想是不是这个 App 自己的问题.&lt;/p&gt;
&lt;p&gt;Android 手机使用中的卡顿问题 , 一般来说手机厂商和 App 开发商都会非常重视 , 所以不管是手机厂商还是 App 开发者 , 都会对卡顿问题非常重视 , 内部一般也会有专门的基础组或者优化组来进行优化 . 目前市面上有一些非常棒的第三方性能监控工具 , 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 , 由于可以修改源码和避免权限问题 , 所以手机厂商可以拿到更多的数据 , 分析起来也会更方便一些.&lt;/p&gt;
&lt;p&gt;说回流畅度 , 其实就是操作过程中的丢帧 , 本来一秒中画面需要更新 60 帧,但是如果这期间只更新了 55 帧 , 那么在用户看来就是丢帧了 , 主观感觉就是卡了 , 尤其是帧率波动 , 用户的感知会更明显. 引起丢帧的原因非常多, 有硬件层面的 , 有软件层面的 , 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 , 会简单讲一下哪些原因会用户觉得卡顿丢帧 :&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/&quot;&gt;0. Android 中的卡顿丢帧原因概述 - 方法论&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/&quot;&gt;1. Android 中的卡顿丢帧原因概述 - 系统篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/&quot;&gt;2. Android 中的卡顿丢帧原因概述 - 应用篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/&quot;&gt;3. Android 中的卡顿丢帧原因概述 - 低内存篇&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的卡顿丢帧原因概述 - 系统篇</title>
    <link href="https://androidperformance.com/2019/09/05/Android-Jank-Due-To-System/"/>
    <id>https://androidperformance.com/2019/09/05/Android-Jank-Due-To-System/</id>
    <published>2019-09-05T12:35:35.000Z</published>
    <updated>2026-02-07T05:17:47.829Z</updated>
    
    <content type="html"><![CDATA[<p>在<a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a>这篇文章中我们列举了应用自身原因导致的手机卡顿问题 ， 这一篇文章我们主要列举一些由 Android 平台自身原因导致的卡顿问题. 各大国内 Android 厂商的产品由于硬件性能有高有低 ， 功能实现各有差异 ， 团队技术能力各有千秋 ， 所以其系统的质量也有高有低 ， 这里我们就来列举一下 ， 由于系统的硬件和软件原因导致的性能问题.</p><p>Android 手机使用中的卡顿问题 ， 一般来说手机厂商和 App 开发商都会非常重视 ， 所以不管是手机厂商还是 App 开发者 ， 都会对卡顿问题非常重视 ， 内部一般也会有专门的基础组或者优化组来进行优化 . 目前市面上有一些非常棒的第三方性能监控工具 ， 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 ， 由于可以修改源码和避免权限问题 ， 所以手机厂商可以拿到更多的数据 ， 分析起来也会更方便一些.</p><p>说回流畅度 ， 其实就是操作过程中的丢帧 ， 本来一秒中画面需要更新 60 帧，但是如果这期间只更新了 55 帧 ， 那么在用户看来就是丢帧了 ， 主观感觉就是卡了 ， 尤其是帧率波动 ， 用户的感知会更明显. 引起丢帧的原因非常多， 有硬件层面的 ， 有软件层面的 ， 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 ， 会简单讲一下哪些原因会用户觉得卡顿丢帧 :</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a><br><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">3. Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><span id="more"></span><h1 id="Android-平台性能导致的性能案例"><a href="#Android-平台性能导致的性能案例" class="headerlink" title="Android 平台性能导致的性能案例"></a>Android 平台性能导致的性能案例</h1><p>下面我会列出来一些实际的卡顿案例 ， 这些导致卡顿的原因都是由于 Android 系统平台的一些问题导致的 ， 有些问题在开发阶段就会暴露出来 ， 这一类通常会在发给用户之前就解决掉 ; 有些问题是用户在长时间使用之后才会暴露出来 ， 这一类问题最多 ， 但是也比较难以解决 ; 还有一些问题 ， 只有非常特殊的场景或者特殊的硬件才会暴露出来 .</p><p>这些实际的案例 ， 很多都可以在 Systrace 上看出来 ， 所以我的很多贴图都是 Systrace 上实际被发现的问题 ， 如果你对 Systrace 不了解 ， 可以查看这个 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 系列</a> ， 这里你只需要知道 ， Systrace 从系统全局的角度 ， 来展示当前系统的运行状况 ， 通常被用来 Debug Android 性能问题 .</p><h2 id="1-SurfaceFlinger-主线程耗时"><a href="#1-SurfaceFlinger-主线程耗时" class="headerlink" title="1.SurfaceFlinger 主线程耗时"></a>1.SurfaceFlinger 主线程耗时</h2><p>SurfaceFlinger 负责 Surface 的合成 ， 一旦 SurfaceFlinger 主线程调用超时 ， 就会产生掉帧 .</p><p>SurfaceFlinger 主线程耗时会也会导致 hwc service 和 crtc 不能及时完成， 也会阻塞应用的 binder 调用， 如 dequeueBuffer \ queueBuffer 等.</p><p>下图中的 SurfaceFlinger 主线程在后半部分明显超时:<br><img src="/images/15683627237452.jpg"></p><p><img src="/images/15683628900498.jpg"></p><p>SurfaceFlinger 主线程处理不及时导致应用卡顿(第一帧卡顿，后续都为黄帧)<br><img src="/images/15683629041129.jpg"></p><p><img src="/images/15683629162707.jpg"></p><h2 id="2-屏下光感截图导致-SurfaceFlinger-渲染不及时"><a href="#2-屏下光感截图导致-SurfaceFlinger-渲染不及时" class="headerlink" title="2.屏下光感截图导致 SurfaceFlinger 渲染不及时"></a>2.屏下光感截图导致 SurfaceFlinger 渲染不及时</h2><p>有的 Android 机型使用了屏下光感 ， 屏下光感的实现方法也会影响 SurfaceFlinger 主线程的运行 . 屏下指纹需要频繁截图 ， 来区分光线和屏幕的变化 ， 进行对应的亮度变化， 但是其主线程截图的方法会导致 SurfaceFlinger 主线程被截图操作所耽误， 从而导致卡顿<br><img src="/images/15683630179644.jpg"></p><h2 id="3-WHC-Service-执行耗时"><a href="#3-WHC-Service-执行耗时" class="headerlink" title="3.WHC Service 执行耗时"></a>3.WHC Service 执行耗时</h2><p>hwc Service 耗时也会导致 SurfaceFlinger 下一帧不会做合成操作， 导致应用的 dequeueBuffer 和 setTransationState 方法被阻塞， 导致卡顿.<br>如下图， 可以看到 SurfaceFlinger 的掉帧情况， Binder 的阻塞情况 和 CRTC 的耗时情况<br><img src="/images/15683630365546.jpg"></p><p>hwc 耗时<br><img src="/images/15683630455701.jpg"></p><p>crtc 等待 hwc<br><img src="/images/15683630524199.jpg"></p><p><img src="/images/15683630594588.jpg"></p><h2 id="4-CRTC-执行耗时"><a href="#4-CRTC-执行耗时" class="headerlink" title="4.CRTC 执行耗时"></a>4.CRTC 执行耗时</h2><p>crtc 执行耗时的结果就是 SurfaceFlinger 下一帧不会做合成操作， 导致应用的 dequeueBuffer 和 setTransationState 方法被阻塞， 导致卡顿.<br>如下图， 可以看到 SurfaceFlinger 的掉帧情况， Binder 的阻塞情况 和 CRTC 的耗时情况<br><img src="/images/15683630801893.jpg"></p><h2 id="5-CPU-调度问题"><a href="#5-CPU-调度问题" class="headerlink" title="5.CPU 调度问题"></a>5.CPU 调度问题</h2><h3 id="重要任务跑小核性能不足导致卡顿"><a href="#重要任务跑小核性能不足导致卡顿" class="headerlink" title="重要任务跑小核性能不足导致卡顿"></a>重要任务跑小核性能不足导致卡顿</h3><p>如下图 ， RenderThread 跑到了小核， 导致这一帧执行时间过长，造成卡顿图片:<br><img src="/images/15683631259781.jpg"></p><p>如下图 ， cpu 频率对性能的影响图片:<br><img src="/images/15683631426131.jpg"></p><h3 id="优先级低未能及时获取-cpu-时间片导致卡顿"><a href="#优先级低未能及时获取-cpu-时间片导致卡顿" class="headerlink" title="优先级低未能及时获取 cpu 时间片导致卡顿"></a>优先级低未能及时获取 cpu 时间片导致卡顿</h3><p>在调度器看来的低优先级任务 ， 在用户这里未必是低优先级任务 ， 他可能正在和 App 的主线程交互 ， 或者正在和 system_server 进行交互 </p><h3 id="被-RT-进程抢占"><a href="#被-RT-进程抢占" class="headerlink" title="被 RT 进程抢占"></a>被 RT 进程抢占</h3><p>App 主线程或者渲染线程被 RT 进程抢占也会导致系统卡顿或者响应慢 ， Google 也意识到了这个问题 ， 也在尝试在应用启动的时候 ， 把 App 主线程和渲染线程的优先级也设置为 RT ， 不过这个属性一直没开 ， 因为会导致应用启动速度变慢.</p><h3 id="大小核调度导致"><a href="#大小核调度导致" class="headerlink" title="大小核调度导致"></a>大小核调度导致</h3><p>大小核调度的问题通常表现在该跑在大核的任务跑到了小核 ， 或者该在小核运行的任务却持续跑到大核 ，或者错误的被绑定在了某一个核心上 .</p><p>如下图， 这是一个 CTS 问题， CTS 主线程由于被绑定到了 cpu7 ， 由于 cpu7 在执行 RenderThread ， 所以主线程没有调度到， 导致 CTS 失败<br><img src="/images/15683634973760.jpg"></p><h2 id="6-触发-Thermal-导致限频"><a href="#6-触发-Thermal-导致限频" class="headerlink" title="6.触发 Thermal 导致限频"></a>6.触发 Thermal 导致限频</h2><p>触发 Thermal 发热限频也有可能导致卡顿 ， 这算是一种硬件级别的保护 ， 如果手机已经过热 ， 此时如果不进行干涉 ， 那么可能会导致用户手机太烫而无法持续使用手机. 一般这个时候都会对系统的资源进行一些限制 ， 比如降低 cpu\gpu 的最高频率之类的 ， 这么做的话 ， 势必也会对流畅性造成影响.</p><p>如果你手机非常热 ， 而且变卡了 ， 那么放下手机休息一会 ， 查杀一下后台 ， 或者重启一下手机 .</p><h2 id="7-后台活动进程太多导致系统繁忙"><a href="#7-后台活动进程太多导致系统繁忙" class="headerlink" title="7.后台活动进程太多导致系统繁忙"></a>7.后台活动进程太多导致系统繁忙</h2><p>后台进程活动太多，会导致系统非常繁忙， cpu \ io \ memory 等资源都会被占用， 这时候很容易出现卡顿问题 ， 这也是系统这边经常会碰到的问题 </p><h3 id="CPU-繁忙"><a href="#CPU-繁忙" class="headerlink" title="CPU 繁忙"></a>CPU 繁忙</h3><p>dumpsys cpuinfo 可以查看一段时间内 cpu 的使用情况<br><img src="/images/15683636957732.jpg"></p><h3 id="主线程调度不到-，-处于-Runnable-状态"><a href="#主线程调度不到-，-处于-Runnable-状态" class="headerlink" title="主线程调度不到 ， 处于 Runnable 状态"></a>主线程调度不到 ， 处于 Runnable 状态</h3><p>当线程为 Runnable 状态的时候 ， 调度器如果迟迟不能对齐进行调度 ， 那么就会产生长时间的 Runnable 线程状态 ， 导致错过 Vsync 而产生流畅性问题.</p><p><img src="/images/15683637577609.jpg"></p><h3 id="无关进程活跃耗时"><a href="#无关进程活跃耗时" class="headerlink" title="无关进程活跃耗时"></a>无关进程活跃耗时</h3><p>无关进程通常是人为定义的 ， 指的是与当前前台 App 运行无关的进程 ， 这些活跃进程势必会对 App 主线程的调度产生影响 ， 不管这些无关进程是系统的还是 App 自身的 ， 或者是其他三方 App 的.<br><img src="/images/15683638623825.jpg"></p><h3 id="cpu-被占用"><a href="#cpu-被占用" class="headerlink" title="cpu 被占用"></a>cpu 被占用</h3><p>原因同上 ， 当后台任务过多的时候 ， cpu 资源就会异常紧缺 ， 如下图就是在系统低内存的时候 ， HeapTask 和 kswapD 几乎占满了整个 cpu ， 在疯狂地向系统申请内存 .</p><p><img src="/images/15683639544904.jpg"></p><h3 id="System-锁"><a href="#System-锁" class="headerlink" title="System 锁"></a>System 锁</h3><p>system_server 的 AMS 锁和 WMS 锁 ， 在系统异常的情况下 ， 会变得非常严重 ， 如下图所示 ， 许多系统的关键任务都被阻塞 ， 等待锁的释放 ， 这时候如果有 App 发来的 Binder 请求带锁 ， 那么也会进入等待状态 ， 这时候 App 就会产生性能问题 ; 如果此时做 Window 动画 ， 那么 system_server  的这些锁也会导致窗口动画卡顿</p><p><img src="/images/15683640875442.jpg"></p><h2 id="8-Layer过多导致-SurfaceFlinger-Layer-Compute-耗时"><a href="#8-Layer过多导致-SurfaceFlinger-Layer-Compute-耗时" class="headerlink" title="8.Layer过多导致 SurfaceFlinger Layer Compute 耗时"></a>8.Layer过多导致 SurfaceFlinger Layer Compute 耗时</h2><p>Android P 修改了 Layer 的计算方法 ， 把这部分放到了 SurfaceFlinger 主线程去执行， 如果后台 Layer 过多， 就会导致 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 ， 导致 SurfaceFlinger 主线程执行时间过长.<br><img src="/images/15683642678680.jpg"></p><p>所以在使用 Android 系统的时候 ， 记得多用多任务清理后台任务.</p><h2 id="9-Input-报点不均匀"><a href="#9-Input-报点不均匀" class="headerlink" title="9.Input 报点不均匀"></a>9.Input 报点不均匀</h2><p>如果出现 Input 报点不均匀或者没有报点的情况， 那么主线程由于没有收到 Input 事件， 所以不去做绘制， 也会导致卡顿<br>如下图 ， 这是一个连续滑动的 Systrace 图 ， 最下面两行是 InputReader 和  InputDispatcher ， 可以看到在滑动的过程中， InputReader 和 InputDispatcher 没有读出来 Input 事件， 导致卡顿<br><img src="/images/15683643028408.jpg"></p><h2 id="10-LMK-频繁工作抢占-cpu"><a href="#10-LMK-频繁工作抢占-cpu" class="headerlink" title="10.LMK 频繁工作抢占 cpu"></a>10.LMK 频繁工作抢占 cpu</h2><p>LMK 工作时， 会占用 cpu 资源 ， 其表现主要有下面几点</p><ol><li>CPU 资源 : 由于 LMK 杀掉的进程通常都是一些 Cache 或者 Service ， 这些进程由于低内存被杀之后 ， 通常会很快就被其主进程拉起来， 然后又被 LMK 杀掉， 从而进入了一种循环. 由于起进程是一件很消耗 cpu 的操作， 所以如果后台一直有进程被杀和重启， 那么前台的进程很容易出现卡顿</li><li>Memory : 由于低内存的原因， 很容易触发各个进程的 GC ， 如下图的 CPU 状态可以看到， 用于内存回收的 HeapTaskDeamon 出现非常频繁</li><li>IO  : 低内存会导致磁盘 IO 变多， 如果频繁进行磁盘 IO ， 由于磁盘IO 很慢， 那么主线程会有很多进程处于等 IO 的状态， 也就是我们经常看到的 Uninterruptible Sleep</li></ol><p><img src="/images/15683643604442.jpg"></p><p><img src="/images/15683643666942.jpg"></p><h2 id="11-低内存导致-IO-耗时"><a href="#11-低内存导致-IO-耗时" class="headerlink" title="11.低内存导致 IO 耗时"></a>11.低内存导致 IO 耗时</h2><p>低内存情况下， 很容易出现主线程 IO 从而导致应用卡顿<br><img src="/images/15683643799246.jpg"></p><h3 id="主线程-IO-导致卡顿"><a href="#主线程-IO-导致卡顿" class="headerlink" title="主线程 IO 导致卡顿"></a>主线程 IO 导致卡顿</h3><p><img src="/images/15683643881605.jpg"></p><h3 id="主线程-IO-导致应用启动速度慢"><a href="#主线程-IO-导致应用启动速度慢" class="headerlink" title="主线程 IO 导致应用启动速度慢"></a>主线程 IO 导致应用启动速度慢</h3><p><img src="/images/15683643961578.jpg"></p><p><img src="/images/15683644028471.jpg"></p><h3 id="滑动列表时候-IO-导致卡顿"><a href="#滑动列表时候-IO-导致卡顿" class="headerlink" title="滑动列表时候 IO 导致卡顿"></a>滑动列表时候 IO 导致卡顿</h3><p><img src="/images/15683644093621.jpg"></p><h2 id="12-GPU-合成导致-SurfaceFlinger-耗时"><a href="#12-GPU-合成导致-SurfaceFlinger-耗时" class="headerlink" title="12.GPU 合成导致 SurfaceFlinger 耗时"></a>12.GPU 合成导致 SurfaceFlinger 耗时</h2><p>当 SurfaceFlinger 有 GPU 合成时， 其主线程的执行时间就会变长， 也会导致合成不及时而卡顿<br><img src="/images/15683644397329.jpg"></p><p><img src="/images/15683644447973.jpg"></p><h2 id="13-KSWAPD-跑大核"><a href="#13-KSWAPD-跑大核" class="headerlink" title="13.KSWAPD 跑大核"></a>13.KSWAPD 跑大核</h2><p>低内存时， kswapd 由于负载比较高 ， 其 cpu 占用比较高， 且经常会跑到大核上 ， 导致机器发热限频， 或者抢占主线程的 cpu 时间片<br><img src="/images/15683644582339.jpg"></p><h2 id="14-SurfaceFlinger-Vsync-不均匀"><a href="#14-SurfaceFlinger-Vsync-不均匀" class="headerlink" title="14.SurfaceFlinger Vsync 不均匀"></a>14.SurfaceFlinger Vsync 不均匀</h2><p>SurfaceFlinger 有时候会出现 Vsync 不均匀的情况， 不均匀指的是 Vsync 间隔会持续地变化， 一会大一会小， 就会导致用户看到的画面不均匀， 有卡顿感<br>如下图 ， 可以明显看到 SurfaceFlinger 的 VSYNC-sf 这一行间隔是不一样的. 这种问题一般是由于 SurfaceFlinger 这边的修改或者 HWC 的修改导致的 .<br><img src="/images/15683644891039.jpg"></p><h2 id="15-三方应用使用-Accessibility-服务导致系统卡顿"><a href="#15-三方应用使用-Accessibility-服务导致系统卡顿" class="headerlink" title="15.三方应用使用 Accessibility 服务导致系统卡顿"></a>15.三方应用使用 Accessibility 服务导致系统卡顿</h2><p>三方应用如果使用 Accessibility 服务监听了 Input 事件的话， InputDispatcher 的行为就会与预期的出现偏差， 导致 InputDispatcher 没有及时把事件传给主线程导致卡顿</p><p><img src="/images/15683644982138.jpg"></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>Android 原生系统是一个不断进化的过程 ， 目前已经进化到了 Android Q ， 每个版本都会解决非常多的性能问题 ， 同时也会引进一些问题 ; 到了手机厂商这里 ， 由于硬件差异和软件定制 ， 会在系统中加入大量的自己的代码 ， 这无疑也会影响系统的性能 . </p><p>上面列出的这些影响流畅性的案例 ， 只是 Android 系统开发中遇到的性能问题的冰山一角 ， 任何一个问题都会对用户的使用产生影响 ， 这也是为什么手机厂商越来越重视系统优化 . 手机厂商非常重视开发过程中和用户使用过程中遇到的性能问题 ， 并开发和提出各项优化措施 ， 从硬件到软件 ， 从用户行为优化到系统策略动态学习 . 这也是为什么现在的手机厂商的系统越做越好 ， 质量越来越高的一个原因 ， 那些不重视质量只重视设计和产品的手机厂商 ， 都渐渐地被消费者淘汰了.</p><p>大家可以看看这个问题 : <a href="https://www.zhihu.com/question/335226118/answer/751587534">当手机厂商说安卓手机性能优化的时候，他们到底在做什么</a></p><p>这也是流畅性的一个系列文章中的一篇 ， 可以点击下面的链接查看本系列的其他文章.</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a><br><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">3. Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/82521327">知乎 - Android 中的卡顿丢帧原因概述 - 系统篇</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/&quot;&gt;Android 中的卡顿丢帧原因概述 - 应用篇&lt;/a&gt;这篇文章中我们列举了应用自身原因导致的手机卡顿问题 ， 这一篇文章我们主要列举一些由 Android 平台自身原因导致的卡顿问题. 各大国内 Android 厂商的产品由于硬件性能有高有低 ， 功能实现各有差异 ， 团队技术能力各有千秋 ， 所以其系统的质量也有高有低 ， 这里我们就来列举一下 ， 由于系统的硬件和软件原因导致的性能问题.&lt;/p&gt;
&lt;p&gt;Android 手机使用中的卡顿问题 ， 一般来说手机厂商和 App 开发商都会非常重视 ， 所以不管是手机厂商还是 App 开发者 ， 都会对卡顿问题非常重视 ， 内部一般也会有专门的基础组或者优化组来进行优化 . 目前市面上有一些非常棒的第三方性能监控工具 ， 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 ， 由于可以修改源码和避免权限问题 ， 所以手机厂商可以拿到更多的数据 ， 分析起来也会更方便一些.&lt;/p&gt;
&lt;p&gt;说回流畅度 ， 其实就是操作过程中的丢帧 ， 本来一秒中画面需要更新 60 帧，但是如果这期间只更新了 55 帧 ， 那么在用户看来就是丢帧了 ， 主观感觉就是卡了 ， 尤其是帧率波动 ， 用户的感知会更明显. 引起丢帧的原因非常多， 有硬件层面的 ， 有软件层面的 ， 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 ， 会简单讲一下哪些原因会用户觉得卡顿丢帧 :&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/&quot;&gt;0. Android 中的卡顿丢帧原因概述 - 方法论&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/&quot;&gt;1. Android 中的卡顿丢帧原因概述 - 系统篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/&quot;&gt;2. Android 中的卡顿丢帧原因概述 - 应用篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/&quot;&gt;3. Android 中的卡顿丢帧原因概述 - 低内存篇&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的卡顿丢帧原因概述 - 方法论</title>
    <link href="https://androidperformance.com/2019/09/05/Android-Jank-Debug/"/>
    <id>https://androidperformance.com/2019/09/05/Android-Jank-Debug/</id>
    <published>2019-09-05T04:56:42.000Z</published>
    <updated>2026-02-07T05:17:47.825Z</updated>
    
    <content type="html"><![CDATA[<p>Android 手机使用中的卡顿问题 , 一般来说手机厂商和 App 开发商都会非常重视 , 所以不管是手机厂商还是 App 开发者 , 都会对卡顿问题非常重视 , 内部一般也会有专门的基础组或者优化组来进行优化 . </p><p>目前市面上有一些非常棒的第三方性能监控工具 , 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 , 由于可以修改源码和避免权限问题 , 所以手机厂商可以拿到更多的数据 , 分析起来也会更方便一些.</p><p>说回流畅度 , 其实就是操作过程中的丢帧 , 本来一秒中画面需要更新 60 帧,但是如果这期间只更新了 55 帧 , 那么在用户看来就是丢帧了 , 主观感觉就是卡了 , 尤其是帧率波动 , 用户的感知会更明显. 引起丢帧的原因非常多, 有硬件层面的 , 有软件层面的 , 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 , 会简单讲一下哪些原因会用户觉得卡顿丢帧 :</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a><br><a href="https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">3. Android 中的卡顿丢帧原因概述 - 低内存篇</a></p><span id="more"></span><h1 id="1-流畅度相关工作内容概述"><a href="#1-流畅度相关工作内容概述" class="headerlink" title="1. 流畅度相关工作内容概述"></a>1. 流畅度相关工作内容概述</h1><p>作为手机厂商优化组的一员 , 我有必要在开始之前简单描述一下我们工作的流程 . 系统开发的过程中, 有很多引起 Android 卡顿的原因,但是用户和测试感受最直观的是正在使用的应用掉帧和不流畅 . 由于测试和用户没有办法直接确定卡顿的原因, 所以一般会直接将 Bug 提到我们这边, 所以我们的角色更像是一个卡顿问题接口人, 负责分析引起卡顿的原因, 再把 Bug 分配给对应的模块负责人去解决 , 如框架 \ App \ 多媒体 \ Display \ BSP 等.</p><p>所以直接由我们来解决的问题并不是很多, 我们更多的时候是通过专门的分析工具 , 结合<strong>源码</strong>来定位和分析问题 , 最多使用的工具如下:</p><ol><li>Systrace\strace\ftrace : 从整个系统的层面来看问题的大致原因</li><li>MethodTrace : 可以从进程的角度 , 以详细调用栈的形式来显示</li><li>Android Studio 的 Profile 工具</li><li>MAT : 用来分析内存问题</li><li>Log : LogReport 抓取或者录制的 Log , 里面包含大量的信息 , 包括各种常规 Log (Main Log , System Log , Event Log , Kernel Log , Crash Log 等) , 也包含了厂商自己加的一些 Log ( Power Log , Performance Log 等) , 也包含事故发生时候的截图 \ 录制的视频等</li><li>复现视频</li><li>本地复现等</li></ol><p>确定卡顿的根本原因 , 这需要对 Android App 开发 \ Android Framework 知识 \ Display 知识 \ Linux Kernel 知识有一定的了解 , 知道基本的工作流程 , 并能熟练使用对应的工具 , 区分不同的场景 , 迅速找到问题的原因 , 然后和相关模块的负责人一起讨论优化.</p><p>对于一些系统全局性的方案则需要与对应的模块负责人一起分析和解决, 必要的时候我们也会开发一些 Feature 来解决问题 .</p><h1 id="2-性能问题分析的一些工具和套路"><a href="#2-性能问题分析的一些工具和套路" class="headerlink" title="2. 性能问题分析的一些工具和套路"></a>2. 性能问题分析的一些工具和套路</h1><p>应用卡顿问题的原因比较多, 在数据埋点还没有完善的情况下, 更多的依赖 Systrace 来从全局的角度来分析卡顿的具体原因:</p><ol><li>Systrace 分析<ol><li>首先确认卡顿的 App</li><li>通过 App 的主线程和 SurfaceFlinger 的主线程信息可以确定卡顿的现场</li><li>分析 Systrace , Systrace 的分析需要一定的知识储备 : 需要知道 Systrace 每一个模块展示的内容是如何与用户感受到的内容相对应的 ; 需要知道 Systrace 上各个模块的交互式如何展示的 ; 需要知道 Binder 调用信息 ; 需要会看 Kernel 信息 (后续会继续完善 <a href="https://www.androidperformance.com/2019/05/26/Android_Systrace_0/">Systrace 系列</a>)<ol><li>如果是 App 主线程耗时, 则分析 App 主线程的原因 ( 案例里有 App 的卡顿原因 )</li><li>如果是 System 的问题, 则需要分析 System_Server \ SurfaceFlinger \ HWC \ CRTC \ CPU 等 ( 详细参考下面系统卡顿原因)</li></ol></li></ol></li><li>TraceView + 源码分析<ol><li>使用 Systrace 确定原因后, 可以使用 TraceView 结合源码查看对应的代码逻辑 , Android Studio 的 Profile 工具可以以进程为单位 , 进行 Method 的 Profile , 可以打出非常详细的函数调用栈 , 并且可以与 Systrace 相对应</li><li>源码分析可以使用 Android Studio 进行断点调试 App 或者 Framework , 观察 Debug 信息是否与预期相符</li></ol></li><li>很多问题也需要借助 Log 工具抓上来的 Log 进行分析 , Log 分析 Log 里面一些比较重要的点 (一般从 Log 里面很难确定卡顿的原因, 但是可以结合 Systrace 做一定的辅助分析)<ol><li>截图 : 确定卡顿发生的时间点 \ 卡顿的界面  (如果没有尽量提供)</li><li>dumpsys meminfo 信息</li><li>dumpsys cpuinfo 信息</li><li>“Slow dispatch” 和 “Slow delivery” Log 信息</li><li>卡顿发生的一段时间内的 EventLog , 还原卡顿时候用户的操作</li></ol></li><li>本地尝试复现<ol><li>可以录高速录像, 观察细节,如果必现,可以让测试这边提供录像.</li><li>过滤 Log , 找到卡顿时候的异常 Log</li><li>多抓几份 Systrace , 有助于确定原因</li></ol></li><li>可以让测试提供 LogReport 中没有的一些信息, 来分析当时用户的手机的整体的状态.<ol><li>adb shell dumpsys activity oom</li><li>adb shell dumpsys meminfo</li><li>adb shell cat &#x2F;proc&#x2F;buddyinfo</li><li>adb shell dumpsys cpuinfo</li><li>adb shell dumpsys input</li><li>adb shell dumpsys window</li></ol></li></ol><h1 id="3-通过性能数据数据分析"><a href="#3-通过性能数据数据分析" class="headerlink" title="3. 通过性能数据数据分析"></a>3. 通过性能数据数据分析</h1><p>由于用户反馈的不确定性 , 和内部测试的不完备性 , 通过系统或者 App 的性能埋点数据来做分析 , 是改进系统的一个好的方法 . 一方面不用用户主动参与 , 一方面有大量的数据可以来做分析 , 看趋势 .</p><p>目前国内各大手机厂商和 App 厂商基本都有自己的 APM 平台 , 负责监控 App 或者系统的监控程度 , 来做对应的优化方案 , 比如腾讯的 Matrix 平台已经监控了下面这些内容 , 其他的 App 厂商可以直接接入</p><p><img src="/images/15676894655479.jpg" alt="-w1256"></p><p>手机厂商由于有代码权限 , 所以可以采集到更多的数据 , 比如 Kernel 相关的数据 : cpu 负载 \ io 负载 \ Memory 负载 \ FSync \ 异常监控 \ 温度监控 \ 存储大小监控 等 , 每一个大项又都有几十个小项 . 所以可以监控的数据会非常多 , 遇到问题也可以从多个技术指标去分析 . 这就需要在这方面经验非常丰富的团队 , 去定义这些监控指标 , 确定最终要收集那些信息 , 收集上来的数据如何去分析等.</p><p>至于后续的优化工作 , 就考验各个厂商的研发能力了 , 正如伟琳在这篇文章:<a href="https://mp.weixin.qq.com/s/RqywGvblWi29irkXO2oM1g">那些年，我们一起经历过的 Android 系统性能优化</a> 所说 , 目前能力比较强的手机厂商 , 都在底层各个模块 , 结合硬件做优化 , 因为归根结底都是资源的分配 ; 而一些研发实力不是很强的厂商 , 则重点还是围绕在根据场景分配资源.</p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4. 总结"></a>4. 总结</h1><p>这里简单概述了一下流畅性问题的一般分析思路和分析工具 , 而且由于我的方向主要在 Framework 和 App , 所以很多东西都是从上层的角度来说的 , 想必 Kernel 优化团队会有更好的角度和分析 .</p><p>各个厂商的优化大家可以看看这篇总结 , <a href="https://mp.weixin.qq.com/s/RqywGvblWi29irkXO2oM1g">那些年，我们一起经历过的 Android 系统性能优化</a> , 华米 OV 都有涉及 , 下面摘录了一段总结 , 大家可以看看</p><blockquote><p>展望一下，这里想把手机厂商分为三类:</p><ol><li>一类是苹果，自己研发芯片和核心元件，有自己的OS和生态；</li><li>二类是三星、华为，自己研发芯片和核心元件(当然华为和三星还是有所区别)，共享 Android OS 和生态，当然三星在本土化这一块做的是不如华为和其他 Top 厂商的；</li><li>三类是其他 Android 手机厂商，芯片和核心元件来自于不同供应商，共享 Android OS和生态；</li></ol><p>从技术层面看:</p><ol><li>苹果始终会是在性能的第一阵营，可以顺利推行从硬件到 OS 到 APP 级别的任何性能保障方案；</li><li>三星、华为属于第二阵营，可以实现芯片-OS层面的整合优化；</li><li>其他 Top Android 手机厂商差距不会太大，他们有多个不同的 SoC 供应商，方案有差异，非常芯片底层的地方，往往不会去涉及，更多是做纯软件层面的策略性的优化，有价值但是不容易形成壁垒，注意这个不容易形成壁垒指的是在 top 厂商中间，一些小的厂商往往还是心有余而力不足。不过还是很期待看到有更多的突破出现。</li></ol></blockquote><p>这也是流畅性的一个系列文章中的一篇 , 可以点击下面的链接查看本系列的其他文章.</p><p><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">0. Android 中的卡顿丢帧原因概述 - 方法论</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">1. Android 中的卡顿丢帧原因概述 - 系统篇</a><br><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">2. Android 中的卡顿丢帧原因概述 - 应用篇</a></p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/82521741">知乎 - Android 中的卡顿丢帧原因概述 - 方法论</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 手机使用中的卡顿问题 , 一般来说手机厂商和 App 开发商都会非常重视 , 所以不管是手机厂商还是 App 开发者 , 都会对卡顿问题非常重视 , 内部一般也会有专门的基础组或者优化组来进行优化 . &lt;/p&gt;
&lt;p&gt;目前市面上有一些非常棒的第三方性能监控工具 , 比如腾讯的 Matrix ; 手机厂商一般也会有自己的性能监控方案 , 由于可以修改源码和避免权限问题 , 所以手机厂商可以拿到更多的数据 , 分析起来也会更方便一些.&lt;/p&gt;
&lt;p&gt;说回流畅度 , 其实就是操作过程中的丢帧 , 本来一秒中画面需要更新 60 帧,但是如果这期间只更新了 55 帧 , 那么在用户看来就是丢帧了 , 主观感觉就是卡了 , 尤其是帧率波动 , 用户的感知会更明显. 引起丢帧的原因非常多, 有硬件层面的 , 有软件层面的 , 也有 App 自身的问题. 所以这一部分我分为四篇文章去讲 , 会简单讲一下哪些原因会用户觉得卡顿丢帧 :&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/&quot;&gt;0. Android 中的卡顿丢帧原因概述 - 方法论&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/&quot;&gt;1. Android 中的卡顿丢帧原因概述 - 系统篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/&quot;&gt;2. Android 中的卡顿丢帧原因概述 - 应用篇&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/&quot;&gt;3. Android 中的卡顿丢帧原因概述 - 低内存篇&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的 Activity Launch Mode 详解</title>
    <link href="https://androidperformance.com/2019/09/01/Android-Activity-Lunch-Mode/"/>
    <id>https://androidperformance.com/2019/09/01/Android-Activity-Lunch-Mode/</id>
    <published>2019-09-01T04:37:47.000Z</published>
    <updated>2026-02-07T05:17:47.822Z</updated>
    
    <content type="html"><![CDATA[<p>Android 中的 Activity 有几种比较重要的启动模式,Standard\SingleTop\SingleTask\SingleInstance , 每一种启动模式有不同的使用场景, 网上也有许多分析这个的文章, 这里我以 Demo 的模式, 从 Activity 栈的角度来展示不同启动模式下的 Activity 的行为.</p><p>Activity 栈是一个先进后出的数据结构, 各位可以关注在每一步操作之后, 栈内容那一栏 , 可以更好地帮助理解不同的启动模式.</p><p>Demo 比较简单, 我也放到了 Github 上 , <a href="https://github.com/Gracker/AndroidlunchModeTest">https://github.com/Gracker/AndroidLaunchModeTest</a> , 有兴趣的可以自己跑一下 , 看看结果 , 只需要修改 StandardActivity 里面的跳转 Activity 就可以了.</p><span id="more"></span><h1 id="Standard-标准模式"><a href="#Standard-标准模式" class="headerlink" title="Standard 标准模式"></a>Standard 标准模式</h1><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>launchMode=<span class="string">&quot;standard&quot;</span></span><br></pre></td></tr></table></figure><p>最基本的模式，每次启动都会创建一个新的 Activity</p><figure class="highlight plaintext"><figcaption><span>模式</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">// 1. 启动 Activity</span><br><span class="line">MainActivity</span><br><span class="line"></span><br><span class="line">//栈内容</span><br><span class="line">com.example.launchmodetest/.MainActivity</span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line">// 2. 启动 StandardActivity</span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line">//栈内容</span><br><span class="line">com.example.launchmodetest/.StandardActivity</span><br><span class="line">com.example.launchmodetest/.MainActivity</span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line">// 3. 启动 StandardActivity</span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  StandardActivity</span><br><span class="line"></span><br><span class="line">//栈内容</span><br><span class="line">com.example.launchmodetest/.StandardActivity</span><br><span class="line">com.example.launchmodetest/.StandardActivity</span><br><span class="line">com.example.launchmodetest/.MainActivity</span><br></pre></td></tr></table></figure><h1 id="SingleTop-栈顶复用模式"><a href="#SingleTop-栈顶复用模式" class="headerlink" title="SingleTop 栈顶复用模式"></a>SingleTop 栈顶复用模式</h1><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>launchMode=<span class="string">&quot;singleTop&quot;</span></span><br></pre></td></tr></table></figure><p>如果当前 Activity 已经在栈顶，那么其 onNewIntent 会被调用；否则会重新创建 Activity</p><h2 id="测试1-：-SingleTopActivity-不在栈顶"><a href="#测试1-：-SingleTopActivity-不在栈顶" class="headerlink" title="测试1 ： SingleTopActivity 不在栈顶"></a>测试1 ： SingleTopActivity 不在栈顶</h2><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动 MainActivity</span></span><br><span class="line">MainActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleTopActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTopActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTopActivity -&gt; StandardActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 5. 启动 SingleTopActivity：</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleTopActivity -&gt; StandardActivity -&gt; SingleTopActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//这里由于第三个 SingleTopActivity 不在栈顶，栈顶是 StandardActivity ，所以启动新的 SingleTopActivity 时会重新创建 SingleTopActivity</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="测试2-：-SingleTopActivity-在栈顶"><a href="#测试2-：-SingleTopActivity-在栈顶" class="headerlink" title="测试2 ： SingleTopActivity 在栈顶"></a>测试2 ： SingleTopActivity 在栈顶</h2><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动 MainActivity</span></span><br><span class="line">MainActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleTopActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTopActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 4. 启动 SingleTopActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTopActivity -&gt; SingleTopActivity</span><br><span class="line"><span class="comment">//栈内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTopActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//SingleTopActivity 收到 am_new_intent ，而不是创建新的 Activity</span></span><br></pre></td></tr></table></figure><h1 id="SingleTask-栈内复用模式"><a href="#SingleTask-栈内复用模式" class="headerlink" title="SingleTask 栈内复用模式"></a>SingleTask 栈内复用模式</h1><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>launchMode=<span class="string">&quot;singleTask&quot;</span></span><br></pre></td></tr></table></figure><ol><li>如果不加 Affinity ， 那么 SingleTask 标记的 Activity 创建还是在当前的 Task 中</li><li>SingleTask 标记的 Activity 是栈内复用模式，如果当前 Task 内没有这个 Activity，那么创建新的 Activity，如果当前 Task 内有这个 Activity，不管他在 Task 的哪个位置，都会直接复用这个 Activity (收到 onNewIntent)</li><li>如果栈内复用，那么会 Clear Task 中这个 Activity 上面的其他的 Activity</li></ol><h2 id="测试1：SingleTask（Without-Affinity）"><a href="#测试1：SingleTask（Without-Affinity）" class="headerlink" title="测试1：SingleTask（Without Affinity）"></a>测试1：SingleTask（Without Affinity）</h2><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动 MainActivity：</span></span><br><span class="line">MainActivity</span><br><span class="line"><span class="comment">//栈内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"><span class="comment">//栈内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleTaskActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTaskActivity</span><br><span class="line"><span class="comment">//栈内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 4. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleTaskActivity -&gt; StandardActivity</span><br><span class="line"><span class="comment">//栈内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 5. 启动 SingleTaskActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleTaskActivity -&gt; StandardActivity -&gt; SingleTaskActivity</span><br><span class="line"><span class="comment">//栈内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//备注：SingleTaskActivity 收到 am_new_intent ，将其上面的 StandardActivity Clear 调</span></span><br></pre></td></tr></table></figure><h2 id="测试2：SingleTask（WithAffinity）"><a href="#测试2：SingleTask（WithAffinity）" class="headerlink" title="测试2：SingleTask（WithAffinity）"></a>测试2：SingleTask（WithAffinity）</h2><p>在 Manifest 中设置了 android:taskAffinity&#x3D;”” 之后，启动 SingleTask 会启动一个新的 Task</p><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动 MainActivity</span></span><br><span class="line">MainActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleTaskWithAffinity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTaskWithAffinity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskWithAffinity</span></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 4. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTaskWithAffinity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskWithAffinity</span></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 5. 启动 SingleTaskWithAffinity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt;  SingleTaskWithAffinity -&gt; StandardActivity -&gt; SingleTaskWithAffinity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleTaskWithAffinity</span></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol><li>与 SingleTask 相比， SingleTaskWithAffinity 会创建新的 Stack</li><li>在 SingleTaskWithAffinity 启动 StandardActivity ， 这个 StandardActivity 与 SingleTaskWithAffinity 在同一个栈</li><li>在栈 0 里面再启动 SingleTaskWithAffinity ，不会创建新的 Task</li><li>多任务里面会出现 SingleTaskWithAffinity</li></ol><h1 id="SingleInstance-单实例模式"><a href="#SingleInstance-单实例模式" class="headerlink" title="SingleInstance 单实例模式"></a>SingleInstance 单实例模式</h1><figure class="highlight avrasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">android:</span>launchMode=<span class="string">&quot;singleInstance&quot;</span></span><br></pre></td></tr></table></figure><p>单示例模式顾名思义，启动时，无论从哪里启动都会给 A 创建一个唯一的任务栈，后续的创建都不会再创建新的 A，除非 A 被销毁了</p><h2 id="测试1：SingleInstance-（Without-Affinity）"><a href="#测试1：SingleInstance-（Without-Affinity）" class="headerlink" title="测试1：SingleInstance （Without Affinity）"></a>测试1：SingleInstance （Without Affinity）</h2><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line">taskAffinity=com<span class="selector-class">.example</span><span class="selector-class">.launchmodetest</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 启动 MainActivity</span></span><br><span class="line">MainActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleInstanceActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceActivity </span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容(多任务里面没有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceActivity</span></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 4. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容(多任务里面没有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceActivity</span></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 5. 启动 SingleInstanceActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity -&gt; SingleInstanceActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内： (多任务里面没有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceActivity</span></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 6. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内： (多任务里面没有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceActivity</span></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 7. 启动 SingleInstanceActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity -&gt; SingleInstanceActivity -&gt; StandardActivity -&gt; SingleInstanceActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容 (多任务里面没有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceActivity</span></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/.MainActivity</span><br></pre></td></tr></table></figure><h2 id="总结1"><a href="#总结1" class="headerlink" title="总结1"></a>总结1</h2><ol><li>SingleInstanceActivity 会创建新的 Task ，但是不会在多任务中出现</li><li>SingleInstanceActivity 是全局唯一的，如果复用，其 onNewIntent 会被调用</li><li>SingleInstanceActivity 启动新的 Activity，新的 Activity 不会在当前的 Task 里面，而是会回到上一个 Task 里面</li></ol><h2 id="测试2：-SingleInstance-（With-Affinity）"><a href="#测试2：-SingleInstance-（With-Affinity）" class="headerlink" title="测试2： SingleInstance （With Affinity）"></a>测试2： SingleInstance （With Affinity）</h2><p>在 Manifest 中设置了 android:taskAffinity&#x3D;”” 之后，启动 SingleInstanceActivity 会出现在多任务中 ，其余的表现与没有设置 Affinity 一致</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">taskAffinity</span><span class="operator">=</span>null</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 启动 MainActivity</span></span><br><span class="line">MainActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 2. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 3. 启动 SingleInstanceWithAffinityActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity </span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：(多任务里面有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceWithAffinityActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 4. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：(多任务里面有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceWithAffinityActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 5. 启动 SingleInstanceWithAffinityActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：(多任务里面有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceWithAffinityActivity</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 6. 启动 StandardActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：(多任务里面有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceWithAffinityActivity</span></span><br><span class="line"><span class="comment">//栈0内容</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.MainActivity</span></span><br><span class="line"></span><br><span class="line">-------------------------------------------------------------------</span><br><span class="line"><span class="comment">// 7. 启动 SingleInstanceWithAffinityActivity</span></span><br><span class="line">MainActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity -&gt; StandardActivity -&gt; SingleInstanceWithAffinityActivity</span><br><span class="line"></span><br><span class="line"><span class="comment">//栈1内容：(多任务里面有 Task)</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.SingleInstanceWithAffinityActivity</span></span><br><span class="line"><span class="comment">//栈0内容：</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/<span class="selector-class">.StandardActivity</span></span><br><span class="line">com<span class="selector-class">.example</span>.launchmodetest/.MainActivity</span><br></pre></td></tr></table></figure><h1 id="一些概念"><a href="#一些概念" class="headerlink" title="一些概念"></a>一些概念</h1><h2 id="TaskAffinity"><a href="#TaskAffinity" class="headerlink" title="TaskAffinity"></a>TaskAffinity</h2><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">taskAffinity</span><span class="operator">=</span>null</span><br></pre></td></tr></table></figure><p>与 Activity 有着亲和关系的任务。从概念上讲，具有相同亲和关系的 Activity 归属同一Task（从用户的角度来看，则是归属同一“ Application ”）。 Task 的亲和关系由其根 Activity 的亲和关系确定。</p><p>亲和关系确定两件事 - Activity 更改到的父项 Task（请参阅 allowTaskReparenting 属性）和通过 FLAG_ACTIVITY_NEW_TASK 标志启动 Activity 时将用来容纳它的 Task。<br>默认情况下，应用中的所有 Activity 都具有相同的亲和关系。您可以设置该属性来以不同方式组合它们，甚至可以将在不同应用中定义的 Activity 置于同一 Task 内。 要指定 Activity 与任何 Task 均无亲和关系，请将其设置为空字符串。</p><p>如果未设置该属性，则 Activity 继承为应用设置的亲和关系（请参阅 <application> 元素的 taskAffinity 属性）。 应用默认亲和关系的名称是 <manifest> 元素设置的软件包名称。</p><h2 id="ActivityRecord、TaskRecord、ActivityStack-之间的关系"><a href="#ActivityRecord、TaskRecord、ActivityStack-之间的关系" class="headerlink" title="ActivityRecord、TaskRecord、ActivityStack 之间的关系"></a>ActivityRecord、TaskRecord、ActivityStack 之间的关系</h2><ol><li>一个 ActivityRecord 对应一个 Activity 实例，保存了一个 Activity 的所有信息 ; 但是一个 Activity可能会有多个 ActivityRecord ,因为 Activity 可以被多次启动，这个主要取决于其启动模式。</li><li>一个 TaskRecord 由一个或者多个 ActivityRecord 组成，这就是我们常说的任务栈，具有后进先出的特点</li><li>ActivityStack 则是用来管理 TaskRecord 的，包含了多个 TaskRecord<br><img src="/images/15673131853333.jpg"><br><img src="/images/15673132050635.jpg"></li></ol><p>（From <a href="http://gityuan.com/2017/06/11/activity_record/%EF%BC%89">http://gityuan.com/2017/06/11/activity_record/）</a></p><ol><li>一般地，对于没有分屏功能以及虚拟屏的情况下，ActivityStackSupervisor 与ActivityDisplay 都是系统唯一；</li><li>ActivityDisplay 主要有 Home Stack 、 App Stack、Recents Stack 这三个栈；</li><li>每个 ActivityStack 中可以有若干个 TaskRecord 对象；</li><li>每个 TaskRecord 包含如果若干个 ActivityRecord 对象；</li><li>每个 ActivityRecord记 录一个 Activity 信息。</li></ol><p>下面是一个 dump 的例子，可以看到当前手机的 ActivityRecord、TaskRecord、ActivityStack<br>(adb shell dumpsys activity containers)<br><img src="/images/15673132395730.jpg"></p><h2 id="Activity-的几种类型"><a href="#Activity-的几种类型" class="headerlink" title="Activity 的几种类型"></a>Activity 的几种类型</h2><figure class="highlight arduino"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** Activity type is currently not defined. */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">static</span> <span class="keyword">final</span> <span class="type">int</span> ACTIVITY_TYPE_UNDEFINED = <span class="number">0</span>;</span><br><span class="line"><span class="comment">/** Standard activity type. Nothing special about the activity... */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">static</span> <span class="keyword">final</span> <span class="type">int</span> ACTIVITY_TYPE_STANDARD = <span class="number">1</span>;</span><br><span class="line"><span class="comment">/** Home/Launcher activity type. */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">static</span> <span class="keyword">final</span> <span class="type">int</span> ACTIVITY_TYPE_HOME = <span class="number">2</span>;</span><br><span class="line"><span class="comment">/** Recents/Overview activity type. There is only one activity with this type in the system. */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">static</span> <span class="keyword">final</span> <span class="type">int</span> ACTIVITY_TYPE_RECENTS = <span class="number">3</span>;</span><br><span class="line"><span class="comment">/** Assistant activity type. */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">static</span> <span class="keyword">final</span> <span class="type">int</span> ACTIVITY_TYPE_ASSISTANT = <span class="number">4</span>;</span><br></pre></td></tr></table></figure><blockquote><p>如果觉得文章有帮助, 欢迎分享到社交网站 , 希望能帮到大家.</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 中的 Activity 有几种比较重要的启动模式,Standard&#92;SingleTop&#92;SingleTask&#92;SingleInstance , 每一种启动模式有不同的使用场景, 网上也有许多分析这个的文章, 这里我以 Demo 的模式, 从 Activity 栈的角度来展示不同启动模式下的 Activity 的行为.&lt;/p&gt;
&lt;p&gt;Activity 栈是一个先进后出的数据结构, 各位可以关注在每一步操作之后, 栈内容那一栏 , 可以更好地帮助理解不同的启动模式.&lt;/p&gt;
&lt;p&gt;Demo 比较简单, 我也放到了 Github 上 , &lt;a href=&quot;https://github.com/Gracker/AndroidlunchModeTest&quot;&gt;https://github.com/Gracker/AndroidLaunchModeTest&lt;/a&gt; , 有兴趣的可以自己跑一下 , 看看结果 , 只需要修改 StandardActivity 里面的跳转 Activity 就可以了.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Tools" scheme="https://androidperformance.com/tags/Tools/"/>
    
  </entry>
  
  <entry>
    <title>Android 中的 Hardware Layer 详解</title>
    <link href="https://androidperformance.com/2019/07/27/Android-Hardware-Layer/"/>
    <id>https://androidperformance.com/2019/07/27/Android-Hardware-Layer/</id>
    <published>2019-07-27T06:09:13.000Z</published>
    <updated>2026-02-07T05:17:47.825Z</updated>
    
    <content type="html"><![CDATA[<h1 id="硬件加速与软件加速"><a href="#硬件加速与软件加速" class="headerlink" title="硬件加速与软件加速"></a>硬件加速与软件加速</h1><p>很多人会把 Android 中的硬件加速和 Hardware Layer 搞混，会以为启用了硬件加速，就是启用了 Hardware Layer. 所以在说 Hardware Layer 之前，我们先说一下硬件加速</p><p>关于硬件加速的比较详细的文章，推荐大家看这三篇</p><ol><li><a href="https://www.mtyun.com/library/hardware-accelerate">Android硬件加速原理与实现简介</a></li><li><a href="https://juejin.im/post/5a1f7b3e6fb9a0451b0451bb">理解Android硬件加速的小白文</a></li><li><a href="https://developer.android.google.cn/guide/topics/graphics/hardware-accel">官方文档：Hardware acceleration</a></li></ol><p>硬件加速，实际上应该叫 GPU 加速，软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU，如果是 GPU，就认为是硬件加速绘制，反之，则是软件绘制</p><p>目前的 Android 版本， 默认情况下都是开了硬件加速的，如果你的 App 没有特殊声明，那么硬件加速就是默认开启的</p><p>上面三篇文章都有介绍，代码级别和原理级别都讲的比较深，这里我从 Systrace 的角度来给大家展示一下硬件加速下 App 的绘制与软件加速的区别</p><h2 id="硬件加速-App-的表现"><a href="#硬件加速-App-的表现" class="headerlink" title="硬件加速 App 的表现"></a>硬件加速 App 的表现</h2><p>由于默认情况下就是硬件加速，所以我们以最常见的滑动桌面为例，看一下硬件加速情况下 App 在 Systrace 上的表现</p><p>硬件加速情况下，App 存在主线程和渲染线程，一帧的绘制是主线程和渲染线程一起配合执行的<br><img src="/images/15642078929850.jpg"></p><p>我们把 Systrace 放大，来看每一帧主线程和渲染线程是怎么工作的，GPU 是什么时候介入工作，实现”加速”的<br><img src="/images/15642079103161.jpg"></p><p>GPU 的真正介入是在 RenderThread 中的部分操作中</p><h2 id="软件加速-App-的表现"><a href="#软件加速-App-的表现" class="headerlink" title="软件加速 App 的表现"></a>软件加速 App 的表现</h2><p>对应的，软件加速我们也找一个 App 来进行演示：云闪付</p><p>首先放一张全景图，可以看到软件渲染下，只有主线程，没有渲染线程，所有的渲染工作，都在主线程完成，同时可以看到，软件渲染下，每一帧的执行时间都非常长，超过1个 Vsync 周期，所以滑动的时候会一卡一卡的，非常难受 <a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/%E8%BD%AF%E4%BB%B6%E6%B8%B2%E6%9F%93%E7%A4%BA%E4%BE%8B-%E4%BA%91%E9%97%AA%E4%BB%98.html">Systrace 下载</a><br><img src="/images/15642079446767.jpg"></p><p>我们把 Systrace 放大，来看每一帧主线程是怎么工作的<br><img src="/images/15642079799345.jpg"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过上面的对比以及推荐的三篇文章的阅读，你应该对硬件渲染和软件渲染的区别了然于胸，这里总结一下</p><ol><li>硬件渲染情况下，app 存在主线程和渲染线程；软件渲染情况下， app 只有主线程没有渲染线程</li><li>硬件渲染情况下，app 最终绘制是借助 GPU 来实现 ；软件渲染情况下， app 最终绘制是使用 CPU 来实现（调用 skia 库）</li><li>硬件渲染情况下，App 的性能是要优于软件渲染的</li><li>由于部分 api 硬件渲染不支持，所以只能是要软件渲染，做 App 开发的时候，应该尽量避免使用此类 Api(支持情况可以直接在 Android 官方文档里面查看 ：<a href="https://developer.android.google.cn/guide/topics/graphics/hardware-accel">https://developer.android.google.cn/guide/topics/graphics/hardware-accel</a>)</li></ol><h1 id="Software-Layer-VS-Hardware-Layer"><a href="#Software-Layer-VS-Hardware-Layer" class="headerlink" title="Software Layer VS Hardware Layer"></a>Software Layer VS Hardware Layer</h1><p>说完了硬件渲染，我们来说一下　Software Layer 和 Hardware Layer , 这两个概念主要是针对 View 的说的， 与此时 App 是硬件渲染还是软件渲染没有直接关系（但是有依赖关系，稍后会讲）.</p><p>一个 View 的 layerType 共有三种状态( 后面的英文是官方文档，先读英文我再讲解)：</p><ol><li>LAYER_TYPE_NONE ： Indicates that the view does not have a layer.</li><li>LAYER_TYPE_SOFTWARE ：Indicates that the view has a software layer. A software layer is backed by a  and causes the view to be rendered using Android’s software rendering pipeline, even if hardware acceleration is enabled</li><li>LAYER_TYPE_HARDWARE  ：Indicates that the view has a hardware layer. A hardware layer is backed by a hardware specific texture (generally Frame Buffer Objects or FBO on OpenGL hardware) and causes the view to be rendered using Android’s hardware rendering pipeline, but only if hardware acceleration is turned on for the view hierarchy. When hardware acceleration is turned off, hardware layers behave exactly as LAYER_TYPE_SOFTWARE</li></ol><h2 id="LAYER-TYPE-NONE"><a href="#LAYER-TYPE-NONE" class="headerlink" title="LAYER_TYPE_NONE"></a>LAYER_TYPE_NONE</h2><p>默认情况下，所有的 View 都是这个 layerType，这种情况下，这个 View 不会做任何的特殊处理，该怎么走怎么走</p><h2 id="LAYER-TYPE-SOFTWARE"><a href="#LAYER-TYPE-SOFTWARE" class="headerlink" title="LAYER_TYPE_SOFTWARE"></a>LAYER_TYPE_SOFTWARE</h2><p>Software layerType ,  标识这个 View 有一个软件实现的 Layer ，怎么个软件实现法呢，实际上就是把这个 View，根据一定的条件，变成一个  Bitmap 对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">android/view/View.java</span><br><span class="line"> <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> createBitmap(mResources.getDisplayMetrics(),</span><br><span class="line">        width, height, quality);</span><br></pre></td></tr></table></figure><h4 id="Software-layer-的作用如下"><a href="#Software-layer-的作用如下" class="headerlink" title="Software layer 的作用如下"></a>Software layer 的作用如下</h4><ol><li>When the application is not using hardware acceleration, a software layer is useful to apply a specific color filter and&#x2F;or blending mode and&#x2F;or  to a view and all its children.（当应用程序不使用硬件加速时，Software layer 可用于将特定的颜色过滤器、混合模式或半透明应用于 View 及其所有子 View）</li><li>When the application is using hardware acceleration, a software layer is useful to render drawing primitives not supported by the hardware accelerated pipeline. It can also be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when  a complex view tree with a translation, a software layer can be used to render the view tree only once.（当应用程序使用硬件加速时，软件层可用于呈现硬件加速管道不支持的绘图基元。 它还可用于将复杂视图树缓存到纹理中，并降低绘制操作的复杂性。 例如，在使用转换动画复杂视图树时，可以使用软件层仅渲染视图树一次）</li><li>Software layers should be avoided when the affected view tree updates often. Every update will require to re-render the software layer, which can potentially be slow (particularly when hardware acceleration is turned on since the layer will have to be uploaded into a hardware texture after every update（当受影响的视图树经常更新时，应避免使用软件层。 每次更新都需要重新渲染软件层，这可能会很慢（特别是在打开硬件加速时，因为每次更新后都必须将图层上传到硬件纹理中）</li></ol><h2 id="LAYER-TYPE-HARDWARE"><a href="#LAYER-TYPE-HARDWARE" class="headerlink" title="LAYER_TYPE_HARDWARE"></a>LAYER_TYPE_HARDWARE</h2><p>Hardware layerType ，标识这个 View 有一个硬件实现的 Layer ，通过第一小节我知道，这里的硬件指的是 GPU ，那么硬件实现的 Layer 顾名思义就是通过 GPU 来实现的，通常是OpenGL硬件上的帧缓冲对象或FBO（离屏渲染 Buffer）</p><p>注意：这里 Hardware layerType  是依赖硬件加速的，如果硬件加速开启，那么才会有 FBO 或者帧缓冲 ； 如果硬件加速关闭，那么就算你设置一个 View 的 LayerType 是 Hardware Layer ，也会按照 Software Layer 去做处理</p><h3 id="Hardware-layer-的作用："><a href="#Hardware-layer-的作用：" class="headerlink" title="Hardware layer 的作用："></a>Hardware layer 的作用：</h3><ol><li>A hardware layer is useful to apply a specific color filter and&#x2F;or blending mode and&#x2F;or  to a view and all its children.（硬件层可用于将特定颜色过滤器和&#x2F;或混合模式和&#x2F;或半透明应用于视图及其所有子视图</li><li>A hardware layer can be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when  a complex view tree with a translation, a hardware layer can be used to render the view tree only once.(hardware layer 可用于将复杂视图树缓存到纹理中，并降低绘制操作的复杂性。 例如，在使用转换动画复杂视图树时，可以使用硬件层仅渲染视图树一次，这个是最主要的一个点)</li><li>A hardware layer can also be used to increase the rendering quality when rotation transformations are applied on a view. It can also be used to prevent potential  issues when applying 3D transforms on a view (在视图上应用旋转变换时，还可以使用硬件层来提高渲染质量。 它还可用于在视图上应用3D变换时防止潜在的剪切问题)</li></ol><p>而设置 Hardware Layer 对 alpha\translation  \ scale \ rotation \  这几个属性动画性能有帮助(同样的, 设置 Software Layer 也有相同的功效，下面的小例子环节会有详细的讲解)，具体的使用如下</p><p>动画开始前，设置 LayerType 为 LAYER_TYPE_HARDWARE（代码为官方示例）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">view.setLayerType(View.LAYER_TYPE_HARDWARE, <span class="literal">null</span>);</span><br><span class="line">ObjectAnimator.ofFloat(view, <span class="string">&quot;rotationY&quot;</span>, <span class="number">180</span>).start();</span><br></pre></td></tr></table></figure><p>动画结束的时候，重新设置为LAYER_TYPE_NONE（代码为官方示例）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">view.setLayerType(View.LAYER_TYPE_HARDWARE, <span class="literal">null</span>);</span><br><span class="line">ObjectAnimator  = ObjectAnimator.ofFloat(view, <span class="string">&quot;rotationY&quot;</span>, <span class="number">180</span>);</span><br><span class="line">.addListener(<span class="keyword">new</span> <span class="title class_">AnimatorListenerAdapter</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">( animation)</span> &#123;</span><br><span class="line">        view.setLayerType(View.LAYER_TYPE_NONE, <span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">.start();</span><br></pre></td></tr></table></figure><p>由于 Hardware Layer 的特性，属性动画( alpha \ translation  \ scale \ rotation \  )过程中只更新 View 的 property，不会每一帧都去销毁和重建 FBO，其动画性能会有很大的提升。当然这里要注意属性动画的过程中( 比如 AnimationUpdate 回调中)，不要做除了上述属性更新之外的其他事情，比如添加删除子 View、修改 View 的显示内容等，这会使得 FBO 失效，性能反而变差</p><h2 id="总结-1"><a href="#总结-1" class="headerlink" title="总结"></a>总结</h2><ol><li>从上面对 Hardware Layer 和 Software Layer 的描述可以看到，<br>Software Layer 是对 Hardware Layer 的一个补充，如果 App 处于某种情况不能使用 Hardware Layer ，那么 Software Layer 就会派上用场 。 Hardware Layer 不支持的 API 的实现也得用   Software Layer 来实现</li><li>Software Layer 和 Hardware Layer 都可以对 View 进行操作，比如颜色过滤器、混合模式等</li><li>Software Layer 和 Hardware Layer 对 alpha \ translation  \ scale \ rotation \ pivot 这几个属性动画性能有帮助，这也是 Software Layer 和 Hardware Layer  使用最频繁的优化 (也就是我们常说的 ： 在做上述动画的时候，在动画开始前，将这个 View 的 LayerType 设置为 LAYER_TYPE_HARDWARE ，在动画结束后，将 layerType 重新设置为 LAYER_TYPE_NONE , 设置回来的原因是 Hardware Layer 使用的是 Video Memory，设置为 NONE 之后这部分使用的内存将会回收 )</li></ol><h1 id="不正确使用-LayerType-导致的性能问题案例"><a href="#不正确使用-LayerType-导致的性能问题案例" class="headerlink" title="不正确使用 LayerType 导致的性能问题案例"></a>不正确使用 LayerType 导致的性能问题案例</h1><h2 id="不正确使用-Software-layer-引起的性能问题"><a href="#不正确使用-Software-layer-引起的性能问题" class="headerlink" title="不正确使用 Software layer 引起的性能问题"></a>不正确使用 Software layer 引起的性能问题</h2><p>看 Trace 经常会有这样的情况出现 ， 我们知道 Software layer 的生成过程本质上是生成一个 Bitmap Cache  ，这个 Cache 的生成是很耗时的， 从下面的 Trace 也可以看出来，每一帧都比一个 Vsync 周期要长。</p><p>之所以下面的 Trace 每一帧都去调用了 buildDrawingCache&#x2F;SW ，是因为每一帧的过程中，这个 View 的内容进行了更新，导致 Cache 失效，所以每一帧都去触发销毁 Cache 和重建 Cache，导致界面滑动卡顿</p><p>下面这个 Trace 是微信朋友圈的大图滑动情况 <a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/%E4%B8%8D%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8SoftwareLayer%E7%A4%BA%E4%BE%8B-%E5%BE%AE%E4%BF%A1%E6%BB%91%E5%8A%A8%E5%8D%A1%E9%A1%BF.html">Trace 在 Github 上可以下载</a><br><img src="/images/15642082974929.jpg"></p><p>放大来看，每一帧都在做 buildDrawingCache 操作，说明每一帧的缓存都失效了，在进行销毁和重建，性能极差，滑动的时候顿挫感非常严重<br><img src="/images/15642083215860.jpg"></p><h3 id="代码流程"><a href="#代码流程" class="headerlink" title="代码流程"></a>代码流程</h3><p>简单看一下 LAYER_TYPE_HARDWARE 的代码流程，详细的流程可以看上面推荐的文章</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">buildLayer  -&gt; buildDrawingCache -&gt; buildDrawingCacheImpl</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">buildLayer</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mLayerType == LAYER_TYPE_NONE) <span class="keyword">return</span>;</span><br><span class="line">    ......</span><br><span class="line">    <span class="keyword">switch</span> (mLayerType) &#123;</span><br><span class="line">        <span class="keyword">case</span> LAYER_TYPE_HARDWARE:  <span class="comment">// 硬件渲染</span></span><br><span class="line">            updateDisplayListIfDirty();</span><br><span class="line">            <span class="keyword">if</span> (attachInfo.mThreadedRenderer != <span class="literal">null</span> &amp;&amp; mRenderNode.isValid()) &#123;</span><br><span class="line">                attachInfo.mThreadedRenderer.buildLayer(mRenderNode);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> LAYER_TYPE_SOFTWARE:</span><br><span class="line">            buildDrawingCache(<span class="literal">true</span>);  <span class="comment">// 软件渲染</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 buildDrawingCache 的实现， 可以看到对应的 Trace 就是在这里打印的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">buildDrawingCache</span><span class="params">(<span class="type">boolean</span> autoScale)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> ((mPrivateFlags &amp; PFLAG_DRAWING_CACHE_VALID) == <span class="number">0</span> || (autoScale ?</span><br><span class="line">            mDrawingCache == <span class="literal">null</span> : mUnscaledDrawingCache == <span class="literal">null</span>)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) &#123;</span><br><span class="line">            Trace.traceBegin(Trace.TRACE_TAG_VIEW,</span><br><span class="line">                    <span class="string">&quot;buildDrawingCache/SW Layer for &quot;</span> + getClass().getSimpleName());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            buildDrawingCacheImpl(autoScale);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            Trace.traceEnd(Trace.TRACE_TAG_VIEW);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="不正确使用-Hardware-layer-引起的性能问题"><a href="#不正确使用-Hardware-layer-引起的性能问题" class="headerlink" title="不正确使用 Hardware layer 引起的性能问题"></a>不正确使用 Hardware layer 引起的性能问题</h2><p>不正确使用 Hardware Layer 和不正确使用 Software Layer 会引起相同的性能问题，比如下面这个场景 (桌面打开文件夹)，由于开发的实现问题，多个文件夹小图标都被设置了 Hardware LayerType , 导致 RenderThread 非常耗时，又因为每一帧其中的内容都在变，导致每一帧的 Hardware Layer 都失效，被销毁后重建，所以就有了下面的 Systrace 所展示的情况</p><p><a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/%E4%B8%8D%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8HardwareLayer%E7%A4%BA%E4%BE%8B-%E6%A1%8C%E9%9D%A2%E6%96%87%E4%BB%B6%E5%A4%B9%E6%89%93%E5%BC%80.html">Trace 在 Github 上可以下载</a><br><img src="/images/15642083692800.jpg"></p><p>我们放大 RenderThread 的一帧来看<br><img src="/images/15642083808990.jpg"></p><h2 id="Debug-工具"><a href="#Debug-工具" class="headerlink" title="Debug 工具"></a>Debug 工具</h2><p>我们可以在 设置 - 辅助功能 - 开发者选项 - 显示硬件层更新（Show hardware layers updates） 这个工具来追踪硬件层更新导致的性能问题 。</p><p>当 View 渲染 Hardware Layer 的时候整个界面会闪烁绿色，正常情况下，它应该在动画开始的时候闪烁一次（也就是 Layer 渲染初始化的时候），后续的动画不应该再有绿色出现；如果你的 View 在整个动画期间保持绿色不变，这就是持续的缓存失效问题了<br>查看 Systrace 也可以发现相同的问题， 两个工具可以一起使用，早些发现动画的性能问题。</p><h2 id="总结-2"><a href="#总结-2" class="headerlink" title="总结"></a>总结</h2><ol><li>记住 LayerType 使用的场景：View 做 alpha \ translation  \ scale \ rotation \  这几个属性动画</li><li>做动画的时候，如果可以，尽量多使用 Hardware Layer ，使用完成后记得设置为 None，除非有硬件层不支持的 api，才去考虑使用 Software Layer</li><li>如果是使用 setAlpha(), AlphaAnimation, or ObjectAnimator 来设置 View 的透明度的话，默认就会走 off-screen buffer ， 所以如果你操作的 View 比较大的话，也可以把这个 View 的 Type 设置为 LAYER_TYPE_HARDWARE（官方建议）</li><li>在某些情况下，实际上 Hardware Layer 可能要做非常多的工作，而不仅仅是渲染视图。缓存一个层需要花费时间，因为这一步要划分为两个过程：首先，视图渲染入 GPU 上的一个层中，然后，GPU 再渲染那个层到窗口，如果 View 的渲染十分简单（比如一个纯色），那么在初始化的时候设置 Hardware Layer 可能增加不必要的开销</li><li>对所有缓存来讲，存在一个缓存失效的可能性。动画运行时，如果某个地方调用了View.invalidate( )，那么 Layer 就不得不重新渲染一遍。倘若不断地失效，你的Hardware Layer 实际上要比不添加任何 Layer 性能更差(下面的例子可以佐证)，因为Hardware Layer 在设置缓存的时候增加了开销。如果你不断的重缓存 Layer，会对性能造成极大地负担(做动画的 View 越复杂，带来的负担就越重)</li></ol><h1 id="LayerType-对动画性能的影响示例"><a href="#LayerType-对动画性能的影响示例" class="headerlink" title="LayerType 对动画性能的影响示例"></a>LayerType 对动画性能的影响示例</h1><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>为了说明上面所说的情况，我们用一个小例子来做示例，演示在各种情况下，其性能表现，代码非常简单(代码项目地址 ：<a href="https://github.com/Gracker/Android_HardwareLayer_Example">https://github.com/Gracker/Android_HardwareLayer_Example</a>), 项目 Systrace 文件夹中包含此文章中涉及的所有例子(这都是好东西，值得收藏)</p><ol><li>两个 TextView ，一个负责开始动画，一个负责做动画</li><li>动画类型有 TRANSLATION_X 、ALPHA、TRANSLATION_Y、SCALE_X、SCALE_Y</li><li>我们会控制 AnimatorListener 和 AnimatorUpdateListener ，使得动画的实现不一样<ol><li>onAnimationStart 和 onAnimationEnd 中主要是设置是否启用 LAYER_TYPE_HARDWARE 或者 LAYER_TYPE_SOFTWARE</li><li>onAnimationUpdate 主要演示如果在动画过程中改变了 View 的内容，会造成什么影响</li></ol></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">  <span class="comment">//设置动画</span></span><br><span class="line">  animatorSet = <span class="keyword">new</span> <span class="title class_">AnimatorSet</span>();</span><br><span class="line">  objectAnimator1 = ObjectAnimator.ofFloat(animationText, View.TRANSLATION_X,<span class="number">150</span>);</span><br><span class="line">  objectAnimator2 = ObjectAnimator.ofFloat(animationText, View.ALPHA,<span class="number">0</span>);</span><br><span class="line">  objectAnimator3 = ObjectAnimator.ofFloat(animationText, View.TRANSLATION_Y,<span class="number">150</span>);</span><br><span class="line">  objectAnimator4 = ObjectAnimator.ofFloat(animationText, View.SCALE_X,<span class="number">150</span>);</span><br><span class="line">  objectAnimator5 = ObjectAnimator.ofFloat(animationText, View.SCALE_Y,<span class="number">150</span>);</span><br><span class="line">animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3,objectAnimator4,objectAnimator5);</span><br><span class="line">  animatorSet.setDuration(<span class="number">500</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="comment">//添加动画监听器</span></span><br><span class="line">  objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">          animationText.setLayerType(View.LAYER_TYPE_HARDWARE,<span class="literal">null</span>);</span><br><span class="line">          <span class="comment">// animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,null);</span></span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">          animationText.setLayerType(View.LAYER_TYPE_NONE,<span class="literal">null</span>);</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">          <span class="comment">// text.setText(String.format(&quot;%s%d&quot;, text.getText().toString(), i));</span></span><br><span class="line">          <span class="comment">// i ++ ;</span></span><br><span class="line">      &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">//开始动画</span></span><br><span class="line">  startText.setOnClickListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnClickListener() &#123;</span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onClick</span><span class="params">(View view)</span> &#123;</span><br><span class="line">          animatorSet.start();</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure><h3 id="统计工具-GFXInfo"><a href="#统计工具-GFXInfo" class="headerlink" title="统计工具:GFXInfo"></a>统计工具:GFXInfo</h3><p>为了得到准确的数据，我们使用 gfxinfo 得到的数据来进行对比（ adb shell dumpsys gfxinfo）<br><img src="/images/15642085055284.jpg"></p><p>gfxInfo 记录的是每一帧的耗时，我们重点看下面几个指标</p><ol><li>Janky Frames ：超过 16 ms 的帧数 （超过 16 ms 不一定会卡顿，但是会增加卡顿情况出现的风险）</li><li>耗时帧统计：可以看到大部分帧的区间，以及最大耗时</li></ol><h2 id="案例一：Normal-Layer-不动态更新-View-内容"><a href="#案例一：Normal-Layer-不动态更新-View-内容" class="headerlink" title="案例一：Normal Layer  + 不动态更新 View 内容"></a>案例一：Normal Layer  + 不动态更新 View 内容</h2><h3 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">AnimatorListener 和 AnimatorUpdateListener 都不重写, 如下，函数内的都注释掉</span><br><span class="line">objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="Systrace-现象"><a href="#Systrace-现象" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h3><p>可以看到有部分黄帧， 渲染线程中 flush commands 方法执行比较久<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/Normal_Layer.html">Systrace 下载</a><br><img src="/images/15642085469278.jpg"></p><h3 id="gfxInfo-数据"><a href="#gfxInfo-数据" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 46%，99th percentile: 32ms ，说明性能比较差，同时 Number High input latency &#x3D; 30 说明主线程的负载是比较高的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">30</span></span><br><span class="line">Janky frames: <span class="number">14</span> (<span class="number">46.67</span>%)</span><br><span class="line">50th percentile: 16ms</span><br><span class="line">90th percentile: 29ms</span><br><span class="line">95th percentile: 32ms</span><br><span class="line">99th percentile: 32ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">30</span></span><br><span class="line">Number Slow UI thread: <span class="number">0</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">0</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">0</span> 6ms=<span class="number">0</span> 7ms=<span class="number">0</span> 8ms=<span class="number">0</span> 9ms=<span class="number">0</span> 10ms=<span class="number">2</span> 11ms=<span class="number">2</span> 12ms=<span class="number">5</span> 13ms=<span class="number">1</span> 14ms=<span class="number">2</span> 15ms=<span class="number">1</span> 16ms=<span class="number">3</span> 17ms=<span class="number">2</span> 18ms=<span class="number">0</span> 19ms=<span class="number">0</span> 20ms=<span class="number">0</span> 21ms=<span class="number">1</span> 22ms=<span class="number">1</span> 23ms=<span class="number">1</span> 24ms=<span class="number">1</span> 25ms=<span class="number">2</span> 26ms=<span class="number">2</span> 27ms=<span class="number">0</span> 28ms=<span class="number">1</span> 29ms=<span class="number">1</span> 30ms=<span class="number">0</span> 31ms=<span class="number">0</span> 32ms=<span class="number">2</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span>  </span><br></pre></td></tr></table></figure><h2 id="案例二：Software-Layer-不动态更新-View-内容"><a href="#案例二：Software-Layer-不动态更新-View-内容" class="headerlink" title="案例二：Software Layer + 不动态更新 View 内容"></a>案例二：Software Layer + 不动态更新 View 内容</h2><h3 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_NONE,<span class="literal">null</span>);</span><br><span class="line">        i = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="Systrace-现象-1"><a href="#Systrace-现象-1" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h3><p>第一帧执行  buildDrawingCache&#x2F;SW Layer for AppCompatTextView ，后续的属性动画中，都没有在执行这个方法，可以看到动画过程中所有的帧都是绿色，说明性能很好<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/Software_Layer.html">Systrace 下载</a><br><img src="/images/15642086306643.jpg"></p><h3 id="gfxInfo-数据-1"><a href="#gfxInfo-数据-1" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 3%，99th percentile: 16ms ，说明性能非常好，同时 Number High input latency &#x3D; 0 说明主线程的负载是比较低的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">31</span></span><br><span class="line">Janky frames: <span class="number">1</span> (<span class="number">3.23</span>%)</span><br><span class="line">50th percentile: 9ms</span><br><span class="line">90th percentile: 12ms</span><br><span class="line">95th percentile: 16ms</span><br><span class="line">99th percentile: 16ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">0</span></span><br><span class="line">Number Slow UI thread: <span class="number">1</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">1</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">2</span> 6ms=<span class="number">2</span> 7ms=<span class="number">4</span> 8ms=<span class="number">3</span> 9ms=<span class="number">5</span> 10ms=<span class="number">11</span> 11ms=<span class="number">0</span> 12ms=<span class="number">1</span> 13ms=<span class="number">0</span> 14ms=<span class="number">1</span> 15ms=<span class="number">0</span> 16ms=<span class="number">2</span> 17ms=<span class="number">0</span> 18ms=<span class="number">0</span> 19ms=<span class="number">0</span> 20ms=<span class="number">0</span> 21ms=<span class="number">0</span> 22ms=<span class="number">0</span> 23ms=<span class="number">0</span> 24ms=<span class="number">0</span> 25ms=<span class="number">0</span> 26ms=<span class="number">0</span> 27ms=<span class="number">0</span> 28ms=<span class="number">0</span> 29ms=<span class="number">0</span> 30ms=<span class="number">0</span> 31ms=<span class="number">0</span> 32ms=<span class="number">0</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span>  </span><br></pre></td></tr></table></figure><h2 id="案例三：Hardware-Layer-不动态更新-View-内容"><a href="#案例三：Hardware-Layer-不动态更新-View-内容" class="headerlink" title="案例三：Hardware Layer + 不动态更新 View 内容"></a>案例三：Hardware Layer + 不动态更新 View 内容</h2><h3 id="代码-3"><a href="#代码-3" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_HARDWARE,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_NONE,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Systrace-现象-2"><a href="#Systrace-现象-2" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h2><p>可以看到，动画过程全是绿帧，性能非常好<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/Hardware_Layer.html">Systrace 下载</a><br><img src="/images/15642086995840.jpg"></p><h3 id="gfxInfo-数据-2"><a href="#gfxInfo-数据-2" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 0%，99th percentile: 14ms ，说明性能非常好，同时 Number High input latency &#x3D; 0 说明主线程的负载是非常低的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">31</span></span><br><span class="line">Janky frames: <span class="number">0</span> (<span class="number">0.00</span>%)</span><br><span class="line">50th percentile: 7ms</span><br><span class="line">90th percentile: 9ms</span><br><span class="line">95th percentile: 12ms</span><br><span class="line">99th percentile: 14ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">0</span></span><br><span class="line">Number Slow UI thread: <span class="number">0</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">0</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">3</span> 6ms=<span class="number">2</span> 7ms=<span class="number">15</span> 8ms=<span class="number">7</span> 9ms=<span class="number">2</span> 10ms=<span class="number">0</span> 11ms=<span class="number">0</span> 12ms=<span class="number">1</span> 13ms=<span class="number">0</span> 14ms=<span class="number">1</span> 15ms=<span class="number">0</span> 16ms=<span class="number">0</span> 17ms=<span class="number">0</span> 18ms=<span class="number">0</span> 19ms=<span class="number">0</span> 20ms=<span class="number">0</span> 21ms=<span class="number">0</span> 22ms=<span class="number">0</span> 23ms=<span class="number">0</span> 24ms=<span class="number">0</span> 25ms=<span class="number">0</span> 26ms=<span class="number">0</span> 27ms=<span class="number">0</span> 28ms=<span class="number">0</span> 29ms=<span class="number">0</span> 30ms=<span class="number">0</span> 31ms=<span class="number">0</span> 32ms=<span class="number">0</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span> </span><br></pre></td></tr></table></figure><h2 id="案例四：Normal-Layer-动态更新-View-内容"><a href="#案例四：Normal-Layer-动态更新-View-内容" class="headerlink" title="案例四：Normal Layer + 动态更新 View 内容"></a>案例四：Normal Layer + 动态更新 View 内容</h2><h3 id="代码-4"><a href="#代码-4" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        i = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">        animationText.setText(String.format(<span class="string">&quot;%s%d&quot;</span>, animationText.getText().toString(), i));</span><br><span class="line">        i ++ ;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="Systrace-现象-3"><a href="#Systrace-现象-3" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h3><p>可以看到动画过程中有部分黄帧，部分帧的 Animation、measure、layout、draw 比较耗时<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/Normal_Layer_UpdateView.html">Systrace 下载</a><br><img src="/images/15642087831154.jpg"></p><h3 id="gfxInfo-数据-3"><a href="#gfxInfo-数据-3" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 38%，99th percentile: 29ms ，说明性能比较差，同时 Number High input latency &#x3D; 31 说明主线程的负载是比较高的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">31</span></span><br><span class="line">Janky frames: <span class="number">12</span> (<span class="number">38.71</span>%)</span><br><span class="line">50th percentile: 14ms</span><br><span class="line">90th percentile: 25ms</span><br><span class="line">95th percentile: 29ms</span><br><span class="line">99th percentile: 29ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">31</span></span><br><span class="line">Number Slow UI thread: <span class="number">0</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">0</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">0</span> 6ms=<span class="number">0</span> 7ms=<span class="number">1</span> 8ms=<span class="number">2</span> 9ms=<span class="number">4</span> 10ms=<span class="number">1</span> 11ms=<span class="number">1</span> 12ms=<span class="number">4</span> 13ms=<span class="number">2</span> 14ms=<span class="number">1</span> 15ms=<span class="number">2</span> 16ms=<span class="number">2</span> 17ms=<span class="number">1</span> 18ms=<span class="number">2</span> 19ms=<span class="number">0</span> 20ms=<span class="number">2</span> 21ms=<span class="number">1</span> 22ms=<span class="number">0</span> 23ms=<span class="number">0</span> 24ms=<span class="number">1</span> 25ms=<span class="number">1</span> 26ms=<span class="number">1</span> 27ms=<span class="number">0</span> 28ms=<span class="number">0</span> 29ms=<span class="number">2</span> 30ms=<span class="number">0</span> 31ms=<span class="number">0</span> 32ms=<span class="number">0</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span> </span><br></pre></td></tr></table></figure><h2 id="案例五：Software-Layer-动态更新-View-内容"><a href="#案例五：Software-Layer-动态更新-View-内容" class="headerlink" title="案例五：Software Layer + 动态更新 View 内容"></a>案例五：Software Layer + 动态更新 View 内容</h2><h3 id="代码-5"><a href="#代码-5" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_NONE,<span class="literal">null</span>);</span><br><span class="line">        i = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">        animationText.setText(String.format(<span class="string">&quot;%s%d&quot;</span>, animationText.getText().toString(), i));</span><br><span class="line">        i ++ ;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="Systrace-现象-4"><a href="#Systrace-现象-4" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h3><p>由于每一帧都在更新内容，所以每次 buildDrawingCache 生成的 Bitmap 都会被销毁和重建，此时的瓶颈都在主线程中，由于 buildDrawingCache 每一帧都执行，导致 Animation 和 Draw 的执行时间都很长<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/Software_Layer_UpdateView.html">Systrace 下载</a><br><img src="/images/15642088391947.jpg"></p><h3 id="gfxInfo-数据-4"><a href="#gfxInfo-数据-4" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 41%，99th percentile: 32ms ，说明性能比较差，同时 Number High input latency &#x3D; 18 说明主线程的负载是比较高的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">29</span></span><br><span class="line">Janky frames: <span class="number">12</span> (<span class="number">41.38</span>%)</span><br><span class="line">50th percentile: 14ms</span><br><span class="line">90th percentile: 30ms</span><br><span class="line">95th percentile: 31ms</span><br><span class="line">99th percentile: 32ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">18</span></span><br><span class="line">Number Slow UI thread: <span class="number">4</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">4</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">0</span> 6ms=<span class="number">1</span> 7ms=<span class="number">0</span> 8ms=<span class="number">0</span> 9ms=<span class="number">0</span> 10ms=<span class="number">1</span> 11ms=<span class="number">6</span> 12ms=<span class="number">3</span> 13ms=<span class="number">2</span> 14ms=<span class="number">3</span> 15ms=<span class="number">0</span> 16ms=<span class="number">1</span> 17ms=<span class="number">1</span> 18ms=<span class="number">1</span> 19ms=<span class="number">2</span> 20ms=<span class="number">0</span> 21ms=<span class="number">1</span> 22ms=<span class="number">0</span> 23ms=<span class="number">0</span> 24ms=<span class="number">2</span> 25ms=<span class="number">1</span> 26ms=<span class="number">0</span> 27ms=<span class="number">1</span> 28ms=<span class="number">0</span> 29ms=<span class="number">0</span> 30ms=<span class="number">1</span> 31ms=<span class="number">1</span> 32ms=<span class="number">1</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span> </span><br></pre></td></tr></table></figure><h2 id="案例六：Hardware-Layer-动态更新-View-内容"><a href="#案例六：Hardware-Layer-动态更新-View-内容" class="headerlink" title="案例六：Hardware Layer + 动态更新 View 内容"></a>案例六：Hardware Layer + 动态更新 View 内容</h2><h3 id="代码-6"><a href="#代码-6" class="headerlink" title="代码"></a>代码</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">objectAnimator1.addListener(<span class="keyword">new</span> <span class="title class_">Animator</span>.AnimatorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationStart</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_HARDWARE,<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationEnd</span><span class="params">(Animator animator)</span> &#123;</span><br><span class="line">        animationText.setLayerType(View.LAYER_TYPE_NONE,<span class="literal">null</span>);</span><br><span class="line">        i = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">objectAnimator1.addUpdateListener(<span class="keyword">new</span> <span class="title class_">ValueAnimator</span>.AnimatorUpdateListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAnimationUpdate</span><span class="params">(ValueAnimator valueAnimator)</span> &#123;</span><br><span class="line">        animationText.setText(String.format(<span class="string">&quot;%s%d&quot;</span>, animationText.getText().toString(), i));</span><br><span class="line">        i ++ ;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="Systrace-现象-5"><a href="#Systrace-现象-5" class="headerlink" title="Systrace 现象"></a>Systrace 现象</h3><p>与 Software Layer 情况类似，由于每一帧都在更新内容，所以每次 drawLayer  生成的 Buffer 都会被销毁和重建，此时的瓶颈都在主线程 + 渲染线程中，由于每一帧内容更新和 Buffer 销毁重建，导致主线程和渲染线程执行时间都很长，性能比较差<a href="https://github.com/Gracker/Android_HardwareLayer_Example/blob/master/Systrace/HardWare_Layer_UpdateView.html">Systrace 下载</a><br><img src="/images/15642088948719.jpg"></p><h3 id="gfxInfo-数据-5"><a href="#gfxInfo-数据-5" class="headerlink" title="gfxInfo 数据"></a>gfxInfo 数据</h3><p>可以看到 Janky Frames 比例为 46%，99th percentile: 32ms ，说明性能比较差，同时 Number High input latency &#x3D; 30 说明主线程的负载是比较高的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Total frames rendered: <span class="number">30</span></span><br><span class="line">Janky frames: <span class="number">14</span> (<span class="number">46.67</span>%)</span><br><span class="line">50th percentile: 16ms</span><br><span class="line">90th percentile: 29ms</span><br><span class="line">95th percentile: 32ms</span><br><span class="line">99th percentile: 32ms</span><br><span class="line">Number Missed Vsync: <span class="number">0</span></span><br><span class="line">Number High input latency: <span class="number">30</span></span><br><span class="line">Number Slow UI thread: <span class="number">0</span></span><br><span class="line">Number Slow bitmap uploads: <span class="number">0</span></span><br><span class="line">Number Slow issue draw commands: <span class="number">0</span></span><br><span class="line">Number Frame deadline missed: <span class="number">0</span></span><br><span class="line">HISTOGRAM: 5ms=<span class="number">0</span> 6ms=<span class="number">0</span> 7ms=<span class="number">0</span> 8ms=<span class="number">0</span> 9ms=<span class="number">0</span> 10ms=<span class="number">2</span> 11ms=<span class="number">2</span> 12ms=<span class="number">5</span> 13ms=<span class="number">1</span> 14ms=<span class="number">2</span> 15ms=<span class="number">1</span> 16ms=<span class="number">3</span> 17ms=<span class="number">2</span> 18ms=<span class="number">0</span> 19ms=<span class="number">0</span> 20ms=<span class="number">0</span> 21ms=<span class="number">1</span> 22ms=<span class="number">1</span> 23ms=<span class="number">1</span> 24ms=<span class="number">1</span> 25ms=<span class="number">2</span> 26ms=<span class="number">2</span> 27ms=<span class="number">0</span> 28ms=<span class="number">1</span> 29ms=<span class="number">1</span> 30ms=<span class="number">0</span> 31ms=<span class="number">0</span> 32ms=<span class="number">2</span> 34ms=<span class="number">0</span> 36ms=<span class="number">0</span> 38ms=<span class="number">0</span> 40ms=<span class="number">0</span> </span><br></pre></td></tr></table></figure><h2 id="总结-3"><a href="#总结-3" class="headerlink" title="总结"></a>总结</h2><p>从上面的六个案例可以看到，相同的动画，在不同的 LayerType 之下，其性能表现差别很大，这还只是简单的属性动画，如果碰到更加复杂的动画，性能差别会更大。<br>我们对上面几个案例和表现出来的性能数据做一下简单的总结：</p><ol><li>如果只是单纯的做动画，不动态修改 View 的内容，那么性能表现为 ：Hardware Layer &gt;&#x3D; Software Layer &gt; Normal Layer</li><li>如果做动画同时动态修改 View 的内容，那么性能表现为 ：Normal Layer &gt; Software Layer &#x3D; Hardware Layer</li><li>Hardware Layer 对动画性能确实有很大的提升，但是如果你用不好，那么还不如不用</li><li>如果通过 Systrace 发现你做动画的时候每一帧都在 buildDrawingCache&#x2F;SW(主线程) 或者 buildLayer(渲染线程)，那么请查看你的代码的逻辑</li><li>有些情况下是由于系统的原因，比如图片比 Cache 大，invalidate 逻辑问题，可以联系手机厂商进行一起修改</li></ol><p>既然读完了,如果有什么想法可以留言沟通,也可以扫文章下面的微信二维码加好友一起讨论;如有疏漏或者错误的地方,辛苦大家告知一下,我尽早更新以免误导他人;如果觉得有用,也请把这篇文章分享给其他人.</p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/75458539">知乎 - Android 中的 Hardware Layer 详解</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;硬件加速与软件加速&quot;&gt;&lt;a href=&quot;#硬件加速与软件加速&quot; class=&quot;headerlink&quot; title=&quot;硬件加速与软件加速&quot;&gt;&lt;/a&gt;硬件加速与软件加速&lt;/h1&gt;&lt;p&gt;很多人会把 Android 中的硬件加速和 Hardware Layer 搞混，会以</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Tools" scheme="https://androidperformance.com/tags/Tools/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 -- 分析 Systrace 预备知识</title>
    <link href="https://androidperformance.com/2019/07/23/Android-Systrace-Pre/"/>
    <id>https://androidperformance.com/2019/07/23/Android-Systrace-Pre/</id>
    <published>2019-07-23T14:09:59.000Z</published>
    <updated>2026-02-07T05:17:47.845Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第二篇，主要是讲解一些分析 Systrace 的预备知识, 有了这些预备知识, 分析 Systrace 才会事半功倍, 更快也更有效率地找到问题点.</p><p>本文介绍了如何查看 Systrace 中的线程状态 , 如何对线程的唤醒信息进行分析, 如何解读信息区的数据, 以及介绍了常用的快捷键. 通过本篇文章的学习, 相信你可以掌握进程和线程相关的一些信息, 也知道如何查看复杂的 Systrace 中包含的关键信息</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#thread-states">线程状态查看</a></li><li><a href="#wakeup">线程唤醒信息分析</a></li><li><a href="#info-panel">信息区数据解析</a></li><li><a href="#shortcuts">快捷键使用</a></li><li><a href="#zhihu">本文知乎地址</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p><a id="thread-states"></a></p><h2 id="线程状态查看"><a href="#线程状态查看" class="headerlink" title="线程状态查看"></a>线程状态查看</h2><p>Systrace 会用不同的颜色来标识不同的线程状态,  在每个方法上面都会有对应的线程状态来标识目前线程所处的状态，通过查看线程状态我们可以知道目前的瓶颈是什么, 是 cpu 执行慢还是因为 Binder 调用, 又或是进行 io 操作, 又或是拿不到 cpu 时间片 </p><p>线程状态主要有下面几个</p><h3 id="绿色-运行中（Running）"><a href="#绿色-运行中（Running）" class="headerlink" title="绿色 : 运行中（Running）"></a>绿色 : 运行中（Running）</h3><p>只有在该状态的线程才可能在 cpu 上运行。而同一时刻可能有多个线程处于可执行状态，这些线程的 task_struct 结构被放入对应 cpu 的可执行队列中（一个线程最多只能出现在一个 cpu 的可执行队列中）。调度器的任务就是从各个 cpu 的可执行队列中分别选择一个线程在该cpu 上运行</p><p>作用：我们经常会查看 Running 状态的线程，查看其运行的时间，与竞品做对比，分析快或者慢的原因：</p><ol><li>是否频率不够？</li><li>是否跑在了小核上？</li><li>是否频繁在 Running 和 Runnable 之间切换？为什么？</li><li>是否频繁在 Running 和 Sleep 之间切换？为什么？</li><li>是否跑在了不该跑的核上面？比如不重要的线程占用了超大核</li></ol><p><img src="/images/15638915926547.jpg"></p><h3 id="蓝色-可运行（Runnable）"><a href="#蓝色-可运行（Runnable）" class="headerlink" title="蓝色 : 可运行（Runnable）"></a>蓝色 : 可运行（Runnable）</h3><p>线程可以运行但当前没有安排，在等待 cpu 调度</p><p>作用：Runnable 状态的线程状态持续时间越长，则表示 cpu 的调度越忙，没有及时处理到这个任务：</p><ol><li>是否后台有太多的任务在跑？</li><li>没有及时处理是因为频率太低？</li><li>没有及时处理是因为被限制到某个 cpuset 里面，但是 cpu 很满？</li><li>此时 Running 的任务是什么？为什么？</li></ol><p><img src="/images/15638916092620.jpg"></p><h3 id="白色-休眠中（Sleep）"><a href="#白色-休眠中（Sleep）" class="headerlink" title="白色 : 休眠中（Sleep）"></a>白色 : 休眠中（Sleep）</h3><p>线程没有工作要做，可能是因为线程在互斥锁上被阻塞。</p><p>作用 ： 这里一般是在等事件驱动<br><img src="/images/15638916218040.jpg"></p><h3 id="橘色-不可中断的睡眠态-（Uninterruptible-Sleep-IO-Block）"><a href="#橘色-不可中断的睡眠态-（Uninterruptible-Sleep-IO-Block）" class="headerlink" title="橘色 : 不可中断的睡眠态 （Uninterruptible Sleep - IO Block）"></a>橘色 : 不可中断的睡眠态 （Uninterruptible Sleep - IO Block）</h3><p>线程在I &#x2F; O上被阻塞或等待磁盘操作完成，一般底线都会标识出此时的 callsite ：wait_on_page_locked_killable</p><p>作用：这个一般是标示 io 操作慢，如果有大量的橘色不可中断的睡眠态出现，那么一般是由于进入了低内存状态，申请内存的时候触发 pageFault, linux 系统的 page cache 链表中有时会出现一些还没准备好的 page(即还没把磁盘中的内容完全地读出来) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.</p><p><img src="/images/15638916331888.jpg"></p><h3 id="紫色-不可中断的睡眠态（Uninterruptible-Sleep）"><a href="#紫色-不可中断的睡眠态（Uninterruptible-Sleep）" class="headerlink" title="紫色 : 不可中断的睡眠态（Uninterruptible Sleep）"></a>紫色 : 不可中断的睡眠态（Uninterruptible Sleep）</h3><p>线程在另一个内核操作（通常是内存管理）上被阻塞。</p><p>作用：一般是陷入了内核态，有些情况下是正常的，有些情况下是不正常的，需要按照具体的情况去分析<br><img src="/images/15638916451317.jpg"></p><p><a id="wakeup"></a></p><h2 id="线程唤醒信息分析"><a href="#线程唤醒信息分析" class="headerlink" title="线程唤醒信息分析"></a>线程唤醒信息分析</h2><p>Systrace 会标识出一个非常有用的信息，可以帮助我们进行线程调用等待相关的分析。</p><p>一个线程被唤醒的信息往往比较重要，知道他被谁唤醒，那么我们也就知道了他们之间的调用等待关系，如果一个线程出现一段比较长的 sleep 情况，然后被唤醒，那么我们就可以去看是谁唤醒了这个线程，对应的就可以查看唤醒者的信息，看看为什么唤醒者这么晚才唤醒。</p><p>一个常见的情况是：应用主线程程使用 Binder 与 SystemServer 的 AMS 进行通信，但是恰好 AMS 的这个函数正在等待锁释放（或者这个函数本身执行时间很长），那么应用主线程就需要等待比较长的时间，那么就会出现性能问题，比如响应慢或者卡顿，这就是为什么后台有大量的进程在运行，或者跑完 Monkey 之后，整机性能会下降的一个主要原因</p><p>另外一个场景的情况是：应用主线程在等待此应用的其他线程执行的结果，这时候线程唤醒信息就可以用来分析主线程到底被哪个线程 Block 住了，比如下面这个场景，这一帧 doFrame 执行了 152ms，有明显的异常，但是大部分时间是在 sleep</p><p><img src="/images/Android-Systrace-Pre/image-20211210185851589.webp" alt="image-20211210185851589"></p><p>这时候放大来看，可以看到是一段一段被唤醒的，这时候点击图中的 runnable ，下面的信息区就会出现唤醒信息，可以顺着看这个线程到底在做什么</p><p><img src="/images/Android-Systrace-Pre/image-20211213145728467.webp" alt="image-20211213145728467"></p><p>20424  线程是 RenderHeartbeat，这就牵扯到了 App 自身的代码逻辑，需要 App 自己去分析 RenderHeartbeat 到底做了什么事情</p><p><img src="/images/Android-Systrace-Pre/image-20211210190921614.webp" alt="image-20211210190921614"></p><p>Systrace 可以标示出这个的一个原因是，一个任务在进入 Running 状态之前，会先进入 Runnable 状态进行等待，而 Systrace 会把这个状态也标示在 Systrace 上（非常短，需要放大进行看）</p><p><img src="/images/15638916556947.jpg"></p><p>拉到最上面查看对应的 cpu 上的 taks 信息，会标识这个 task 在被唤醒之前的状态：<br><img src="/images/15638916674736.jpg"></p><p>顺便贴一下 Linux 常见的进程状态</p><ol><li><strong>D</strong> 无法中断的休眠状态（通常 IO 的进程）；</li><li><strong>R</strong> 正在可运行队列中等待被调度的；</li><li><strong>S</strong> 处于休眠状态；</li><li><strong>T</strong> 停止或被追踪；</li><li><strong>W</strong> 进入内存交换  （从内核2.6开始无效）；</li><li><strong>X</strong> 死掉的进程   （基本很少見）；</li><li><strong>Z</strong> 僵尸进程；</li><li><strong>&lt;</strong> 优先级高的进程</li><li><strong>N</strong> 优先级较低的进程</li><li><strong>L</strong> 有些页被锁进内存</li><li><strong>s</strong> 进程的领导者（在它之下有子进程）</li><li><strong>l</strong> 多进程的（使用 CLONE_THREAD, 类似 NPTL pthreads）</li><li><strong>+</strong> 位于后台的进程组</li></ol><p><a id="info-panel"></a></p><h2 id="信息区数据解析"><a href="#信息区数据解析" class="headerlink" title="信息区数据解析"></a>信息区数据解析</h2><h3 id="线程状态信息解析"><a href="#线程状态信息解析" class="headerlink" title="线程状态信息解析"></a>线程状态信息解析</h3><p><img src="/images/15638916860044.jpg"></p><h3 id="函数-Slice-信息解析"><a href="#函数-Slice-信息解析" class="headerlink" title="函数 Slice 信息解析"></a>函数 Slice 信息解析</h3><p><img src="/images/15638916944506.jpg"></p><h3 id="Counter-Sample-信息解析"><a href="#Counter-Sample-信息解析" class="headerlink" title="Counter Sample 信息解析"></a>Counter Sample 信息解析</h3><p><img src="/images/15638917076247.jpg"></p><h3 id="Async-Slice-信息解析"><a href="#Async-Slice-信息解析" class="headerlink" title="Async Slice 信息解析"></a>Async Slice 信息解析</h3><p><img src="/images/15638917151530.jpg"></p><h3 id="CPU-Slice-信息解析"><a href="#CPU-Slice-信息解析" class="headerlink" title="CPU Slice 信息解析"></a>CPU Slice 信息解析</h3><p><img src="/images/15638917222302.jpg"></p><h3 id="User-Expectation-信息解析"><a href="#User-Expectation-信息解析" class="headerlink" title="User Expectation 信息解析"></a>User Expectation 信息解析</h3><p>位于整个 Systrace 最上面的部分,标识了 Rendering Response 和  Input Response<br><img src="/images/15638917348214.jpg"></p><p><a id="shortcuts"></a></p><h2 id="快捷键使用"><a href="#快捷键使用" class="headerlink" title="快捷键使用"></a>快捷键使用</h2><p>快捷键的使用可以加快查看 Systrace 的速度,下面是一些常用的快捷键</p><p><strong>W</strong> : 放大 Systrace , 放大可以更好地看清局部细节<br><strong>S</strong> : 缩小 Systrace, 缩小以查看整体<br><strong>A</strong> : 左移<br><strong>D</strong> : 右移<br><strong>M</strong> : 高亮选中当前鼠标点击的段(这个比较常用,可以快速标识出这个方法的左右边界和执行时间,方便上下查看)</p><p>鼠标模式快捷切换 : 主要是针对鼠标的工作模式进行切换 , 默认是 1 ,也就是选择模式,查看 Systrace 的时候,需要经常在各个模式之间切换 , 所以点击切换模式效率比较低,直接用快捷键切换效率要高很多</p><p><strong>数字键1</strong> : 切换到 <strong>Selection 模式</strong> , 这个模式下鼠标可以点击某一个段查看其详细信息, 一般打开 Systrace 默认就是这个模式 , 也是最常用的一个模式  , 配合 M 和 ASDW 可以做基本的操作<br><strong>数字键2</strong> : 切换到 <strong>Pan 模式</strong> , 这个模式下长按鼠标可以左右拖动, 有时候会用到<br><strong>数字键3</strong> : 切换到 <strong>Zoom 模式</strong> , 这个模式下长按鼠标可以放大和缩小, 有时候会用到<br><strong>数字键4</strong> : 切换到 <strong>Timing 模式</strong> , 这个模式下主要是用来衡量时间的,比如选择一个起点, 选择一个终点, 查看起点和终点这中间的操作所花费的时间.</p><p><a id="zhihu"></a></p><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面<br><a href="https://zhuanlan.zhihu.com/p/82522750">知乎 - Systrace 基础知识 – 分析 Systrace 预备知识</a><br><a href="https://juejin.im/post/5dc18576f265da4d307f1878">掘金 - Systrace 基础知识 – 分析 Systrace 预备知识</a></p><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第二篇，主要是讲解一些分析 Systrace 的预备知识, 有了这些预备知识, 分析 Systrace 才会事半功倍, 更快也更有效率地找到问题点.&lt;/p&gt;
&lt;p&gt;本文介绍了如何查看 Systrace 中的线程状态 , 如何对线程的唤醒信息进行分析, 如何解读信息区的数据, 以及介绍了常用的快捷键. 通过本篇文章的学习, 相信你可以掌握进程和线程相关的一些信息, 也知道如何查看复杂的 Systrace 中包含的关键信息&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 - SystemServer 解读</title>
    <link href="https://androidperformance.com/2019/06/29/Android-Systrace-SystemServer/"/>
    <id>https://androidperformance.com/2019/06/29/Android-Systrace-SystemServer/</id>
    <published>2019-06-29T09:52:52.000Z</published>
    <updated>2026-02-07T05:17:47.846Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第四篇，主要是对 SystemServer 进行简单介绍，介绍了 SystemServer 中几个比较重要的线程，由于 Input 和 Binder 比较重要，所以单独拿出来讲，在这里就没有再涉及到。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#series">系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#wm-anim">窗口动画</a></li><li><a href="#ams">ActivityManagerService</a></li><li><a href="#wms">WindowManagerService</a></li><li><a href="#input">Input</a></li><li><a href="#binder">Binder</a></li><li><a href="#handlerthreads">HandlerThread</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="series"></a></p><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>  </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p><a id="wm-anim"></a></p><h2 id="窗口动画"><a href="#窗口动画" class="headerlink" title="窗口动画"></a>窗口动画</h2><p>Systrace 中的 SystemServer 一个比较重要的地方就是窗口动画，由于窗口归 SystemServer 来管，那么窗口动画也就是由 SystemServer 来进行统一的处理，其中涉及到两个比较重要的线程，Android.Anim 和 Android.Anim.if 这两个线程，这两个线程的基本知识在下面有讲。</p><p>这里我们以<strong>应用启动</strong>为例，查看窗口时如何在两个线程之间进行切换(Android P 里面，应用的启动动画由 Launcher 和应用自己的第一帧组成，之前是在 SystemServer 里面的，现在多任务的动画为了性能部分移到了 Launcher 去实现)</p><p>首先我们点击图标启动应用的时候，由于 App 还在启动，Launcher 首先启动一个 StartingWindow，等 App 的第一帧绘制好了之后，再切换到 App 的窗口动画</p><p>Launcher 动画<br><img src="/images/15811380751710.jpg" alt="-w1019"></p><p>此时对应的，App 正在启动<br><img src="/images/15811380510520.jpg" alt="-w1025"></p><p>从上图可以看到，应用第一帧已经准备好了，接下来看对应的 SystemServer ，可以看到应用启动第一帧绘制完成后，动画切换到 App 的 Window 动画</p><p><img src="/images/15811383348116.jpg" alt="-w1236"></p><p><a id="ams"></a></p><h2 id="ActivityManagerService"><a href="#ActivityManagerService" class="headerlink" title="ActivityManagerService"></a>ActivityManagerService</h2><p>AMS 和 WMS 算是 SystemServer 中最繁忙的两个 Service 了，与 AMS 相关的 Trace 一般会用 TRACE_TAG_ACTIVITY_MANAGER 这个 TAG，在 Systrace 中的名字是 ActivityManager</p><p>下面是启动一个新的进程的时候，AMS 的输出<br><img src="/images/15808922537197.jpg" alt="-w826"></p><p>在进程和四大组件的各种场景一般都会有对应的 Trace 点来记录，比如大家熟悉的 ActivityStart、ActivityResume、activityStop  等，这些 Trace 点有一些在应用进程，有一些在 SystemServer 进程，所以大家在看 Activity 相关的代码逻辑的时候，需要不断在这两个进程之间进行切换，这样才能从一个整体的角度来看应用的状态变化和 SystemServer 在其中起到的作用。<br><img src="/images/15808919921881.jpg" alt="-w660"></p><p><a id="wms"></a></p><h2 id="WindowManagerService"><a href="#WindowManagerService" class="headerlink" title="WindowManagerService"></a>WindowManagerService</h2><p>与 WMS 相关的 Trace 一般会用 TRACE_TAG_WINDOW_MANAGER 这个 TAG，在 Systrace 中 WindowManagerService 在 SystemServer 中多在对应的 Binder 中出现，比如下面应用启动的时候，relayoutWindow 的 Trace 输出</p><p><img src="/images/15808923853151.jpg" alt="-w957"></p><p>在 Window 的各种场景一般都会有对应的 Trace 点来记录，比如大家熟悉的 relayoutWIndow、performLayout、prepareToDisplay 等<br><img src="/images/15808918520410.jpg" alt="-w659"></p><p><a id="input"></a></p><h2 id="Input"><a href="#Input" class="headerlink" title="Input"></a>Input</h2><p>Input 是 SystemServer 线程里面非常重要的一部分，主要是由 InputReader 和 InputDispatcher 这两个 Native 线程组成，关于这一部分在 <a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a> 里面已经详细讲过，这里就不再详细讲了</p><p><img src="/images/15808245020456.jpg" alt="-w725"></p><p><a id="binder"></a></p><h2 id="Binder"><a href="#Binder" class="headerlink" title="Binder"></a>Binder</h2><p>SystemServer 由于提供大量的基础服务，所以进程间的通信非常繁忙，且大部分通信都是通过 Binder ，所以 Binder 在 SystemServer 中的作用非常关键，很多时候当后台有大量的 App 存在的时候，SystemServer 就会由于 Binder 通信和锁竞争，导致系统或者 App 卡顿。关于这一部分在 <a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Binder 和锁竞争解读</a> 里面已经详细讲过，这里就不再详细讲了</p><p><img src="/images/15808245356047.jpg" alt="-w1028"></p><p><a id="handlerthreads"></a></p><h2 id="HandlerThread"><a href="#HandlerThread" class="headerlink" title="HandlerThread"></a>HandlerThread</h2><h3 id="BackgroundThread"><a href="#BackgroundThread" class="headerlink" title="BackgroundThread"></a>BackgroundThread</h3><p>com&#x2F;android&#x2F;internal&#x2F;os&#x2F;BackgroundThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">BackgroundThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.bg&quot;</span>, android.os.Process.THREAD_PRIORITY_BACKGROUND);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 BackgroundThread<br><img src="/images/15808252037825.jpg" alt="-w1082"></p><p>BackgroundThread 在系统中使用比较多，许多对性能没有要求的任务，一般都会放到 BackgroundThread 中去执行</p><p><img src="/images/15808271946061.jpg" alt="-w654"></p><h2 id="ServiceThread"><a href="#ServiceThread" class="headerlink" title="ServiceThread"></a>ServiceThread</h2><p>ServiceThread 继承自 HandlerThread ，下面介绍的几个工作线程都是继承自 ServiceThread ，分别实现不同的功能，根据线程功能不同，其线程优先级也不同：UIThread、IoThread、DisplayThread、AnimationThread、FgThread、SurfaceAnimationThread</p><p>每个 Thread 都有自己的 Looper 、Thread 和 MessageQueue，互相不会影响。Android 系统根据功能，会使用不同的 Thread 来完成。</p><h3 id="UiThread"><a href="#UiThread" class="headerlink" title="UiThread"></a>UiThread</h3><p>com&#x2F;android&#x2F;server&#x2F;UiThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">UiThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.ui&quot;</span>, Process.THREAD_PRIORITY_FOREGROUND, <span class="literal">false</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 UiThread<br><img src="/images/15808252975757.jpg" alt="-w1049"></p><p>UiThread 被使用的地方如下，具体的功能可以自己去源码里面查看，关键字是 UiThread.get()<br><img src="/images/15808258949148.jpg" alt="-w650"></p><h3 id="IoThread"><a href="#IoThread" class="headerlink" title="IoThread"></a>IoThread</h3><p>com&#x2F;android&#x2F;server&#x2F;IoThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">IoThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.io&quot;</span>, android.os.Process.THREAD_PRIORITY_DEFAULT, <span class="literal">true</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>IoThread 被使用的地方如下，具体的功能可以自己去源码里面查看，关键字是 IoThread.get()<br><img src="/images/15808257964346.jpg" alt="-w654"></p><h3 id="DisplayThread"><a href="#DisplayThread" class="headerlink" title="DisplayThread"></a>DisplayThread</h3><p>com&#x2F;android&#x2F;server&#x2F;DisplayThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">DisplayThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// DisplayThread runs important stuff, but these are not as important as things running in</span></span><br><span class="line">    <span class="comment">// AnimationThread. Thus, set the priority to one lower.</span></span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.display&quot;</span>, Process.THREAD_PRIORITY_DISPLAY + <span class="number">1</span>, <span class="literal">false</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 DisplayThread<br><img src="/images/15808251210767.jpg" alt="-w1108"></p><p><img src="/images/15808259701453.jpg" alt="-w656"></p><h3 id="AnimationThread"><a href="#AnimationThread" class="headerlink" title="AnimationThread"></a>AnimationThread</h3><p>com&#x2F;android&#x2F;server&#x2F;AnimationThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">AnimationThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.anim&quot;</span>, THREAD_PRIORITY_DISPLAY, <span class="literal">false</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 AnimationThread<br><img src="/images/15808255124784.jpg" alt="-w902"></p><p>AnimationThread 在源码中的使用，可以看到 WindowAnimator 的动画执行也是在 AnimationThread 线程中的，Android P 增加了一个 SurfaceAnimationThread 来分担 AnimationThread 的部分工作，来提高 WindowAnimation 的动画性能</p><p><img src="/images/15808260775808.jpg" alt="-w657"></p><h3 id="FgThread"><a href="#FgThread" class="headerlink" title="FgThread"></a>FgThread</h3><p>com&#x2F;android&#x2F;server&#x2F;FgThread.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">FgThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.fg&quot;</span>, android.os.Process.THREAD_PRIORITY_DEFAULT, <span class="literal">true</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 FgThread<br><img src="/images/15808253825450.jpg" alt="-w1018"></p><p>FgThread 在源码中的使用，可以自己搜一下，下面是具体的使用的一个例子</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">FgThread.getHandler().post(() -&gt; &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mStartedUsers.get(userIdToLockF) != <span class="literal">null</span>) &#123;</span><br><span class="line">            Slog.w(TAG, <span class="string">&quot;User was restarted, skipping key eviction&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        mInjector.getStorageManager().lockUserKey(userIdToLockF);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemoteException re) &#123;</span><br><span class="line">        <span class="keyword">throw</span> re.rethrowAsRuntimeException();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (userIdToLockF == userId) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">final</span> KeyEvictedCallback callback : keyEvictedCallbacks) &#123;</span><br><span class="line">            callback.keyEvicted(userId);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="SurfaceAnimationThread"><a href="#SurfaceAnimationThread" class="headerlink" title="SurfaceAnimationThread"></a>SurfaceAnimationThread</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">com/android/server/wm/SurfaceAnimationThread.java</span><br><span class="line"><span class="keyword">private</span> <span class="title function_">SurfaceAnimationThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(<span class="string">&quot;android.anim.lf&quot;</span>, THREAD_PRIORITY_DISPLAY, <span class="literal">false</span> <span class="comment">/*allowIo*/</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Systrace 中的 SurfaceAnimationThread<br><img src="/images/15808254715766.jpg" alt="-w1148"></p><p>SurfaceAnimationThread 的名字叫 android.anim.lf ， 与 android.anim 有区别，<br><img src="/images/15808262588087.jpg" alt="-w657"></p><p>这个 Thread 主要是执行窗口动画，用于分担 android.anim 线程的一部分动画工作，减少由于锁导致的窗口动画卡顿问题，具体的内容可以看这篇文章：<a href="https://zhuanlan.zhihu.com/p/44864987">Android P——LockFreeAnimation</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">SurfaceAnimationRunner(<span class="meta">@Nullable</span> AnimationFrameCallbackProvider callbackProvider,</span><br><span class="line">        AnimatorFactory animatorFactory, Transaction frameTransaction,</span><br><span class="line">        PowerManagerInternal powerManagerInternal) &#123;</span><br><span class="line">    SurfaceAnimationThread.getHandler().runWithScissors(() -&gt; mChoreographer = getSfInstance(),</span><br><span class="line">            <span class="number">0</span> <span class="comment">/* timeout */</span>);</span><br><span class="line">    mFrameTransaction = frameTransaction;</span><br><span class="line">    mAnimationHandler = <span class="keyword">new</span> <span class="title class_">AnimationHandler</span>();</span><br><span class="line">    mAnimationHandler.setProvider(callbackProvider != <span class="literal">null</span></span><br><span class="line">            ? callbackProvider</span><br><span class="line">            : <span class="keyword">new</span> <span class="title class_">SfVsyncFrameCallbackProvider</span>(mChoreographer));</span><br><span class="line">    mAnimatorFactory = animatorFactory != <span class="literal">null</span></span><br><span class="line">            ? animatorFactory</span><br><span class="line">            : SfValueAnimator::<span class="keyword">new</span>;</span><br><span class="line">    mPowerManagerInternal = powerManagerInternal;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第四篇，主要是对 SystemServer 进行简单介绍，介绍了 SystemServer 中几个比较重要的线程，由于 Input 和 Binder 比较重要，所以单独拿出来讲，在这里就没有再涉及到。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 -- Systrace 简介</title>
    <link href="https://androidperformance.com/2019/05/28/Android-Systrace-About/"/>
    <id>https://androidperformance.com/2019/05/28/Android-Systrace-About/</id>
    <published>2019-05-28T08:58:00.000Z</published>
    <updated>2026-02-07T05:17:47.843Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第一篇，主要是对 Systrace 进行简单介绍，介绍其简单使用方法；如何去看 Systrace；如何结合其他工具对 Systrace 中的现象进行分析。</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h2 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h2><ul><li><a href="#perf-series">Perfetto 系列文章目录</a></li><li><a href="#systrace-series">Systrace 系列文章目录</a></li><li><a href="#content">正文</a></li><li><a href="#simple-use">Systrace 简单使用</a></li><li><a href="#cli">使用命令行工具抓取 Systrace</a></li><li><a href="#tags">查看支持的 TAG</a></li><li><a href="#about">关于我 &amp;&amp; 博客</a></li></ul><p><a id="perf-series"></a></p><h1 id="Perfetto-系列文章目录"><a href="#Perfetto-系列文章目录" class="headerlink" title="Perfetto 系列文章目录"></a>Perfetto 系列文章目录</h1><h3 id="2025-4-17-Update"><a href="#2025-4-17-Update" class="headerlink" title="2025-4-17 Update"></a>2025-4-17 Update</h3><ol><li>Google 已经放弃了 Systrace 工具的维护，如果你是新人，建议直接使用  <a href="(https://perfetto.dev/docs/)">Perfetto</a> 来抓取 Systrace。详细使用可以参考本博客 <a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Perfetto 系列</a></li><li>最新版本的 platform-tools 里面已经移除了 Systrace 工具，Google 推荐使用 <a href="(https://perfetto.dev/docs/)">Perfetto</a> 来抓 Trace ，如果你需要 Systrace 工具，请下载老版本的 platform-tools</li><li>Systrace 和 Perfetto 怎么选 ：个人建议 Systrace 和 Perfetto 都可以用，哪个用着顺手就用哪个，不过最终 Google 是要用 Perfetto 来替代 Systrace 的，所以可以把默认的 Trace 打开工具切换成 <a href="(https://ui.perfetto.dev/)">Perfetto UI</a> </li><li>Perfetto 的优势：<ol><li>Perfetto 相比 Systrace 最大的改进是可以支持长时间数据抓取，这是得益于它有一个可在后台运行的服务，通过它实现了对收集上来的数据进行 Protobuf 的编码并存盘。从数据来源来看，核心原理与 Systrace 是一致的，也都是基于 Linux 内核的 Ftrace 机制实现了用户空间与内核空间关键事件的记录（ATRACE、CPU 调度）。Systrace 提供的功能 Perfetto 都支持，由此才说 Systrace 最终会被 Perfetto 替代。</li><li>Perfetto 所支持的数据类型、获取方法，以及分析方式上看也是前所未有的全面，它几乎支持所有的类型与方法。数据类型上通过 ATRACE 实现了 Trace 类型支持，通过可定制的节点读取机制实现了 Metric 类型的支持，在 UserDebug 版本上通过获取 Logd 数据实现了 Log 类型的支持。</li><li>你可以通过 Perfetto.dev 网页、命令行工具手动触发抓取与结束，通过设置中的开发者选项触发长时间抓取，甚至你可以通过框架中提供的 Perfetto Trigger API 来动态开启数据抓取，基本上涵盖了我们在项目上能遇到的所有的情境。</li><li>在数据分析层面，Perfetto 提供了类似 Systrace 操作的数据可视化分析网页，但底层实现机制完全不同，最大的好处是可以支持超大文件的渲染，这是 Systrace 做不到的（超过 300M 以上时可能会崩溃、可能会超卡）。在这个可视化网页上，可以看到各种二次处理的数据、可以执行 SQL 查询命令、甚至还可以看到 logcat 的内容。Perfetto Trace 文件可以转换成基于 SQLite 的数据库文件，既可以现场敲 SQL 也可以把已经写好的 SQL 形成执行文件。甚至你可以把他导入到 Jupyter 等数据科学工具栈，将你的分析思路分享给其他伙伴。</li><li>比如你想要计算 SurfaceFlinger 线程消耗 CPU 的总量，或者运行在大核中的线程都有哪一些等等，可以与领域专家合作，把他们的经验转成 SQL 指令。如果这个还不满足你的需求， Perfetto 也提供了 Python API，将数据导出成 DataFrame 格式近乎可以实现任意你想要的数据分析效果。</li><li>可以在 <a href="https://www.androidperformance.com/2022/01/07/The-Performace-1-Performance-Tools/">Android 性能优化的术、道、器</a> 这篇文章中查看各个工具的介绍和使用，以及他们的优劣比。</li></ol></li></ol><p>笔者后续会持续更新 Perfetto 系列，后续所有的图例也都会用 Perfetto 来演示。<strong>涉及到的知识点，Systrace 系列和 Perfetto 是共通的，所以也不必纠结，哪个方便用哪个，Perfetto 系列文章和 Systrace 系列文章都可以用来学习。</strong></p><h1 id="Perfetto-系列文章目录-1"><a href="#Perfetto-系列文章目录-1" class="headerlink" title="Perfetto 系列文章目录"></a>Perfetto 系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><p><a id="systrace-series"></a></p><h1 id="Systrace-系列文章目录"><a href="#Systrace-系列文章目录" class="headerlink" title="Systrace 系列文章目录"></a>Systrace 系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>  </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p><a id="content"></a></p><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>Systrace 是 Android4.1 中新增的性能数据采样和分析工具。它可帮助开发者收集 Android 关键子系统（如 SurfaceFlinger&#x2F;SystemServer&#x2F;Kernel&#x2F;Input&#x2F;Display 等 Framework 部分关键模块、服务，View系统等）的运行信息，从而帮助开发者更直观的分析系统瓶颈，改进性能。</p><p>Systrace 的功能包括跟踪系统的 I&#x2F;O 操作、内核工作队列、CPU 负载以及 Android 各个子系统的运行状况等。在 Android 平台中，它主要由3部分组成：</p><ul><li><strong>内核部分</strong>：Systrace 利用了 Linux Kernel 中的 ftrace 功能。所以，如果要使用 Systrace 的话，必须开启 kernel 中和 ftrace 相关的模块。</li><li><strong>数据采集部分</strong>：Android 定义了一个 Trace 类。应用程序可利用该类把统计信息输出给ftrace。同时，Android 还有一个 atrace 程序，它可以从 ftrace 中读取统计信息然后交给数据分析工具来处理。</li><li><strong>数据分析工具</strong>：Android 提供一个 systrace.py（ python 脚本文件，位于 Android SDK目录&#x2F;platform-tools&#x2F;systrace 中，其内部将调用 atrace 程序）用来配置数据采集的方式（如采集数据的标签、输出文件名等）和收集 ftrace 统计数据并生成一个结果网页文件供用户查看。 从本质上说，Systrace 是对 Linux Kernel中 ftrace 的封装。应用进程需要利用 Android 提供的 Trace 类来使用 Systrace.<br>关于 Systrace 的官方介绍和使用可以看这里：<a href="http://developer.android.com/tools/help/systrace.html" title="SysTrace官方介绍">Systrace</a></li></ul><h3 id="Systrace-的设计思路"><a href="#Systrace-的设计思路" class="headerlink" title="Systrace 的设计思路"></a><strong>Systrace 的设计思路</strong></h3><p>在<strong>系统的一些关键操作</strong>（比如 Touch 操作、Power 按钮、滑动操作等）、<strong>系统机制</strong>（input 分发、View 绘制、进程间通信、进程管理机制等）、<strong>软硬件信息</strong>（CPU 频率信息、CPU 调度信息、磁盘信息、内存信息等）的关键流程上，插入类似 Log 的信息，我们称之为 TracePoint（本质是 Ftrace 信息），通过这些 TracePoint 来展示一个核心操作过程的执行时间、某些变量的值等信息。然后 Android 系统把这些散布在各个进程中的 TracePoint 收集起来，写入到一个文件中。导出这个文件后，Systrace 通过解析这些 TracePoint 的信息，得到一段时间内整个系统的运行信息。</p><p><img src="/images/Android-Systrace-About/56bebe5b-e5fd-4b69-8e36-3197fe205034.webp"></p><p>Android 系统中，一些重要的模块都已经默认插入了一些 TracePoint，通过 TraceTag 来分类，其中信息来源如下</p><ol><li>Framework Java 层的 TracePoint 通过 android.os.Trace 类完成</li><li>Framework Native 层的 TracePoint 通过 ATrace 宏完成</li><li>App 开发者可以通过 android.os.Trace 类自定义 Trace</li></ol><p>这样 Systrace 就可以把 Android 上下层的所有信息都收集起来并集中展示，对于 Android 开发者来说，Systrace 最大的作用就是把整个 Android 系统的运行状态，从黑盒变成了白盒。全局性和可视化使得 Systrace 成为 Android 开发者在分析复杂的性能问题的时候的首选。</p><h3 id="实践中的应用情况"><a href="#实践中的应用情况" class="headerlink" title="实践中的应用情况"></a>实践中的应用情况</h3><p>解析后的 Systrace 由于有大量的系统信息，天然适合分析 Android App 和 Android 系统的性能问题， Android 的 App 开发者、系统开发者、Kernel 开发者都可以使用 Systrace 来分析性能问题。</p><ol><li>从技术角度来说，Systrace 可覆盖性能涉及到的 <strong>响应速度</strong> 、<strong>卡顿丢帧</strong>、 <strong>ANR</strong> 这几个大类。</li><li>从用户角度来说，Systrace 可以分析用户遇到的性能问题，包括但不限于: <ol><li>应用启动速度问题，包括冷启动、热启动、温启动</li><li>界面跳转速度慢、跳转动画卡顿</li><li>其他非跳转的点击操作慢（开关、弹窗、长按、选择等）</li><li>亮灭屏速度慢、开关机慢、解锁慢、人脸识别慢等</li><li>列表滑动卡顿</li><li>窗口动画卡顿</li><li>界面加载卡顿</li><li>整机卡顿</li><li>App 点击无响应、卡死闪退</li></ol></li></ol><p>在遇到上述问题后，可以使用多种方式抓取 Systrace ，将解析后的文件在 Chrome 打开，然后就可以进行分析</p><p><a id="simple-use"></a></p><h2 id="Systrace-简单使用"><a href="#Systrace-简单使用" class="headerlink" title="Systrace 简单使用"></a>Systrace 简单使用</h2><p>使用 Systrace 前，要先了解一下 Systrace 在各个平台上的使用方法，鉴于大家使用Eclipse 和 Android Studio 的居多，所以直接摘抄官网关于这个的使用方法，不过不管是什么工具，流程是一样的：</p><ul><li>手机准备好你要进行抓取的界面</li><li>点击开始抓取(命令行的话就是开始执行命令)</li><li>手机上开始操作(不要太长时间)</li><li>设定好的时间到了之后，会将生成 Trace.html 文件，使用 <strong>Chrome</strong> 将这个文件打开进行分析</li></ul><p>一般抓到的 Systrace 文件如下<br><img src="/images/15618018036720.jpg"></p><p><a id="cli"></a></p><h2 id="使用命令行工具抓取-Systrace"><a href="#使用命令行工具抓取-Systrace" class="headerlink" title="使用命令行工具抓取 Systrace"></a>使用命令行工具抓取 Systrace</h2><p>命令行形式比较灵活，速度也比较快，一次性配置好之后，以后再使用的时候就会很快就出结果（<strong>强烈推荐</strong>）<br>Systrace 工具在 Android-SDK 目录下的 platform-tools 里面（<strong>最新版本的 platform-tools 里面已经移除了 systrace 工具，需要下载老版本的 platform-tools ，33 之前的版本</strong>，可以在这里下载：<a href="https://androidsdkmanager.azurewebsites.net/Platformtools">https://androidsdkmanager.azurewebsites.net/Platformtools</a> ）,下面是简单的使用方法</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> android-sdk/platform-tools/systrace</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">python systrace.py</span></span><br></pre></td></tr></table></figure><p>可以在 Bash 中配置好对应的路径和 Alias，使用起来还是很快速的。另外 User 版本所抓的 Systrce 文件所包含的信息,是比 eng 版本或者 Userdebug 版本要少的,建议使用 Userdebug 版本的机器来进行 debug,这样既保证了性能,又能有比较详细的输出结果.</p><p>抓取结束后，会生成对应的 Trace.html 文件，注意这个文件只能被 Chrome 打开。关于如何分析 Trace 文件，我们下面的章节会讲。不论使用那种工具，在抓取之前都可以选择参数，下面说一下这些参数的意思：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"> -a appname      enable app-level tracing for a comma separated list of cmdlines; * is a wildcard matching any process</span><br><span class="line"> -b N            use a trace buffer size of N KB</span><br><span class="line"> -c              trace into a circular buffer</span><br><span class="line"> -f filename     use the categories written in a file as space-separated</span><br><span class="line">                   values in a line</span><br><span class="line"> -k fname,...    trace the listed kernel functions</span><br><span class="line"> -n              ignore signals</span><br><span class="line"> -s N            sleep for N seconds before tracing [default 0]</span><br><span class="line"> -t N            trace for N seconds [default 5]</span><br><span class="line"> -z              compress the trace dump</span><br><span class="line"> --async_start   start circular trace and return immediately</span><br><span class="line"> --async_dump    dump the current contents of circular trace buffer</span><br><span class="line"> --async_stop    stop tracing and dump the current contents of circular</span><br><span class="line">                   trace buffer</span><br><span class="line"> --stream        stream trace to stdout as it enters the trace buffer</span><br><span class="line">                   Note: this can take significant CPU time, and is best</span><br><span class="line">                   used for measuring things that are not affected by</span><br><span class="line">                   CPU performance, like pagecache usage.</span><br><span class="line"> --list_categories</span><br><span class="line">                 list the available tracing categories</span><br><span class="line">-o filename      write the trace to the specified file instead</span><br><span class="line">                   of stdout.</span><br></pre></td></tr></table></figure><p>上面的参数虽然比较多，但使用工具的时候不需考虑这么多，在对应的项目前打钩即可，命令行的时候才会去手动加参数，我们一般会把这个命令配置成 Alias，比如（下面列出的 am，binder_driver 这些，不同的手机、root 和非 root，会有一些不同，可以查看下一节，使用 adb shell atrace –list_categories 来查看你的手机支持的 tag）：</p><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">alias <span class="keyword">st</span>-start=<span class="string">&#x27;python /path-to-sdk/platform-tools/systrace/systrace.py&#x27;</span>  </span><br><span class="line">alias <span class="keyword">st</span>-start-gfx-trace = ‘<span class="keyword">st</span>-start -t <span class="number">8</span> <span class="keyword">am</span>,binder_driver,camera,dalvik,freq,gfx,hal,idle,<span class="built_in">input</span>,memory,memreclaim,<span class="keyword">res</span>,sched,<span class="keyword">sync</span>,<span class="keyword">view</span>,webview,wm,workq,binder’</span><br></pre></td></tr></table></figure><p>这样在使用的时候，可以直接敲 <strong>st-start</strong> 即可，当然为了区分和保持各个文件，还需要加上 <strong>-o xxx.html</strong> .上面的命令和参数不必一次就理解，只需要记住如何简单使用即可，在分析的过程中，这些东西都会慢慢熟悉的。</p><p>一般来说比较常用的是</p><ol><li>-o : 指示输出文件的路径和名字</li><li>-t : 抓取时间(最新版本可以不用指定, 按 Enter 即可结束)</li><li>-b : 指定 buffer 大小 (一般情况下,默认的 Buffer 是够用的,如果你要抓很长的 Trae , 那么建议调大 Buffer )</li><li>-a : 指定 app 包名 (如果要 Debug 自定义的 Trace 点, 记得要加这个)</li></ol><p><a id="tags"></a></p><h1 id="查看支持的-TAG"><a href="#查看支持的-TAG" class="headerlink" title="查看支持的 TAG"></a>查看支持的 TAG</h1><p>Systrace 默认支持的 TAG，可以通过下面的命令来进行抓取，不同厂商的机器可能有不同的配置，在使用的时候可以根据自己的需求来进行选择和配置，TAG 选的少的话，Trace 文件的体积也会相应的变小，但是抓取的内容也会相应变少。Trace 文件大小会影响其在 Chrome 中打开后的操作性能，所以这个需要自己取舍</p><p>以我手上的 Android 12 的机器为例，可以看到这台机器支持下面的 tag（左边是 tag 名，右边是解释）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adb shell atrace --list_categories</span></span><br><span class="line">         gfx - Graphics</span><br><span class="line">       input - Input</span><br><span class="line">        view - View System</span><br><span class="line">     webview - WebView</span><br><span class="line">          wm - Window Manager</span><br><span class="line">          am - Activity Manager</span><br><span class="line">          sm - Sync Manager</span><br><span class="line">       audio - Audio</span><br><span class="line">       video - Video</span><br><span class="line">      camera - Camera</span><br><span class="line">         hal - Hardware Modules</span><br><span class="line">         res - Resource Loading</span><br><span class="line">      dalvik - Dalvik VM</span><br><span class="line">          rs - RenderScript</span><br><span class="line">      bionic - Bionic C Library</span><br><span class="line">       power - Power Management</span><br><span class="line">          pm - Package Manager</span><br><span class="line">          ss - System Server</span><br><span class="line">    database - Database</span><br><span class="line">     network - Network</span><br><span class="line">         adb - ADB</span><br><span class="line">    vibrator - Vibrator</span><br><span class="line">        aidl - AIDL calls</span><br><span class="line">       nnapi - NNAPI</span><br><span class="line">         rro - Runtime Resource Overlay</span><br><span class="line">         pdx - PDX services</span><br><span class="line">       sched - CPU Scheduling</span><br><span class="line">         irq - IRQ Events</span><br><span class="line">         i2c - I2C Events</span><br><span class="line">        freq - CPU Frequency</span><br><span class="line">        idle - CPU Idle</span><br><span class="line">        disk - Disk I/O</span><br><span class="line">        sync - Synchronization</span><br><span class="line">       workq - Kernel Workqueues</span><br><span class="line">  memreclaim - Kernel Memory Reclaim</span><br><span class="line">  regulators - Voltage and Current Regulators</span><br><span class="line">  binder_driver - Binder Kernel driver</span><br><span class="line">  binder_lock - Binder global lock trace</span><br><span class="line">   pagecache - Page cache</span><br><span class="line">      memory - Memory</span><br><span class="line">     thermal - Thermal event</span><br><span class="line">        freq - CPU Frequency and System Clock (HAL)</span><br><span class="line">         gfx - Graphics (HAL)</span><br><span class="line">         ion - ION Allocation (HAL)</span><br><span class="line">      memory - Memory (HAL)</span><br><span class="line">       sched - CPU Scheduling and Trustzone (HAL)</span><br><span class="line">  thermal_tj - Tj power limits and frequency (HAL)</span><br></pre></td></tr></table></figure><p><a id="about"></a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第一篇，主要是对 Systrace 进行简单介绍，介绍其简单使用方法；如何去看 Systrace；如何结合其他工具对 Systrace 中的现象进行分析。&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace 基础知识 -- Why 60 fps ？</title>
    <link href="https://androidperformance.com/2019/05/27/why-60-fps/"/>
    <id>https://androidperformance.com/2019/05/27/why-60-fps/</id>
    <published>2019-05-27T08:58:00.000Z</published>
    <updated>2026-02-07T05:17:47.882Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 Systrace 系列文章的第三篇，解释一下为何大家总是强调 60 fps。60 fps 是一个软件的概念，与屏幕刷新率里面提到的 60hz 是不一样的，可以参考这篇文章：<a href="https://www.androidperformance.com/2019/05/15/90hz-on-android/">新的流畅体验，90Hz 漫谈</a></p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 的运行，从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><h1 id="系列文章目录"><a href="#系列文章目录" class="headerlink" title="系列文章目录"></a>系列文章目录</h1><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>   </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><h1 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h1><p>今天来讲一下为何我们讲到流畅度，要首先说 60 帧。</p><p>我们先来理一下基本的概念：</p><ol><li>60 fps 的意思是说，画面每秒更新 60 次</li><li>这 60 次更新，是要均匀更新的，不是说一会快，一会慢，那样视觉上也会觉得不流畅</li><li>每秒 60 次，也就是 1&#x2F;60 ~&#x3D; 16.67 ms 要更新一次</li></ol><p>在理解了上面的基本概念之后，我们再回到 Android 这边，为何 Android 现在的渲染机制，是使用 60 fps 作为标准呢？这主要和屏幕的刷新率有关。</p><!--more--><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><ol><li>我们前面说的 60 fps，是针对软件的，我们一般称为 fps</li><li>屏幕的刷新率，是针对硬件的，现在大部分手机屏幕的刷新率，都维持在60 HZ，<strong>移动设备上一般使用60HZ，是因为移动设备对于功耗的要求更高，提高手机屏幕的刷新率，对于手机来说，逻辑功耗会随着频率的增加而线性增大，同时更高的刷新率，意味着更短的TFT数据写入时间，对屏幕设计来说难度更大。</strong></li><li>屏幕刷新率 60 HZ 只能说<strong>够用</strong>，在目前的情况下是最优解，但是未来肯定是高刷新率屏幕的天下（2023 年的现在 120Hz 已经是 Android 手机的标配了，连 iOS 都已经上到了 120Hz），个人觉得主要依赖下面几点的突破：<ol start="4"><li>电池技术</li><li>软件技术</li><li>硬件能力</li></ol></li></ol><p>目前的情况下</p><ol><li>60 FPS 的情况下：Android 的渲染机制是 16.67 ms 绘制一次， 60hz 的屏幕也是 16.67 ms 刷新一次</li><li>120 FPS 的情况下：Android 的渲染机制是 8.33 ms 绘制一次， 120hz 的屏幕也是 8.33 ms 刷新一次</li></ol><h2 id="效果提升"><a href="#效果提升" class="headerlink" title="效果提升"></a>效果提升</h2><p>如果要提升，那么软件和硬件需要一起提升，光提升其中一个，是基本没有效果的，比如你屏幕刷新率是 75 hz，软件是 60 fps，每秒软件渲染60次，你刷新 75 次，是没有啥效果的，除了重复帧率费电；同样，如果你屏幕刷新率是 30 hz，软件是 60 fps，那么软件每秒绘制的60次有一半是没有显示就被抛弃了的。</p><p>如果你想体验120hz 刷新率的屏幕，建议你试试 ipad pro ，用过之后你会觉得，60 hz 的屏幕确实有改善的空间。</p><p>这一篇主要是简单介绍，如果你想更深入的去了解，可以去 Google 一下，另外 Google 出过一个短视频，介绍了 Why 60 fps， 有条件翻墙的同学可以去看看 ：</p><ol><li><a href="https://www.youtube.com/watch?v=CaMTIgxCSqU">Why 60 fps</a></li><li><a href="https://www.youtube.com/watch?v=--OKrYxOb6Y">玩游戏为何要60帧才流畅，电影却只需24帧</a></li></ol><p>下面这张图是 Android 应用在一帧内所需要完成的任务，后续我们还会详细讲这个：</p><p><img src="/images/media/15225938262396.jpg" alt="GPU Profile 的含义"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 Systrace 系列文章的第三篇，解释一下为何大家总是强调 60 fps。60 fps 是一个软件的概念，与屏幕刷新率里面提到的 60hz 是不一样的，可以参考这篇文章：&lt;a href=&quot;https://www.androidperformance.com/2019/05/15/90hz-on-android/&quot;&gt;新的流畅体验，90Hz 漫谈&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 的运行，从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android Systrace -- 系列文章目录</title>
    <link href="https://androidperformance.com/2019/05/26/Android_Systrace_0/"/>
    <id>https://androidperformance.com/2019/05/26/Android_Systrace_0/</id>
    <published>2019-05-26T08:54:34.000Z</published>
    <updated>2026-02-07T05:17:47.852Z</updated>
    
    <content type="html"><![CDATA[<p>随着 Systrace 的功能越来越完善，加上 Android 版本的更迭，之前写的 Systrace 系列教程已经有点过时；另外随着自己技能的完善，从 Systrace 里挖掘了更多的信息，对解决各种性能问题很有帮助。这些技能我需要记录下来，增强自己的总结和归纳的能力，如果能帮助到看文章的人，也是极好的</p><p>本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。</p><span id="more"></span><p>随着 Google 宣布 Systrace 工具停更，推出 Perfetto 工具，Perfetto 在我的日常工作中已经基本能取代 Systrace 工具。同时 Oppo、Vivo 等大厂也已经把 Systrace 切换成了 Perfetto，许多新接触 Android 性能优化的小伙伴对于 Perfetto 那眼花缭乱的界面和复杂的功能感觉头疼，希望我能把之前的那些 Systrace 文章使用 Perfetto 来呈现。</p><p>本系列旨在通过 Perfetto 这个工具，从一个新的视角审视 Android 系统的整体运作方式。此外，它还旨在提供一个不同的角度来学习 App 、 Framework、Linux 等关键模块。尽管你可能已经阅读过许多关于 Android Framework、App 、性能优化的文章，但或许因为难以记住代码或不明白其运行流程，你仍感到困惑。通过 Perfetto 这个图形化工具，你可能会获得更深入的理解。</p><h1 id="Perfetto-系列目录"><a href="#Perfetto-系列目录" class="headerlink" title="Perfetto 系列目录"></a>Perfetto 系列目录</h1><ol><li><a href="https://www.androidperformance.com/2024/03/27/Android-Perfetto-101/#/Perfetto-%E7%B3%BB%E5%88%97%E7%9B%AE%E5%BD%95">Android Perfetto 系列目录</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-01-What-is-perfetto/">Android Perfetto 系列 1：Perfetto 工具简介</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-02-how-to-get-perfetto/">Android Perfetto 系列 2：Perfetto Trace 抓取</a></li><li><a href="https://www.androidperformance.com/2024/05/21/Android-Perfetto-03-how-to-analysis-perfetto/">Android Perfetto 系列 3：熟悉 Perfetto View</a></li><li><a href="https://www.androidperformance.com/2025/02/08/Android-Perfetto-04-Open-Big-Trace-With-Command-Line/">Android Perfetto 系列 4：使用命令行在本地打开超大 Trace</a></li><li><a href="https://www.androidperformance.com/2025/03/26/Android-Perfetto-05-Chorergrapher/">Android Perfetto 系列 5：Android App 基于 Choreographer 的渲染流程</a></li><li><a href="https://www.androidperformance.com/2025/04/26/Android-Perfetto-06-Why-120Hz/">Android Perfetto 系列 6：为什么是 120Hz？高刷新率的优势与挑战</a></li><li><a href="https://androidperformance.com/2025/08/02/Android-Perfetto-07-MainThread-And-RenderThread/">Android Perfetto 系列 7 - MainThread 和 RenderThread 解读</a></li><li><a href="https://androidperformance.com/2025/08/05/Android-Perfetto-08-Vsync/">Android Perfetto 系列 8：深入理解 Vsync 机制与性能分析</a></li><li><a href="https://www.androidperformance.com/2025/11/12/Android-Perfetto-09-CPU/">Android Perfetto 系列 9 - CPU 信息解读</a></li><li><a href="https://www.androidperformance.com/2025/11/16/Android-Perfetto-10-Binder/">Android Perfetto 系列 10 - Binder 调度与锁竞争</a></li><li><a href="https://www.bilibili.com/video/BV1oi82efE4D/?vd_source=0c6d2191e785de0a36dc21a9da7e664e">视频(B站) - Android Perfetto 基础和案例分享</a></li><li><a href="https://www.bilibili.com/video/BV17A6bBLECu/">视频(B站) - Android Perfetto 分享 - 出图类型分享：AOSP、WebView、Flutter + OEM 系统优化分享</a></li></ol><h1 id="Systrace-系列文章目录"><a href="#Systrace-系列文章目录" class="headerlink" title="Systrace 系列文章目录"></a>Systrace 系列文章目录</h1><p>本篇文章是一个目录，之后的文章更新后，会在这里进行汇总，内容如下：</p><ol><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 - Why 60 fps ？</a></li><li><a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a> </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li></ol><p>2023-10 update：</p><p>开始准备 Perfetto 系列</p><h2 id="面向读者群"><a href="#面向读者群" class="headerlink" title="面向读者群"></a>面向读者群</h2><p>本系列的文章既适用于应用开发者，也适用于系统开发者，我会在写作的时候兼顾两者。作为一名系统开发者，我大概理了一下我的优势：</p><ol><li>有系统源码，可以很方便地进行调试和修改代码，并随时在手机上看到结果</li><li>对系统框架有一定的了解</li><li>对系统调优有一定的了解</li></ol><p>基于上面几点，我在写作的时候也会利用这些优势，给大家带来不太一样的：</p><ol><li>通过 Systrace 来学习 Android Framework</li><li>修改某些系统配置，让大家看看结果：比如 Buffer 个数对应用的影响</li><li>介绍一些系统厂商在做系统的时候的考虑和取舍</li><li>介绍一些应用开发时候你不会注意但是很重要的点</li></ol><h2 id="更新日志"><a href="#更新日志" class="headerlink" title="更新日志"></a>更新日志</h2><p>计划是每周更新一篇，给自己一个目标 ，给自己一个交代吧。</p><ol><li>2018-03-30 目录更新：<a href="https://androidperformance.com/2018/05/03/Android_Systrace_0.html">Systrace 系列文章目录</a></li><li>2018-04-01 更新文章：<a href="https://androidperformance.com/2018/04/01/why-60-fps.html">Systrace 基础知识 -  Why 60 fps ？</a></li><li>2019-07-23 更新文章：<a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li>2019-10-22 更新问题：<a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li>2019-11-04 更新文章：<a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li>2019-11-06 更新文章：<a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li>2019-12-01 更新文章：<a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 解读</a></li><li>2019-12-06 更新文章: <a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li>2019-12-15 更新文章：<a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li>2019-12-21 更新文章：<a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li>2020-02-04 更新文章：<a href="https://www.androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li>2020-02-14 更新文章：<a href="https://www.androidperformance.com/2020/02/14/Android-Systrace-SurfaceFlinger/">Systrace 基础知识 - SurfaceFlinger 解读</a></li><li>2021-04-24 更新文章：<a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li>2021-04-24 更新文章：<a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li>2021-04-24 更新文章：<a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li>2021-10-28 更新文章：<a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li>2021-10-28 更新文章：<a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li>2021-10-28 更新文章：<a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;随着 Systrace 的功能越来越完善，加上 Android 版本的更迭，之前写的 Systrace 系列教程已经有点过时；另外随着自己技能的完善，从 Systrace 里挖掘了更多的信息，对解决各种性能问题很有帮助。这些技能我需要记录下来，增强自己的总结和归纳的能力，如果能帮助到看文章的人，也是极好的&lt;/p&gt;
&lt;p&gt;本系列的目的是通过 Systrace 这个工具，从另外一个角度来看待 Android 系统整体的运行，同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章，但是总是记不住代码，或者不清楚其运行的流程，也许从 Systrace 这个图形化的角度，你可以理解的更深入一些。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Perfetto" scheme="https://androidperformance.com/tags/Perfetto/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 新的流畅体验，90Hz 漫谈</title>
    <link href="https://androidperformance.com/2019/05/15/90hz-on-android/"/>
    <id>https://androidperformance.com/2019/05/15/90hz-on-android/</id>
    <published>2019-05-15T09:57:53.000Z</published>
    <updated>2026-02-07T05:17:47.819Z</updated>
    
    <content type="html"><![CDATA[<p>PC 上高刷新率屏幕很早就出来了，但是 Android 上高刷新率屏幕出现在 Android 手机上还只是这两年的事情，尽管雷蛇去年就推出了 120Hz 屏幕的手机，但是反响平平；而个人认为今年努比亚的红魔 3 和 OnePlus 的 7 Pro 推出的 90Hz 的屏幕才是 Android 手机目前这个阶段最好的选择，也是目前能显著提升用户体验的一个点。对高刷新率屏幕没啥概念的，可以去苹果体验店体验一下 120Hz 刷新率的 iPad Pro。</p><h1 id="技术术语解释"><a href="#技术术语解释" class="headerlink" title="技术术语解释"></a>技术术语解释</h1><h2 id="什么是屏幕刷新率-？"><a href="#什么是屏幕刷新率-？" class="headerlink" title="什么是屏幕刷新率 ？"></a>什么是屏幕刷新率 ？</h2><p>首先我们需要知道什么是屏幕刷新率，简单来说，屏幕刷新率是一个<strong>硬件</strong>的概念，是说屏幕这个硬件刷新画面的频率。</p><p>举例来说，60Hz 刷新率意思是：这个屏幕在 1 秒内，会刷新显示内容 60 次；那么对应的，90Hz 是说在 1 秒内刷新显示内容 90 次。至于显示的内容是什么，屏幕这边是不关心的，他只是从规定的地方取需要显示的内容，然后显示到屏幕上。</p><h2 id="什么是-FPS-？"><a href="#什么是-FPS-？" class="headerlink" title="什么是 FPS ？"></a>什么是 FPS ？</h2><p>首先需要说明的是 FPS 是一个<strong>软件</strong>的概念，与屏幕刷新率这个<strong>硬件</strong>概念要区分开，FPS 是由软件系统决定的。</p><p>FPS 是 Frame per Second 的缩写，意思是每秒产生画面的个数。举例来说，60FPS 指的是每秒产生 60 个画面；90FPS 指的是每秒产生 90 个画面。</p><h2 id="什么是-Vsync-？"><a href="#什么是-Vsync-？" class="headerlink" title="什么是 Vsync ？"></a>什么是 Vsync ？</h2><p>VSync 是垂直同期( Vertical Synchronization )的简称。基本的思路是将你的 FPS 和显示器的刷新率同期起来。其目的是避免一种称之为”撕裂”的现象.<br>对比 60 fps :<br>60 fps 的系统 , 1s 内需要生成 60 个可供显示的 Frame , 也就是说绘制一帧需要 16.67ms ( 1&#x2F;60 ) , 才会不掉帧 ( FrameMiss ).<br>90 fps 的系统 , 1s 内生成 90 个可供显示的 Frame , 也就是说绘制一帧需要 11.11ms ( 1&#x2F;90 ) , 才不会掉帧 ( FrameMiss ).</p><h2 id="什么是-Input-扫描周期-？"><a href="#什么是-Input-扫描周期-？" class="headerlink" title="什么是 Input 扫描周期 ？"></a>什么是 Input 扫描周期 ？</h2><p>Input 的扫描周期在 8 ms左右, 不同的手机会有不同, 由于 Android 系统的 Display 系统是以 Vsync 为基础的, Input 事件也是在 Vsync 到来的时候才会去处理.</p><p>所以当一个 Vsync 周期为 16.67ms , Input 周期为 8ms 的时候, 可以保证一个 Vsync 周期内存在 2 个 Input 点.<br>当一个 Vsync 周期为 11.11ms , Input 周期为 8ms 的时候, 一个 Vsync 周期内可能存在 2 个 Input 点. 也可能存在 1个 Input 点. 这会带来不均匀的 Input 体验.</p><h1 id="90Hz-用户体验"><a href="#90Hz-用户体验" class="headerlink" title="90Hz 用户体验"></a>90Hz 用户体验</h1><p>当<strong>屏幕刷新率</strong>和<strong>系统fps</strong>相对应的时候，才会带来最好的效果。从目前的硬件水平来看，90Hz 屏幕 + 90Fps 系统的组合才是目前最好的选择；其他的组合比如 90Hz 屏幕跑 60 fps 的系统，则会有屏幕刷新的时候，系统内容还没有准备好，只能显示上一帧这种情况；比如 60Hz 跑 90 fps 的系统，则会出现丢帧的情况，因为系统内容准备速度要比屏幕刷新速度快，势必有的帧绘制好却没有显示就被丢弃了。</p><p>另外 120Hz 的屏幕 + 120 fps 的系统肯定是未来发展的趋势，看看 iPad Pro 就知道了，但是目前的 Android 系统架构和硬件，都不足以支持 120Hz 的屏幕 + 120 fps 的系统，就算强行上，体验也会比较差，而且也会有很多的妥协。</p><p>相信到了 2020 年，120Hz 的屏幕 + 120 fps 的 Android 手机大家就可以买到了。</p><p>对比视频我们就直接看 OnePlus 的这个视频：<a href="https://weibo.com/tv/v/HusfeiEbX?fid=1034:4372578974817409">90Hz 和 60Hz 屏幕使用对比</a></p><h1 id="从系统角度来看-90fps-的工作原理"><a href="#从系统角度来看-90fps-的工作原理" class="headerlink" title="从系统角度来看 90fps 的工作原理"></a>从系统角度来看 90fps 的工作原理</h1><p>简单来说，Android 系统在工作的时候，由下面几个步骤：</p><ol><li>事件触发，比如说用户点击图标，或者滑动 List</li><li>系统把事件分发给对应的 App，比如在桌面点击图标启动应用–对应点击事件，或者微信朋友圈滑动 – 对应滑动事件</li><li>App 拿到事件之后，对事件进行处理，不过有的是应用本身来处理，有的是系统帮忙处理，比如滑动事件的话，则会触发列表上下滑动，加载新的 Item 之类的</li><li>一旦 App 内容有更新，则会触发 App 的 measure、layout、draw 操作，开了硬件加速的话则会使用 RenderThread 进行这一帧的绘制，绘制好之后的 Buffer 会给到 SurfaceFlinger 进行合成</li><li>SurfaceFlinger 把各个图层合成后通过服务交到屏幕这边进行显示。</li></ol><p>需要说明的是，90fps 和 60fps 在流程上基本没有差别。</p><h2 id="90fps-下-App-的渲染流程"><a href="#90fps-下-App-的渲染流程" class="headerlink" title="90fps 下 App 的渲染流程"></a>90fps 下 App 的渲染流程</h2><p><img src="/images/15586889336528.jpg" alt="90fps 下 App 的渲染流程"><br>注: Systrace 是按照时间线从左到右展示</p><h2 id="90fps-下-SurfaceFlinger-的渲染流程"><a href="#90fps-下-SurfaceFlinger-的渲染流程" class="headerlink" title="90fps 下 SurfaceFlinger 的渲染流程"></a>90fps 下 SurfaceFlinger 的渲染流程</h2><p><img src="/images/15586889505177.jpg" alt="90fps 下 SurfaceFlinger 的渲染流程"><br>注: Systrace 是按照时间线从左到右展示</p><h1 id="90fps-的优势和挑战"><a href="#90fps-的优势和挑战" class="headerlink" title="90fps 的优势和挑战"></a>90fps 的优势和挑战</h1><h2 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h2><ol><li>90fps 会带来更加顺滑的用户体验, 60fps 的时候, 1s 才更新 60 张图,这 60 张图在 1s 内顺序播放从而让人的视觉认为是连续的画面在播放 , 但是人眼会比较挑剔 , 所以当看多了 90 fps 的画面, 即每秒播放 90 张画面的时候, 再回去看 60 fps 就会觉得卡顿, 不连贯.</li><li>高刷新率屏幕使用习惯了，会没法适应低刷新率的屏幕，这是一个不可逆的过程，在挑选手机的时候需要注意这一点。</li><li>另外随着用户更换手机的频率变低，买一台一步到位的手机还是比较重要的，顶级屏幕 + 大电池 + 顶级 SOC + UFS + 大内存。今年下半年会有不少这样的机器出现，目前还在观望的同志可以再等等。</li></ol><h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><ol><li>性能<ol><li>90fps 的挑战主要在于性能, App 在 60fps 的时候, 只需要 App 和 SurfaceFlinger 把每一帧处理的时间控制在 16.6 ms之内, 就可以保证不会掉帧 (当然由于 Triple Buffer 的存在, 偶尔超过也不会掉)</li><li>到了 90fps , App 和 SF 都需要将一帧的时间压榨在 11.11ms 之内, 才可以保证不掉帧. 这里的两个挑战, 一个是 App 端, 一个是 SurfaceFlinger 及其相关的进程端 (加上 crtc 和 hw service).</li><li>由于对性能要求比较高,主要是对 cpu 的性能要求比较高 (其次是 io 和 gpu ) , 所以 90fps 需要更加强劲的硬件做支撑 (高通 855 , UFS 3.0 ), 这也是为什么到现在 90hz 的屏幕 + 90 fps 的系统才得以出现并提供良好的用户体验 ( OnePlus 7 Pro). (所以 120 fps 的雷蛇,需要把一帧的时间压在 8.3 ms , 其硬件性能是不足以支撑的.)</li></ol></li><li>功耗<ol><li>90Hz 的屏幕会比 60Hz 的屏幕更加耗电，尤其在高分辨率的情况下，所以优化好使用时功耗，是后续厂商需要考虑的问题。</li><li>有些场景和 App 是不支持 90fps 的，这时候可以把系统的 fps 降低到 60</li><li>有些场景用户为了省电，可以把屏幕分辨率从 2K 调整到 1080P</li></ol></li><li>App 适配<ol><li>大部分 App 不用做什么事情，就可以在 90 fps 的系统上跑，但是是否流畅就只能各自发挥了，有些写的好或者本身就不是很复杂的 app，跑起来就很顺畅，有些 App 则在 60 fps 下表现很好，但是到了 90 fps 下就比较差。</li><li>大部分游戏都是自主控制刷新率的，要在 90 fps 的系统下跑，需要游戏厂商进行针对性的优化，使其可以跑 90fps，也可以流畅跑 90 fps。</li><li>其他场景，比如 WebView，SurfaceView 这些，则需要根据内容来做调整。</li></ol></li></ol><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/66900738">知乎 - 新的流畅体验，90Hz 漫谈</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;PC 上高刷新率屏幕很早就出来了，但是 Android 上高刷新率屏幕出现在 Android 手机上还只是这两年的事情，尽管雷蛇去年就推出了 120Hz 屏幕的手机，但是反响平平；而个人认为今年努比亚的红魔 3 和 OnePlus 的 7 Pro 推出的 90Hz 的屏幕才</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
  </entry>
  
  <entry>
    <title>利器 - 高效工具推荐</title>
    <link href="https://androidperformance.com/2019/04/07/liqi/"/>
    <id>https://androidperformance.com/2019/04/07/liqi/</id>
    <published>2019-04-07T08:22:32.000Z</published>
    <updated>2026-02-07T05:17:47.877Z</updated>
    
    <content type="html"><![CDATA[<p>之前很久就看到过<a href="https://mp.weixin.qq.com/s?__biz=MzA3NTgzNzU2NQ==&mid=400594784&idx=1&sn=a88b34faa7522206957d448d40ea0b31&scene=21#wechat_redirect">利器社群计划</a>，看了大家的分享，学到了很多东西，正如开头所讲 “<strong>工具和灵感，都是利器。当来自不同的领域，不同类型的创造者的工具和灵感互相碰撞的时候，才会迸发出更多的可能性</strong>”。下面是我针对利器社群计划的6个问题的简单回答，希望有能帮助到大家的部分。</p><h2 id="介绍一下你自己和所做的工作？"><a href="#介绍一下你自己和所做的工作？" class="headerlink" title="介绍一下你自己和所做的工作？"></a>介绍一下你自己和所做的工作？</h2><p>我是一名 Android 系统开发工程师，在某手机厂商的系统研发部门。工作主要是负责 Android 系统相关的工作，比如性能、功耗、稳定性、框架维护等，工作比较杂，都是打杂的工作。<br>爱好方面，打篮球、跑步、打游戏这些程序员必备技能自然是不能少；读书、养猫、喝茶这些艺术青年的爱好也不落下；业余时间喜欢刷刷 B 站和 YouTube；有空了就把之前落下的没有完成的文章补全；目前沉迷于减肥，正在与小肚子做殊死斗争。</p><h2 id="你都在使用哪些硬件？"><a href="#你都在使用哪些硬件？" class="headerlink" title="你都在使用哪些硬件？"></a>你都在使用哪些硬件？</h2><p><strong>电脑</strong>：电脑主要有三台，一台 MacBook Pro 用来码字、写自己的小 App、处理照片等；一台公司配置的台式机，Ubuntu 系统，主要用来工作，编译 Android 源码必备；一台自己配的台式机，主要是用来玩游戏、编译 AOSP 代码。</p><p><strong>手机</strong>：手机有三台，主力机器是 <strong>iPhone X</strong>，除了续航其他的都很满意；备用机器是一台 <strong>OnePlus 6</strong>，Android 旗舰，Android 工程师还是得要有一台使用的 Android 机器的；另外一台是 <strong>Pixle</strong>，主要是来跑自己编译的 AOSP 代码的，业余学习框架必备。</p><p><strong>其他硬件</strong> ：Apple 家族的 Watch、iPad Pro、AirPods；索尼大法的无线耳机 MX-1000；Kindle Voyage，看电子书专用。</p><h2 id="软件呢？"><a href="#软件呢？" class="headerlink" title="软件呢？"></a>软件呢？</h2><ul><li><strong>Things3</strong>：主要是安排每天的工作、记录要做的事情</li><li><strong>MWeb</strong>：码字专用，此文就是用 MWeb 写的</li><li><strong>Keep</strong>：健身好帮手，有些课程跟着做就可以了，主要是记录，有时候有个目标，要比没有目标更好坚持一些，尤其是跑步的时候（推荐跑步机 HIT 训练）</li><li><strong>海豚记账本</strong>：记账软件，基本的需要。</li><li>**Kindle &amp;&amp; 微信读书 &amp;&amp;**：读书软件，两个书库可以互补，电子书还是比较方便，尤其是用 iPad Pro 去看</li><li><strong>YouTube &amp;&amp; Bilibili</strong>：学习、娱乐，基本上就靠他了</li><li><strong>ShadowSocks &amp;&amp; Wingy</strong> ，Android 和 Mac 基本上都是用 ShadowSocks，手机和 iPad 用 Wingy，方便的是有个小插件，有时候软件被杀了，小插件开关很方便。</li><li><strong>极客时间 &amp;&amp; 得到</strong>：知识付费软件，三人行，则必有我师；个人觉得听听别人在做什么，做了什么，怎么做的，对自己的提升还是蛮大的，很多弯路不用去走。</li><li><strong>Google Photos</strong>：照片基本上都在这里，真怕有一天上不了了，估计要哭死..</li><li><strong>Reeder</strong>：之前维护了一个开发者的 <a href="https://github.com/Gracker/Rss-IT">Feed 列表</a>，我自己是用 Reeder3 来看的。</li><li><strong>Android Studio &amp;&amp; VS Code</strong>：生产力软件，一个用来看 Framework Java代码，一个用来看 C\C++ 代码。</li><li><strong>RescueTime</strong>：主要是用来记录工作时间的，每天下来可以看自己的时间都花费在了哪里，全平台支持（不过 iOS 上只能看，不能记录，Mac 就可以记录，Android 和 Ubuntu 以及 Windows 都可以记录），有时候看看自己十几个小时的工作时间，简直欲哭无泪..</li><li><strong>石墨文档</strong>：记录自己的日报、周报、研究的一些内容，全平台支持，同步非常方便，多人协作效率也高，网页端最好用。</li><li><strong>坚果云</strong>：全平台支持的云盘，资料往上一丢，就再也不怕丢了（支持 Ubuntu 简直对程序员太友善了）</li><li><strong>印象笔记</strong>：之前用来记录，现在更多是用来当收藏中心，遇到好文章一般会记录到这里，回头再整理和复习，日常工作记录都交给石墨文档了。</li></ul><h2 id="你最理想的工作环境是什么？"><a href="#你最理想的工作环境是什么？" class="headerlink" title="你最理想的工作环境是什么？"></a>你最理想的工作环境是什么？</h2><p>最理想的工作环境是 Work Life Balance ，就像最近大火的 <a href="https://996.icu/#/zh_CN">996 项目</a>里面说的，目前自身的工作状况也差不多，所以我更希望我的工作环境是<a href="https://github.com/formulahendry/955.WLB"> 955.WLB </a>这个项目里面描述的，work–life balance。</p><p>996 给人最大的压力是没有自己的时间，去提升自己，每天忙到晚上十点十一点，回去洗个澡就睡了，什么健身、读书、学习这些，根本没有时间。正如这篇文章<a href="https://mp.weixin.qq.com/s/9-ZIoCvJmnYpkGpZBou4Dw">废掉一个人最隐蔽的方式，是让他忙到没时间成长</a> 所说：</p><blockquote><p>如果一个人只是从工作过程中学习，那么这种单一的学习方式，必然导致它对成长的边际贡献越来越低。<br>每天一定要让自己有时间成长，其实是让你拓展除工作外的其它成长方式，你学习的方式多元化，对成长的边际贡献就越高，你的成长就越快。<br>一个人在职场里持续上升，必须要有持续的增量成长。</p></blockquote><p>总结来说，我最理想的工作环境：<strong>有志同道合的同事、有自己热爱的工作、有自身成长的空间和时间。</strong></p><h2 id="你平时获得工作灵感的方式有哪些？"><a href="#你平时获得工作灵感的方式有哪些？" class="headerlink" title="你平时获得工作灵感的方式有哪些？"></a>你平时获得工作灵感的方式有哪些？</h2><p>一个是阅读，从别人的思考里面提取精华，看看是否可以为我所用或者改进或者发扬光大；一个是思考和记录，边思考边画流程图，记录某个思想的火花。</p><p>不过效果都不怎么好，忙起来很少有时间阅读和思考，求各位大神指教。</p><h2 id="推荐一件生活中的利器给大家？"><a href="#推荐一件生活中的利器给大家？" class="headerlink" title="推荐一件生活中的利器给大家？"></a>推荐一件生活中的利器给大家？</h2><p>我最想推荐给大家的不是某个硬件或者软件，而是一种学习方式：<strong>记录-总结-输出</strong>。这三个环节环环相扣：</p><p><strong>记录</strong>：我们总说，好记性不如烂笔头，尤其是在现在这个信息爆炸的时代，有自己的一套记录方式显得尤其重要。待办事项、偶尔冒出来的奇怪念头、遇到的问题和解决的方法、在某个网站上看到的好文章、待更新的 Blog、YouTube 上的教学视频等等，记录会让你显得井井有条。</p><p><strong>总结</strong>：总结可以把之前零散的思路或者知识汇总到一起，清晰的脉络有助于从总体去看某个时期或者某个知识，比如项目总结可以让我们在后续的项目里面避免再走这个项目里面的坑；某个知识点的总结可以让我们从点到面了解这个知识，加深对这个知识的印象。</p><p><strong>输出</strong>：输出可以是私人笔记、Blog、视频，输出这个更多的是通过向别人讲述来强化自己。为了给别人讲清楚，自己要对输出的点非常熟悉；大家对于输出的讨论也会更加强化你对这个知识点的理解；输出也有助于帮助他人，同时提升自己的名气，你很强，你得让别人也知道你很强。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>最近去了趟日本，赶上了樱花季，放一张樱花结束吧。<br><img src="/images/media/performancecase/15546301230322.jpg" alt="樱花"></p><blockquote><p>本文参与了「利器社群计划」，发现更多创造者和他们的工具：<a href="http://liqi.io/community/">http://liqi.io/community/</a></p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;之前很久就看到过&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzA3NTgzNzU2NQ==&amp;mid=400594784&amp;idx=1&amp;sn=a88b34faa7522206957d448d40ea0b31&amp;scene=21#wecha</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="利器" scheme="https://androidperformance.com/tags/%E5%88%A9%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>Android 无障碍服务导致的整机卡顿案例分析</title>
    <link href="https://androidperformance.com/2019/01/21/android-performance-case-jank-accessbility/"/>
    <id>https://androidperformance.com/2019/01/21/android-performance-case-jank-accessbility/</id>
    <published>2019-01-20T16:22:22.000Z</published>
    <updated>2026-02-07T05:17:47.861Z</updated>
    
    <content type="html"><![CDATA[<h1 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h1><p>有用户反馈，手机在滑动的时候, 列表会一抖一抖的, 滑动桌面或者设置（只要是可以滑动的），都会出现，但是这个并不是必现，而是某些用户会出现，某些用户则不会出现。</p><p>吃瓜群众可以直接拉到下面看 罪魁祸首和自检 ，对分析问题比较感兴趣的可以看一下分析的过程。</p><h1 id="Systrace-分析"><a href="#Systrace-分析" class="headerlink" title="Systrace 分析"></a>Systrace 分析</h1><p>本地测试有一台复现, 拿过来之后分析发现,手指滑动桌面或者设置,都会必现卡顿, 从 Trace 上看就是下面这样<br><img src="/images/media/performancecase/15482434530990.jpg"></p><p>红色箭头处就是掉帧的地方. 从上面的 Buffer 个数可以看到, SF没有绘制的原因是 Launcher 没有提交 Buffer 上来.</p><p>对应的 Launcher Trace如下 , 可以看到 Launcher 没有绘制的原因是没有 Input 事件传上来. 所以 Launcher 的画面没有更新, 所以才会出现掉帧.<br><img src="/images/media/performancecase/15482435308066.jpg"></p><p>没有事件上来这个本身就是有问题的 , 我们手指是连续从屏幕上划过的, 事件的上报应该是连续的才对, 我们怀疑是屏幕报点有问题, 不过 Check 硬件之前我们首先看一下 InputReader 和 InputDispatcher 线程是否正常工作</p><p><img src="/images/media/performancecase/15482435458655.jpg"></p><p>从图中可以看到 InputReader 线程是正常工作的 , 但是 InputDIspatcher 线程却有问题, 大家可以看一下正常情况下这两个线程的对应关系</p><p><img src="/images/media/performancecase/15482435564755.jpg"></p><p>再回到有问题的那个图 , 仔细看发现 InputDispatcher 线程的周期是和 Vsync 是相同的, 也就是说, InputDispatcher 的唤醒逻辑由 InputReader 唤醒变为由 Vsync 唤醒</p><p>再仔细看的话，点开 InputDIspatcher 的线程 cpu 状态可以看到， 唤醒执行任务的 InputDispatcher 线程并不是被 InputReader 线程唤醒的, 而是被 System_Server 的 UI Thread 唤醒的.</p><p>那么接下来， 就需要从代码的角度来看为什么 InputReader 没有唤醒 InputDIspatcher 。</p><h1 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h1><p>InputReader 唤醒 InputDispatcher 线程的逻辑如下（以本例中的 Move 手势为例。），</p><p>frameworks&#x2F;native&#x2F;services&#x2F;inputflinger&#x2F;InputDispatcher.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">InputDispatcher::notifyMotion</span><span class="params">(<span class="type">const</span> NotifyMotionArgs* args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">validateMotionEvent</span>(args-&gt;action, args-&gt;actionButton,</span><br><span class="line">                args-&gt;pointerCount, args-&gt;pointerProperties)) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">uint32_t</span> policyFlags = args-&gt;policyFlags;</span><br><span class="line">    policyFlags |= POLICY_FLAG_TRUSTED;</span><br><span class="line"></span><br><span class="line">    android::base::Timer t;</span><br><span class="line">    mPolicy-&gt;<span class="built_in">interceptMotionBeforeQueueing</span>(args-&gt;eventTime, <span class="comment">/*byref*/</span> policyFlags);</span><br><span class="line">    <span class="keyword">if</span> (t.<span class="built_in">duration</span>() &gt; SLOW_INTERCEPTION_THRESHOLD) &#123;</span><br><span class="line">        <span class="built_in">ALOGW</span>(<span class="string">&quot;Excessive delay in interceptMotionBeforeQueueing; took %s ms&quot;</span>,</span><br><span class="line">                std::<span class="built_in">to_string</span>(t.<span class="built_in">duration</span>().<span class="built_in">count</span>()).<span class="built_in">c_str</span>());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> needWake; <span class="comment">//是否需要唤醒</span></span><br><span class="line">    &#123; <span class="comment">// acquire lock</span></span><br><span class="line">        mLock.<span class="built_in">lock</span>();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">shouldSendMotionToInputFilterLocked</span>(args)) &#123;</span><br><span class="line">            mLock.<span class="built_in">unlock</span>();</span><br><span class="line"></span><br><span class="line">            MotionEvent event;</span><br><span class="line">            event.<span class="built_in">initialize</span>(args-&gt;deviceId, args-&gt;source, args-&gt;action, args-&gt;actionButton,</span><br><span class="line">                    args-&gt;flags, args-&gt;edgeFlags, args-&gt;metaState, args-&gt;buttonState,</span><br><span class="line">                    <span class="number">0</span>, <span class="number">0</span>, args-&gt;xPrecision, args-&gt;yPrecision,</span><br><span class="line">                    args-&gt;downTime, args-&gt;eventTime,</span><br><span class="line">                    args-&gt;pointerCount, args-&gt;pointerProperties, args-&gt;pointerCoords);</span><br><span class="line"></span><br><span class="line">            policyFlags |= POLICY_FLAG_FILTERED;</span><br><span class="line">            <span class="comment">// SystemServer 上层要对事件进行过滤</span></span><br><span class="line">            <span class="keyword">if</span> (!mPolicy-&gt;<span class="built_in">filterInputEvent</span>(&amp;event, policyFlags)) &#123;</span><br><span class="line">                <span class="keyword">return</span>; <span class="comment">// event was consumed by the filter</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            mLock.<span class="built_in">lock</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Just enqueue a new motion event.</span></span><br><span class="line">        MotionEntry* newEntry = <span class="keyword">new</span> <span class="built_in">MotionEntry</span>(args-&gt;eventTime,</span><br><span class="line">                args-&gt;deviceId, args-&gt;source, policyFlags,</span><br><span class="line">                args-&gt;action, args-&gt;actionButton, args-&gt;flags,</span><br><span class="line">                args-&gt;metaState, args-&gt;buttonState,</span><br><span class="line">                args-&gt;edgeFlags, args-&gt;xPrecision, args-&gt;yPrecision, args-&gt;downTime,</span><br><span class="line">                args-&gt;displayId,</span><br><span class="line">                args-&gt;pointerCount, args-&gt;pointerProperties, args-&gt;pointerCoords, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        needWake = <span class="built_in">enqueueInboundEventLocked</span>(newEntry); </span><br><span class="line">        mLock.<span class="built_in">unlock</span>();</span><br><span class="line">    &#125; <span class="comment">// release lock</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (needWake) &#123;</span><br><span class="line">        mLooper-&gt;<span class="built_in">wake</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>需要注意这里 ，mPolicy-&gt;filterInputEvent 直接 return了，也就是说这里如果返回 false，那么就直接 return 了， 不继续执行下面的步骤。</p><p>继续看 mPolicy-&gt;filterInputEvent</p><p>frameworks&#x2F;base&#x2F;services&#x2F;core&#x2F;jni&#x2F;com_android_server_input_InputManagerService.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">NativeInputManager::filterInputEvent</span><span class="params">(<span class="type">const</span> InputEvent* inputEvent, <span class="type">uint32_t</span> policyFlags)</span> </span>&#123;</span><br><span class="line">    <span class="built_in">ATRACE_CALL</span>();</span><br><span class="line">    jobject inputEventObj;</span><br><span class="line"></span><br><span class="line">    JNIEnv* env = <span class="built_in">jniEnv</span>();</span><br><span class="line">    <span class="keyword">switch</span> (inputEvent-&gt;<span class="built_in">getType</span>()) &#123;</span><br><span class="line">    <span class="keyword">case</span> AINPUT_EVENT_TYPE_KEY:</span><br><span class="line">        inputEventObj = <span class="built_in">android_view_KeyEvent_fromNative</span>(env,</span><br><span class="line">                <span class="built_in">static_cast</span>&lt;<span class="type">const</span> KeyEvent*&gt;(inputEvent));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> AINPUT_EVENT_TYPE_MOTION:</span><br><span class="line">        inputEventObj = <span class="built_in">android_view_MotionEvent_obtainAsCopy</span>(env,</span><br><span class="line">                <span class="built_in">static_cast</span>&lt;<span class="type">const</span> MotionEvent*&gt;(inputEvent));</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>; <span class="comment">// dispatch the event normally</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">     </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// The callee is responsible for recycling the event.</span></span><br><span class="line">    jboolean pass = env-&gt;<span class="built_in">CallBooleanMethod</span>(mServiceObj, gServiceClassInfo.filterInputEvent,</span><br><span class="line">            inputEventObj, policyFlags);</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">checkAndClearExceptionFromCallback</span>(env, <span class="string">&quot;filterInputEvent&quot;</span>)) &#123;</span><br><span class="line">        pass = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    env-&gt;<span class="built_in">DeleteLocalRef</span>(inputEventObj);</span><br><span class="line">    <span class="keyword">return</span> pass;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里从 jni 调回到 java 层， 也就是 InputManagerService 的 filterInputEvent 方法。</p><p>com&#x2F;android&#x2F;server&#x2F;input&#x2F;InputManagerService.java</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Native callback.</span></span><br><span class="line"><span class="function"><span class="keyword">final</span> boolean <span class="title">filterInputEvent</span><span class="params">(InputEvent event, <span class="type">int</span> policyFlags)</span> </span>&#123;</span><br><span class="line">    <span class="built_in">synchronized</span> (mInputFilterLock) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mInputFilter != null) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                mInputFilter.<span class="built_in">filterInputEvent</span>(event, policyFlags);</span><br><span class="line">            &#125; <span class="built_in">catch</span> (RemoteException e) &#123;</span><br><span class="line">                <span class="comment">/* ignore */</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    event.<span class="built_in">recycle</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>跟代码流程发现, 这个 mInputFilter 是 AccessibilityInputFilter 的一个实例, 在 辅助功能里面打开开关的时候,会调用 AccessibilityManagerService 的 updateInputFilter 方法来设置 InputFilter.</p><p>android&#x2F;view&#x2F;InputFilter.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">filterInputEvent</span><span class="params">(InputEvent event, <span class="type">int</span> policyFlags)</span> &#123;</span><br><span class="line">    mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, <span class="number">0</span>, event).sendToTarget();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> MSG_INPUT_EVENT: &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">InputEvent</span> <span class="variable">event</span> <span class="operator">=</span> (InputEvent)msg.obj;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (mInboundInputEventConsistencyVerifier != <span class="literal">null</span>) &#123;</span><br><span class="line">            mInboundInputEventConsistencyVerifier.onInputEvent(event, <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        onInputEvent(event, msg.arg1);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        event.recycle();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继续看 onInputEvent(event, msg.arg1);<br>com&#x2F;android&#x2F;server&#x2F;accessibility&#x2F;AccessibilityInputFilter.java</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onInputEvent</span><span class="params">(InputEvent event, <span class="type">int</span> policyFlags)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mEventHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (DEBUG) Slog.d(TAG, <span class="string">&quot;mEventHandler == null for event &quot;</span> + event);</span><br><span class="line">        <span class="built_in">super</span>.onInputEvent(event, policyFlags);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">EventStreamState</span> <span class="variable">state</span> <span class="operator">=</span> getEventStreamState(event);</span><br><span class="line">    <span class="keyword">if</span> (state == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">super</span>.onInputEvent(event, policyFlags);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">eventSource</span> <span class="operator">=</span> event.getSource();</span><br><span class="line">    <span class="keyword">if</span> ((policyFlags &amp; WindowManagerPolicy.FLAG_PASS_TO_USER) == <span class="number">0</span>) &#123;</span><br><span class="line">        state.reset();</span><br><span class="line">        mEventHandler.clearEvents(eventSource);</span><br><span class="line">        <span class="built_in">super</span>.onInputEvent(event, policyFlags);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (state.updateDeviceId(event.getDeviceId())) &#123;</span><br><span class="line">        mEventHandler.clearEvents(eventSource);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!state.deviceIdValid()) &#123;</span><br><span class="line">        <span class="built_in">super</span>.onInputEvent(event, policyFlags);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (event <span class="keyword">instanceof</span> MotionEvent) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((mEnabledFeatures &amp; FEATURES_AFFECTING_MOTION_EVENTS) != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">MotionEvent</span> <span class="variable">motionEvent</span> <span class="operator">=</span> (MotionEvent) event;</span><br><span class="line">            processMotionEvent(state, motionEvent, policyFlags);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>.onInputEvent(event, policyFlags);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (event <span class="keyword">instanceof</span> KeyEvent) &#123;</span><br><span class="line">        <span class="type">KeyEvent</span> <span class="variable">keyEvent</span> <span class="operator">=</span> (KeyEvent) event;</span><br><span class="line">        processKeyEvent(state, keyEvent, policyFlags);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继续看 processMotionEvent</p><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">private void processMotionEvent(EventStreamState <span class="keyword">state</span>, MotionEvent event, int policyFlags) &#123;</span><br><span class="line">    if (!<span class="keyword">state</span>.shouldProcessScroll() &amp;&amp; event.getActionMasked() == MotionEvent.ACTION_SCROLL) &#123;</span><br><span class="line">        super.<span class="keyword">on</span>InputEvent(event, policyFlags);</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (!<span class="keyword">state</span>.shouldProcessMotionEvent(event)) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    batchMotionEvent(event, policyFlags);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继续看 batchMotionEvent</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">batchMotionEvent</span><span class="params">(MotionEvent event, <span class="type">int</span> policyFlags)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">        Slog.i(TAG, <span class="string">&quot;Batching event: &quot;</span> + event + <span class="string">&quot;, policyFlags: &quot;</span> + policyFlags);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (mEventQueue == <span class="literal">null</span>) &#123;</span><br><span class="line">        mEventQueue = MotionEventHolder.obtain(event, policyFlags);</span><br><span class="line">        scheduleProcessBatchedEvents();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (mEventQueue.event.addBatch(event)) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">MotionEventHolder</span> <span class="variable">holder</span> <span class="operator">=</span> MotionEventHolder.obtain(event, policyFlags);</span><br><span class="line">    holder.next = mEventQueue;</span><br><span class="line">    mEventQueue.previous = holder;</span><br><span class="line">    mEventQueue = holder;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继续看 scheduleProcessBatchedEvents</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleProcessBatchedEvents</span><span class="params">()</span> &#123;</span><br><span class="line">    mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,</span><br><span class="line">            mProcessBatchedEventsRunnable, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>会在下一个 Vsync 周期的时候执行 mProcessBatchedEventsRunnable , 也就是 Choreographer.CALLBACK_INPUT ， 熟悉 Choregrapher 的同学应该知道这里在做什么。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Runnable</span> <span class="variable">mProcessBatchedEventsRunnable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">frameTimeNanos</span> <span class="operator">=</span> mChoreographer.getFrameTimeNanos();</span><br><span class="line">        &#125;</span><br><span class="line">        processBatchedEvents(frameTimeNanos);</span><br><span class="line">        <span class="keyword">if</span> (mEventQueue != <span class="literal">null</span>) &#123;</span><br><span class="line">            scheduleProcessBatchedEvents();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>那么代码在这里就比较清晰了， 是因为存在 AccessibilityInputFilter 导致 InputDIspatcher 线程没有被唤醒，而是把事件处理放到了下一个 Vsync 里面去处理。<br>结论</p><p>问题就在这个 Runnable 里面, 正常情况下, 如果没有打开 AccessibilityInputFilter , 那么上层不会对 Input 事件做任何的拦截, 一旦有 AccessibilityInputFilter , 那么就会走上面的逻辑, 这时候 InputDispatcher 不会跟着 InputReader 的节奏来走 , 而是跟着 Vsync 的节奏来走, 从  Trace 上也可看到这点;</p><p><img src="/images/media/performancecase/15482437370372.jpg"></p><p>那么这个 AccessibilityInputFilter 是从哪里来的呢？答案就是 Accessibility 服务，也就是常说的无障碍服务。</p><h1 id="罪魁祸首"><a href="#罪魁祸首" class="headerlink" title="罪魁祸首"></a>罪魁祸首</h1><p>经过上面的分析我们知道问题的原因是无障碍服务 ，无障碍服务的本质是为了服务哪些盲人之类的不方便操作的用户，但是某些 App 为了实现特定的功能，也加入了自己的 Accessibility 服务， 比如各大手机市场的“一键安装”功能，用户是方便了，但是用不好，也会有负面的作用，比如这一例，导致用户手机整机卡顿，不知道的用户，我估计都要退机了。</p><p>那么罪魁祸首是谁呢？目前发现有两个，一个讯飞输入法，一个是应用宝。打开 设置-系统-无障碍服务，可以看到里面的各种软件都有参与到，不过这个默认是关闭的，很多应用会引导用户去开启，许多用户不明所以，就稀里糊涂打开了。</p><p>无障碍服务页面如下：<br><img src="/images/media/performancecase/15482437928005.jpg"></p><p>关于无障碍服务有多NB，大家可以自己看看下面的弹框，这东西可以检测你的信用卡号和密码，至于短信内容、微信聊天内容那都是小 Case。</p><p><img src="/images/media/performancecase/15482438028084.jpg"></p><p>至于在这个例子里面引起整机卡顿的，就是下面这个 监听 ”执行手势“ 这个，一旦有应用监听这个的话， InputDIspatcher 线程就会走 Vsync 的周期，导致报点处理不及时，从而让滑动的对象以为这一帧没有事件进入，所以也没有内容的变更，就不会进行页面的更新，从而导致卡顿。<br><img src="/images/media/performancecase/15482438143185.jpg"></p><h1 id="自检"><a href="#自检" class="headerlink" title="自检"></a>自检</h1><p>如果你使用的是 Android 手机，强烈建议你关掉所有的无障碍服务（如果你不需要的话），像自动安装应用这种功能，不值得你为此付出这么大的风险。这个是 Android 原生的问题，我们在 Pixle 和 其他三方手机上都有发现这个问题。</p><ol><li><p>关闭路径：设置-系统-无障碍服务 ， 进去后把你已经打开的都关上。</p></li><li><p>强烈建议 <strong>应用宝、讯飞输入法</strong> ，不要监听手势事件。</p></li></ol><h1 id="本文知乎地址"><a href="#本文知乎地址" class="headerlink" title="本文知乎地址"></a>本文知乎地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎界面<br><a href="https://zhuanlan.zhihu.com/p/55585722">知乎 - Android 平台应用宝和讯飞输入法无障碍服务导致的全局卡顿分析</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;现象&quot;&gt;&lt;a href=&quot;#现象&quot; class=&quot;headerlink&quot; title=&quot;现象&quot;&gt;&lt;/a&gt;现象&lt;/h1&gt;&lt;p&gt;有用户反馈，手机在滑动的时候, 列表会一抖一抖的, 滑动桌面或者设置（只要是可以滑动的），都会出现，但是这个并不是必现，而是某些用户会出现，</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="卡顿" scheme="https://androidperformance.com/tags/%E5%8D%A1%E9%A1%BF/"/>
    
  </entry>
  
  <entry>
    <title>2018 年度好物推荐 - 给辛勤工作的自己一点奖励</title>
    <link href="https://androidperformance.com/2019/01/12/recommend-of-2018/"/>
    <id>https://androidperformance.com/2019/01/12/recommend-of-2018/</id>
    <published>2019-01-12T02:51:05.000Z</published>
    <updated>2026-02-07T05:17:47.877Z</updated>
    
    <content type="html"><![CDATA[<p>2018 年初写了一篇<a href="https://www.androidperformance.com/2018/01/06/2017%E5%B9%B4%E5%BA%A6%E6%9C%80%E6%8E%A8%E8%8D%90/">&lt;2017 年度最推荐-给辛勤工作的自己一点奖励&gt;</a> , 最近有人问我今年是否会有 2018 年的年度推荐，我想了想，2018 年虽然不景气，但是还是有一些好东西可以推荐给大家，于是便有了这一篇文章。</p><p>跟 2017年一样，我会把 2018 年里面我觉得体验很棒或者对工作生活很有帮助的东西推荐给大家，或许 2019 你会需要他们。推荐的内容包含了 App、硬件、书籍、博客、专栏等，希望能帮助到看到这篇文章的你</p><span id="more"></span><h1 id="最推荐-App"><a href="#最推荐-App" class="headerlink" title="最推荐 App"></a>最推荐 App</h1><h2 id="Rescuetime"><a href="#Rescuetime" class="headerlink" title="Rescuetime"></a>Rescuetime</h2><p><a href="https://www.rescuetime.com/">Rescuetime</a> 是一款记录时间花费的软件，它可以准确记录你每一天的时间都花在了哪里（需要安装对应的客户端）</p><p>比如你是一个程序员，你想知道自己每天的工作效率，那么使用 <a href="https://www.rescuetime.com/">Rescuetime</a> 绝对是个好的选择，你可以清晰地看到每天都花了时间在哪里。比如我，每天的大部分时间都是在 Android-Studio、VSCode、Terminator。下面是我1月15号的截图：<br><img src="/images/recomend-2018/15478860621933.jpg" alt="-w1049"></p><p><img src="/images/recomend-2018/15478860883680.jpg" alt="-w692"></p><p>编程栏的详细信息<br><img src="/images/recomend-2018/15478861440328.jpg" alt="-w974"></p><p>有了上面的数据，你就知道自己每天在电脑上的时间都去哪里了。 这个软件基本上支持所有的平台，尤其是 Linux（不过 iOS上由于系统的原因，只能查看没法记录）。</p><h2 id="石墨文档"><a href="#石墨文档" class="headerlink" title="石墨文档"></a>石墨文档</h2><p>2018 年我记录每日的工作的软件从 MWeb 换成了<a href="https://shimo.im/">石墨文档</a>，很大的原因是<a href="https://shimo.im/">石墨文档</a>支持多平台（Mac、iOS、Android、微信小程序），最重要的是 Web 端也非常好用。这样我就可以实时进行记录和查看，分享也非常的方便，可以设置只读和可读可写，多人协作很方便。</p><p><img src="/images/recomend-2018/15478867439552.jpg" alt="-w973"></p><p><img src="/images/recomend-2018/15478866944155.jpg" alt="-w949"></p><p>另外一个原因是 ：一个笔记网页端对程序员最大的尊重，应该是能拦截 和处理 Ctrl+S，印象笔记这个方面做的就很不好，总是出现保存网页，石墨文档则会保存内容。</p><h2 id="坚果云"><a href="#坚果云" class="headerlink" title="坚果云"></a>坚果云</h2><p>坚果云是一款提供网盘|云盘|云服务的团队协助软件,可随时随地实现共享文件夹。坚果云网盘支持移动办公,协同办公,文件同步,数据备份,智能管理,在线编辑等功能。</p><p>目前在国内支持文件同步的软件，我找来找去，最终还是用了<a href="https://www.jianguoyun.com/#/">坚果云</a>，很大原因是坚果云支持 Linux，在 Linux 下工作的时候，很多文件直接扔到坚果云的同步文件夹里面，其他各个平台就都有了，突出一个方便。</p><p><img src="/images/recomend-2018/15478871443953.jpg" alt="-w983"><br><img src="/images/recomend-2018/15478871670996.jpg" alt="-w953"></p><h1 id="最推荐技术书籍"><a href="#最推荐技术书籍" class="headerlink" title="最推荐技术书籍"></a>最推荐技术书籍</h1><h2 id="深入理解-Android-架构（第二版）"><a href="#深入理解-Android-架构（第二版）" class="headerlink" title="深入理解 Android 架构（第二版）"></a>深入理解 Android 架构（第二版）</h2><p><img src="/images/media/15152587790243.jpg" alt="深入理解 Android 内核设计思想"></p><p>算是系统开发的经典书了，这书今年出了第二版，加了一些新的内容。不管是应用开发工程师，还是系统开发工程师，多了解 Android 系统的架构和设计，对自己知识的深度是很有帮助的。</p><p>不过 Android 的版本发展实在是太快了，阅读此书建议配合最新的 Android 源代码。梳理流程的同时，也要深度思考设计思想。</p><blockquote><p>《深入理解Android内核设计思想》适用于 Android 4.3以上的版本。全书从操作系统的基础知识入手，全面剖析进程&#x2F;线程、内存管理、Binderv机制、GUIv显示系统、多媒体管理、输入系统等核心技术在 Android 中的实现原理。书中讲述的知识点大部分来源于工程项目研发，因而具有较强的实用性，希望可以让读者“知其然，更知其所以然”。全书分为编译篇、系统原理篇、应用原理篇、系统工具篇共4篇22章，基本涵盖了参与Android开发所需具备的知识，并通过大量图片与实例来引导读者学习，以求尽量在源代码分析外为读者提供更易于理解的思维方式。</p></blockquote><blockquote><p>《深入理解Android内核设计思想》既适合 Android 系统工程师，也适合于应用开发工程师来阅读提升Android开发能力。读者可以在《深入理解vAndroidv内核设计思想》潜移默化的学习过程中更深刻地理解Android系统，并将所学知识自然地应用到实际开发难题的解决中。</p></blockquote><p>豆瓣 ： <a href="https://book.douban.com/subject/25921329/">https://book.douban.com/subject/25921329/</a></p><h2 id="Android-进阶解密"><a href="#Android-进阶解密" class="headerlink" title="Android 进阶解密"></a>Android 进阶解密</h2><p><img src="/images/recomend-2018/15478811989772.jpg" alt="Android 进阶解密"></p><p>《Android进阶解密》是一本Android进阶书籍，主要针对Android 8.0系统源码并结合应用开发相关知识进行介绍。</p><p>《Android进阶解密》共分为17章，从3个方面来组织内容。 第一方面介绍Android应用开发所需要掌握的系统源码知识，第二方面介绍JNI、ClassLoader、Java虚拟机、DVM&amp;ART虚拟机和Hook等技术，第三方面介绍热修复原理、插件化原理、绘制优化和内存优化等与应用开发相关的知识点。3个方面有所关联并形成一个知识体系，从而使Android开发者能通过阅读本书达到融会贯通的目的。</p><p>《Android进阶解密》适合有一定基础的Android应用开发工程师、Android系统开发工程师和对Android系统源码感兴趣的读者阅读。</p><p>jd : <a href="https://item.jd.com/12447229.html">https://item.jd.com/12447229.html</a><br>##奔跑吧 Linux 内核<br><img src="/images/media/15152591841672.jpg" alt="奔跑吧 Linux 内核"><br>Android 系统工程师必备。</p><p>本书内容基于Linux4.x内核，主要选取了Linux内核中比较基本和常用的内存管理、进程管理、并发与同步，以及中断管理这4个内核模块进行讲述。全书共分为6章，依次介绍了ARM体系结构、Linux内存管理、进程调度管理、并发与同步、中断管理、内核调试技巧等内容。本书的每节内容都是一个Linux内核的话题或者技术点，读者可以根据每小节前的问题进行思考，进而围绕问题进行内核源代码的分析。</p><p>本书内容丰富，讲解清晰透彻，不仅适合有一定Linux相关基础的人员，包括从事与Linux相关的开发人员、操作系统的研究人员、嵌入式开发人员及Android底层开发人员等学习和使用，而且适合作为对Linux感兴趣的程序员的学习用书，也可以作为大专院校相关专业师生的学习用书和培训学校的教材。</p><p>豆瓣 ： <a href="https://book.douban.com/subject/27108677/">https://book.douban.com/subject/27108677/</a></p><h1 id="最推荐非技术书籍"><a href="#最推荐非技术书籍" class="headerlink" title="最推荐非技术书籍"></a>最推荐非技术书籍</h1><h2 id="镖人"><a href="#镖人" class="headerlink" title="镖人"></a>镖人</h2><p><img src="/images/recomend-2018/15478820694446.jpg"></p><p>目前出到了第五卷，豆瓣评分 8.3 的国漫，微信读书 5 卷全都有，非常方便看。</p><p><a href="https://book.douban.com/subject/30163844/">《镖人》</a>，一部重现隋唐江湖的热血漫画！</p><p>大业三年（公元607年），隋王朝在隋炀帝杨广的残暴统治下民不聊生。身手不凡的镖客刀马行走于西域大漠，在躲避朝廷追杀的途中，他接下了一个目的地为首都长安的护送任务，本以为只是一趟简单的护镖，却没想到一路危机四伏，险象环生。一场牵动天下命运的旅途就此拉开帷幕……</p><p>豆瓣：<br><a href="https://book.douban.com/subject/30163844/">《镖人1》</a><br><a href="https://book.douban.com/subject/30218904/">《镖人2》</a><br><a href="https://book.douban.com/subject/30247517/">《镖人3》</a><br><a href="https://book.douban.com/subject/30331243/">《镖人4》</a><br><a href="https://book.douban.com/subject/30394166/">《镖人5》</a></p><h2 id="大败局2"><a href="#大败局2" class="headerlink" title="大败局2"></a>大败局2</h2><p><img src="/images/recomend-2018/15478822095577.jpg"><br>之前读过<a href="https://book.douban.com/subject/1072438/">《大败局1》</a>，被里面的故事深深的吸引，正如豆瓣评论：“三流的文笔，一流的现实。时代的洪流中，企业家、创始人，与赌徒并无区别。野心家迷失于成功的光环，却只能在惨败后看清自己。”</p><p><a href="https://book.douban.com/subject/25908390/">《大败局2》</a>解读九大著名企业盛极而衰的失败原因：“中国第一饮料品牌”是如何陨落的？家电业最具现代气质的公司，是怎样被肢解和蹂躏的？中国民营企业的航母，为何会彻底沉没？股市庄家如何布下资本迷局？最具想象力的汽车革命为什么会一夜流产？房地产最大的黑马失陷何处？最低调的钢铁公司如何迎来最致命的打击？中药业的领头兵因何溃不成军？资本狂人究竟是在点燃全民的热情，还是在玩火自焚？</p><p>在<a href="https://book.douban.com/subject/25908390/">《大败局2》</a>中，我们更多地看到了一种“工程师+赌徒”的商业人格模式。他们往往有较好的专业素养，在某些领域有超人的直觉和运营天赋，同时更有着不可遏制的豪情赌性，敢于在机遇降临的那一刻，倾命一搏。这是企业家职业中最惊心动魄的一跳，成者上天堂，败者落地狱，其微妙控制完全取决于天时、地利与人和等因素。</p><h2 id="程序员的修炼–从优秀到卓越"><a href="#程序员的修炼–从优秀到卓越" class="headerlink" title="程序员的修炼–从优秀到卓越"></a>程序员的修炼–从优秀到卓越</h2><p><img src="/images/recomend-2018/15478826704343.jpg"></p><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了 Coding Horror 博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web 设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下。</p><p>Jeff Atwood 于 2004 年创办 Coding Horror 博客，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近 10 万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞。</p><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折。</p><p>我的一些读书笔记：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><h1 id="最推荐公众号"><a href="#最推荐公众号" class="headerlink" title="最推荐公众号"></a>最推荐公众号</h1><ol><li>hongyangAndroid 鸿洋</li><li>Google_Developers 谷歌开发者</li><li>guolin_blog 郭霖</li><li>googdev 张奇</li><li>LinuxDev</li><li>hencoder 扔物线</li><li>androidperf ‘Android性能优化</li><li>nanchen_android 南尘</li><li>flutter-io</li><li>腾讯 Bugly</li></ol><h1 id="软件服务"><a href="#软件服务" class="headerlink" title="软件服务"></a>软件服务</h1><h2 id="微信读书会员"><a href="#微信读书会员" class="headerlink" title="微信读书会员"></a>微信读书会员</h2><p>今年付费会员里面感觉最值的，就是微信读书的年费会员了。年费会员可以免费看所有的付费出版物，包括听书。所以今年我的大部分的书都是在微信读书里面读的，所以我觉得这个会员办的很值，如果你是一位喜欢读书的人，那么办个会员吧。 我在微信读书读完的书包括</p><ol><li>《日据时期台湾与大陆关系史》</li><li>《软技能：代码之外的生存指南》</li><li>《大败局1》 + 《大败局2》</li><li>《八卦微积分》</li><li>《刻意练习》</li><li>《镖人1、2、3、4》</li><li>《谷物大脑》</li><li>《股权战争》</li><li>《深度学习》</li><li>《无缘社会》</li><li>《三体》</li><li>《图解 HTTP》</li><li>《万里十五年》</li><li>《黑客与画家》</li><li>《海湾战争中的地面作战》</li><li>《浅谈日本 IT 行业》</li><li>《骗局》</li><li>《柏林墙》</li><li>《万万没想到》</li><li>《硅谷钢铁侠》</li><li>《逃离德黑兰》</li><li>《芳华》</li></ol><h1 id="最推荐消费品"><a href="#最推荐消费品" class="headerlink" title="最推荐消费品"></a>最推荐消费品</h1><h2 id="12-9-iPad-pro"><a href="#12-9-iPad-pro" class="headerlink" title="12.9 iPad pro"></a>12.9 iPad pro</h2><p>2018 年的新版的 <a href="https://item.jd.com/100000206154.html">iPad Pro</a> ，不论是在外观、性能、还是配置、价格，都是一个字：NB； 基本上可以拿来当电脑用，不论是看书、看剧、做笔记、看PDF 都非常的舒服，120 HZ 的刷新率简直是一种享受。</p><p>如果你计划入手 ipad 的话，强烈建议你入手这款，我自己的 iPad Pro 10.5 那版感觉屏幕有点小了。</p><p><img src="/images/recomend-2018/15478853976136.jpg"></p><h1 id="最推荐专栏"><a href="#最推荐专栏" class="headerlink" title="最推荐专栏"></a>最推荐专栏</h1><h2 id="极客时间-Android-开发高手课"><a href="#极客时间-Android-开发高手课" class="headerlink" title="极客时间 - Android 开发高手课"></a>极客时间 - Android 开发高手课</h2><p>《Android 开发高手课》是极客时间推出的专门为 Android 开发者定制的课程，专栏内容包括奔溃、内存、卡顿、启动、IO、存储、网络、耗电、UI、安装包体积等常见的复杂问题的原理分析和借鉴方法，非常值得 Android 开发工程师学习。</p><p>微信扫描下面的二维码即可加入学习<br><img src="/images/recomend-2018/15478836427121.jpg"></p><p><img src="/images/recomend-2018/15478835714307.jpg"></p><h2 id="极客时间-Linux-性能优化实战"><a href="#极客时间-Linux-性能优化实战" class="headerlink" title="极客时间 - Linux 性能优化实战"></a>极客时间 - Linux 性能优化实战</h2><p>《Linux 性能优化实战》是极客时间推出的面向 Linux 和 Android 底层开发者的课程，作者是微软 Azure 资深工程师。专栏中他会以案例驱动的思路，从实际问题触发，带你由浅入深学习一些基本的底层原理，掌握常见的性能指标和工具，学习实际工作中的优化技巧，让你可以准确分析和优化大多数性能问题。</p><p>微信扫描下面的二维码即可加入学习<br><img src="/images/recomend-2018/15478839286830.jpg"></p><p>其目录如下：<br><img src="/images/recomend-2018/15478838836765.jpg"></p><h2 id="得到-香帅的北大金融学课"><a href="#得到-香帅的北大金融学课" class="headerlink" title="得到 - 香帅的北大金融学课"></a>得到 - 香帅的北大金融学课</h2><p>香帅，真名唐涯，北京大学光华管理学院金融系副教授、博士生导师。香帅承诺用一年的时间，让你透彻掌握金融学的核心知识、全面理解金融学的架构和本质，建立一套完整的金融学思维。内容包好近日世界观、金融机构、工具与市场、投资者决策、公司决策、监管创新与危机、科技金融、金融术与道。</p><p>听这门课，记得老老实实做笔记。</p><p>其目录如下<br><img src="/images/recomend-2018/15478843588502.jpg"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;2018 年初写了一篇&lt;a href=&quot;https://www.androidperformance.com/2018/01/06/2017%E5%B9%B4%E5%BA%A6%E6%9C%80%E6%8E%A8%E8%8D%90/&quot;&gt;&amp;lt;2017 年度最推荐-给辛勤工作的自己一点奖励&amp;gt;&lt;/a&gt; , 最近有人问我今年是否会有 2018 年的年度推荐，我想了想，2018 年虽然不景气，但是还是有一些好东西可以推荐给大家，于是便有了这一篇文章。&lt;/p&gt;
&lt;p&gt;跟 2017年一样，我会把 2018 年里面我觉得体验很棒或者对工作生活很有帮助的东西推荐给大家，或许 2019 你会需要他们。推荐的内容包含了 App、硬件、书籍、博客、专栏等，希望能帮助到看到这篇文章的你&lt;/p&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://androidperformance.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="年度推荐" scheme="https://androidperformance.com/tags/%E5%B9%B4%E5%BA%A6%E6%8E%A8%E8%8D%90/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Android 系统开发源码环境搭建</title>
    <link href="https://androidperformance.com/2018/11/01/android-system-develop-0/"/>
    <id>https://androidperformance.com/2018/11/01/android-system-develop-0/</id>
    <published>2018-11-01T11:40:16.000Z</published>
    <updated>2026-02-07T05:17:47.863Z</updated>
    
    <content type="html"><![CDATA[<p>不管是 Android App 开发工程师还是 Android 系统开发工程师，对 Android 系统有一定的了解是很有必要的，正好我这几天在家里搭建了个开发环境，把过程分享出来，有需要的人可以看一下。</p><p>大概的步骤包含下面几个：</p><ol><li>安装 Ubuntu 系统</li><li>配置 Ubuntu 系统</li><li>安装必备的软件</li><li>配置 VPN (可选)</li><li>下载 AOSP 代码</li><li>配置编译环境并编译 Pixel 的代码</li><li>刷机</li><li>修改和编译 Framework 、Service、Res</li></ol><p>建议准备下面的硬件，当然没有也没关系，有了更好</p><ol><li>PC 或者笔记本</li><li>512GB 的 SSD</li><li>Pixel 手机一台</li></ol><h1 id="安装-Ubuntu-系统"><a href="#安装-Ubuntu-系统" class="headerlink" title="安装 Ubuntu 系统"></a>安装 Ubuntu 系统</h1><p>Linux 这边我建议用 Ubuntu 系统，不建议用虚拟机，直接安装一个新的 Ubuntu 系统会比较好，Ubuntu 目前最新的 LTS 版本是 18.04，目前安装 Ubuntu 的步骤会比较简单：</p><ol><li>下载 Ubuntu18.04 版本:<a href="http://mirrors.opencas.cn/ubuntu-releases/18.04.1/ubuntu-18.04.1-desktop-amd64.iso">ubuntu 18.04</a></li><li>使用 Ubuntu 推荐的工具做一个 U 盘启动盘</li><li>使用 U 盘安装 Ubuntu 系统</li></ol><p><img src="/images/android-system/mydesktop.webp" alt="mydesktop"></p><h1 id="配置-Ubuntu-系统"><a href="#配置-Ubuntu-系统" class="headerlink" title="配置 Ubuntu 系统"></a>配置 Ubuntu 系统</h1><ol><li>安装搜狗输入法</li><li>安装 vim ：sudo apt install vim</li><li>安装 adb </li><li>安装 fastboot</li></ol><h1 id="安装必备的软件"><a href="#安装必备的软件" class="headerlink" title="安装必备的软件"></a>安装必备的软件</h1><ol><li>VS Code</li><li>Android Studio</li><li>Meld</li><li>Wine</li><li>WPS</li></ol><h1 id="配置-VPN-可选"><a href="#配置-VPN-可选" class="headerlink" title="配置 VPN (可选)"></a>配置 VPN (可选)</h1><ol><li>ShadowSocks</li></ol><h1 id="下载-AOSP-代码"><a href="#下载-AOSP-代码" class="headerlink" title="下载 AOSP 代码"></a>下载 AOSP 代码</h1><p>没有 v-p-n 的话，推荐使用清华的镜像站：<a href="https://mirror.tuna.tsinghua.edu.cn/help/AOSP/">https://mirror.tuna.tsinghua.edu.cn/help/AOSP/</a></p><p>## repo 下载   </p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mkdir ~/bin</span><br><span class="line">PATH=~/bin:<span class="variable">$PATH</span></span><br><span class="line">curl https:<span class="regexp">//</span>storage.googleapis.com<span class="regexp">/git-repo-downloads/</span>repo &gt; ~<span class="regexp">/bin/</span>repo</span><br><span class="line">chmod a+x ~<span class="regexp">/bin/</span>repo</span><br></pre></td></tr></table></figure><h2 id="建立工作目录"><a href="#建立工作目录" class="headerlink" title="建立工作目录"></a>建立工作目录</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> WORKING_DIRECTORY</span><br><span class="line"><span class="built_in">cd</span> WORKING_DIRECTORY</span><br></pre></td></tr></table></figure><h2 id="初始化仓库"><a href="#初始化仓库" class="headerlink" title="初始化仓库"></a>初始化仓库</h2><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">repo init -u https:<span class="regexp">//</span>aosp.tuna.tsinghua.edu.cn<span class="regexp">/platform/m</span>anifest</span><br></pre></td></tr></table></figure><h2 id="下载代码（-c-–no-tags-能下载更少的代码）"><a href="#下载代码（-c-–no-tags-能下载更少的代码）" class="headerlink" title="下载代码（-c –no-tags 能下载更少的代码）"></a>下载代码（-c –no-tags 能下载更少的代码）</h2><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">repo <span class="keyword">sync</span> -<span class="keyword">c</span> --<span class="keyword">no</span>-<span class="keyword">tags</span></span><br></pre></td></tr></table></figure><h1 id="配置编译环境并编译-Pixel-的代码"><a href="#配置编译环境并编译-Pixel-的代码" class="headerlink" title="配置编译环境并编译 Pixel 的代码"></a>配置编译环境并编译 Pixel 的代码</h1><h2 id="配置编译环境"><a href="#配置编译环境" class="headerlink" title="配置编译环境"></a>配置编译环境</h2><h3 id="安装jdk"><a href="#安装jdk" class="headerlink" title="安装jdk"></a>安装jdk</h3><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-<span class="built_in">get</span> update</span><br><span class="line">sudo apt-<span class="built_in">get</span> install openjdk-8-jdk</span><br></pre></td></tr></table></figure><h3 id="安装相关依赖"><a href="#安装相关依赖" class="headerlink" title="安装相关依赖"></a>安装相关依赖</h3><figure class="highlight q"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-<span class="built_in">get</span> install git-core gnupg flex bison gperf build-essential zip curl zlib1g-<span class="built_in">dev</span> gcc-multilib g++-multilib libc6-<span class="built_in">dev</span>-i386 lib32ncurses5-<span class="built_in">dev</span> x11proto-core-<span class="built_in">dev</span> libx11-<span class="built_in">dev</span> lib32z-<span class="built_in">dev</span> libgl1-mesa-<span class="built_in">dev</span> libxml2-utils xsltproc unzip</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="下载-Pixle-的驱动"><a href="#下载-Pixle-的驱动" class="headerlink" title="下载 Pixle 的驱动"></a>下载 Pixle 的驱动</h2><p>编译 Android Master 的代码的话，需要下载对应的手机的驱动，在这个页面找到自己需要的驱动：<br><a href="https://developers.google.cn/android/blobs-preview">https://developers.google.cn/android/blobs-preview</a><br><a href="/images/android-system/extract-qcom-sailfish.">extract-qcom-sailfish</a></p><p>解压如下：<br><img src="/media/extract-google_devices-sailfish-1.webp" alt="extract-google_devices-sailfish"></p><h2 id="编译-Pixle-的系统镜像"><a href="#编译-Pixle-的系统镜像" class="headerlink" title="编译 Pixle 的系统镜像"></a>编译 Pixle 的系统镜像</h2><p>在源码根目录下执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">source</span> build/envsetup.sh</span><br></pre></td></tr></table></figure><p>执行下面的命令选择要编译的手机型号和版本（user、userdebug、eng）</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">lunch</span></span><br></pre></td></tr></table></figure><p><img src="/images/android-system/lunch.webp" alt="lunch"></p><p>选择好了之后，输入对应的数字或者数字后面的,执行 make 开始编译（可选择加 -j4，4带代表线程数，机器性能好的话可以8或者16，看cpu）：</p><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">make</span> -j8</span><br></pre></td></tr></table></figure><p><img src="/images/android-system/make-j8.webp" alt="make-j8"></p><h2 id="编译成功"><a href="#编译成功" class="headerlink" title="编译成功"></a>编译成功</h2><p>out 目录会生成对应的 image<br><img src="/images/android-system/out_folder.webp" alt="out_folde"></p><h1 id="刷机"><a href="#刷机" class="headerlink" title="刷机"></a>刷机</h1><p>在源码根目录下，执行下面的命令,即可刷入对应的系统到</p><p><img src="/images/android-system/fastboot.webp" alt="fastboot"></p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">fastboot flashall</span></span><br></pre></td></tr></table></figure><p><img src="/images/android-system/fastboot-flashall.webp" alt="fastboot-flashal"></p><h1 id="修改和编译-Framework-、Service、Res"><a href="#修改和编译-Framework-、Service、Res" class="headerlink" title="修改和编译 Framework 、Service、Res"></a>修改和编译 Framework 、Service、Res</h1><p>以下命令都在源码根目录下执行</p><h2 id="修改代码-IDE"><a href="#修改代码-IDE" class="headerlink" title="修改代码 IDE"></a>修改代码 IDE</h2><h3 id="java代码"><a href="#java代码" class="headerlink" title="java代码"></a>java代码</h3><p>java 代码推荐使用 AndroidStudio 打开、编辑</p><h3 id="c-x2F-cpp代码"><a href="#c-x2F-cpp代码" class="headerlink" title="c&#x2F;cpp代码"></a>c&#x2F;cpp代码</h3><p>c&#x2F;cpp 代码推荐使用 SourceInsight 、Eclipse、VS Code 打开、编辑</p><h2 id="编译-Framework"><a href="#编译-Framework" class="headerlink" title="编译 Framework"></a>编译 Framework</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mmm framework/base</span><br></pre></td></tr></table></figure><h2 id="编译-Services"><a href="#编译-Services" class="headerlink" title="编译 Services"></a>编译 Services</h2><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mmm frameworks<span class="regexp">/base/</span>services</span><br></pre></td></tr></table></figure><h2 id="编译-res"><a href="#编译-res" class="headerlink" title="编译 res"></a>编译 res</h2><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mmm frameworks<span class="regexp">/base/</span>core/res</span><br></pre></td></tr></table></figure><h2 id="root-amp-amp-remount"><a href="#root-amp-amp-remount" class="headerlink" title="root &amp;&amp; remount"></a>root &amp;&amp; remount</h2><p><img src="/images/android-system/root--remount.webp" alt="root--remount"></p><h2 id="push"><a href="#push" class="headerlink" title="push"></a>push</h2><p>root &amp;&amp; remount 之后，就可以把对应的 framework、Services、Res 等 push 进去，重启 shell 即可生效,或者直接 adb sync system 即可, sync system 会把 out目录下对应机型的 system 目录和手机的 system 目录进行同步，很是方便。</p><p>例子：</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb root <span class="variable">&amp;&amp;</span> adb remount <span class="variable">&amp;&amp;</span> adb shell <span class="keyword">stop</span> <span class="variable">&amp;&amp;</span> adb sync system <span class="variable">&amp;&amp;</span> adb shell start</span><br></pre></td></tr></table></figure><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;不管是 Android App 开发工程师还是 Android 系统开发工程师，对 Android 系统有一定的了解是很有必要的，正好我这几天在家里搭建了个开发环境，把过程分享出来，有需要的人可以看一下。&lt;/p&gt;
&lt;p&gt;大概的步骤包含下面几个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Framework" scheme="https://androidperformance.com/tags/Framework/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>陆奇：除了好代码，工程师怎样才算优秀？</title>
    <link href="https://androidperformance.com/2018/10/25/How-do-engineers-count-well/"/>
    <id>https://androidperformance.com/2018/10/25/How-do-engineers-count-well/</id>
    <published>2018-10-25T02:37:34.000Z</published>
    <updated>2026-02-07T05:17:47.854Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>转自：百度Family，内容为陆奇 2017 年 7 月 的百度内部分享，非常值得技术人员学习和思考</p></blockquote><p>个人非常崇拜<a href="https://zh.wikipedia.org/wiki/%E9%99%B8%E5%A5%87">陆奇</a>,  “陆奇以精力旺盛著称，通常凌晨4点起床，先查邮件，然后在跑步机上跑4英里，边跑边听古典音乐或看新闻。早上5点至6点就办公室，利用这段时间不受别人干扰准备一天的工作，然后一直工作到晚上10点，有时也会在半夜给同事发电子邮件。LinkedIn CEO 杰夫·维纳(Jeff Weiner)曾在雅虎与陆奇共事多年，他开始以为这种日程安排无法持久，后来不得不说：“陆奇的确是我所见过最有干劲的人。”前雅虎工程师阿米特·库玛尔(Amit Kumar)也夸奖陆奇人缘好。陆奇说：“我不觉得累，我热爱每天的工作。”陆奇获有20项美国专利。”</p><p><img src="/images/media/15404360836694.jpg" alt="陆奇"></p><p>陆奇的演讲核心思想可以归纳为下面五个点，我把它贴在了书房的墙上，时刻提醒自己。</p><blockquote><ol><li>“我们一定要有一个坚定不移的深刻的理念，相信整个世界终究是为技术所驱动的。”</li><li>“有没有其他人已经解决这个问题？然后你可以把你的时间放在更好的创新上。” </li><li>“做什么事情一定要做最好，一定要是做业界最强的。”</li><li>“我把自己想象是一个软件、一个代码，今天的版本一定要比昨天版本好，明天的版本肯定会比今天好。”</li><li>“看到问题也不要去问别人，就把它 Fix。”</li></ol></blockquote><h1 id="Believe-in-技术"><a href="#Believe-in-技术" class="headerlink" title="Believe in 技术"></a>Believe in 技术</h1><p>首先要相信技术，我刚才已经讲了，整个我们工业界，特别是像百度这样的公司，对技术坚定的、不动摇的信念特别重要。</p><p>我也分享一下，盖茨提到微软公司的宗旨就是：写软件代表的是世界的将来。</p><p>为什么？未来任何一个工业都会变成软件工业。盖茨是对的，因为任何工业任何行业自动化的程度会越来越高，最后你所处理的就是信息和知识。</p><p>但现在软件的做法又往前提了一次，因为在人工智能时代，不光是写代码，你必须懂算法，懂硬件，懂数据，整个人工智能的开发过程有一个很大程度的提高，但是，技术，特别是我们这个工业所代表的技术一定是将来任何工业的前沿。</p><p>所以我们一定要有一个坚定不移的深刻的理念，<strong>相信整个世界终究是为技术所驱动的</strong>。</p><h1 id="站在巨人的肩膀上做创新"><a href="#站在巨人的肩膀上做创新" class="headerlink" title="站在巨人的肩膀上做创新"></a>站在巨人的肩膀上做创新</h1><p>我们观察一下，在美国硅谷、在中国，互联网创业公司也好，大型公司也好，大家的起点是越来越高的。为什么现在创新速度那么快？主要是起点高了。我们可以使用的代码模块，使用的服务的能力，都是大大的提升。</p><p>在内部我想强调这一点，很多大公司包括微软在内，内部的Code都重做了无数遍。</p><p>我现在的要求是，<strong>每一次你写一行新的代码，第一要做的，先想一想你这行代码值得不值得写，是不是有人已经做了同样的工作，可能做得比你还好一点。有没有其他人已经解决这个问题，然后你可以把你的时间放在更好的创新上。</strong></p><p>特别是大公司里面重复或者是几乎重复的Code实在太多，浪费太多的资源，对每个人的职业生涯都不是好事情。</p><p>我再强调，在大公司内部，你写代码之前想一想，你这行代码要不要写，是不是别人已经有了，站在别人的肩膀上去做这件事情。</p><h1 id="追求Engineering-Excellence"><a href="#追求Engineering-Excellence" class="headerlink" title="追求Engineering Excellence"></a>追求Engineering Excellence</h1><p>我要另外强调的一点就是Engineering Excellence，工程的技术的卓越性和能力。</p><p>任何市场上竞争就像打仗一样，就看你的部队体能、质量，每一个士兵他的训练的程度，和你给他使机关枪、坦克，还是什么样的武器。</p><p>所以Engineering Excellence跟这个类比，我们要建的是一支世界上最强的部队，每一个士兵，每一个领军人，每个人的能力，他的训练都是超强的，然后我们给每个人提供的工具和武器都是一流的。</p><p>所以 Engineering Excellence 是一个永无止境的、个人的、团队的，能力的追求和工具平台的创新，综合在一起可以给我们带来的长期的、核心的竞争力，为社会创造价值，最终的目的是给每个用户、每个企业、整个社会创造价值。</p><p>我另外还要在这里强调的一点就是 Relentless pursuit of excellence：<strong>永无止境的不断的持续的追求。</strong></p><p><strong>我们要么不做，要做的事情一定做最好</strong>，这是我对大家的要求。数据库也好，做大平台也好，大数据也好，我们要做什么事情，我们一定要下决心，这是我对你们每个人的要求，做什么事情一定要做最好，一定要是做业界最强的。</p><h1 id="每天学习"><a href="#每天学习" class="headerlink" title="每天学习"></a>每天学习</h1><p>每天学习，可能是对每个人都是最最重要的。</p><p>我今天分享一下，我自己怎么想我自己的。就很简单一个概念，我把自己想象是一个软件、一个代码，今天的版本一定要比昨天版本好，明天的版本肯定会比今天好，因为即使犯了错误，我里面有If statement，说如果见到这个错误，绝对不要再犯。</p><p>英语，另外有一句说法就是Life is too short, don’t live the same day twice. 同样一天不要重活两次。每天都是不一样，每天为什么不一样，因为每天都变成最好，每天都变得更好。今天的版本一定要比昨天好，每个好的、杰出的工程师，杰出的技术领袖，一定要保持自己学习的能力，特别是学习的范围。</p><p>在这上面我也稍微引申一下，做Computer science的，如果只学Computer science，不去学一些其他的行业，肯定不够。我举个例子，经济学必须要学。为什么这样讲？Computer science它有个很大的限制，他是假定你有输入以后有输出，这种解决问题的方式有它的好处，但有它的限制性。</p><p>我给大家举个例子，地图导航，如果你纯粹用这个方式去做，你只是把一个拥挤的地方移到另外一个拥挤的地方。经济学，它对问题的建模是不一样的。它起点是假定是一个整体的一个生态，每个人的输入都是另外一个人的输出，你要用经济学的方式来描述地图导航的问题，你就会去算一个Equilibrium，市场也是这样。</p><p>如果把深度学习真的要想彻底，必须把物理重学一遍，把生物学看一遍，再把进化论再看一遍。因为深度学习跟这些东西完全相关，自己肯定想不清楚，要彻底想清楚，必须学。</p><p>另外，学产品，我以前跟所有的工程师都讲，如果不懂产品，你不可能成为一个最好的工程师。真正要做世界一流的工程师不光要懂产品，还要懂整个商业，懂生态。因为你的工作的责任，是能够看到将来，把技术展望到将来的需求，把平台、把开发流程、把你的团队为将来做准备。所以学习是非常非常重要的。</p><h1 id="Ownership"><a href="#Ownership" class="headerlink" title="Ownership"></a>Ownership</h1><p>最后是从我做起。</p><p>我们公司有个非常大的使命，用科技让复杂的世界更简单。整个世界非常非常复杂，人其实所做的事情基本上都是Reduce entropy。</p><p>因为从热力学第二定律来讲，世界是会变得越来越乱的，我们想做的事情就是把它变的更简单，让我们生活变得更美好。</p><p>而且具体的，我们可以通过人工智能技术来做到唤醒万物，但是这一切是通过每一个人的一点一滴的行为累计起来，从我做起。还有Ownership，看到机会不需要问别人，有机会就去做，看到问题也不要去问别人，就把它Fix。</p><p>把我们的使命、把我们的公司当成我们自己每个人的事业来做，我可以坦诚的给每个人讲，如果你把公司的使命，把公司的事业，当成你自己个人的事业，Own everything，你在职业生涯一定是走得最快。从我做起，从身边的每一件事情做起。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;转自：百度Family，内容为陆奇 2017 年 7 月 的百度内部分享，非常值得技术人员学习和思考&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;个人非常崇拜&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%99%</summary>
      
    
    
    
    <category term="随笔" scheme="https://androidperformance.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="程序员" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-08-阅读之美</title>
    <link href="https://androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/"/>
    <id>https://androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/</id>
    <published>2018-10-01T06:34:15.000Z</published>
    <updated>2026-02-07T05:17:47.876Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第八篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="不读书，谁之过"><a href="#不读书，谁之过" class="headerlink" title="不读书，谁之过"></a>不读书，谁之过</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/programmers-dont-read-books-but-you-should/">Programmers Don’t Read Books – But You Should</a>)</em></p><p>俗话说：“程序员不读书。” Jeff 探讨了为什么会这样。通常，技术书籍很枯燥，或者在印刷出来的时候就已经过时了，或者只是单纯的无聊。对于我们许多人来说，互联网已经取代了参考书。</p><p>然而，Jeff 认为<strong>我们错失良机</strong>。深度的知识往往需要长篇结构，这只有书（或者一系列非常长的文章）才能提供。阅读代码很重要，但阅读<em>关于</em>代码的书籍——哲学、架构、“为什么”——也同样至关重要。</p><p>我们不能只怪行业或媒介。如果我们不读书，那是我们自己的错。我们会停滞不前。我们错过了我们所站立的“巨人”的智慧。像《程序员修炼之道（The Pragmatic Programmer）》、《代码大全（Code Complete）》和《人月神话（The Mythical Man-Month）》这样的书之所以永恒，是有原因的。</p><h2 id="自助者，天助之"><a href="#自助者，天助之" class="headerlink" title="自助者，天助之"></a>自助者，天助之</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/how-to-ask-questions-the-smart-way/">How To Ask Questions The Smart Way</a> 和 <a href="https://blog.codinghorror.com/what-have-you-tried/">What have you tried?</a>)</em></p><p>本节呼应了 Stack Overflow 的核心哲学：<strong>自力更生</strong>。</p><p>在你寻求帮助之前，你必须证明你已经尝试过自助。“天助自助者”意味着如果你没有先付出努力，就不应该期望别人来解决你的问题。</p><p>Jeff 著名的提问是：<strong>“你尝试过什么？”</strong></p><p>如果你遇到一个 Bug，不要只发布错误信息。告诉我们：</p><ol><li>你预期发生什么。</li><li>实际发生了什么。</li><li>你为了解决它已经做了什么。</li></ol><p>这不仅仅是礼貌问题；这是关于成为一名更好的工程师。组织问题和记录你尝试过的步骤的行为，往往会在你提问之前就引导你找到答案（橡皮鸭调试法）。</p><h2 id="计算机犯罪的历史与现状"><a href="#计算机犯罪的历史与现状" class="headerlink" title="计算机犯罪的历史与现状"></a>计算机犯罪的历史与现状</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/computer-crime-then-and-now/">Computer Crime, Then and Now</a> 和 <a href="https://blog.codinghorror.com/i-was-a-teenage-hacker/">I Was a Teenage Hacker</a>)</em></p><p>在 “I Was a Teenage Hacker” 中，Jeff 讲述了他年轻时与计算机黑暗面的一次小接触——编写了一个战争拨号器（wardialer）脚本。他用这个轶事来讨论计算机犯罪的演变。</p><p>几十年前，黑客行为往往是出于好奇或为了出名。今天，它是一个巨大的、有组织的犯罪产业。但有趣的是，<strong>技术并没有太大的变化</strong>。社会工程学、弱密码和未修补的漏洞仍然是主要的攻击载体。</p><p><em>真正改变</em>的是规模和利害关系。我们要么现在是“默认数字化”的，这意味着违规不仅仅是烦恼；它是对我们就身份、财务和基础设施的威胁。了解计算机犯罪的历史有助于我们认识到，安全不是你购买的产品；它是你必须践行的过程。</p><h2 id="如何与人交流"><a href="#如何与人交流" class="headerlink" title="如何与人交流"></a>如何与人交流</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/the-art-of-speaking/">The Art of Speaking</a> 和 <a href="https://blog.codinghorror.com/communication-skills/">Communication Skills</a>)</em></p><p>理想情况下，代码应该能自己说话。实际上，<strong>沟通是程序员最重要的技能。</strong></p><p>你可以是世界上最好的编码员，但如果你不能向你的团队解释你的想法，说服你的经理，或理解你的用户，你就会失败。Jeff 强调了清晰写作（写博客有帮助！）和自信演讲的重要性。</p><p>软技能很难。它们需要同理心、耐心和练习。但它们是你技术技能的倍增器。</p><h2 id="勤练基本功"><a href="#勤练基本功" class="headerlink" title="勤练基本功"></a>勤练基本功</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/the-ten-commandments-of-egoless-programming/">The Ten Commandments of Egoless Programming</a> 和 <a href="https://blog.codinghorror.com/practice-makes-perfect/">Practice Makes Perfect</a>)</em></p><p>最后，没有捷径。要变得卓越，你必须练习。</p><p>但“练习”不仅仅意味着每天敲 10 个小时的代码。它意味着：</p><ul><li><strong>刻意练习：</strong> 致力于你<em>不擅长</em>的事情，而不仅仅是你擅长的事情。</li><li><strong>代码套路（Code Katas）：</strong> 做一些小练习来保持你的技能敏锐。</li><li><strong>阅读代码：</strong> 阅读别人的代码来学习新的模式。</li></ul><p>卓越是一种习惯。它来自于每天自律地尝试比昨天少“烂”一点点（suck a little less）。</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第八篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="阅读之美" scheme="https://androidperformance.com/tags/%E9%98%85%E8%AF%BB%E4%B9%8B%E7%BE%8E/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-07-游戏与编程</title>
    <link href="https://androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/"/>
    <id>https://androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/</id>
    <published>2018-09-30T06:34:15.000Z</published>
    <updated>2026-02-07T05:17:47.875Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第七篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="我的编程生涯是与-BASIC"><a href="#我的编程生涯是与-BASIC" class="headerlink" title="我的编程生涯是与 BASIC"></a>我的编程生涯是与 BASIC</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/about-me/">About Me</a> 和 <a href="https://blog.codinghorror.com/please-dont-learn-to-code/">Please Don’t Learn to Code</a>)</em></p><p>Jeff Atwood 经常提到他的编程之旅始于 BASIC。他的第一台电脑是 <strong>Texas Instruments TI-99&#x2F;4a</strong>，像那个时代的许多微型计算机一样，它直接启动进入 BASIC 解释器。这种即时性——打开机器，输入几行代码，然后看到事情发生——至关重要。</p><p>他认为我们已经失去了这种低门槛的切入点。在“美好的旧时光”里，机器<em>邀请</em>你给它编程。今天，层层抽象和复杂的工具使得迈出第一步变得令人生畏。</p><p>然而，他也曾写过著名的“请不要学习编程（Please Don’t Learn to Code）”，认为并不是每个人<em>都必须</em>成为程序员。但对于那些被它吸引的人来说，最初的创造火花——往往由像 BASIC 这样简单的工具点燃——是不可替代的。这是关于<strong>构建的乐趣</strong>。</p><h2 id="想玩游戏就自己写"><a href="#想玩游戏就自己写" class="headerlink" title="想玩游戏就自己写"></a>想玩游戏就自己写</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/coding-horror-the-game/">Coding Horror: The Game</a> 及相关思考)</em></p><p>没有比尝试制作游戏更好的方法来理解软件是如何工作的了。游戏是复杂的系统，需要掌握循环、逻辑、图形和用户交互。</p><p>Jeff 鼓励有抱负的程序员尝试游戏开发，不一定是为了成为职业游戏开发者，而是为了理解<strong>参与机制</strong>。如果你能制作一个好玩的游戏，你就学到了适用于所有软件开发的关于用户体验（UX）和性能的宝贵经验。</p><blockquote><p>“最好的学习方式是边做边学。而最有趣的‘做’的事情就是游戏。”</p></blockquote><h2 id="游戏玩家到程序员的蜕变"><a href="#游戏玩家到程序员的蜕变" class="headerlink" title="游戏玩家到程序员的蜕变"></a>游戏玩家到程序员的蜕变</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/the-gamer-programmer/">The Gamer &#x2F; Programmer</a> 和 <a href="https://blog.codinghorror.com/the-pc-weenies/">The PC Weenies</a>)</em></p><p>游戏玩家和程序员之间存在巨大的重叠。我们中的许多人进入这个领域是因为我们想了解我们最喜欢的游戏是如何工作的——或者是因为我们想修改它们。</p><p>Jeff 讨论了这种背景如何影响我们的思维方式。游戏玩家习惯于：</p><ol><li><strong>解决问题：</strong> 每个游戏都是一系列需要解决的谜题。</li><li><strong>坚持不懈：</strong> 我们习惯于失败（挂掉即 Game Over）并再次尝试，直到成功。</li><li><strong>优化：</strong> 我们总是在寻找“极限（Min-Max）”策略，以最小的努力获得最好的结果。</li></ol><p>当你不再仅仅<em>消费</em>内容，而是开始<em>创造</em>内容时，蜕变就发生了。我们窥视幕后。我们意识到“魔法”只是代码，我们可以自己挥舞这种魔法。驱动玩家通关的热情，与驱动程序员掌握新语言或框架的热情是一样的。</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第七篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="游戏与编程" scheme="https://androidperformance.com/tags/%E6%B8%B8%E6%88%8F%E4%B8%8E%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-06-互联网那些事</title>
    <link href="https://androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/"/>
    <id>https://androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/</id>
    <published>2018-09-29T06:34:15.000Z</published>
    <updated>2026-02-07T05:17:47.875Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第六篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="保存互联网，留住所有记忆"><a href="#保存互联网，留住所有记忆" class="headerlink" title="保存互联网，留住所有记忆"></a>保存互联网，留住所有记忆</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/preserving-the-internet-and-everything-else/">Preserving The Internet… and Everything Else</a>)</em></p><p>在 “Preserving Our Digital Pre-History” 一文中，Jeff 提议 Jason Scott 担任我们这一代的数字历史学家。看来很多人都赞同这一点，因为在 2011 年 3 月，他正式成为了互联网档案馆（Internet Archive）的档案管理员。</p><p>Jason 最近邀请 Jeff 参观了互联网档案馆，他对他们的使命印象深刻。互联网档案馆是治愈健忘的良药——它是社会完整、详尽、可访问、可搜索记忆的开端。</p><p>最直接的好处之一是<strong>治愈链接腐烂（Linkrot）</strong>。我们要么都经历过点击死链的挫败感。这简直是“数字心脏病发作”。互联网档案馆的 Wayback Machine 允许我们穿越回过去，查看网络曾经的样子。</p><p>但这不仅仅是网页。他们正在存档<em>所有东西</em>：书籍、音频、视频，甚至软件。正如 Jeff 所指出的：</p><blockquote><p>“如果你关心互联网的历史——你应该关心，因为那就是我们自己的历史——那么你应该支持互联网档案馆。”</p></blockquote><h2 id="网络中立的重要性"><a href="#网络中立的重要性" class="headerlink" title="网络中立的重要性"></a>网络中立的重要性</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/the-importance-of-net-neutrality/">The Importance of Net Neutrality</a>)</em></p><p>网络中立是“你可能从未听说过的最重要的公共政策”。它指的是互联网服务提供商应平等对待互联网上的所有数据，不得因用户、内容、网站、平台、应用程序、依附设备类型或通信方式的不同而进行歧视或差别收费。</p><p>Jeff 承认，在阅读了 Lawrence Lessig 和 Tim Wu 的著作之前，他并没有完全理解其重要性。核心论点很简单：<strong>创新通过中立的网络得以实现。</strong></p><p>如果网络所有者可以挑选赢家和输家（通过限制流量或对快速通道额外收费），那么下一个 Google、Netflix 或 Facebook 可能永远无法起步。互联网被设计成一个不带偏见地移动比特的笨网络。保持这种状态对于言论自由和创新的未来至关重要。</p><blockquote><p>“互联网是第一个能够真正与电视和广播的广播垄断相抗衡的媒介。它是第一个允许任何人成为广播者的媒介。但是，如果网络本身不是中立的，那么这种自由就会受到威胁。”</p></blockquote><h2 id="YouTube-上的版权保护"><a href="#YouTube-上的版权保护" class="headerlink" title="YouTube 上的版权保护"></a>YouTube 上的版权保护</h2><p><em>(基于 Jeff Atwood 的文章 <a href="https://blog.codinghorror.com/youtube-vs-fair-use/">YouTube vs. Fair Use</a> 和 <a href="https://blog.codinghorror.com/youtube-the-big-copyright-lie/">YouTube: The Big Copyright Lie</a>)</em></p><p>YouTube 是人类创造力的巨大宝库，但它也是版权的战场。Jeff 指出了 YouTube 的“大版权谎言”：绝大多数最受欢迎的内容<em>不是</em> 100% 原创的。它是对现有受版权保护材料的混剪、片段和重新混合。</p><p>这就引出了<strong>合理使用（Fair Use）</strong>的概念。合理使用是一种法律原则，允许在未获得权利人许可的情况下有限地使用受版权保护的材料。它包括评论、批评、新闻报道、研究、教学或学术研究。</p><p>然而，YouTube 的自动化 Content ID 系统经常忽略合理使用。它仅根据数字指纹标记内容，而不考虑上下文。这这就造成了一个“有罪推定”的系统，创作者必须通过斗争来恢复他们的合法内容。</p><p>Jeff 认为我们需要一个更好的平衡。我们需要保护版权所有者的权利，但我们也需要保护创作者在文化基础上进行再创作的权利。目前的制度严重偏向大型媒体公司，往往以牺牲个人创造力为代价。</p><blockquote><p>“文化是一种混音（Remix）。一切都是混音。如果我们不能在前人的基础上进行构建，我们就停止了进步。”</p></blockquote><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第六篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="互联网那些事" scheme="https://androidperformance.com/tags/%E4%BA%92%E8%81%94%E7%BD%91%E9%82%A3%E4%BA%9B%E4%BA%8B/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-05-了解你的用户</title>
    <link href="https://androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/"/>
    <id>https://androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/</id>
    <published>2018-09-28T03:57:02.000Z</published>
    <updated>2026-02-07T05:17:47.875Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第五篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h1 id="逻辑人的争议"><a href="#逻辑人的争议" class="headerlink" title="逻辑人的争议"></a>逻辑人的争议</h1><p>软件开发者身上的所有的“坏毛病”中，最严重的可能是：<strong>我们自以为是典型用户。然而大部分的开发者没有意识到的是，我们其实是异类，我们绝非等闲之辈 — 我们是边缘人。</strong></p><p>作者提到了“逻辑人”和“现代智人”的概念</p><ul><li><p><strong>逻辑人</strong>：逻辑人渴望控制那些让他们感兴趣的东西，而那些让他们感兴趣的都是些复杂的确定性系统。人是复杂的，但他们不像机器，他们的行为不具有逻辑性和可预见性，最好的机器是数字的，因为这样它就能变得最为复杂、精细，病区能被程序员轻易改变。</p></li><li><p><strong>现代智人</strong>：一般的普通用户，他们只是想简单地使用，而不是去控制。</p></li></ul><p>对于逻辑人来说，控制是他们的目标，而复杂是他们愿意为之付出的代价，对于普通人来说，简单是他们的目标，失去控制权是他们愿意付出的代价。逻辑人被一种对工作原理难以抗拒的认知欲望驱使着，相比之下，现代智人渴望的是成功。</p><p>Alan Cooper 列了一些逻辑人的典型特征，你可以对号入座一下：</p><ol><li>不惜牺牲简单以换取控制</li><li>不惜失败以换取认知</li><li>不放过任何一点可能性</li><li>行为像体育特长生</li></ol><p>另外一句话软件工程师们需要谨记：任何人都能做出一个没人会用的复杂软件，这其实并不难！把软件做的简单易用才是真本事。你必须停止像逻辑人一样思考，而应该学会像现代智人那样思考。</p><h1 id="象牙塔式的开发"><a href="#象牙塔式的开发" class="headerlink" title="象牙塔式的开发"></a>象牙塔式的开发</h1><p>象牙塔式的开发指的是：开发团队常年封闭在“高塔”之中，一门心思地做着魔法一般的软件。因为缺乏强有力的证据，开发者都假设其他人都是开发者，这是很危险的。</p><p>作者建议：在整个项目周期内，请尽力将你的开发人员暴露在用户面前：参加用户会议，参与可用性测试和验收测试，与用户进行交流，分析用户的数据和行为。</p><p>Eric 提出的“互信关系”：当人们从你那里购买软件的时候，他们对眼下和将来有很多期望：</p><ol><li>他们相信，你的产品可以在他们的机器上正常工作</li><li>他们相信，如果他们碰到了问题，你会帮助他们</li><li>他们相信，你会坚持不懈地改进产品</li><li>他们相信，你会一一个公平、合理的价格为他们提供改进后的版本</li><li>他们相信，你的公司不会再短时间内破产</li></ol><h1 id="让程序员设计界面的后果"><a href="#让程序员设计界面的后果" class="headerlink" title="让程序员设计界面的后果"></a>让程序员设计界面的后果</h1><p>优秀的程序员都有自知之明，知道自己能做什么，不能做什么。他们要么直接拷贝别人的优秀设计；要么本分地只做编码，而把界面设计的工作交给其他专家。</p><p>是朋友，就别让你的朋友做出只有程序员才会使用的界面。</p><h1 id="保护“中间分子”"><a href="#保护“中间分子”" class="headerlink" title="保护“中间分子”"></a>保护“中间分子”</h1><p>专家和小白都只是一小部分人，大部分水平相当的用户都属于“中间分子”，你应该重视这些<strong>中间分子</strong>，中等水平的用户数量是如此巨大，他们如此具有主导性，以至于你可以放弃新手和专家级别的用户。</p><p>为了迎合为数不多的新手和专家，你在软件开发过程中耗费了大量的时间，最终只是让产品变得更差，结果还冷落了核心用户群。</p><h1 id="每个用户都会说谎"><a href="#每个用户都会说谎" class="headerlink" title="每个用户都会说谎"></a>每个用户都会说谎</h1><p>用户的愿望与现实几乎总是相悖的，我们提倡要观察用户的实际行为，而不是听他们叙述他们的所作所为，其原因就在于这种背离。观察是一种很强大的技能，要学会通过人民的行为来判断，而不是听他们说什么就是什么。</p><p>作者提到了“活跃用户的矛盾体”这个概念：活跃用户的矛盾体是一种自相矛盾，因为如果用户对系统多一些了解，从长远来看，是会节省时间的。但现实世界里，人们的行为模式不是那样的。因此，我们不能忍工程师针对理想化的用户开发产品，因为现实中的人是非理性的，我们必须根据用户的实际行为模式来设计产品。</p><p>每个用户都会说谎，预期询问用户是否喜欢你的软件–他们当然会说喜欢，因为当面说你的软件有多么糟糕头型是多么的无理 – 你应该效仿 Gregory House ： 去观察他们是否使用了你的软件，以及他们是怎么使用的。基于行为数据去设计你的软件，而不要靠用户说的“谎言”（不管那些谎言带有多大的善意）</p><h1 id="别把产品发布当目标"><a href="#别把产品发布当目标" class="headerlink" title="别把产品发布当目标"></a>别把产品发布当目标</h1><p>衡量程序员是否成功，有个标准是看他发布了多少代码，但是仅仅发布是不够的，<strong>有多少用户正在使用你的软件，这才是衡量成功的终极目标</strong></p><p>聪明的软件开发者知道，他们的工作远远不止编写代码和发布产品；他们的工作是开发出人民真正想要的使用的软件。这当然包括编码，但还有大量的全局性的其他的事情，比如撰写技术文档、交互设计、培养社区用户、乃至产品愿景，这些对于软件的全面成功都是至关重要的。如果连这一点都没有搞明白，那么你写了什么样的代码就无关紧要了。</p><h1 id="别问，需观察"><a href="#别问，需观察" class="headerlink" title="别问，需观察"></a>别问，需观察</h1><p>用户口述他们想做的事情，与他们实际的所作所为相比，往往天差地别。从可用性的角度来看，询问用户他们想要什么是徒劳的，原因就在这里 – 你必须观察他们正在做了些什么。在可用性方面，你不能猜测行事，你必须去观察用户如何使用你的软件，除此之外别无他法。</p><p>在做设计的时候，如果能基于用户对你的软件的实际使用方式来做决定，岂不是更合理？不管你是在“低保真的可用性测试”转几篇每个观察用户，还是收集用户行为数据、然后在无形之中观察用户，宗旨是一样的：别问，须观察。</p><h1 id="功能越多越好吗"><a href="#功能越多越好吗" class="headerlink" title="功能越多越好吗"></a>功能越多越好吗</h1><p>软件依靠新功能来推动销售，但久而久之，那些新增的功能恰恰是使得软件越变越糟的罪魁祸首 。那种正在慢慢滋生的微妙的“功能癖” — 他会摧毁人们最喜爱的软件。</p><p>一个不好的趋势是：软件公司把现有软件修复 bug 的优先级设得比较低，而把为接下来的版本开发新功能这事看的特别重要。导致的结果就是，软件的质量每况愈下。（就像 Flyme 和 MIUI）</p><p><strong>我们也许不该在盲目地把软件当成一堆功能来衡量</strong> – 人们总有“食量”限制，就像在吃自助餐时，那么多事物你吃得完么？我们应该以结果为导向，衡量软件在帮助我们完成任务时的生产力或效力。</p><h1 id="生物会为所欲为"><a href="#生物会为所欲为" class="headerlink" title="生物会为所欲为"></a>生物会为所欲为</h1><p>作者认为，社会工程充其量是一种不精确的科学，即使在网络空间原型里也是这样。有人曾经说过，“在最精心准备的实验中”，即使条件收到最严格的控制，生物也将为所欲为</p><p><strong>在构造社会性软件时，人是所有问题的根源，但解决问题最终还得靠那些人</strong></p><h1 id="为了一点绶带"><a href="#为了一点绶带" class="headerlink" title="为了一点绶带"></a>为了一点绶带</h1><p>作者总结了自己在 Stack Exchange 的工作内容：我所做的是、我最擅长的是、我最最热爱并且胜过世界上任何其他事情的是，<strong>为喜欢相互写几段文字的人们设计大型多人游戏</strong>。我吧他们的痴迷，引导到某种积极的事情上面；他们可以从中学习，还可以为整个世界创造一些可以重复利用的美妙作品 — 这依然是我所欲之事，因为我还保留有源源不断的痴迷。</p><h1 id="为反社会人群构建社交软件"><a href="#为反社会人群构建社交软件" class="headerlink" title="为反社会人群构建社交软件"></a>为反社会人群构建社交软件</h1><p>作者提出了 “10个可怕的想法”：</p><ol><li>从根本降低参与的门槛</li><li>信任用户</li><li>生活就是世界上最大型的多人在线角色扮演游戏</li><li>总有坏事发生</li><li>喜好胜过金钱</li><li>规则可以很有趣，并且具有社交性</li><li>所有的现代网站都按游戏的方式来设计</li><li>考虑周到的游戏设计促成可持续发展的社区</li><li>社区的观点不一定是对的</li><li>需要一定的调解</li></ol><p><strong>如果你想在网上学点东西，你必须好好设计你的软件，引导人们与生俱来的社会群体冲动，并使他们重新聚焦在有价值的事情上。</strong></p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第五篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="了解你的用户" scheme="https://androidperformance.com/tags/%E4%BA%86%E8%A7%A3%E4%BD%A0%E7%9A%84%E7%94%A8%E6%88%B7/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-04-关于测试的一些思考</title>
    <link href="https://androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/"/>
    <id>https://androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/</id>
    <published>2018-09-27T00:50:32.000Z</published>
    <updated>2026-02-07T05:17:47.874Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第四篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="单元测试是必要的"><a href="#单元测试是必要的" class="headerlink" title="单元测试是必要的"></a>单元测试是必要的</h2><p>单元测试真正的价值在于，它迫使你停下来，为测试思考一番。大部分开发人员都不做测试！他们只是随意输入一些数字，然后点几个按钮，如果这个过程中没有发现尚未处理的异常，他们就觉得代码已经足够好了，可以交付给测试团队了。</p><p>单元测试让你为刚刚写下的代码思考一连串艰难但又不得不思考的问题：</p><ol><li>我该怎样测试这块代码？</li><li>我该执行何种测试？</li><li>通常的情况是怎么样的？</li><li>可能碰到的异常情况有哪些？</li><li>我有多少外部依赖关系？</li><li>我可能碰到哪些系统故障？</li></ol><h2 id="有时候是硬件问题"><a href="#有时候是硬件问题" class="headerlink" title="有时候是硬件问题"></a>有时候是硬件问题</h2><p>作者这一节主要举了一个他们实际遇到的例子，由硬件引起的 bug，排查起来异常困难，但如果用对了工具，那么将事半功倍。</p><p>尽管软件是不可靠的，但我们不能总把矛头指向软件，有时候，你面对的确确实实是一个硬件问题。</p><h2 id="异常驱动的开发"><a href="#异常驱动的开发" class="headerlink" title="异常驱动的开发"></a>异常驱动的开发</h2><p>作为一个开发者，你不应该让用户来指出哪里有错误，你应该比用户更加熟悉你的系统。所以你需要建立一种异常和错误报告机制，你需要集中在一个地方去处理所有的错误，这个地方是你团队里面的所有的开发人员非常熟悉的，而且每天会接触到的。比如 Stack Overflow ，用 ELMAH</p><p>对于 “测试驱动开发” 的一个思考是时间投入回报比，<strong>如果你修复了一个真实用户永远也碰不到的 bug，那么你的修复有什么价值呢？</strong></p><p>作者建议大家使用 “异常驱动的开发”：</p><ol><li>将你的软件发布出去，让尽可能多的用户去使用它</li><li>然后一心一意地研究他们产生的错误日志，使用那些异常日志去找出问题的根源，并且专注在你的代码中有问题的区域</li><li>重新架构，重构代码，以消除最严重的3个问题</li><li>快速迭代，部署，如此周而复始</li></ol><p>这种数据驱动的反馈机制是非常有效地，几个迭代下来，你的程序将非常稳定，坚如磐石。</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第四篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="测试" scheme="https://androidperformance.com/tags/%E6%B5%8B%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-03-Web 设计原则</title>
    <link href="https://androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/"/>
    <id>https://androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/</id>
    <published>2018-09-26T07:39:25.000Z</published>
    <updated>2026-02-07T05:17:47.874Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第三篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="网站的评判标准"><a href="#网站的评判标准" class="headerlink" title="网站的评判标准"></a>网站的评判标准</h2><p>作者在做网站的评委的时候，每个参赛的网站只有 30s 的时间，对于此：“在 30 秒内作出评判是完全不公平的，但那也恰恰反映了现实世界中的真实情况”</p><p>作者给参赛的提了一些建议，你的网站首页需要给人一种眼前一亮的感觉：</p><ol><li>加载速度要快，尤其是移动互联网时代，超过1s 用户就会失去耐心</li><li>这到底是什么东西？不能让进来网站的用户云里雾里，让用户一进来就知道这个网页是干嘛的。</li><li>给我看一个例子</li><li>清清楚楚告诉我要做什么，并且扫除障碍</li><li>拥抱你的受众，即使这意味着要把其他受众排除在外</li></ol><p>在任何 Web 应用中，设计首页的基本草图是你应该做的第一件事情，因为他是至关重要的初始设计文稿，也是你的远景声明。</p><h2 id="追求简单"><a href="#追求简单" class="headerlink" title="追求简单"></a>追求简单</h2><p><strong>追求简单更在于把简单进行到底</strong>，从雅虎首页和 Google 首页的历年对比来看，Google 在首页做到了非常克制的简单，而雅虎的首页越来越复杂，信息越来越多，现在来看，这简直就是门户网站的“灾难”</p><p>Google 的简单，是把复杂的事情放在了背后，而不是一股脑推给用户</p><h2 id="应用会取代网站吗？"><a href="#应用会取代网站吗？" class="headerlink" title="应用会取代网站吗？"></a>应用会取代网站吗？</h2><p>我们应该从简单设计入手，必要时按比例放大，而不是一开始就把事情搞得很复杂，然后被迫收缩，这与目前 Moble First 的设计理念类似。</p><p>为什么应用比网站更好？</p><ol><li>运行速度更快</li><li>使用简单的原生 UI 控件</li><li>更好地利用了屏幕空间</li><li>更适合于移动环境，甚至离线的情况</li></ol><p>为什么网站比应用更好？</p><ol><li>网站可以运行在任何设备的浏览器上</li><li>网站不需要安装</li><li>网站不需要手动升级</li><li>网站提供了统一的用户体验</li></ol><p>从上面的对比来看，其实是各有利弊的，不过我们也可以看到后续的发展，网页和 App 会越来越接近，其开发语言、运行环境越来越一致，各种跨平台的框架让 App 和网页的开发不再差异巨大，随着移动互联网的普及，任何 App 和网页都会是以移动设备优先的角度去开发的。</p><p><strong>网页和 App 的界限会非常模糊，最后统一。</strong></p><h2 id="切记墨守成规"><a href="#切记墨守成规" class="headerlink" title="切记墨守成规"></a>切记墨守成规</h2><p>我们需要采用正确地做事方式，而不是标准的做事方式：</p><ol><li>全面了解当前的规范以及它形成的缘由（知其然知其所以然）</li><li>偏离这个规范需要有理有据</li><li>在实验过程中收集用户使用数据（AB Test）</li><li>基于数据做决定</li></ol><p><strong>做 Android 系统优化最好也遵循上面的步骤，先弄懂代码逻辑，再弄懂代码为何这么写，然后再去思考如何优化，这期间就需要找到瓶颈，作出修改，拿到用户数据，对比用户数据选择最优解。</strong></p><h2 id="诡异的单键设计"><a href="#诡异的单键设计" class="headerlink" title="诡异的单键设计"></a>诡异的单键设计</h2><p>iPhone 的单 Home 键设置一直饱受争议，因为这使得后退这个操作比较复杂，在屏幕比较小的机器上还好，可以手势操作，但是在大屏幕机器上，后退到上一页非常不方便，你不得不用另外一个手来进行操作。</p><h2 id="可用性并非阳春白雪"><a href="#可用性并非阳春白雪" class="headerlink" title="可用性并非阳春白雪"></a>可用性并非阳春白雪</h2><p>如果你的项目里面没人关心可用性，那么记得项目注定会失败。</p><ol><li>可用性测试是人们为改进网站所能做的最有效的措施之一</li><li>既然大部分组织均有财力请专人来从事常规性的测试工作，每个人都应该自己学着做可用性测试</li></ol><p>作者推荐了一本书：《用眼动追踪提升网站可用性》，感兴趣的可以买一本看看</p><h2 id="费茨定律的另一面"><a href="#费茨定律的另一面" class="headerlink" title="费茨定律的另一面"></a>费茨定律的另一面</h2><p>费茨定律: 一个东西越大，离光标越近，它就越容易被点击。</p><p>作者概括了一篇“Visualizing Fitts’s Law”  的文章的核心思想：</p><ol><li><strong>把常用的 UI 元素摆在屏幕的边缘，因为光标自动停留在屏幕的边缘，这样的话，那些 UI 元素将更加容易被点击</strong></li><li><strong>让可点击的区域尽可能大。目标越大，越容易被点击</strong></li></ol><p>同理，如果有的必要的按钮你不希望用户点击，那么做小一点总没有错，<strong>要让不常用或者危险的 UI 元素难以被点击</strong></p><h2 id="可用性与易学性"><a href="#可用性与易学性" class="headerlink" title="可用性与易学性"></a>可用性与易学性</h2><p>网站的写作应该采用“倒金字塔”的风格：<strong>在文章的开头先把结论告诉读者，接着再写最重要的辅助信息，最后才介绍相关的背景。</strong></p><p><strong>毋庸置疑的是，你应该尽量吧最重要的信息放在顶部，不管你是做一个网页，写一段程序，写一封电子邮件，还是做一份简历，等等</strong></p><p>另外作者推荐了一本书 <a href="https://book.douban.com/review/1131988/">写给程序员的UI设计指南</a>,感兴趣的可以看一下</p><h2 id="只是多一个"><a href="#只是多一个" class="headerlink" title="只是多一个"></a>只是多一个</h2><p><strong>如果你想再加一个什么 UI 元素，请确信，你所加的那个 UI 元素不是压倒骆驼的最后一根稻草</strong></p><h2 id="敢于说不"><a href="#敢于说不" class="headerlink" title="敢于说不"></a>敢于说不</h2><p><strong>创新并不是要接受所有的东西，而应该对除了关键性功能之外的所有东西通通说不。</strong></p><h2 id="用户界面很难做"><a href="#用户界面很难做" class="headerlink" title="用户界面很难做"></a>用户界面很难做</h2><p>这里主要说的是程序员在做用户界面的时候，总是很粗糙，不易用。不过从现在的发展来看，用户界面设计不再那么困难，各种美观的界面框架可以非常容易的套用。</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第三篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
    <category term="设计原则" scheme="https://androidperformance.com/tags/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-02-编程之道</title>
    <link href="https://androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/"/>
    <id>https://androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/</id>
    <published>2018-09-20T06:09:01.000Z</published>
    <updated>2026-02-07T05:17:47.873Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第二篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="切记一根筋"><a href="#切记一根筋" class="headerlink" title="切记一根筋"></a>切记一根筋</h2><p>优秀的程序员擅长编程，成为更加优秀的程序员的方法是抛开编程，你必须培养对于编程周边的所有事情的热情</p><p>比尔盖茨在2005年的访谈中提到：“工作的本质不是闭门造车，最最匮乏的人才是那些既对工程技术有超强的领悟能力，有可以与核心开发人员建立良好的关系，并且可以充当与客户、市场等之间桥梁的人”</p><p>你的兴趣爱好越广泛，就越能胜任你的工作</p><h2 id="破窗理论"><a href="#破窗理论" class="headerlink" title="破窗理论"></a>破窗理论</h2><p>破窗理论：如果一栋楼的一个窗户破了，并且留在那里不去修复，这栋楼的其他窗户很快就会被破坏。一个长久没有修复的破窗户释放出来的信号是“没人管”，这会让人觉得，即使再破坏更多的窗户也不会付出什么代价</p><p>从编程的角度来看，破窗理论也存在，你需要及时采取措施：不要放任“破窗”（不良的设计、错误的决定或者糟糕的代码）不管，一但发现就要尽快修复。如果时间不够，就先把他隔离起来。你可以把这些令人不快的代码注释掉，或者显示“尚未实现”的消息，或者用虚假的数据来代替。</p><p>编程是非常注重细节的！如果你不能够掌握这些细节，你就会有一种失控的感觉，而你的项目失控也只是一个时间问题。或许，我们就应该谨小慎微。</p><h2 id="要么热爱，要么离开"><a href="#要么热爱，要么离开" class="headerlink" title="要么热爱，要么离开"></a>要么热爱，要么离开</h2><p>我所认识的最杰出的程序员，他们对所从事的事情都有着终生的热忱，他们绝不会因为一次微弱的经济波动而转行去做其他的事情</p><p>对于编程：要么热爱，要么离开</p><h2 id="简单之美"><a href="#简单之美" class="headerlink" title="简单之美"></a>简单之美</h2><p>在编程开发领域，人们很容易就陷入“越新越好”的思维模式，而忘记了“想法往往比代码更重要”。</p><p>Forth 的进化指出了 Charles 在发明和实现 Forth 语言的时候的知道原则：</p><ol><li>保持坚定</li><li>不要妄加推测</li><li>自己动手</li></ol><p>Charles 认为：简单必须被强制执行，而不是作为一个可有可无的目标。现实中，很多开发者难以保持程序的简单，因为他们没有在需要做艰难决定的时候坚持说“不”，而事事允诺，处处妥协却容易的多</p><p>很多人看不上 oppo，但是 oppo 的系统在设计上保持了简单之美，所以 oppo 的系统用起来很轻，很舒服，长时间使用也很少有卡顿的情况出现，低端机的上面的表现也比同类竞品要好很多，这也与 oppo 的开发理念契合：优先保证基础体验（即快省稳）</p><h2 id="乐于删代码"><a href="#乐于删代码" class="headerlink" title="乐于删代码"></a>乐于删代码</h2><p>如果有一段你不再需要的代码，请真正地删除它而不是把它闲置在那里，其主要的原因是为了去除噪音和不确定性，开发者面临的众多最困难的事情之一就是代码流的噪音或者不确定性，因为这会影响他们将来的工作效率。无用的代码会引发其他开发者的思考：</p><ol><li>为什么这段代码以前是这样写的？</li><li>为什么这段新代码更好？</li><li>将来我们会重新使用老代码么？</li><li>我们评判的标准是什么？</li></ol><p>在目前 Git 横行的时代，你更不应该保留那些没用的老代码，如果想看之前的写法，也就是 Git 几行命令的事情。如果你不能有一个好的不删除的理由，那么删掉它就是合理的</p><h2 id="你是程序员这块料吗"><a href="#你是程序员这块料吗" class="headerlink" title="你是程序员这块料吗"></a>你是程序员这块料吗</h2><p>这一节提到，并不是所有人都适合编程，事实上，大部分人学不会编程，其中决定因素是他们对无意义事物的态度</p><p>形式逻辑证明，进而用一种叫编程语言的形式系统来表达，通过执行某种特别的计算得出结果，这其实是完全没有意义的。为了编写一个计算机程序，你必须做出妥协，赋予程序某种意义，但不管你想要这个程序做什么，计算机都会按照这些没有意义的规则运行，并且得到一些没有意义的结果。在测试中，那些有稳定思维模型的人都体现出了在这方面的先天接受能力，他们都有能力看见规则背后的数学计算问题，而且无论怎样都能遵循那些规则。另外一个方面，那些没用稳定思维模型的人总是找不到头绪。</p><h2 id="你循规蹈矩么"><a href="#你循规蹈矩么" class="headerlink" title="你循规蹈矩么"></a>你循规蹈矩么</h2><p>这节我感觉标题不好，循规蹈矩：原指遵守规矩，不敢违反。现也指拘守旧准则，不敢稍做变动。而文中则说的是你是否遵循基本的规则。</p><p>这一节可以理解为：很多事情，包括软件开发，都是有一定的套路或者基本规则的，这些套路或者基本规则是被大家验证过的，你要做一件事的时候，最好按照这些套路或者基本规则来办，比如作者列出了一篇博客中写出更好的代码的 12 个步骤：</p><ol><li>你使用源代码管理系统么？</li><li>你能一步之内完成软件的一次构建么？</li><li>你每天都出版本么？</li><li>你有一个 bug 跟踪数据库么？</li><li>在写新代码之前，你先解决 bug 么？</li><li>你有最新的软件开发计划表么？</li><li>你有产品规范文档么?</li><li>程序员有安静的工作环境么？</li><li>你在使用最好的商业工具么？</li><li>你有测试人员么？</li><li>在面试过程中，你让应聘者写代码么？</li><li>你做可用性测试么？</li></ol><p>虽然上面都是疑问句，但答案是肯定的</p><h2 id="科里定律：坚守一个目标"><a href="#科里定律：坚守一个目标" class="headerlink" title="科里定律：坚守一个目标"></a>科里定律：坚守一个目标</h2><p>科里定律：坚守一个目标。这个定律在现代人居开发的下面几个核心原则中都有体现</p><ol><li>Don‘t Repeat Yourself （DRY，避免重复）</li><li>Once And Only Once （OAOO，唯一一次）</li><li>Single Point Of Truth （SPOT，单点真理）</li></ol><p>科里定律同时也告诉我们，要有意识地选择你的代码不做什么</p><h2 id="最牛的编码套路"><a href="#最牛的编码套路" class="headerlink" title="最牛的编码套路"></a>最牛的编码套路</h2><p>与你所相信的恰恰相反，单纯地每天埋头于工作并不能算是真正意义上的锻炼 – 参加会议并不能锻炼你的人际交往能力；回复邮件并不能提高你的打字水平。你必须定期流出时间，集中锻炼，这样才能把事情做得更好</p><p>上面这个理论也就是我比较推崇的两本书《深度学习》和 《刻意练习》里面所强调的（如果你还没有看过这两本书，那么建议你看一下）：一万小时不是一个小时重复一万次，而是每个小时都能全心全意投入学习，深入思考，总结归纳</p><p>另外作者也提到了“努力地学习”：要不断地挑战自身能力之外的东西，学习和训练的主要价值在于发现弱点，有针对性地进行提高。<strong>“努力地学习”意味着，要常常去处理那些刚好在你能力极限上的问题，也就是那些对你来说有很大可能失败的事情</strong>。如果不经历一些失败的话，你可能就不会成长。你必须不断挑战自我，超越自己的极限</p><p>作者列举了一些套路（是那些真正可以实施的套路，有些长，不过我还是把所有的条目都摘抄出来，因为我自己也要实施）：</p><ol><li>写一份自己的简历。把自己所有的相关技能都罗列出来，然后把那些在100年后还用得到的标出来。给每个技能打分，满分为10分</li><li><strong>罗列出你所景仰的程序员。尽量包括那些与你一起工作的人，因为你会在工作中从他们身上获取一些技能。记录下他们身上的1 ~ 2个闪光点，也就是你希望自己有所提高的方面</strong></li><li>查看维基百科上的“计算机科学”栏目，找到“计算机领域先驱者”这个分类，从这个列表中挑选一个人，阅读他的事迹，并且在阅读时打开任何你感兴趣的链接</li><li>花20分钟通读别人的代码。读出色的代码和读糟糕的代码都是有益的，两者都要读，轮流切换。如果你无法感觉出它们之间的区别，可以求助于一位你尊敬的程序员，让他给你展示一下什么是出色的代码、什么是糟糕的代码。把你读过的代码给别人也看看，问问他们的看法</li><li>罗列出你最喜欢的10个编程工具——那些你觉得你用得最多、非有不行的工具。随机挑选其中的一个工具，花一个小时去阅读它的文档。在这一个小时里，努力去学习这个工具的某个你不曾意识到的新功能，或者发现某种新的使用方法</li><li><strong>想一想，除了编程之外你最擅长什么事情？再想一想，你是通过怎样的锻炼才变得如此熟练和专业的？这对于你的编程工作又有什么启发呢？（怎么把这些经验应用到编程方面？）</strong></li><li>拿出一叠简历，并和一组面试官在同一个房间里待上一个小时。确保每份简历都至少被3个面试官看过，并且要给出1 ~ 3分的评分。针对那些不同面试官评判大相径庭的简历展开讨论</li><li>参与一个电话面试。事后写下你的反馈，抛出你的观点，然后与主持电话面试的人聊一聊，看看你们是否达成了一致的结论</li><li>进行一次技术面试，并且被面试的人应该是某个你不太了解的领域里的专家。让他假定听众在该领域里一无所知，因此请他从最基础的讲起。努力去理解他所说的，必要时问一些问题</li><li>有机会参与别人的技术面试。期间，你只是认真地听、认真地学。在应聘者努力解决技术问题的同时，你也要在自己脑子里尝试解决这些问题</li><li>找到一个能和你交换实际问题的人，每隔一周，相互交流编程问题。花10 ~ 15分钟来尝试解决这些问题，再用10 ~ 15分钟进行讨论（无论能否解决）</li><li><strong>当你听到任何你一时之间也无法解决的面试问题时，赶紧回到你的座位上，把这个问题用电子邮件发给自己，以留作日后的提醒。在那一周里找出点时间，用自己最喜欢的编程语言来解决它</strong></li></ol><p>另外作者也提到了 Peter Norvig 所列出的一些建议：</p><ol><li>与别的程序员交流。读别人的代码。这比任何书籍或培训课程都更重要</li><li>动手写程序！最好的学习方法就是边做边学</li><li>在本科或研究生的课程中学习编程课程</li><li>找一些项目来做，并且需要与其他程序员形成团队来合作。在项目的进行过程中，学会辨别最出色的程序员以及最糟糕的程序员</li><li>在项目中跟随别的程序员一起工作，了解如何维护那些不是你写的代码，并且学习如何写出利于他人维护的代码</li><li>学习多种不同的编程语言，特别是那些与你现在所熟悉的语言有着不同的世界观和编程模型的</li><li>了解硬件对软件的影响。知道你的电脑执行一条指令需要多少时间，从内存中取出一个字（在有缓存或没缓存的情况下）需要多少时间，在以太网（或者因特网）上传输数据需要多少时间，从磁盘中读取连续的数据或者在磁盘上跳转到另一个位置需要多少时间，等等</li></ol><p>最后作者也阐述了自己的编程套路：</p><ol><li><strong>写博客</strong>。我在2004年初创办了CodingHorror.com博客，作为我自己努力学习的一种形式。它在一开始很不起眼，到后来成为我职业生涯中做过的最重要的一件事。所以，你也应该写博客。最后“闻达于天下”的人，往往就是那些能够有效书写和沟通的人。他们的声音最响亮，是他们在制定游戏规则，并且引领世界的潮流</li><li><strong>积极参与著名的开源项目</strong>。所有的高谈阔论听起来都很好，但是，你是一个大话王还是一名实干家呢？别光说不练，这个非常重要，因为人们会用你的行动来衡量你，而不是你的言论。努力在公众面前留下些实实在在有用的东西吧，到时候你就可以说，“我在那个项目中出过力”</li></ol><p>当你能编写精彩的代码、并且能用精彩的言辞向世人解释那些代码时，到那时候，我会觉得你已经掌握了最牛的编码套路！</p><h2 id="孤独的人是可耻的"><a href="#孤独的人是可耻的" class="headerlink" title="孤独的人是可耻的"></a>孤独的人是可耻的</h2><p>我觉得这一节作者引用的 “Creating My Own Personal Hell” 中阐述独自编程的危害性非常值得深思：</p><blockquote><p>有些人宣称，“独自工作”为建立起自己的工作流程提供了极好的机会。但是，根据我的经验，在团队只有一个人的时候是没有流程可言的。没有任何东西可以帮你抵挡住如潮水般涌来的大量工作。当你的代码太急于求成时，没有人去纠正你的错误。没有人检查你的代码。没有人保证你的代码能准时提交、打好标签、进行常规的单元测试。没有人保证你遵循了某个编码标准。没有人督促你及时修复代码里的缺陷。没有人检验你是否把一个实际存在的问题标注成了“无法重现”。没有人复核你的估算，在你玩忽职守的时候把你抓回来</p></blockquote><blockquote><p>没有人在你生病时或者出差时接过你的工作。没有人在你工作繁重时帮助你，在你深陷于骚扰电话、无聊会议、还有在最后关头忽然被扔过来（但需要立即解决）的杂碎任务时，没有人能拉你一把。没有人忽然有奇思妙想，帮助你走出困境。没有人在设计、架构或技术上与你合作。你在一个真空中工作；在真空中，没有人能听到你绝望的尖叫</p></blockquote><blockquote><p>如果你读到了这些内容，请以此为鉴。如果某个公司只招你作为唯一的一位开发者，在你答应他们之前请三思。那根本就是另一种地狱。如果有机会的话，请选择那些能与其他开发者一起工作的职位，这样你至少可以在与别人一起工作的过程中得到指导，这有助于你发展自身的技能，让你在技术方面与时俱进</p></blockquote><p>如果你不能展示给别人看，再漂亮的编码技巧又有什么意义？如果你不去接触其他程序员的不同观点、不同方法以及不同的技术，你又怎么能学到更多的技艺？谁又能检查你的代码并告诉你，那个问题有更简单的解决方法？如果你对待编程的态度是认真的，你应该要求与同伴们一起工作</p><p><strong>个人的能力总是有限的，它决定了你在这个领域里只能走那么远。找一些其他的聪明程序员吧，和他们一起工作。努力让自己保持谦逊低调，然后你会很快发现，软件开发其实是一种社会活动——它的社会性比大部分人想象的要大得多。你可以从那些性格内向的同伴身上学到很多东西</strong></p><p>就像我们常说的，<strong>一个人可以走的更快，但是一群人可以走的更远</strong></p><h2 id="你要编程伙伴么"><a href="#你要编程伙伴么" class="headerlink" title="你要编程伙伴么"></a>你要编程伙伴么</h2><p>在健康的软件工程文化影响下，团队成员通过结对的方式来提高他们的工作质量和生产力，他们明白，他们花在查看同事工作上的时间，总会在别人反过来查看他们自己的交付物的时候得到回报。</p><p>作者更是给出了一份数据来说明代码 Review 的作用（有点颠覆我的认知）：</p><blockquote><p>在平均缺陷发现率方面，单元测试只能到到 25%，功能测试可以达到 35%，而集成测试也不过45%，相比之下，设计和代码审查的平均功效可以达到 55% 和 60%</p></blockquote><p>编程有很多乐趣，其中一点在于：“你不必独自一人去做。”，所以，谁是你的编程伙伴？</p><h2 id="软件学徒制"><a href="#软件学徒制" class="headerlink" title="软件学徒制"></a>软件学徒制</h2><p>很多公司会给新人配一个导师，通过这种 1V1 的指导，让新人能尽快融入到公司，这与学徒制度很类似</p><p>晚上学习理论，白天编程工作 — 这种组合方式特别有效</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第二篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="读书笔记" scheme="https://androidperformance.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
  </entry>
  
  <entry>
    <title>程序员的修炼-01-绝地反击之术</title>
    <link href="https://androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/"/>
    <id>https://androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/</id>
    <published>2018-09-19T11:03:55.000Z</published>
    <updated>2026-02-07T05:17:47.873Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 &lt;程序员的修炼-从优秀到卓越&gt; 的读书笔记的第一篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师，还是资深工程师，本书都值得一读。随着你的阅历的增加，每一次重读这本书，都会有不一样的感悟，正如书名“从优秀到卓越”，作者为你指明了道路，至于是否能成功，则要看自己的修炼了。</p><p>我会把读书过程中一些精彩的言论摘录下来，有时会加入一些自己的见解或者经历，读书笔记的大纲与书本身的大纲是一致的，这也是我从另外一个地方学到并一直在用的“如何阅读一本书”，记录下来方便自己经常查看，也方便读者查看。下面是&lt;程序员的修炼-从优秀到卓越&gt; 读书笔记系列：</p><ol><li><a href="https://www.androidperformance.com/2018/09/19/how-to-stop-sucking-and-be-awesome-instead-1/">程序员的修炼-01：绝地反击之术</a></li><li><a href="https://www.androidperformance.com/2018/09/20/how-to-stop-sucking-and-be-awesome-instead-2/">程序员的修炼-02：编程之道</a></li><li><a href="https://www.androidperformance.com/2018/09/26/how-to-stop-sucking-and-be-awesome-instead-3/">程序员的修炼-03：Web 设计原则</a></li><li><a href="https://www.androidperformance.com/2018/09/27/how-to-stop-sucking-and-be-awesome-instead-4/">程序员的修炼-04：关于测试的一些思考</a></li><li><a href="https://www.androidperformance.com/2018/09/28/how-to-stop-sucking-and-be-awesome-instead-5/">程序员的修炼-05：了解你的用户</a></li><li><a href="https://www.androidperformance.com/2018/09/29/how-to-stop-sucking-and-be-awesome-instead-6/">程序员的修炼-06：互联网那些事</a></li><li><a href="https://www.androidperformance.com/2018/09/30/how-to-stop-sucking-and-be-awesome-instead-7/">程序员的修炼-07：游戏与编程</a></li><li><a href="https://www.androidperformance.com/2018/10/01/how-to-stop-sucking-and-be-awesome-instead-8/">程序员的修炼-08：阅读之美</a></li></ol><hr><h2 id="待办事项不靠谱"><a href="#待办事项不靠谱" class="headerlink" title="待办事项不靠谱"></a>待办事项不靠谱</h2><p>作者吐槽说，每天都会有新的 TO-DO 应用诞生，很多人还是管不好自己的时间，TO-DO 列表正在变得吃力不讨好：</p><ol><li>列表会给你一种正在进行的错觉</li><li>列表会给你一种完成的错觉</li><li>对于那些未完成的事情，列表会让你产生罪恶感</li><li>对于那些总被延期的事情，列表会让你产生罪恶感</li><li>对于那些不想做而没做的事情，列表会让你产生罪恶感</li><li>你会根据列表错误地排出优先级</li><li>列表不够高效</li><li><strong>列表吞噬了主动做事的乐趣，让你觉得做事情成了一种义务</strong></li><li>从长远来看，列表博纳更没有让你变得有条理</li><li>列表会让你做事缺乏主动性</li></ol><p>作者的建议是，每天早上醒来的时候，如果你不能够用上帝赐予你的大脑使劲想出今天你需要做的<strong>最重要的三件事</strong>，那么你必须先把这个问题认真解决了。<strong>你必须要搞明白，什么东西对于你来说是最重要的，并且能让你充满激情。</strong></p><p>工具是浮云，而你的大脑将会伴随你的一生，信任他，训练他。</p><h2 id="今天上班可以放羊"><a href="#今天上班可以放羊" class="headerlink" title="今天上班可以放羊"></a>今天上班可以放羊</h2><p>本节主要探讨了 Google 的 20% 时间理论：在 Google，理论上你可以拿上班时间的 20% 来做任何自己想做的事情。Google 许多伟大的软件都是这 20% 的时间里成型的，比如 Gmail、Google News、Google Talk、AdSense 等。</p><p>真正能否在公司执行，需要公司的支持：</p><ol><li>公司项目计划是否足够宽松？</li><li>公司文化容得下“白日做梦”么？</li><li>可以接受失败么？</li><li>个人的实验是否能得到尊重？</li></ol><p>在工程师文化主导的公司应该会比较容易实行，“<strong>重要的创新和改进可能会在任何时候以自下而上的方式来自于公司的任何人—他们不会按照神奇的总体规划上预定的间隔自己蹦出来。</strong>”</p><h2 id="你没有说服我"><a href="#你没有说服我" class="headerlink" title="你没有说服我"></a>你没有说服我</h2><p>本节以《末代独裁》里面的一个例子，来说明说服别人的重要性。如果想落实一件事情，你不能只是把自己的观点表达出来，等待别人的判断，你要说服别人认可你的观点：<strong>如果你想要影响别人，你必须有能力说服他们。</strong></p><p>对于软件工程师来说，他们所要知道的不仅仅是怎样去写出技惊四座的代码，更重要的是怎样推销他们的想法和产品。</p><p>作者做了举了一个说服他人的草根方法：</p><ol><li>从整体上来说，他的观点是相当出色的</li><li>他做事方式是自下而上的，而不是自上而下的</li><li>他总是能够带头亲自去做事，以赢得别人的信任</li><li>他拥有足够的耐心去等待机会的到来</li></ol><p><strong>如果你一味地保持沉默，总是像局外人一样冷眼旁观，你就什么也改变不了。如果你想改变你的工作和生活，你必须学会说服别人。</strong> 对于某些人来说（比如我），就常常受困于此，我总结了一下原因，也算是后续的职业生涯需要加强的部分：</p><ol><li>性格原因</li><li>不自信</li><li>准备不足</li></ol><p>当然，打铁还需自身硬，最近我面试有一句话我印象特别深刻：<strong>公认的牛人应该是</strong></p><ol><li><strong>自己得行</strong>：技术过硬</li><li><strong>别人说你行</strong>：得到大家的认可</li><li><strong>说你行的人也得行</strong>：得到业界的认可</li></ol><h2 id="真正失败的项目"><a href="#真正失败的项目" class="headerlink" title="真正失败的项目"></a>真正失败的项目</h2><p>不要害怕失败，也不要主动寻求失败，失败会自己找上门来。不管你在做什么项目，怀揣着学习和锻炼的态度去完成它，这绝对是值得的，与项目的结果相比，过程才是最大的财富。</p><p><strong>如果你没能从一个项目的过程中学到一点东西，这才是真正失败的项目。</strong> 所以真正的工作中，项目做完要及时 Review，总结经验和教训，防止下次再踩坑，就算是失败的项目，也有大量的经验在其中。</p><h2 id="激情造就天才"><a href="#激情造就天才" class="headerlink" title="激情造就天才"></a>激情造就天才</h2><p>很喜欢的一句话：“我将会把剩下的5天的课程全都用于让他们变得更加聪明，而不是让他们明白我有多聪明”，不管是做培训还是写博客，要有这样的心态，你不是在炫耀自己多厉害，而是要让看博客或者参加培训的人学到知识，获得真的的成长。</p><p>千万不要被身边很多天赋比你高的开发者吓倒，勤能补拙，激情造就天才。</p><h2 id="勿以专家自居"><a href="#勿以专家自居" class="headerlink" title="勿以专家自居"></a>勿以专家自居</h2><p>作者以维基百科为例，专家并没有更多的权限，在知识面前，人人平等。有时候反而因为你是专家，而对你的期望或者要求会更多。</p><p>古人云：<strong>三人行，则必有我师；闻道有先后，术业有专攻，如是而已；无他，唯手熟尔。</strong></p><p><strong>作为一名专家，重要的不是告诉别人你知道什么，而是要清楚你应该问什么样的问题，病区灵活运用你所掌握的知识去解决眼下的具体问题，作为专家，你的作用是提供明智的、可执行的方向。</strong></p><p>最近面试也有提到专家的问题，作者在这里也介绍了几个阶段，大家可以对号入座一下，记得摒弃自己的领域、专业知识、名声和声誉，然后按照下面的阶段来进行重塑：</p><ol><li><strong>第 0 阶段：我克服了漫不经心</strong><br>我现在明白了，我需要学点东西</li><li><strong>第 1 阶段：我克服了恐惧</strong><br>我觉得我可以学会这个科目或者技能，我会对它变得很了解，而不会惧怕比我懂得多的人。</li><li><strong>第 2 阶段：我变得有条理</strong><br>我不再觉得自己不懂装懂或者不学无术，我觉得我有能力参与讨论或者实践，我对自己说的话很有信心</li><li><strong>第 4 阶段：我超越了自身的能力</strong><br>我现在觉得对自己有了更高的要求，不再停留在“过得去”而沾沾自喜，我想冒一点风险，富有创造力，不断学习，不断推动自己进步，我想要与那些充满热情的人共事</li></ol><h2 id="行百里者半九十"><a href="#行百里者半九十" class="headerlink" title="行百里者半九十"></a>行百里者半九十</h2><p>本节主要讲的是项目管理相关的，包括个人项目管理和团队项目管理，如果你总是把 “快完成了” 挂在嘴边，那么你需要思考一下自己的项目管理是否有问题，作者建议：<strong>鼓励并强制要求程序员创建一张他们所要做的全部事情的列表，然后再为其中的每一项列出子项，并且尽可能把所有的子项都加进来</strong></p><p>下面是具体可能的措施，把他用在项目里面吧</p><ol><li>把你在一个大项目中需要做的事情全部罗列出来，包括哪些基础设施工作，比如配置源代码管理系统的分支</li><li>估计这个列表中每一项所要花费的时间，这这最初的估计可以帮助你看到真个项目大致的时间花费</li><li>看看你列表中的每一项要花费多少时间，如果有一项的时间超过一天，则把这项拆分成若干小项，这种将大任务拆分成小任务的方式是解决“只能完成 90%”问题的关键步骤</li><li>找出一种呈现任务状态的方式，以便那些感兴趣的人可以了解</li><li><strong>追踪每天的任务进度，把每个小任务的原定计划和实际完成时间放在同一张表上，这样就可以对工作进度做一个把控。</strong></li></ol><h2 id="管理中要有信任"><a href="#管理中要有信任" class="headerlink" title="管理中要有信任"></a>管理中要有信任</h2><p>信任可以解决大型软件管理的问题，但是信任无法代替管理，两者相辅相成。</p><h2 id="博伊德迭代法则"><a href="#博伊德迭代法则" class="headerlink" title="博伊德迭代法则"></a>博伊德迭代法则</h2><p>博伊德迭代法则：迭代的速度胜过迭代的质量。也就是大家常说的，天下武功唯快不破。</p><h2 id="十年磨一剑"><a href="#十年磨一剑" class="headerlink" title="十年磨一剑"></a>十年磨一剑</h2><p>Gmail 的成功之路是漫长的，很多人一开始并不看好他，但是在产品发布之后，用户的反馈反而很好。</p><p>一夜成名的传说容易让人误入歧途，并且遗毒不浅，如果你打算做一个全新的东西，要有打持久战的准备。</p><p>作者认为，<strong>成功需要付出多年的努力，你必须踏踏实实地在这件事上花费几年的时机去磨练，每天一醒来就开始工作，日复一日地坚持，不断取得反馈，每一天都比过去做的更好。即使你偶尔会不开心，甚至失去了乐趣，单这些都是为了获取成功所必须的。</strong></p><p>作者同时也举了写博客的例子，日复一日的投入，作者花了3年，才让他的博客在行业有了一席之地。在目前这个浮躁的环境下，能静下心来，持续做知识输出的人，我相信一定会有好的结果，只要你相信：<strong>你正在做的事情是真正值得去做的</strong>。</p><hr><blockquote><p>《程序员的修炼——从优秀到卓越》是《高效能程序员的修炼》的姊妹篇，包含了Coding Horror博客中的精华文章。全书分为8章，涵盖了时间管理、编程方法、Web设计、测试、用户需求、互联网、游戏编程以及技术阅读等方面的话题。作者选取的话题，无一不是程序员职业生涯中的痛点。很多文章在博客和网络上的点击率和回帖率居高不下—— from 豆瓣</p></blockquote><blockquote><p>Jeff Atwood于2004年创办Coding Horror博客(.codinghorror.)，记录其在软件开发经历中的所思所想、点点滴滴。时至今日，该博客每天都有近10万人次的访问量，读者纷纷参与评论，各种观点与智慧在那里不断激情碰撞 —— from 豆瓣</p></blockquote><blockquote><p>《程序员的修炼——从优秀到卓越》的写作风格风趣幽默，且充满理解和关怀；适合从新手到老手的各个阶段的程序员阅读，也适合即将成为程序员的计算机和相关专业的学生阅读。《程序员的修炼——从优秀到卓越》能够帮助读者更多地关注技术工作的人性和人文因素，从而实现程序员职业生涯的成功转折 —— from 豆瓣</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;本文是 &amp;lt;程序员的修炼-从优秀到卓越&amp;gt; 的读书笔记的第一篇，这本书的作者是 Jeff Atwood，StackOverflow 的创始人之一，Jeff 的文章涉及面很广，他是一个经验老道的程序员、管理者、创业者，这本书谈到了编程之外的很多东西，不管你是初级工程师</summary>
      
    
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="读书笔记" scheme="https://androidperformance.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    <category term="程序员的修炼" scheme="https://androidperformance.com/tags/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E4%BF%AE%E7%82%BC/"/>
    
  </entry>
  
  <entry>
    <title>Android 系统不释放内存吗？</title>
    <link href="https://androidperformance.com/2018/09/13/android-memory/"/>
    <id>https://androidperformance.com/2018/09/13/android-memory/</id>
    <published>2018-09-13T13:27:57.000Z</published>
    <updated>2026-02-07T05:17:47.860Z</updated>
    
    <content type="html"><![CDATA[<p>除了 CPU，很多用户在选购手机的时候通常也会考虑内存大小，不同版本内存的手机价格也不一样，买多大内存的合适呢？Android 系统是怎么管理内存的呢？普通用户对 Android 手机的内存使用总是一头雾水，这个应用到底占了多少内存？系统到底占了多少内存？内存对我手机的使用体验有什么影响？到底怎么才能用好 Android 手机？换新手机换多大内存的会比较合适呢？</p><p>知乎上有一个问题，一个用户问 <a href="https://www.zhihu.com/question/24976909/answer/49711238">“Android 系统不释放内存吗？</a>”，用户并不是不知道系统会释放内存，而是想知道其中的细节，好优化使用的体验，下面我就从用户的几个问题入手，来简单说明一下，比较深入的细节我后续的文章会详细介绍。</p><p>同时也会对第一段中提到的几个问题提出一些个人的见解，欢迎一起来讨论，留下你的问题，提出你的见解，大家共同进步。</p><h2 id="Android系统下关闭程序后，系统内存并不释放？"><a href="#Android系统下关闭程序后，系统内存并不释放？" class="headerlink" title="Android系统下关闭程序后，系统内存并不释放？"></a>Android系统下关闭程序后，系统内存并不释放？</h2><p>这个是不准确的,只能说对了一半. 你所描述的”android系统下关闭程序”,指的是怎么个关闭法呢?目前阶段有好几种关闭程序的方法:</p><h3 id="点击Back键退出"><a href="#点击Back键退出" class="headerlink" title="点击Back键退出."></a>点击Back键退出.</h3><p>这种退出的方法, 进程是否被杀掉,取决于这个应用程序的实现. 举个栗子,如果你创建一个空的应用, 这时候查看系统内存信息(包名为com.exmaple.gaojianwu.myapplication,pid为5708,内存为13910kb):<br><img src="/images/media/15368461581041.jpg"></p><p>可以看到,这个应用程序的pid为5708 , 其优先级为Foreground,即前台程序.</p><p>这时候我们点击Back键退出,然后再查看系统的内存信息(adb shell dumpsys meminfo)<br><img src="/images/media/15368462124980.jpg"></p><p>我们看到,这个程序在 Back 键之后,其进程 5708 依旧是存在的.只是其进程优先级变成了Cache.其占用内存变成了 12337kb,和之前的 13910kb 相比是变小了一些. 但是大部分内存是没有被释放掉的.</p><h3 id="在任务管理器中杀掉应用"><a href="#在任务管理器中杀掉应用" class="headerlink" title="在任务管理器中杀掉应用"></a>在任务管理器中杀掉应用</h3><p>在任务管理器中杀掉应用,这个结果是不一致的,其取决于这个OS的任务管理器的实现,大部分国内的厂家都会对任务管理器进行定制,以达到更有效的杀掉应用的效果.一般来说厂家定制的任务管理器都会比较暴力,除了少数白名单,其他的应用一概直接将进程杀掉</p><p>我们以上面的那个测试程序为例,打开这个程序之后, 其进程优先级为Foreground,这时候我们直接调用任务管理器杀掉改程序(以魅族MX4 Pro为栗子):<br><img src="/images/media/15368463131512.jpg"></p><p>可以看到用任务管理器杀掉之后, 整个应用程序的进程都被杀掉了.</p><h3 id="通过命令行或者开发者工具杀掉应用"><a href="#通过命令行或者开发者工具杀掉应用" class="headerlink" title="通过命令行或者开发者工具杀掉应用"></a>通过命令行或者开发者工具杀掉应用</h3><p>我们可以通过 adb shell am force-stop 包名来杀掉这个程序，其结果也是进程直接被杀掉，IDE(比如Android Studio)选择一个进程后,点击图中的按钮</p><p><img src="/images/media/15368463452505.jpg"></p><p>也是可以干掉这个进程的. 这时候进程是被直接杀掉的。</p><h2 id="即使关掉后台进程，内存也增加不多？"><a href="#即使关掉后台进程，内存也增加不多？" class="headerlink" title="即使关掉后台进程，内存也增加不多？"></a>即使关掉后台进程，内存也增加不多？</h2><p>这个不对，一个进程被杀死后，其内存会被释放掉的，不管是虚拟机内存还是 Native 内存还是图像所申请的内存，都会被系统回收，放到可用内存中。</p><p>我们以知乎 App Android客户端为栗子，打开这个程序之前,系统剩余内存 1.8G:</p><p><img src="/images/media/15368465468675.jpg" alt="打开知乎之前，系统剩余内存"></p><p>打开知乎这个程序之后,系统剩余内存:</p><p><img src="/images/media/15368465916092.jpg" alt="打开知乎之后，系统剩余内存"></p><p>知乎占用的内存:</p><p><img src="/images/media/15368466486814.jpg" alt="知乎内存占用"></p><p>使用任务管理器杀掉知乎(直接杀掉进程),系统剩余内存:</p><p><img src="/images/media/15368466879470.jpg" alt="杀掉知乎知乎系统剩余内存"></p><p>可以看到，杀掉进程之后，系统可用内存是会增加的。</p><h2 id="据说即使前台关掉进程，其实该进程在后台还在运行？"><a href="#据说即使前台关掉进程，其实该进程在后台还在运行？" class="headerlink" title="据说即使前台关掉进程，其实该进程在后台还在运行？"></a>据说即使前台关掉进程，其实该进程在后台还在运行？</h2><p>这个和第一条一样，取决于你关掉进程的方法：</p><ol><li>如果是按 Back 键，那么该进程还是会在后台。<strong>是否在运行，则要取决于这个应用的行为，有的应用 Back 到后台之后，其优先级会降到 Cache，系统内存不足或者触发 Cache  进程的数量限制，都会被系统直接杀掉，回收内存；有的应用 Back 到后台之后，会触发一些后台任务，起个 Service 之类的，还是会继续运行，比如导航软件和听歌软件</strong></li><li>如果是按 Home 键，那么该进程依然会在后台，其大部分资源都没有释放，包括 Activity、Service 等。是否在运行，与 Back 到后台的行为一样，取决于应用的行为。</li><li>如果是被任务管理器或者命令行(Force Stop)杀掉，那么除非自启动（很多国内的应用被杀之后都会走自启动模式，同样很多国内的手机厂商，都会禁止应用被杀后走自启动模式，或者被其他进程唤醒，一般有名单进行控制，名单内的可以自启动，其他的自己想办法），应用是不会在后台运行的。</li></ol><p>从上面的三个逻辑来看，用户最佳的使用方法应该遵循下面的建议：</p><ol><li>如果是正在使用的应用，临时切出去干个别的事情，一会还要切回来，那么使用 Home 键把当前应用退到后台最佳</li><li>如果是不想看了，想退出应用了，那么使用 Back 键把当前应用退到后台最佳</li><li>如果不希望这个应用退到后台之后还有可能运行，或者想释放内存，那么用任务管理器把这个应用直接杀掉最好（推荐一键全杀，国内的 Rom 基本都有）</li></ol><p>上面也讲到，<strong>国内的手机厂商，都会禁止应用被杀后走自启动模式，或者被其他进程唤醒，一般有名单进行控制</strong>，这样做是为什么呢？厂商这么做无非是为了限制应用对资源的占用，一个应用退到后台之后，系统肯定是希望其占用最少的资源，能不占资源最好（cpu、gpu、io、memory），但是国内的部分应用却没那么安分守己，大家肯定听说过全家桶和相互唤醒，应用被杀了没问题，我另外一个应用偷偷把你拉起来就可以了，这拉起来的过程就占用了系统的资源，可能会导致前台应用出现卡顿，或者导致整机内存不足，所以厂商对应用的限制是无可厚非的。</p><p><img src="/images/media/15368493914981.jpg" alt="百度全家桶"></p><p>那么应用为什么要抱团取暖，相互唤醒呢？第四个问题就是说这个的。</p><h2 id="智能手机无需将程序彻底关掉，可以减少再启动的时间。是这样吗？"><a href="#智能手机无需将程序彻底关掉，可以减少再启动的时间。是这样吗？" class="headerlink" title="智能手机无需将程序彻底关掉，可以减少再启动的时间。是这样吗？"></a>智能手机无需将程序彻底关掉，可以减少再启动的时间。是这样吗？</h2><p>这句话没毛病，但是是有前提的，前提就是，如果这个应用在后台可以安分守己，至于现实嘛..不说大家也知道，参考<a href="https://zh.wikipedia.org/wiki/%E7%99%BE%E5%BA%A6%E5%85%A8%E5%AE%B6%E6%A1%B6">百度全家桶</a></p><p>Android设计的时候,确实是想让大家不去关心内存问题,Android会有一套自己的内存管理机制,在内存不足的时候通过优先级干掉一些应用。每个应用在接收到内存不足的信号，需要根据内存不足的程度,来释放掉一部分内存.以保持自己的进程不被杀死,这样下次启动的时候就不用去fork zygote，这样的话，下一次启动的时间确实会少很多，也就是大家常说的冷启动和热启动的差距。</p><p>但是……………..凡是总有个但是，理想是丰满的，现实是很残酷的。严格按照Google想的那一套去做的应用不多，国内开发者对内存的敏感程度很低，导致很多应用程序跑起来分分钟就100-200MB 了，墨迹天气这样的应用，400m 妥妥的(不好意思又黑了墨迹天气) 。所以手机低内存的情况非常常见，所以低内存的情况会很频繁。这时候你再起一个应用，申请内存的时候发现内存不够，就开始杀应用了。</p><p>所以经常会出现你在看电子书，突然这时候微信来了个消息，你切过去回了个消息，打开相机拍了个照，然后发给朋友，又发了条微博，再回来看书的时候发现电子书已经挂了，正在重新加载程序….WLGQ…</p><p>这时候你就发现限制后台进程的重要性了，把不重要的进程直接干掉，限制应用的自启动和相互唤醒，保证重要的进程不会被系统杀掉，也就保证了用户的基本使用体验。</p><p>所以说不重要的程序是需要在使用结束后直接干掉的.一劳永逸,麻麻再也不用担心这货偷跑流量&#x2F;后台安装程序&#x2F;占内存&#x2F;占 CPU 了….</p><p>再说后半句: 可以减少启动的时间. 这个是对的, 如果一个应用程序的进程没有被杀死,那么下一次启动这个应用程序的时候,就不需要去创建这个进程了(fork zygote,这个耗时还是蛮多的), 而是直接在这个进程中创建对应的组件即可(Android四大组件)，速度比冷启动要快很多。</p><p>以汽车发动为例:</p><ol><li>冷启动相当于 上车 -&gt; 拧钥匙 -&gt; 等发动机启动 -&gt; 踩刹车换挡 -&gt; 放手刹 -&gt; Go</li><li>热启动相当于 上车 -&gt; 踩刹车换挡 -&gt; 放手刹 -&gt; Go</li></ol><p><img src="/images/media/15368489339779.jpg"></p><h2 id="关于-Android-内存的其他一些问题"><a href="#关于-Android-内存的其他一些问题" class="headerlink" title="关于 Android 内存的其他一些问题"></a>关于 Android 内存的其他一些问题</h2><p>这里来简单解答一下第一段中提到的那些问题</p><ol start="2"><li>Android 系统是怎么管理内存的呢？ – 这个嘛，后续再详细讲</li><li>应用到底占了多少内存？ – 这个嘛，后续再详细讲</li><li>系统到底占了多少内存？ – 这个嘛，后续再详细讲</li><li>内存对我手机的使用体验有什么影响？– 低内存会影响整机的流畅性和响应速度，也会导致杀应用变得很频繁，用户体验差。</li><li>到底怎么才能用好 Android 手机？– 买 Android 旗舰，多用任务管理器的全杀功能，尽量禁止应用后台运行（Flyme 用户可以在手机关机里面设置）</li><li>换新手机换多大内存的会比较合适呢？ – 越大越好，6G 起步，8G 最佳。</li></ol><p><img src="/images/media/15368490914358.jpg"></p><h2 id="内存相关的文章参考"><a href="#内存相关的文章参考" class="headerlink" title="内存相关的文章参考"></a>内存相关的文章参考</h2><ol><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android代码内存优化建议-Java官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android代码内存优化建议-Android资源篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android代码内存优化建议-Android官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android代码内存优化建议-OnTrimMemory优化</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/">Android性能优化之过渡绘制(一)</a></li><li><a href="https://www.androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/">Android性能优化之过渡绘制(二)</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android内存优化之一：MAT使用入门</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android内存优化之二：MAT使用进阶</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android内存优化之三：打开MAT中的Bitmap原图</a></li><li><a href="https://www.androidperformance.com/2018/08/13/Some-Thoughts-on-the-Fluency-of-Android/">关于 Android 系统流畅性的一些思考</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;除了 CPU，很多用户在选购手机的时候通常也会考虑内存大小，不同版本内存的手机价格也不一样，买多大内存的合适呢？Android 系统是怎么管理内存的呢？普通用户对 Android 手机的内存使用总是一头雾水，这个应用到底占了多少内存？系统到底占了多少内存？内存对我手机的使用</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Process" scheme="https://androidperformance.com/tags/Process/"/>
    
  </entry>
  
  <entry>
    <title>关于 Android 系统流畅性的一些思考</title>
    <link href="https://androidperformance.com/2018/08/13/Some-Thoughts-on-the-Fluency-of-Android/"/>
    <id>https://androidperformance.com/2018/08/13/Some-Thoughts-on-the-Fluency-of-Android/</id>
    <published>2018-08-13T04:01:23.000Z</published>
    <updated>2026-02-07T05:17:47.856Z</updated>
    
    <content type="html"><![CDATA[<p>最近一直想写一些关于 Android 系统流畅度的东西，因为流畅度这个东西，是消费者最直接能体验到的，再加上 Android 一直为人诟病的越用越卡顿，使得大家在提到安卓机的时候，都会有一丝阴影。这也是大部分人买手机首先会考虑 iPhone 的一个原因。</p><p>由于 Google 对 Android 持开放态度，各个手机厂商生产不同产品定位的机器，以及各个 Android 应用的质量良莠不齐，导致影响 Android 流畅度的因素非常非常多，并非大家简单的以为是系统没有优化好，很多时候你会发现，不同 SOC 但是相同的系统，体验完全就是两种。</p><p>所以我想和大家聊聊影响 Android 系统流畅性的一些原因，后续大家遇到卡顿的问题，也不会单纯把锅甩给系统，或许你卸载一个 App 就解决了呢.</p><p>我想从下面几个方面展开聊这个话题:</p><blockquote><ol><li>硬件层面</li><li>系统层面</li><li>应用层面</li><li>流畅度优化闭环</li></ol></blockquote><p>准备好了，那就开始吧，欢迎你加入讨论</p><h2 id="1-硬件层面"><a href="#1-硬件层面" class="headerlink" title="1. 硬件层面"></a>1. 硬件层面</h2><h3 id="CPU"><a href="#CPU" class="headerlink" title="CPU"></a>CPU</h3><p>cpu 是手机硬件里面最核心的一个器件，这也是把 cpu 作为第一个来说的主要原因，cpu 之所以重要，是因为 Android 系统的运行过程中，大部分是跟 cpu 打交道，cpu 的能力强弱，直接决定了这款手机的档次。</p><p>手机 cpu 目前主要有高通、华为、三星、联发科四家在做，每家都有高中低档，高端 cpu 的排名大概是 高通&gt;华为&gt;三星&gt;联发科, 具体的排名可以去这里看(仅供参考)：<a href="http://www.mydrivers.com/zhuanti/tianti/01/">http://www.mydrivers.com/zhuanti/tianti/01/</a></p><h3 id="GPU"><a href="#GPU" class="headerlink" title="GPU"></a>GPU</h3><p>各个厂商提供的 SOC 里面，通常包含了 cpu 和 gpu ，所以大部分情况下，只要一说 cpu，其 gpu 也是对应确定的，比如高通骁龙845 SOC 带的 gpu 就是 Adreno 630。</p><p>gpu 的能力强弱更多的影响的是 gpu 强相关的应用和游戏，比如绝地求生-刺激战场 、 崩坏3 、极品飞车、狂野飙车等。反而王者荣耀这样的游戏更多的是吃 cpu 而不是 gpu。</p><h3 id="Ram"><a href="#Ram" class="headerlink" title="Ram"></a>Ram</h3><p>随着 Android 版本的更新，以及硬件的更新换代，Android 系统对内存的需求越来越强，目前4G内存的手机基本上已经成了标配，旗舰机器没个 6G 或者 8G 你都不好意思说自己是旗舰。</p><p>内存主要影响系统行为，内存越大，系统就越可以以空间换时间：后台可以缓存更多的进程，杀进程不再那么激进；可以根据用户习惯预加载一些文件或者进程；各种虚拟机、hwui、进程的参数可以往宽松里调。反馈到用户那里，就是快。</p><p>当然如果后台进程多了又没有管住在后台跑，那么又会很耗电，有点得不偿失，这也是为什么国内的系统都会对进程管理这一块进行魔改。</p><h3 id="UFS-amp-amp-EMMC"><a href="#UFS-amp-amp-EMMC" class="headerlink" title="UFS &amp;&amp; EMMC"></a>UFS &amp;&amp; EMMC</h3><p>ufs 和 emmc 都是面向移动端的 flash 标准，我们最长听说的就是 emmc5.1 和 ufs 2.1 ，具体可以参考这篇文章：<a href="https://zhuanlan.zhihu.com/p/26431201">https://zhuanlan.zhihu.com/p/26431201</a></p><p>对于用户来说，ufs 和 emmc 的差异主要在文件读取速度、视频加载速度、文件拷贝等方面，总之能上ufs就别考虑 emmc。</p><p>不过有时候这个也是需要 SOC 支持的，比如高通 660，就不支持 ufs，能不买就别买吧。</p><h3 id="屏幕分辨率"><a href="#屏幕分辨率" class="headerlink" title="屏幕分辨率"></a>屏幕分辨率</h3><p>我们最常见的屏幕分辨率是 1080P，即 1920*1080. 对用户来说，屏幕分辨率除了会影响视觉感官外，还会在系统某些地方有差异，比如截图、录屏、合成等操作。越高的屏幕分辨率，在这里的耗时就越久，也越耗电。</p><p>这也是部分 2K 手机在某些场景下，把分辨率降低到 1080P 去运行的原因。比较失败的一个例子就是当年的魅族 MX4 Pro ，在硬件性能不足以支撑 2K 的情况下，强行上了 2K 屏幕，导致很多情况下，用户反馈又卡又耗电。</p><h3 id="电池大小"><a href="#电池大小" class="headerlink" title="电池大小"></a>电池大小</h3><p>电池大小决定着续航，也决定着手机设计，手机厂家往往需要在这两者之间找一个平衡，在电池技术没有突破的情况下，就算各家都有快充，还是建议用户在选购手机的时候，尽量选大容量电池的手机，比如 Oppo Find X 或者 Vivo NEX，或者华为 Mate 10.</p><h3 id="SoC-平台"><a href="#SoC-平台" class="headerlink" title="SoC 平台"></a>SoC 平台</h3><p>SoC的全称叫做：System-on-a-Chip，除了我们之前说的 cpu、gpu，Soc 上还有很多器件，具体可以看这篇文章：<a href="https://zhuanlan.zhihu.com/p/37634251">https://zhuanlan.zhihu.com/p/37634251</a><br>，这里就不展开讲了。</p><p>SoC 是整个手机最重要的部分，是一切体验的基础。现在高通、三星、MTK 给手机厂家提供的硬件就是 Soc ，以及其配套的 Android 适配系统。手机厂商拿到这个之后，在其基础上做整机的设计，系统这边会在配套的 Android 适配系统上做移植，也就是把各家系统差异化的东西移植到新系统上。</p><p>从目前的高通、三星、MTK 三家的适配系统的质量来看，高通提供的适配系统是功能最完善的，高通在 AOSP 的基础上，加上了高通自己的非常多的优化代码，并提供了完善的参数供手机厂商去配置，总的来说开发起来是很舒服的，本身系统的问题不会太多，加上高通的文档完善，支持速度快，国内那么多手机厂商都在用高通也就不足为奇了。</p><p>至于专利费，该给的要给啊。</p><p>我们经常会说，如果魅族早点用高通的 Soc，早 TM 上市了。</p><h2 id="2-系统层面"><a href="#2-系统层面" class="headerlink" title="2. 系统层面"></a>2. 系统层面</h2><h3 id="应用的管控策略"><a href="#应用的管控策略" class="headerlink" title="应用的管控策略"></a>应用的管控策略</h3><p>大部分 Android 应用开发者对国内的手机厂商恨的咬牙切齿，最大的原因就是国内系统对应用管控这一块进行了大量的魔改，除非你是 QQ 或者微信，否则灭了屏结果都一样。</p><p>国内厂商这么做，不是没有原因的，国内应用厂商的全家桶相互唤醒，已经到了一种丧心病狂的地步，牵一发而动全身，一点都不夸张。</p><p>我们遇到的很多用户反馈的整机卡顿问题，抓 Trace 和 Log 来看，都是后台有应用在乱跑，或者后台大量的进程常驻，内存根本不够，而这些普通用户根本就不知道怎么去处理。</p><p>所以国内厂商一般会在系统里面做限制，以保障用户的基础体验：</p><ol><li>除非必须，一个应用偷偷拉起来另外一个应用的行为是不被允许的</li><li>除非必须，一个应用常驻后台是不被允许的</li><li>除非必须，一个应用在灭屏后在后台乱跑是不被允许的</li><li>除非必须，一个应用在后台长时间占用 cpu 是不被允许的</li><li>除非必须，一个应用弹窗是不被允许的</li></ol><p>另外手机厂商会有其他的逻辑清理后台的应用，尽管你是合理存在的。</p><p>对进程的严格管控，也导致了国内系统的体验有一定的影响，首当其冲的是通知，如果一个应用没有接入这个手机厂商提供的 push sdk，那么他这辈子别想发通知给用户了，如果接入了手机厂商提供的 sdk（目前大部分应用的普遍做法），由于应用不在后台，用户点击通知要等好久才可以进入到对应的界面，毫无用户体验可言。</p><h3 id="内存策略"><a href="#内存策略" class="headerlink" title="内存策略"></a>内存策略</h3><p>手机厂商常常会根据手机的内存大小来定制各种不同的策略，比如后台应用的缓存个数、LowMemoryKiller 的阈值、杀进程模块的阈值、显示模块的缓存大小阈值、用户最常用应用的个数等。</p><p>很多低端机用户反馈卡顿，我们查看发现，内存是造成卡顿的主要元凶，在低内存的机器上，由于内存不足，系统会频繁杀后台，同时也有频繁的内存-&gt;文件，文件-&gt;内存 的操作，Trace 上很多 BlockIO，很多平时执行很快的操作，现在执行要很久，再加上部分进程被杀之后马上重启，重启之后又被杀，cpu 占用很高，此时就会很卡。</p><p>随着 Android 系统和应用的更新，只会越来越吃内存，目前4G内存是标配，明年或许 6G 才是标配了，能上 8G 尽量上 8G。</p><h3 id="进程调度策略"><a href="#进程调度策略" class="headerlink" title="进程调度策略"></a>进程调度策略</h3><p>进程调度策略有时候也会影响用户的流畅性，当应用的渲染链路上，有哪个环节因为某些原因，没有被调度到的时候，很大可能会造成卡顿。</p><p>调度不到在 Trace 上的表现是 Runnable，常见的调度不到的情况有：</p><ol><li>同时运行的进程太多，cpu 这边的几个核处理的任务基本都是满的</li><li>进程优先级较低</li><li>调度器过于不灵敏，不能及时响应大任务</li></ol><p>另外由于 cpu 引起的卡顿情况还有：</p><ol><li>从大核心掉落到小核心上，小核心处理能力不足，会造成短暂的卡顿</li><li>触发温控或者触发低电量，此时某些系统会限制大核的使用，导致卡顿</li><li>系统锁也是造成卡顿的一大元凶，尤其是 wms 锁和 ams 锁，再加上 binder 通信，relayoutWindow 了解一下？ </li><li>核心频率不足，导致函数执行时间过长导致卡顿</li><li>大核心被占用，任务又调度不到小核，导致卡顿</li></ol><p>系统调优往往需要针对上面的情况做对应的处理，给用户一个好的用户体验。具体的调优方式，往往跟系统和 Soc 强相关，又涉及到 Kernel 和 功耗，改起来是牵一发而动全身，需要非常谨慎。</p><h3 id="渲染线程和主线程"><a href="#渲染线程和主线程" class="headerlink" title="渲染线程和主线程"></a>渲染线程和主线程</h3><p>Android 应用的渲染链路上最重要的就是主线程和渲染线程，主线程就是应用启动时创建的 MainThread，对应的也会创建一个 RenderThread（硬件加速默认开启），我们平时比较看重的 GPU Profile 那条线，基本就包含了主线程和渲染线程的各个阶段的执行时间，从 GPU Profile ，就可以很容易看到应用的瓶颈</p><p>大部分应用的卡顿都发生在主线程和渲染线程上，比如：</p><ol><li>较长时间的 input 事件处理</li><li>较长时间的动画事件处理，比如 ListView 的新 Item 的生成</li><li>复杂界面的 Measure、 Layout、Draw</li><li>较大 Bitmap 的频繁 upload </li><li>复杂渲染指令的执行</li></ol><p>很多编程的不好的实现，都可以在上面几个步骤里面体现出来，这些都可以通过 Systrace 看出来。</p><p>当前应用的渲染链路上的一切优先级都应该是最高的，后台的进程不应该对其造成影响，这也是系统优化的核心要素，不过要做到这一点也是比较难的，你很难考虑到所有的情况，比如有的用户的使用环境就是很复杂，而且都是必须的，这时候就不是很好处理。</p><h3 id="TripleBuffer"><a href="#TripleBuffer" class="headerlink" title="TripleBuffer"></a>TripleBuffer</h3><p>之前有提到 TripleBuffer，这个是 Project Butter 引进的，其中 Vsyncv 和 TripleBuffer 的引进使得 Android 的流畅度上了一个台阶，关于这个可以参考这篇文章 ： <a href="https://niorgai.github.io/2017/03/21/Android-Draw-System/">https://niorgai.github.io/2017/03/21/Android-Draw-System/</a></p><p>对于用户来说这个是透明的，影响的是 GPU Profile 的展示，有时候如果有一条线超过 16 ms 的警戒线，它不一定代表着卡顿，这就是 TrileBuffer 的作用。</p><p>后续我会有文章专门讲这个，如何判断是真正的卡顿。</p><h3 id="虚拟机-Art-和-Dalvik"><a href="#虚拟机-Art-和-Dalvik" class="headerlink" title="虚拟机 - Art 和 Dalvik"></a>虚拟机 - Art 和 Dalvik</h3><p>对用户来说，Art 虚拟机相比 Dalvik 虚拟机，最大的提升就是解放了应用的主线程，主线程不再频繁被 GC 线程 Stop ，相应卡顿也减少了很多。</p><p>当然 Art 带来的好处不止这一点，Art 随着几个大版本的缝缝补补，已经在很多地方远远超过了 Dalvik，有兴趣的可以自己查一下。</p><h3 id="温控-amp-amp-低电量"><a href="#温控-amp-amp-低电量" class="headerlink" title="温控 &amp;&amp; 低电量"></a>温控 &amp;&amp; 低电量</h3><p>之前提到，一旦触发温控或者低电量，系统会对资源做一定的限制，防止手机无限制过热或者快速关机。这限制就包括</p><ol><li>降低 cpu、gpu 最高频率</li><li>减少可运行的 cpu 的核心数</li><li>杀掉部分后台进程</li><li>关闭部分特效</li><li>限制网络连接</li></ol><p>总之，这些限制或多或少会对用户造成影响，最大的影响就是卡顿，这就是很多人会遇到打游戏的时候突然很卡的一个原因。</p><p>所以说选购手机的时候，除了要看 Soc，还要看散热是否做的够好，电池是否做的够大。</p><h2 id="3-应用层面"><a href="#3-应用层面" class="headerlink" title="3. 应用层面"></a>3. 应用层面</h2><h3 id="复杂的布局"><a href="#复杂的布局" class="headerlink" title="复杂的布局"></a>复杂的布局</h3><p>复杂的布局往往是应用卡顿的最主要的元凶之一，复杂的布局意味着更长的 Measure、Layout、Draw ，这会拖慢主线程的执行速度</p><p>ListView、RecyclerView 的新的 Item 在初始化的时候也会有类似的问题，由于此时一般是在滑动，这时候的卡顿感会更明显，用户也更容易察觉，这个从 Trace 上也很容易看出来。</p><h3 id="过多的业务逻辑"><a href="#过多的业务逻辑" class="headerlink" title="过多的业务逻辑"></a>过多的业务逻辑</h3><p>过多业务逻辑导致的卡顿和响应慢的问题，拿淘宝来举例子最合适不过了，每次你冷启动淘宝的时候，进入主界面马上滑动，总感觉跟吃了屎一样，点按钮点不动，滑界面滑不动，虽然最近的版本有优化，不过你找个低端 Android 机，还是原来的配方。</p><p>淘宝在启动的时候，需要动态加载很多东西，导致主界面响应很慢，很多东西要动态加载完成后才可以操作，后台还有大量的 dex2oat 操作，可以说是很忙了。</p><h3 id="内存颠簸"><a href="#内存颠簸" class="headerlink" title="内存颠簸"></a>内存颠簸</h3><p>频繁申请和释放内存，会导致内存颠簸，从 AS 的内存监视器可以看到这一点，短时间内内存曲线上下跳动非常频繁，这时候你需要检查一下是否代码写的有问题。</p><h3 id="慢网络"><a href="#慢网络" class="headerlink" title="慢网络"></a>慢网络</h3><p>慢网络指的是用户请求网络耗时很久，这会导致用户在某些界面等待内容需要很久，比如知乎经常会出现这种情况，在用户看来，这就是卡了。</p><h3 id="不合理的设计"><a href="#不合理的设计" class="headerlink" title="不合理的设计"></a>不合理的设计</h3><p>设计和性能往往不可兼得，需要从两者之间做取舍，设计师的设计往往很炫酷，互相嵌套的动画往往是程序员的噩梦，为了实现这些复杂的效果，程序员往往需要复杂的代码来实现，这对应用的渲染链路的压力是非常大的，而且在不同性能的机器上表现差异很大，高端机用户觉得这个效果棒棒哒，低端机用户卡的要骂娘。</p><p>程序员需要有这方面的知识和数据，好与设计师动之以情晓之以理。</p><p>不过用户是很挑剔的，现在的用户对性能的要求越来越高，哪怕是低端机用户，所以合适的设计应该考虑到这部分用户、或者针对低端机用户做区分。</p><h3 id="代码实现错误"><a href="#代码实现错误" class="headerlink" title="代码实现错误"></a>代码实现错误</h3><p>俗称 bug ，很多程序员不喜欢解决性能问题，因为这个东西解决起来，性价比很低，拿我司的程序员来说，解一个性能问题的时间，可以解决十几个界面显示的问题，还未必能真的解决。</p><p>不过由于代码实现错误引起的性能问题，必须要最高优先级解决。</p><h2 id="4-流畅度优化闭环"><a href="#4-流畅度优化闭环" class="headerlink" title="4. 流畅度优化闭环"></a>4. 流畅度优化闭环</h2><h3 id="实验室监控-amp-amp-模拟用户"><a href="#实验室监控-amp-amp-模拟用户" class="headerlink" title="实验室监控 &amp;&amp; 模拟用户"></a>实验室监控 &amp;&amp; 模拟用户</h3><p>开发阶段就用各项数据来做监控和对比，尽量模拟用户的使用环境，尽早暴漏性能问题，早日解决。</p><h3 id="用户流畅度数据收集"><a href="#用户流畅度数据收集" class="headerlink" title="用户流畅度数据收集"></a>用户流畅度数据收集</h3><p>在用户使用阶段，收集性能数据，针对这些数据做分析，找出用户最多遇到的性能问题，针对性地做优化。需要注意此时不能在用户阶段手机太多的信息，否则会导致观察者效应</p><p>至于需要收集的数据，则需要根据相关度模型来做判断，卡顿发生的时候，系统的哪些指标是可能导致卡顿的原因，那么这些指标就是我们收集的数据。</p><p>另外用户的场景判断也非常重要，需要知道用户是在哪个场景出现的卡顿，一旦用户的数量到了一定的级别，这个是很容易发现问题的。</p><h3 id="针对性地优化"><a href="#针对性地优化" class="headerlink" title="针对性地优化"></a>针对性地优化</h3><p>大数据发现问题后，后续就是针对性地进行优化，把用户最常遇到性能问题的场景进行排序，对最常见的场景进行调研和优化。很多时候需要与应用开发厂商进行沟通，</p><p>然后需要把这些场景纳入到实验室监控环境里，做到 实验室监控 —&gt; 模拟用户 — &gt; 大数据收集 —&gt; 针对性优化 —&gt; 实验室监控补充. 这样一个闭环。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近一直想写一些关于 Android 系统流畅度的东西，因为流畅度这个东西，是消费者最直接能体验到的，再加上 Android 一直为人诟病的越用越卡顿，使得大家在提到安卓机的时候，都会有一丝阴影。这也是大部分人买手机首先会考虑 iPhone 的一个原因。&lt;/p&gt;
&lt;p&gt;由于</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>知乎 救救你的 StartingWindow</title>
    <link href="https://androidperformance.com/2018/05/20/zhihu-startingwindow/"/>
    <id>https://androidperformance.com/2018/05/20/zhihu-startingwindow/</id>
    <published>2018-05-20T11:50:13.000Z</published>
    <updated>2026-02-07T05:17:47.883Z</updated>
    
    <content type="html"><![CDATA[<p>我们经常说 iOS 整机体验要比 Android 好，这里有第三方软件的功劳（iOS 版本质量要高于 Android 版本质量），当然也归功于苹果对 iOS 的封闭系统的管控。应用想上 App Store，就得先过审核，不合规就给你打回来。</p><p>今天我们要说的，就是 iOS 和 Android 系统差异化的一个重要体现：StartingWindow，通俗点说，就是应用启动页。iOS 和 Android 都有 StartingWindow，但是表现却完全不一样。iOS 开发要求应用必须要有一个 StartingWindow，且必须是一张图片（iOS 开发可以说说是否支持定制 Layout），用户点击桌面图标启动应用，不会有任何延迟，会立即显示这个图片；Android 系统的 StartingWindow 虽然也是系统提供的，但是由于开放性，Android 允许开发者自己定制 StartingWindow、disable StartingWindow、透明化 StartingWindow。</p><span id="more"></span><h2 id="StartingWindow-对用户体验的影响"><a href="#StartingWindow-对用户体验的影响" class="headerlink" title="StartingWindow 对用户体验的影响"></a>StartingWindow 对用户体验的影响</h2><p>由于 StartingWindow 对用户体验非常重要，但是应用开发者对 StartingWindow 的滥用，导致了用户启动应用的时候，会获得非常不好的体验，我举个例子你们就明白了：</p><ol><li>使用系统默认的 StartingWindow ：用户点了应用图标启动应用，马上弹出系统默认的 StartingWindow（就是做动画的那个 Window） ，等应用加载好第一帧之后，StartingWindow 消失，显示应用第一帧，无缝衔接，体验还不错，这也是通常大部分 Android 应用的场景；比如大部分 Android 系统的自带应用，即刻、汽车之家等</li><li>自己定制简单的 StartingWindow ：用户点了应用图标启动应用，弹出应用自己定制的StartingWindow，等应用加载好第一帧之后，定制的 StartingWindow 消失，显示应用主界面，由于 StartingWindow 是自己定制的，启动的时候 Decode Bitmap 或者  Inflate 自定义 Layout 会有一定的耗时，但是总的来说与系统默认的差别不大，用户体验优；这样的应用包括淘宝、京东、微博、今日头条、美团等</li><li>把 StartingWindow 禁掉或者设置透明 ：用户点了应用图标启动应用，由于 StartingWindow 被禁掉或者被设置透明，所以会出现点击图标后，除了图标黑一下之外没有任何响应，过个 1-N 秒（取决于应用第一帧的加载速度），直接显示应用主界面。这样的<strong>毒瘤</strong>应用包括：微信、微信读书、UC 浏览器、支付宝、工商银行、米家等。</li></ol><p>大家可以安装一下我上面说的那些个应用自己体验一下，我是真的没法体会为何会有那么多应用采用第三种方式，偏偏很多用户只有一台手机，不认为是应用的问题，反而觉得是系统比较慢，然后各种投诉到系统这边，还是四五星的 Bug，真的是欲哭无泪。</p><p>我建议采用了第三种方式的应用的开发者，面壁思过一下，你们的产品经理是不合格的，你们作为开发没有主动提出优化这个也是失职的。不用怀疑微信也是他们的一员，只不过微信作为超级 App，在很多系统里面不杀他而已，不信你 <strong>adb shell am force-stop com.tencent.mm</strong> 一下试试。</p><h2 id="知乎用户体验-数据展示"><a href="#知乎用户体验-数据展示" class="headerlink" title="知乎用户体验 - 数据展示"></a>知乎用户体验 - 数据展示</h2><p>不过我今天不想说微信，我想说说知乎，因为我本人是知乎的重度用户，所以最近被知乎的体验搞得心情极差，再加上 N 多用户投诉启动慢，我必须来说两句了。</p><p>最近知乎安卓版的版本（5.17.2），虽然没有采用第三种方式，采用的是第一种方式，但是用户体验极差，具体说就是：点击桌面图标启动应用 -&gt; 显示白屏（取决于你手机的性能，在1-5s 之间）-&gt; 显示知乎是广告页 -&gt; 显示知乎首页。我们之前说，白屏是大部分系统的默认 StartingWindow ，之所以会出现这样的现象是因为知乎那令人发指的应用启动速度。</p><p>我并不想从代码级别去分析他都干了啥，因为很多人对这个不感兴趣，感兴趣的可以自己抓 MethodTrace 看一下就可以了，这里我们简单从 Systrace 来看一眼就可以了，修复 Bug 的事情，让知乎的程序员去干吧。</p><ol><li><p>从 StartingWindow（白屏） 出现到应用显示第一帧（SurfaceFlinger 视角，数据来自 Mix2s）<br> <img src="/images/media/15269126222428.jpg" alt="第一帧"></p></li><li><p>从 StartingWindow（白屏） 出现到应用显示第一帧（应用进程视角，数据来自 Mix2S）<br><img src="/images/media/15269125336746.jpg" alt="知乎冷启动"></p></li><li><p>Log 视角(看 am_activity_launch_time ：2684)(数据来自 Pro7，Mix2s 是1552)</p></li></ol><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">033</span>  <span class="number">1252</span>  <span class="number">3371</span> I am_create_activity:<span class="meta"> [0,128018471,73,com.zhihu.android/.app.ui.activity.LauncherActivity,android.intent.action.MAIN,NULL,NULL,270532608]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">037</span>  <span class="number">1252</span>  <span class="number">3371</span> I am_focused_activity:<span class="meta"> [0,com.zhihu.android/.app.ui.activity.LauncherActivity,startedActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">042</span>  <span class="number">1252</span>  <span class="number">3371</span> I sysui_action:<span class="meta"> [323,com.zhihu.android/.app.ui.activity.LauncherActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">092</span>  <span class="number">1252</span>  <span class="number">1293</span> I am_proc_start:<span class="meta"> [0,20985,10111,com.zhihu.android,activity,com.zhihu.android/.app.ui.activity.LauncherActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">133</span>  <span class="number">1252</span>  <span class="number">2857</span> I am_proc_bound:<span class="meta"> [0,20985,com.zhihu.android]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">58</span>.<span class="number">140</span>  <span class="number">1252</span>  <span class="number">2857</span> I am_restart_activity:<span class="meta"> [0,128018471,73,com.zhihu.android/.app.ui.activity.LauncherActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">564</span>  <span class="number">1252</span>  <span class="number">3109</span> I am_create_activity:<span class="meta"> [0,123681068,73,com.zhihu.android/.app.ui.activity.MainActivity,android.intent.action.MAIN,NULL,NULL,270532608]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">566</span>  <span class="number">1252</span>  <span class="number">3109</span> I am_focused_activity:<span class="meta"> [0,com.zhihu.android/.app.ui.activity.MainActivity,startedActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">567</span>  <span class="number">1252</span>  <span class="number">3109</span> I am_pause_activity:<span class="meta"> [0,128018471,com.zhihu.android/.app.ui.activity.LauncherActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">570</span>  <span class="number">1252</span>  <span class="number">3109</span> I sysui_action:<span class="meta"> [323,com.zhihu.android/.app.ui.activity.LauncherActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">571</span>  <span class="number">1252</span>  <span class="number">3126</span> I am_finish_activity:<span class="meta"> [0,128018471,73,com.zhihu.android/.app.ui.activity.LauncherActivity,app-request]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">40</span>:<span class="number">59</span>.<span class="number">661</span>  <span class="number">1252</span>  <span class="number">3094</span> I am_restart_activity:<span class="meta"> [0,123681068,73,com.zhihu.android/.app.ui.activity.MainActivity]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">41</span>:<span class="number">00</span>.<span class="number">222</span> <span class="number">20985</span> <span class="number">20985</span> I am_on_resume_called:<span class="meta"> [0,com.zhihu.android.app.ui.activity.MainActivity,LAUNCH_ACTIVITY]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">41</span>:<span class="number">00</span>.<span class="number">736</span>  <span class="number">1252</span>  <span class="number">1305</span> I am_activity_launch_time:<span class="meta"> [0,123681068,com.zhihu.android/.app.ui.activity.MainActivity,1078,2684]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">41</span>:<span class="number">01</span>.<span class="number">269</span>  <span class="number">1252</span>  <span class="number">2906</span> I am_destroy_activity:<span class="meta"> [0,128018471,73,com.zhihu.android/.app.ui.activity.LauncherActivity,finish-imm]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">41</span>:<span class="number">01</span>.<span class="number">859</span>  <span class="number">1252</span>  <span class="number">3072</span> I am_proc_start:<span class="meta"> [0,21229,10111,com.zhihu.android:pushservice,service,com.zhihu.android/.push.getui.GetuiPushService]</span></span><br><span class="line"><span class="attribute">05</span>-<span class="number">21</span> <span class="number">22</span>:<span class="number">41</span>:<span class="number">01</span>.<span class="number">882</span>  <span class="number">1252</span>  <span class="number">2857</span> I am_proc_bound:<span class="meta"> [0,21229,com.zhihu.android:pushservice]</span></span><br></pre></td></tr></table></figure><p>这长达 1.9s 的时间，用户看到的都是白屏，更不用说后面还有 3s 的广告页，然后才到主界面，说好的用户体验呢。。。而且我用的机器是小米 Mix2S，高通845加持，那些低端机器就更不用说了，时间只会多不会少。</p><h2 id="知乎用户体验-用户感受"><a href="#知乎用户体验-用户感受" class="headerlink" title="知乎用户体验 - 用户感受"></a>知乎用户体验 - 用户感受</h2><p>用户看到的就是下面三个步骤（截图来自 Pro7）</p><ol><li><p>首先点击桌面后，显示 StartingWindow（白屏页面）<br> <img src="/images/media/S80521-222728-1.jpg" alt="S80521-222728"></p></li><li><p>2s 后，白屏页面跳转广告页面：<br> <img src="/images/media/S80521-222748-1.jpg" alt="S80521-222748"></p></li><li><p>3S 后跳转知乎主界面<br> <img src="/images/media/S80521-222753-1.jpg" alt="S80521-222753"></p></li></ol><p>我建议知乎还是采用大多数 Android 应用目前采用的第二种方法，即定制简单的 StartingWindow，用户体验好不说，也能拉近 iOS 和 Android 版本的体验差距，比如下图（截取自知乎 iOS 版本）<br><img src="/images/media/Image-1-1.jpg" alt="Image-1"></p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>很大程度上来说，Android 系统的用户体验取决于第三方应用，各个厂商定制的系统能提升一部分，但是一旦进入了应用的界面，体验就完全取决于应用了。</p><p>StartingWindow 是一个很小的点，但毕竟是应用的入口，入口做的都不好的话，怎么能吸引用户每次都去访问你呢？知乎只是我举的一个小例子，毕竟还有非常恶劣的采用第三种方式的应用垫底，希望通过这个例子，各位应用开发者能体验到应用启动速度对用户的直接影响，俗话说，<strong>天下武功，唯快不破</strong>，不是没有道理的。应用优化的好，系统自然也会沾光，最终受益的还是用户。</p><p>最后还是挂一下那些非常影响用户体验的应用（采用了第三种方法），也希望读者可以反馈，我会持续更新，他们是</p><ol><li>微信</li><li>微信读书</li><li>UC 浏览器</li><li>支付宝</li><li>工商银行</li><li>米家</li><li>……</li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;我们经常说 iOS 整机体验要比 Android 好，这里有第三方软件的功劳（iOS 版本质量要高于 Android 版本质量），当然也归功于苹果对 iOS 的封闭系统的管控。应用想上 App Store，就得先过审核，不合规就给你打回来。&lt;/p&gt;
&lt;p&gt;今天我们要说的，就是 iOS 和 Android 系统差异化的一个重要体现：StartingWindow，通俗点说，就是应用启动页。iOS 和 Android 都有 StartingWindow，但是表现却完全不一样。iOS 开发要求应用必须要有一个 StartingWindow，且必须是一张图片（iOS 开发可以说说是否支持定制 Layout），用户点击桌面图标启动应用，不会有任何延迟，会立即显示这个图片；Android 系统的 StartingWindow 虽然也是系统提供的，但是由于开放性，Android 允许开发者自己定制 StartingWindow、disable StartingWindow、透明化 StartingWindow。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="用户体验" scheme="https://androidperformance.com/tags/%E7%94%A8%E6%88%B7%E4%BD%93%E9%AA%8C/"/>
    
  </entry>
  
  <entry>
    <title>「置顶」Android 性能优化必知必会</title>
    <link href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/"/>
    <id>https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/</id>
    <published>2018-05-07T12:10:44.000Z</published>
    <updated>2026-02-07T05:17:47.849Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章记录了 Android 性能优化所必须掌握的知识（主要是对应的优秀文章、公众号、博客、技术团队等），涵盖性能优化相关的方方面面。本文会持续更新，欢迎各位自荐或者推荐。</p><span id="more"></span><p>做了这么久性能相关的工作，也接触了不少模块，说实话要做好性能这一块，真心不容易。为什么这么说? 是因为需要接触的知识实在是太多了，Android 是一个整体，牵一发而动全身，不是说只懂一个模块就可以做好。</p><p>在学习的过程中，除了看源码，我还接触到了很多互联网上已有的知识，各位前辈们将他们的知识和经验倾囊相授，让我少走了很多弯路。我在自己的笔记里面存了很多很优秀的技术文章和技术文档，现在我决定将这些放到网上，让每一个想进入 Android 系统开发和优化这个领域的人，能通过阅读这篇文章，快速入门。同时也算是我对知识的一个梳理，查漏补缺，终身学习。</p><p>这篇文章记录了 Android 性能优化所必须掌握的知识，涵盖性能优化相关的方方面面(当然如果读者同学你也有很棒的私藏文章，也可以加入到这篇文章里面)。部分文章可能需要特殊的技巧才能看到，希望你已经掌握了这一部分技巧。另外附送 <a href="https://androidperformance.com/2020/02/03/android-development-learning-path-2020-edition/">Android 开发者学习路线(2020 版本)</a>。</p><p>这篇文章会持续更新，最新更新时间：2022-06-27。</p><h1 id="优化心得和经验"><a href="#优化心得和经验" class="headerlink" title="优化心得和经验"></a>优化心得和经验</h1><ol><li><a href="https://mp.weixin.qq.com/s/-3uY3aSF67xTzWEJsa7e-A">抖音 Android 性能优化系列：启动优化实践</a></li><li><a href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">系列视频 Android Performance Patterns</a></li><li><a href="https://juejin.im/entry/5aa24187518825557207f8e2">给 App 提速：Android 性能优化总结 </a></li><li><a href="https://tech.meituan.com/hertz.html">移动端性能监控方案 Hertz </a></li><li><a href="https://androidperformance.com/2015/03/31/android-performance-case-study-follow-up/">Android 性能优化后续</a></li><li><a href="http://weishu.me/2016/12/23/dive-into-android-optimize-vm-heap/">Android性能优化之虚拟机调优</a></li><li><a href="https://zhuanlan.zhihu.com/p/27065828">Android UI 性能优化</a></li><li><a href="https://developer.android.google.cn/training/articles/perf-tips">Performance Tips</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651747849&idx=1&sn=e6bb86aadb6b7146f3d9ecfb1469a94b&chksm=bd12af448a652652c2d9a8f38bcff5f61b44ad26d86d4d85f6187d6e13296765c1e23dd744f9&mpshare=1&scene=1&srcid=0412q53XS0opyXmxjPp2thAv%23rd">美团外卖Android Lint代码检查实践</a></li><li><a href="https://www.youtube.com/watch?v=VC2Hlb22mZM">Android battery and memory optimizations - Google I&#x2F;O 2016</a></li><li><a href="https://www.udacity.com/course/android-performance--ud825">Google 免费的公开课 : Android Performance</a></li><li><a href="http://imgtec.eetrend.com/blog/11583">Android APP 性能优化的一些思考</a></li><li><a href="https://zhuanlan.zhihu.com/p/27593816">使用Android Studio和MAT进行内存泄漏分析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&mid=2650403370&idx=1&sn=b4297b138eb7f73c95a6279c3458f025&chksm=83953a32b4e2b3247fc18cbee08a2682d8b09720a1c5fef0c36257ae92b1e201cb1ad3125455&mpshare=1&scene=1&srcid=#rd">历时1年，上百万行代码！首次揭秘手淘全链路性能优化（上）</a></li><li><a href="https://wemp.app/posts/cb3c5a67-0502-4d0a-8afa-b0e9863d8b83?utm_source=latest-posts">别催更啦！手淘全链路性能优化下篇</a></li><li><a href="https://time.geekbang.org/column/intro/142">Android开发高手课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653579716&idx=1&sn=2f65e52559ae46b42222f72219a52198&chksm=84b3bbc3b3c432d52e3d01cc7b686877100556d18610eb80906596d5efa9c0284d1d38c44078&mpshare=1&scene=1&srcid=0311Jtai5vFmqNwNp8WUgsRT#rd">手 Q Android缓存监控与优化实践</a></li><li><a href="https://www.infoq.cn/article/weixin-reading-stuck-monitor-and-test?useSponsorshipSuggestions=true&utm_source=articles_about_architecture-design&utm_medium=link&utm_campaign=architecture-design">微信读书（Android）阅读引擎卡顿监控测试</a></li><li><a href="http://www.caveman.work/2019/06/01/Data-science-for-mobile-OS-system-optimization/">Data science for mobile OS system optimization</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649287054&idx=1&sn=40f1b9935c280547926fc5f799c0b9c2&chksm=8334cd0cb443441aad977bd462df6cafcb20ae55bf9d70c99a7b3045178c848a7e75b6e02aa1&mpshare=1&scene=1&srcid=#rd">Matrix TraceCanary – 初恋·卡顿</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649287034&idx=1&sn=6706196ff0824578f1400fdf9906c025&chksm=8334cdf8b44344ee51bc30820756ea737d2615fc5b30122c91a88dd7f7e7694847071a7c3b9c&mpshare=1&scene=1&srcid=#rd">Matrix IOCanary – I&#x2F;O 质量监控</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwNTAzMjcxNg==&mid=2651425463&idx=1&sn=778b483ee453f0ba987e5c9edd85c3a4">见微知著，Google Photos Web UI 完善之旅</a></li><li><a href="https://mp.weixin.qq.com/s/KtGfi5th-4YHOZsEmTOsjg">微信 Android 终端内存优化实践</a></li><li><a href="https://juejin.im/post/5be1077d518825171140dbfa">支付宝客户端架构解析：Android 客户端启动速度优化之「垃圾回收」</a></li><li><a href="https://juejin.im/post/5e5b9466518825494b3cd5aa">抖音BoostMultiDex优化实践：Android低版本上APP首次启动时间减少80%</a></li><li><a href="https://juejin.im/post/5e809cf46fb9a03c763cf348">抖音包大小优化-资源优化</a></li><li><a href="https://mp.weixin.qq.com/s/YJJdh3220y7TvBu3hAE3dQ">性能问题终结者”Olympic</a></li><li><a href="https://zhuanlan.zhihu.com/p/123328822">美团 Java 线程池实现原理及其在美团业务中的实践</a></li><li><a href="https://mp.weixin.qq.com/s/tO1yxFs2qNQlQ2bJ8vGzQA">Probe：Android线上OOM问题定位组件</a></li><li><a href="https://link.zhihu.com/?target=https://mp.weixin.qq.com/s/Kc1BSlbJgAUxOSIkPF4GzA">优化实践: 高刷下列表滑动出现卡顿掉帧的现象</a></li><li><a href="https://mp.weixin.qq.com/s/gMxTq0_nmE-xW7GA3pkBJg">iOS 高刷屏监控 + 优化：从理论到实践全面解析</a></li><li><a href="https://juejin.cn/post/6844904136266219534">通过 ASM 实现大图监控</a></li><li><a href="https://www.jianshu.com/p/b0de542204f8">Android S 原生系统内存泄露问题案例</a></li><li><a href="https://proandroiddev.com/can-you-trust-time-measurements-in-profiler-5b3566a55e0c">Can you trust time measurements in Profiler?</a></li><li><a href="https://mp.weixin.qq.com/s/UizFbjIjrOzDLmLc0uLkbA">移动域全链路可观测架构和关键技术</a></li></ol><h1 id="响应速度"><a href="#响应速度" class="headerlink" title="响应速度"></a>响应速度</h1><ol><li><a href="https://www.jianshu.com/p/37370c1d17fc">Android应用启动全流程分析（源码深度剖析）</a></li><li><a href="https://androidperformance.com/2019/11/18/Android-App-Lunch-Optimize/">Android App 启动优化全记录</a></li><li><a href="https://source.android.com/devices/tech/perf/boot-times">Optimizing Boot Times</a></li><li><a href="https://androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/">Android 中如何计算 App 的启动时间</a></li><li><a href="https://developer.android.google.cn/topic/performance/launch-time">Google 官方文档 - Launch-time performance</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650829097&idx=2&sn=e59841d4b1ed7e12a30e29ec51072d70&chksm=80b7a5b7b7c02ca184e0c06289d90823d589e738c55712318875f51e4aeb8646294b8d426299&mpshare=1&scene=1&srcid=&sharer_sharetime=1571275213308&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">都 9102 年了，Android 冷启动优化除了老三样还有哪些新招？</a></li><li><a href="https://mp.weixin.qq.com/s/79tAFx6zi3JRG-ewoapIVQ">支付宝 App 构建优化解析：通过安装包重排布优化 Android 端启动性能</a></li><li><a href="https://mp.weixin.qq.com/s/Bf41Kez_OLZTyty4EondHA">Redex 初探与 Interdex：Andorid 冷启动优化</a></li><li><a href="https://juejin.im/post/5c21ea325188254eaa5c45b1#heading-5">Android 性能优化笔记（一）——启动优化</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247485101&idx=1&sn=abbbb6da1aba37a04047fc210363bcc9&chksm=e9d0cd4fdea7445989cf26623a16fc8ce2876bf3bda95a5532bb0e5e5b1420765653df0b94d1&mpshare=1&scene=1&srcid=&sharer_sharetime=1565403851018&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">抖音研发实践：基于二进制文件重排的解决方案 App 启动速度提升超15%</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI4MTQyNDg3Mg==&mid=2247485301&idx=2&sn=bb1e9c17b705d0dce176f127e539dd97&chksm=eba821f2dcdfa8e4890696f0870a6f48306c0bdb7bffaab146f7250446494470245647a4eb09&mpshare=1&scene=1&srcid=0114JKmlCgbw5D3pMso2K2i8#rd">爱奇艺 Android 客户端启动优化与分析</a></li><li><a href="https://juejin.im/post/5e6f18a951882549422ef333">深入探索 Android 启动速度优化</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247491335&idx=1&sn=e3eabd9253ab2f83925af974db3f3072&chksm=e9d0d4e5dea75df3f718744d1f5fe3aa518f7c119d3d1a7392952274fc0d90a07fad88cad133&scene=178&cur_album_id=1659632937832595461#rd">抖音 Android 性能优化系列：启动优化之理论和工具篇</a></li><li><a href="https://mp.weixin.qq.com/s/Hgjm0GaI27he7VWbPB56YA">APM 页面加载耗时校准</a></li><li><a href="https://android-developers.googleblog.com/2022/01/improving-app-performance-with-baseline.html">Improving App Performance with Baseline Profiles</a></li></ol><h1 id="流畅度"><a href="#流畅度" class="headerlink" title="流畅度"></a>流畅度</h1><ol><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Debug/">Android 中的卡顿丢帧原因概述 - 方法论</a> </li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-System/">Android 中的卡顿丢帧原因概述 - 系统篇</a></li><li><a href="https://www.androidperformance.com/2019/09/05/Android-Jank-Due-To-App/">Android 中的卡顿丢帧原因概述 - 应用篇</a></li><li><a href="https://www.jianshu.com/p/386bbb5fa29a">Android 卡顿掉帧问题分析之原理篇</a> </li><li><a href="https://www.jianshu.com/p/cf531a3af828">Android 卡顿掉帧问题分析之工具篇</a> </li><li><a href="https://www.jianshu.com/p/f1a777551b70">Android 卡顿掉帧问题分析之实战篇</a></li><li><a href="https://androidperformance.com/2019/01/21/android-performance-case-jank-accessbility/">Android 平台应用宝和讯飞输入法无障碍服务导致的全局卡顿分析</a></li><li><a href="https://source.android.com/devices/tech/debug/eval_perf">Evaluating Performance</a></li><li><a href="https://source.android.com/devices/tech/debug/systrace">Understanding Systrace</a></li><li><a href="https://source.android.com/devices/tech/debug/ftrace">Using ftrace</a></li><li><a href="https://source.android.com/devices/tech/debug/jank_capacity">Identifying Capacity-Related Jank</a></li><li><a href="https://source.android.com/devices/tech/debug/jank_jitter">Identifying Jitter-Related Jank</a></li><li><a href="http://blog.csdn.net/tencent_bugly/article/details/51354517">那些年我们用过的显示性能指标</a></li><li><a href="https://developer.android.google.cn/topic/performance/vitals/render">Slow rendering</a></li><li><a href="https://juejin.im/post/5ae98d33518825670b33e5e6">Android 流畅度检测原理简析</a></li><li><a href="https://blog.csdn.net/msf568834002/article/details/79015497">Android JankTracker 原理解析</a></li><li><a href="https://juejin.im/entry/56dce0bb5bbb50004cce752d">Android 界面性能调优手册</a></li><li><a href="https://juejin.im/post/5da33dc56fb9a04e35597a47">App 流畅度优化：利用字节码插桩实现一个快速排查高耗时方法的工具</a></li><li><a href="https://juejin.cn/post/6844904182898524168">或许是迄今为止第一篇讲解 fps 计算原理的文章吧</a></li><li><a href="https://perfdog.qq.com/article_detail?id=10162&issue_id=0&plat_id=1">Jank 卡顿及 stutter 卡顿率说明</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247491520&idx=1&sn=dbd14f1d0d6107c87c137433ef435b5b">一文读懂直播卡顿优化那些事儿</a></li></ol><h1 id="内存"><a href="#内存" class="headerlink" title="内存"></a>内存</h1><ol><li><a href="https://mp.weixin.qq.com/s/S-YJ72qW_amYgIkBSEnsGg">抖音 Android 性能优化系列：Java OOM 优化之 NativeBitmap 方案</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247491166&idx=1&sn=eca012171fbdf0a4e79f6d3dfe72d8d3&chksm=e9d0d5bcdea75caa9a5b34499cc462b461c2fe886087065df1c9f32d09a3f25620bb54c0dbbf&scene=178&cur_album_id=2220817947374174211#rd">拯救OOM！字节自研 Android 虚拟机内存管理优化黑科技 mSponge</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487267&idx=1&sn=64858e39d3c0ac3b3444213856f0d9a3&chksm=e9d0c4c1dea74dd72482c94f936fa31d5609eff5f09b7ee2405e95eecba7ce00bbbb5657730b&scene=178&cur_album_id=1590407741716611075#rd">抖音 Android 性能优化系列：Java 内存优化篇</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247486499&idx=1&sn=1f38a8dd301d6fe1d0b62f7e027113de&chksm=e9d0c7c1dea74ed70621fb46b1f081626177610d98e1fbb4c4867099eb43edc051f61f1a2371&scene=178&cur_album_id=1568330323321470981#rd">Android Camera内存问题剖析</a></li><li><a href="https://mp.weixin.qq.com/s/yYb4pkkb30SCEGrb2w_evQ">一次大量 JVM Native 内存泄露的排查分析（64M 问题）</a></li><li><a href="https://juejin.cn/post/7027025137320853534">Andriod Native | 采样型内存调试工具GWP-ASan</a></li><li><a href="https://juejin.cn/post/7013595058125406238">Android Native | 内存问题的终极武器——MTE</a></li><li><a href="https://androidperformance.com/2019/09/18/Android-Jank-Due-To-Low-Memory/">Android 中低内存对性能的影响</a></li><li><a href="https://source.android.com/devices/tech/perf/low-ram">Low RAM Configuration</a></li><li><a href="http://www.tinylab.cn/linux-swap-and-zramfs/#zram-">Linux Swap 与 Zram 详解</a></li><li><a href="http://www.tinylab.cn/android-loading-a-different-relationship-between-dpi-and-memory-consumption-of-resources/">Android 加载不同 DPI 资源与内存消耗间的关系</a></li><li><a href="https://nekosc.com/technology/zram.html">ZRAM SWAP 内存管理讲解</a></li><li><a href="https://tech.meituan.com/oom_analysis.html">Android OOM 案例分析</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android 代码内存优化建议-Android 资源篇</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android 代码内存优化建议-Android 官方篇</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android 代码内存优化建议-Java 官方篇</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android 内存优化之一：MAT 使用入门</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android 内存优化之二：MAT 使用进阶</a></li><li><a href="https://androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android 内存优化之三：打开 MAT 中的 Bitmap 原图</a></li><li><a href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android 代码内存优化建议-OnTrimMemory 优化</a></li><li><a href="http://gityuan.com/2016/09/17/android-lowmemorykiller/">Android LowMemoryKiller原理分析</a></li><li><a href="https://juejin.im/post/59e818bb6fb9a044fd10de38">Android 匿名共享内存（Ashmem）原理</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664286&idx=1&sn=370af253ab45e8ce6da7c7eaeae0fbd1&chksm=810f3743b678be55df8f08dd554586e7e6a4e93fe61a9c557f5b161a6242eee6cd463f68a7e9&mpshare=1&scene=1&srcid=0425PBKSCz1pnccS17NcC4GX%23rd">郝健: Linux内存管理学习笔记-第1节课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664286&idx=2&sn=d6f22ef91a616e8e9232217e6dea96d4&chksm=810f3743b678be559350754a23bae993303f785ab9a46e71c09dc52ac980dd53b0d8ea7d2892&mpshare=1&scene=1&srcid=0425t2vpFk5Q1h02R7jrapFy%23rd">郝健: Linux内存管理学习笔记-第2节课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664286&idx=3&sn=9124b05c129eedc47e6dd222779a4ea6&chksm=810f3743b678be556d267e68102911aaaa68a31299705c5714a184e97478f45c8aa69cb96472&mpshare=1&scene=1&srcid=0425yjP3kUsqYPi4WgBMNs0q%23rd">郝健: Linux内存管理学习笔记-第3节课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664305&idx=1&sn=4f6de1dedec10704704ece849d395525&chksm=810f376cb678be7a7a18a7a3626615e5b8ecedae737a3f18d01db6a1af59cc04529832d1f7cb&mpshare=1&scene=1&srcid=0425iqjnsJkZEnueYQGAhWkj%23rd">郝健: Linux内存管理学习笔记-第4节课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664329&idx=1&sn=6b7841d49a5ddff8383097a3c60490e9&chksm=810f3494b678bd82fa38e95fa07e411820499f5a4a772ac60a9ade35bde48c248a30929758a1&mpshare=1&scene=1&srcid=0425aPqzRj0bES2nCueOQMqb%23rd">郝健: Linux内存管理学习笔记-第5节课</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664344&idx=1&sn=e5975e775e25e2ad1612c9dda5df52f4&chksm=810f3485b678bd933fa0c4a5bcad4298ae797724614018933f426a53b952d130557badd19518&mpshare=1&scene=1&srcid=04262p3N4EDsCVq23hxqLteY%23rd">郝健: Linux内存管理学习笔记-第6节课</a></li><li><a href="https://developer.android.google.cn/topic/performance/memory">Manage your app’s memory</a></li><li><a href="https://developer.android.google.cn/topic/performance/memory-overview">Overview of memory management</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651481746&idx=1&sn=17ffd1a167e6cd4abb71ef84a1d78dac&chksm=bd250aed8a5283fb9a09a166b58606dc7bccf03eef3e28594470a84f94ef4db1b0c2cc5e5fb6&mpshare=1&scene=1&srcid=0916ILFD69CAZIwr7fzDjip8%23rd">Linux 查看进程消耗内存情况总结</a></li><li><a href="https://blog.csdn.net/mychen/article/details/80001687">一次过程 Android 平台 native heap 内存泄露的追查过程</a></li><li><a href="http://www.wowotech.net/memory_management/458.html">浅谈 Cache Memory</a></li><li><a href="https://juejin.im/post/5bfbd5406fb9a049be5d2a20">Bitmap 优化详谈</a></li><li><a href="https://mp.weixin.qq.com/s/EerrwaRGdTkOFPLrg8_-oQ">探索 Android 内存优化方法</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650828463&idx=1&sn=414bdd7012ed465664d3b99ec8f55566&chksm=80b7ba31b7c03327e86c08a9be9216b51788bce46a33be3444e1f306802be3f62d9f72a3e45d&mpshare=1&scene=1&srcid=#rd">看完这篇文章,解决 APP 中 90 % 的内存异常问题</a></li><li><a href="https://juejin.im/post/5b1b5e29f265da6e01174b84">实践App内存优化：如何有序地做内存分析与优化</a></li><li><a href="https://mp.weixin.qq.com/s/dUCrkGGSvIzYwOxBNd-Ltw">谈谈内存压缩那些事</a></li><li><a href="https://mp.weixin.qq.com/s/pwOFI-l2seDrZgik-KvIMg">dumpsys meminfo 的原理和应用</a></li><li><a href="https://juejin.cn/post/6844904113046568973">Binder | 内存拷贝的本质和变迁</a></li><li><a href="https://mp.weixin.qq.com/s/V7sK1WwQ4D3LikK2AHgrZQ">内存优化: 纹理压缩技术</a></li><li><a href="https://www.jianshu.com/p/f25539c80768">使用 perfetto 分析 Android native 物理内存泄漏</a></li><li><a href="https://www.jianshu.com/p/b609f34f77c4">使用 Android profiler 分析 Android native 物理内存泄漏</a></li><li><a href="https://www.jianshu.com/p/ab7bbc319dd9">Android JVMTI 实现应用内存动态检测</a></li><li><a href="https://juejin.cn/post/7065931214120550413">ThreadLocal 源码分析以及可能引发的内存泄漏问题</a></li><li><a href="https://juejin.cn/post/7063068797304832030">App处于前台，Activity就不会被回收了？</a></li></ol><h1 id="图形栈"><a href="#图形栈" class="headerlink" title="图形栈"></a>图形栈</h1><ol><li><a href="https://www.cnblogs.com/roger-yu/p/15641545.html">Android 12(S) 图形显示系统 - 开篇</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15702027.html">Android 12(S) 图形显示系统 - 基本概念（一）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15707940.html">Android 12(S) 图形显示系统 - 示例应用（二）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15714247.html">Android 12(S) 图形显示系统 - 应用建立和 SurfaceFlinger 的沟通桥梁（三）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15761646.html">Android 12(S) 图形显示系统 - SurfaceFlinger 的启动和消息队列处理机制（四）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15768028.html">Android 12(S) 图形显示系统 - createSurface 的流程（五）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15773008.html">Android 12(S) 图形显示系统 - BufferQueue&#x2F;BLASTBufferQueue 之初识（六）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15773010.html">Android 12(S) 图形显示系统 - 几个常用类的介绍（七）</a></li><li><a href="https://mp.weixin.qq.com/s/MuPX4s46tR7Hn_ak51x95Q">Android display pipeline 本身以及进程调度</a></li><li><a href="https://androidperformance.com/2019/07/27/Android-Hardware-Layer/">Android 中的 Hardware Layer 详解</a></li><li><a href="https://zhuanlan.zhihu.com/p/25477828">Android 硬件加速原理与实现简介</a></li><li><a href="http://gityuan.com/2017/02/05/graphic_arch/">Android 图形系统概述</a></li><li><a href="http://gityuan.com/2017/02/25/choreographer/">Choreographer 原理</a></li><li><a href="http://gityuan.com/2017/02/11/surface_flinger/">SurfaceFlinger 启动篇</a></li><li><a href="http://gityuan.com/2017/02/18/surface_flinger_2/">SurfaceFlinger 绘图篇</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/45601143">Android 应用程序 UI 硬件加速渲染技术简要介绍和学习计划</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/45769759">Android 应用程序 UI 硬件加速渲染环境初始化过程分析</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/45831269">Android 应用程序 UI 硬件加速渲染的预加载资源地图集服务（Asset Atlas Service）分析</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/45943255">Android 应用程序 UI 硬件加速渲染的 Display List 构建过程分析</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/46281499">Android 应用程序 UI 硬件加速渲染的 Display List 渲染过程分析</a></li><li><a href="http://blog.csdn.net/luoshengyang/article/details/46449677">Android 应用程序 UI 硬件加速渲染的动画执行过程分析</a></li><li><a href="https://www.jianshu.com/p/40f660e17a73">Android硬件加速（一）- 理解Android硬件加速原理的小白文</a></li><li><a href="https://www.jianshu.com/p/dd800800145b">Android硬件加速（二）- RenderThread与OpenGL GPU渲染</a></li><li><a href="https://blog.csdn.net/jinzhuojun/article/details/39698317">Android中的GraphicBuffer同步机制-Fence</a></li><li><a href="https://www.jianshu.com/p/824a9ddf68b9">Android P 图形显示系统（一）硬件合成HWC2</a></li><li><a href="https://www.jianshu.com/p/dd0b38832346">Android P 图像显示系统（二）GraphicBuffer和Gralloc分析</a></li><li><a href="https://www.jianshu.com/p/abfaea892611">Android P 图像显示系统（三）Android HWUI 绘制流程</a></li><li><a href="https://www.jianshu.com/p/c4ea60bc73d2">Android P 图形显示系统（四） Android VirtualDisplay解析</a></li><li><a href="https://www.jianshu.com/p/8e7a9a0b5726">Android P 图形显示系统（五） 上层Client和SurfaceFlinger的交互</a></li><li><a href="https://www.jianshu.com/p/fa115146949f">Android P 图形显示系统（六） SurfaceFlinger合成流程(一)</a></li><li><a href="https://www.jianshu.com/p/fd16dcb4dfb6">Android P 图形显示系统（七） SurfaceFlinger合成流程(二)</a></li><li><a href="https://www.jianshu.com/p/cf4455021fd5">Android P 图形显示系统（八） SurfaceFlinger合成流程(三)</a></li><li><a href="https://www.jianshu.com/p/b1b75ab6f17f">Android P 图形显示系统（九） Android图形显示子系统概述</a></li><li><a href="https://www.jianshu.com/p/81e9c814f10a">Android P 图形显示系统（十） BufferQueue（一）</a></li><li><a href="https://www.jianshu.com/p/f808813880b0">Android P 图形显示系统（十一） BufferQueue（二）</a></li><li><a href="https://www.jianshu.com/p/6d83dea3652b">Android P 图形显示系统（十二） BufferQueue（三）</a></li><li><a href="https://zhuanlan.zhihu.com/p/78758247">渲染流水线中的光栅化1</a></li><li><a href="https://zhuanlan.zhihu.com/p/81974121">渲染流水线中的光栅化2</a></li></ol><h1 id="虚拟机"><a href="#虚拟机" class="headerlink" title="虚拟机"></a>虚拟机</h1><ol><li><a href="https://juejin.cn/post/6875678394332217357">ART虚拟机 | GC的触发时机和条件</a></li><li><a href="https://juejin.cn/post/7088976209387716622">ART虚拟机 | Large Object Space</a></li><li><a href="https://juejin.cn/post/6888987190857039885">ART虚拟机 | JNI调用的中间环节</a></li><li><a href="https://juejin.cn/post/6891918738846105614">ART虚拟机 | Finalize的替代者Cleaner</a></li><li><a href="https://juejin.cn/post/6894153239907237902">ART虚拟机 | 如何让GC同步回收native内存</a></li><li><a href="https://juejin.cn/post/6950920684768296996">ART虚拟机 | Java对象和类的内存结构</a></li><li><a href="https://juejin.cn/post/6955322183895908366">ART虚拟机 | JNI静态注册和动态注册</a></li><li><a href="https://juejin.cn/post/6956213033806872606">ART虚拟机 | 锁</a></li><li><a href="https://juejin.cn/post/6966169836703449119">ART虚拟机 | Android应用中SIGSEGV信号的处理流程</a></li><li><a href="http://lihaizhou.top/2021/11/01/%E7%AC%AC%E4%B8%89%E8%A7%86%E8%A7%92-%E4%B8%80%E4%B8%AAART-GC%E7%9A%84%E4%BC%98%E5%8C%96%E6%95%85%E4%BA%8B/">第三视角: 一个 ART GC 的优化故事</a></li><li><a href="http://lihaizhou.top/2021/09/08/GC%E8%B6%85%E6%97%B6%E5%AF%BC%E8%87%B4%E7%9A%84%E5%90%8E%E5%8F%B0%E5%BA%94%E7%94%A8%E5%B4%A9%E6%BA%83%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90/">GC 超时导致的后台应用崩溃问题分析</a></li><li><a href="https://mp.weixin.qq.com/s/ayhmYTbmnIThGQ-GH5cP2A">带你了解 Android 10 中的 art</a></li><li><a href="https://mp.weixin.qq.com/s/Vea97ljPww8QfAEmATtKGg">带你了解 Android 10 中的 art</a></li><li><a href="https://mp.weixin.qq.com/s/rBxBoOUyXtUBcFqfYdxl_g">带你了解 Android 10 中的 art</a></li><li><a href="https://mp.weixin.qq.com/s/K3JFUIKYfO4hrqLA-4BqfA">带你了解 Android 10 中的 art</a></li><li><a href="https://mp.weixin.qq.com/s/06uEMSVo-nnynNj5FbXQKA">带你了解 Android 10 中的 art</a></li><li><a href="https://source.android.com/devices/tech/dalvik/">ART and Dalvik</a></li><li><a href="https://source.android.com/devices/tech/dalvik/improvements">Android 8.0 ART Improvements</a></li><li><a href="https://source.android.com/devices/tech/dalvik/dalvik-bytecode">Dalvik bytecode</a></li><li><a href="https://source.android.com/devices/tech/dalvik/dex-format">Dalvik Executable format</a></li><li><a href="https://source.android.com/devices/tech/dalvik/instruction-formats">Dalvik Executable instruction formats</a></li><li><a href="https://source.android.com/devices/tech/dalvik/constraints">Constraints</a></li><li><a href="https://source.android.com/devices/tech/dalvik/configure">Configuring ART</a></li><li><a href="https://source.android.com/devices/tech/dalvik/gc-debug">Debugging ART Garbage Collection </a></li><li><a href="https://source.android.com/devices/tech/dalvik/jit-compiler">Implementing ART Just-In-Time (JIT) Compiler</a></li><li><a href="https://zhuanlan.zhihu.com/p/24414378">深入学习Android：虚拟机&amp;运行时</a></li><li><a href="https://zhuanlan.zhihu.com/p/24534940">Android性能优化之虚拟机调优</a></li><li><a href="https://paul.pub/android-dalvik-vm/">Android上的Dalvik虚拟机</a></li><li><a href="https://paul.pub/android-art-vm/">Android上的ART虚拟机</a></li><li><a href="https://zhuanlan.zhihu.com/p/89536376">Android ART 并行拷贝垃圾回收</a></li><li><a href="https://mp.weixin.qq.com/s/pmtbK8aezOcd_yBjGu4n7g">Android ART dex2oat 浅析</a></li></ol><h1 id="系统框架"><a href="#系统框架" class="headerlink" title="系统框架"></a>系统框架</h1><ol><li><a href="https://www.jianshu.com/p/df46e4b39428">Android画面显示流程分析(1)</a></li><li><a href="https://www.jianshu.com/p/f96ab6646ae3">Android画面显示流程分析(2)</a></li><li><a href="https://www.jianshu.com/p/3c61375cc15b">Android画面显示流程分析(3)</a></li><li><a href="https://www.jianshu.com/p/7a18666a43ce">Android画面显示流程分析(4)</a></li><li><a href="https://www.jianshu.com/p/dcaf1eeddeb1">Android画面显示流程分析(5)</a></li><li><a href="https://source.android.com/devices/tech/perf/task-snapshots">Task Snapshots</a></li><li><a href="https://zhuanlan.zhihu.com/p/29152319">Android Input 子系统：Input 进程的创建，监听线程的启动</a></li><li><a href="https://zhuanlan.zhihu.com/p/29386642">Android Input 子系统：Input 事件的产生、读取和分发，InputReader、InputDispatcher</a></li><li><a href="https://zhuanlan.zhihu.com/p/30127752">EventHub 与设备、Input 事件的交互</a></li><li><a href="https://zhuanlan.zhihu.com/p/29929031">Android 消息机制，从Java 层到 Native 层剖析</a></li><li><a href="https://paul.pub/android-binder-driver/">理解 Android Binder 机制(1&#x2F;3)：驱动篇</a></li><li><a href="https://paul.pub/android-binder-cpp/">理解 Android Binder 机制(2&#x2F;3)：C++ 层</a></li><li><a href="https://paul.pub/android-binder-java/">理解 Android Binder 机制(3&#x2F;3)：Java 层</a></li><li><a href="http://blog.csdn.net/universus/article/details/6211589">Android Binder 设计与实现 - 设计篇</a></li><li><a href="http://gityuan.com/2017/05/19/ams-abstract/">四大组件之综述</a></li><li><a href="http://gityuan.com/2017/06/11/activity_record/">四大组件之 ActivityRecord</a></li><li><a href="http://gityuan.com/2017/06/04/content_provider_record/">四大组件之 ContentProviderRecord</a></li><li><a href="http://gityuan.com/2017/06/03/broadcast_record/">四大组件之 BroadcastRecord</a></li><li><a href="http://gityuan.com/2017/05/25/service_record/">四大组件之 ServiceRecord</a></li><li><a href="http://gityuan.com/2017/04/16/activity-with-window/">简述 Activity 与 Window 关系</a></li><li><a href="http://gityuan.com/2017/04/09/android_context/">理解 Android Context</a></li><li><a href="http://gityuan.com/2017/04/02/android-application/">理解 Application 创建过程</a></li><li><a href="http://gityuan.com/2017/01/22/start-activity-wms/">以 Window 视角来看 startActivity</a></li><li><a href="http://gityuan.com/2017/01/15/wms_starting_window/">WMS—启动窗口(StartingWindow) </a></li><li><a href="http://gityuan.com/2017/01/08/windowmanger/">WMS—启动过程 </a></li><li><a href="https://zhuanlan.zhihu.com/p/35519585">写给 Android 应用工程师的 Binder 原理剖析</a></li><li><a href="http://gityuan.com/2015/10/31/binder-prepare/">Binder 系列—开篇</a></li><li><a href="http://gityuan.com/2015/11/01/binder-driver/">Binder 系列1—Binder Driver初探</a></li><li><a href="http://gityuan.com/2015/11/02/binder-driver-2/">Binder 系列2—Binder Driver再探</a></li><li><a href="http://gityuan.com/2015/11/07/binder-start-sm/">Binder 系列3—启动ServiceManager</a></li><li><a href="http://gityuan.com/2015/11/08/binder-get-sm/">Binder 系列4—获取ServiceManager</a></li><li><a href="http://gityuan.com/2015/11/14/binder-add-service/">Binder 系列5—注册服务(addService)</a></li><li><a href="http://gityuan.com/2015/11/15/binder-get-service/">Binder 系列6—获取服务(getService)</a></li><li><a href="http://gityuan.com/2015/11/21/binder-framework/">Binder 系列7—framework层分析</a></li><li><a href="http://gityuan.com/2015/11/22/binder-use/">Binder 系列8—如何使用Binder</a></li><li><a href="http://gityuan.com/2015/11/23/binder-aidl/">Binder 系列9—如何使用AIDL</a></li><li><a href="http://gityuan.com/2015/11/28/binder-summary/">Binder 系列10—总结</a></li><li><a href="http://gityuan.com/2016/09/04/binder-start-service/">彻底理解 Android Binder 通信架构</a></li><li><a href="http://weishu.me/2016/01/12/binder-index-for-newer/">Binder 学习指南</a></li><li><a href="https://wetest.qq.com/lab/view/352.html">你知道 android 的 MessageQueue.IdleHandler 吗？</a></li><li><a href="https://my.oschina.net/youranhongcha/blog/492591?p=2">聊一聊 Android 的消息机制</a></li><li><a href="https://mp.weixin.qq.com/s/69ndd2NCx27JWxJGEqTQBg">聊聊 APK (一) ——直接运行 Dex 文件的黑魔法</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzIxNDE1NjQ2Mw==&mid=2649872639&idx=1&sn=94bd74f4fe5d18c839c14e2cb8c2856b&chksm=8faead3fb8d924293764698889f419bdde71c9b523acf98edba313f3ec6b75b7d8332f43c9c6&mpshare=1&scene=1&srcid=&sharer_sharetime=1565860096242&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">聊聊 APK（二）——Dex 热修复与 Classpath</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzIxNDE1NjQ2Mw==&mid=2649872646&idx=1&sn=61f3f4bf96a9244292f264de3a60bc34&chksm=8faeacc6b8d925d0aaa93141e3232e5c125307fe2e731dd63a4a0740eb71c54275739daf0ab1&mpshare=1&scene=1&srcid=&sharer_sharetime=1565860101974&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">聊聊 APK（三）—— Android 资源编译的秘密</a></li><li><a href="https://juejin.im/post/5e9728c5e51d4547185f919e?utm_source=gold_browser_extension">卢半山 - 反思｜Android 事件拦截机制的设计与实现</a></li><li><a href="https://juejin.im/post/5e85aa5e6fb9a03c341d9737">卢半山 - Binder 内存拷贝的本质和变迁</a></li><li><a href="https://juejin.im/post/5e8caa97518825738a5ace08">卢半山 - AIDL 中 inout 的本质</a></li><li><a href="https://juejin.im/post/5e993b87e51d4546d962075c">卢半山 - Binder 的异常机制</a></li><li><a href="https://juejin.im/post/5daaf2b1e51d4524b601bb0b">卢半山 - Binder 世界中的代理机制（上）</a></li><li><a href="https://juejin.im/post/5d9fea976fb9a04de237a5af">卢半山 - Binder 概述</a></li><li><a href="https://juejin.cn/post/7021558328421515271">卢半山 -Binder | 对象的生命周期</a></li><li><a href="https://juejin.cn/post/7024432171779620894">卢半山 -Binder | 代理对象的泄露及其检测</a></li><li><a href="https://sharrychoo.github.io/blog/android-source/graphic-choreographer">Android 系统架构 —— Choreographer 的工作机制 </a></li><li><a href="https://mp.weixin.qq.com/s/IHQDWKYJY6lbEGZZxPT8lw">Looper的wake机制升级</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469018&idx=1&sn=b6a21164fffda38bf1fe423a8bba4a57&chksm=bd12e6f18a656fe7c59b3b1af47b436bd2472b51e2b12477ab9e87175688299e58c59a2582c5&scene=21#wechat_redirect">Android10.0 Binder 通信原理(一)Binder、HwBinder、VndBinder概要</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469152&idx=1&sn=ca5f0873cccd3413af1b822ea9fdcaae&chksm=bd12e64b8a656f5d44b8199ce8e16af0a72742071f44a34d68d790a1f93dd9bc9344d57162a4&scene=21#wechat_redirect">Android10.0 Binder 通信原理(二)-Binder入门篇</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469154&idx=1&sn=ddab595f47b4f9ae228d174b447a2fd2&chksm=bd12e6498a656f5fadb1ad10c7e92d4b5d50bc0f08387fa7069aeb0be96264dc05891b8787ef&scene=21#wechat_redirect">Android10.0 Binder 通信原理(三)-ServiceManager篇</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469156&idx=1&sn=7793b15df568f2d28e270bf1092ee37d&chksm=bd12e64f8a656f59a0596e00de96db582556cca78b38b334164aad64c804731cedb579b396e4&scene=21#wechat_redirect">Android10.0 Binder 通信原理(四)-Native-C\C++实例分析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469158&idx=1&sn=20e56da97c531da01f96515da9777159&chksm=bd12e64d8a656f5bc34a3b5017517016661b289d0b1b08493da574f8da942586f17fa1478637&scene=21#wechat_redirect">Android10.0 Binder 通信原理(五)-Binder驱动分析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469160&idx=1&sn=c799a8dea63903e8d2293d352c958a51&chksm=bd12e6438a656f5593941640f5f29937066a96a3038b2c51e5f7f912d987f3f13a7c19ac42e0&scene=21#wechat_redirect">Android10.0 Binder 通信原理(六)-Binder数据如何完成定向打击</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469166&idx=1&sn=79603ed6eabd767320be1374da82be3a&chksm=bd12e6458a656f53d37f4108fd4ba576a2c5722773bca293544cd4a18739a6a775e3d4ae4536&scene=21#wechat_redirect">Android10.0 Binder 通信原理(七)-Framework binder示例</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469168&idx=1&sn=6360b7fd3c4b93e426ec232e7dfe23d4&chksm=bd12e65b8a656f4de0ec426d381dba2b8dd969b03c83e18e6f002a55bf7af7c4616139567d3b&scene=21#wechat_redirect">Android10.0 Binder 通信原理(八)-Framework层分析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469170&idx=1&sn=c9c4ff24bec544d8435b55fe87274b7e&chksm=bd12e6598a656f4f9daa31544126182d6ac86f7efe8f4031359867b7651247eea00ef486c0c5&scene=21#wechat_redirect">Android10.0 Binder 通信原理(九)-AIDL Binder示例</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&mid=2652469171&idx=1&sn=6b8498ad20051d343d6f4808d021aa52&chksm=bd12e6588a656f4eb5ec89dff303b8525091696e56e529dd22ffb9512f02b67b5b1546cb2f93&scene=21#wechat_redirect">Android10.0 Binder 通信原理(十)-AIDL原理分析-Proxy-Stub设计模式</a></li><li><a href="https://juejin.cn/post/6922704248195153927">Android Framework | 一种新型的应用启动机制:USAP</a></li><li><a href="https://carsonho.blog.csdn.net/article/details/73560642">Android 跨进程通信：图文详解 Binder 机制 原理</a> </li><li><a href="https://www.cnblogs.com/roger-yu/p/15099541.html">Android Native – Message&#x2F;Handler&#x2F;Looper 机制（原理篇）</a></li><li><a href="https://www.cnblogs.com/roger-yu/p/15100416.html">Android Native – Message&#x2F;Handler&#x2F;Looper 机制（应用篇）</a></li><li><a href="https://blog.csdn.net/learnframework/article/details/123032419">Android P&#x2F;Q&#x2F;R&#x2F;S 9&#x2F;10&#x2F;11&#x2F;12 多任务手势动画 OtherActivityInputConsumer 情况</a></li><li><a href="https://developer.android.google.cn/guide/topics/ui/splash-screen">Android 启动画面</a></li></ol><h1 id="稳定性"><a href="#稳定性" class="headerlink" title="稳定性"></a>稳定性</h1><ol><li><a href="https://mp.weixin.qq.com/s/40T6ITvJNWR8F42530k4DA">Android ANR|原理解析及常见案例</a></li><li><a href="http://gityuan.com/2019/04/06/android-anr/">彻底理解安卓应用无响应机制</a></li><li><a href="https://www.jianshu.com/p/18f16aba79dd">应用与系统稳定性第一篇—ANR 问题分析的一般套路</a></li><li><a href="https://www.jianshu.com/p/ac545e10e39e">应用与系统稳定性第二篇—ANR 的监测与信息采集</a></li><li><a href="https://www.jianshu.com/p/1f9cff12b84f">应用与系统稳定性第三篇—FD 泄露问题漫谈</a></li><li><a href="https://www.jianshu.com/p/3017487b881f">应用与系统稳定性第四篇—单线程导致的空指针问题分析</a></li><li><a href="https://www.jianshu.com/p/f2713f371589">应用与系统稳定性第五篇—Watchdog 原理和问题分析</a></li><li><a href="https://www.jianshu.com/p/e398e450c597">应用与系统稳定性第六篇—JVM 垃圾回收之 finalize 执行时引起 timed out 闪退分析</a></li><li><a href="https://www.jianshu.com/p/2addc08cb84b">应用与系统稳定性第七篇— 用 Asan 提前解决NDK疑难crash</a></li><li><a href="https://juejin.cn/post/7076326625436467214">Android中的进程名和线程名</a></li><li><a href="https://juejin.cn/post/7041834777229393934">Android Runtime | Trace文件的生成机制</a></li><li><a href="https://juejin.cn/post/7028518916355801101">讨论 | 基于FP的栈回溯对于主线程的特殊处理</a></li><li><a href="https://juejin.cn/post/7018153262482194446">Android 资源溢出崩溃轻松解</a></li><li><a href="https://juejin.cn/post/7025432746382065678">字节Android Native Crash治理之Memory Corruption工具原理与实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487203&idx=1&sn=182584b69910c843ae95f60e74127249&chksm=e9d0c501dea74c178e16f95a2ffc5007c5dbca89a02d56895ed9b05883cf0562da689ac6146b&token=2044639920&lang=zh_CN&scene=21#wechat_redirect">西瓜视频稳定性治理体系建设一：Tailor 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s/RF3m9_v5bYTYbwY-d1RloQ">西瓜视频稳定性治理体系建设二：Raphael 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489902&idx=1&sn=bfdf9f48dc6dc973722b5dcab9cd5882&chksm=e9d0d28cdea75b9ad255eb5de227240d2e6f0e9d66e562d3f49cf69f8ed4127c9954ef21bb6d&scene=178&cur_album_id=1833937688379310087#rd">西瓜视频稳定性治理体系建设三：Sliver 原理及实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247489949&idx=1&sn=01948c047c0ce203956a3cf81dd20e83&chksm=e9d0d27fdea75b697e70a665b4c6912a8081649700766cf007a7b75d420a57089fe06d2e85b0&scene=178&cur_album_id=1833937688379310087#rd">西瓜卡顿 &amp; ANR 优化治理及监控体系建设</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488116&idx=1&sn=fdf80fa52c57a3360ad1999da2a9656b&chksm=e9d0d996dea750807aadc62d7ed442948ad197607afb9409dd5a296b16fb3d5243f9224b5763&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 设计原理及影响因素</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488182&idx=1&sn=6337f1b51d487057b162064c3e24c439&chksm=e9d0d954dea75042193ed09f30eb8ba0acd93870227c5d33b33361b739a03562afb685df9215&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 监控工具与分析思路</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488243&idx=1&sn=1f948e0ef616c6dfe54513a2a94357be&chksm=e9d0d911dea75007f36b3701b51842b9fa40969fe8175c2cb4aecf96793504602c574945d636&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列分享 - 实例剖析集锦</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488314&idx=1&sn=559e52288ae2730a580fcd550f22d895&chksm=e9d0d8d8dea751ceecb715d472796f0c678a9358abf91eb279cdb0576329595e87531e221438&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - Barrier 导致主线程假死</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247488558&idx=1&sn=27dda3c3630116d37ab56a8c7bdf1382&chksm=e9d0dfccdea756daed46b340fb8021b57ea8cc300e58bdb59f0305f8290704984308a089bf2d&scene=178&cur_album_id=1780091311874686979#rd">今日头条 ANR 优化实践系列 - 告别 SharedPreference 等待</a></li><li><a href="https://mp.weixin.qq.com/s/FuZ2CBVB_tQgvYyH66Ehcw">自动拦截 50% crash，字节自研 Fastbot 如何助力今日头条稳定性测试</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=Mzk0MzI4OTI1Ng==&mid=2247487678&idx=1&sn=2d8017af87b906ab9fa8f6387ded0a45">线上故障 - 通过系统日志分析和定位</a></li></ol><h1 id="功耗"><a href="#功耗" class="headerlink" title="功耗"></a>功耗</h1><ol><li><a href="https://mp.weixin.qq.com/s/ATppCpaKh1GcJCdts_EsyA">能感知功耗的 Linux 调度器(EAS)</a></li><li><a href="https://paul.pub/android-power">Android 功耗改进</a></li><li><a href="https://www.bilibili.com/video/BV1qf4y1c74A">视频 - iPhone 13 Pro&#x2F;Max 为什么省电？- ProMotion 技术分析</a></li></ol><h1 id="进程管理"><a href="#进程管理" class="headerlink" title="进程管理"></a>进程管理</h1><ol><li><a href="https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt">cpuset</a></li><li><a href="https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt">cgroup</a></li><li><a href="http://gityuan.com/2016/08/07/android-adj/">Android 进程调度之 adj 算法</a></li><li><a href="http://gityuan.com/2017/07/30/linux-process/">Linux 进程管理(一) </a></li><li><a href="http://gityuan.com/2017/08/05/linux-process-fork/">Linux 进程管理(二)–fork</a></li><li><a href="http://gityuan.com/2017/08/06/linux_process_pid/">Linux 进程 pid 分配法</a></li><li><a href="http://edu.csdn.net/course/detail/5995">收费培训视频 打通 Linux 脉络系列：进程、线程和调度 </a></li><li><a href="https://paul.pub/android-process-creation/">Android 系统中的进程管理：进程的创建</a></li><li><a href="https://paul.pub/android-process-priority/">Android 系统中的进程管理：进程的优先级</a></li><li><a href="https://paul.pub/android-process-recycle/">Android 系统中的进程管理：内存的回收</a></li><li><a href="https://paul.pub/android-process-schedule/">Android系统上的进程管理：进程的调度</a></li><li><a href="https://paul.pub/android-init/">Android 系统启动：init 进程与 init 语言</a></li><li><a href="https://segmentfault.com/a/1190000006251859">Android 进程保活招式大全</a></li><li><a href="https://developer.android.google.cn/guide/components/processes-and-threads">进程和线程</a></li><li><a href="https://developer.android.google.cn/topic/performance/threads">通过线程提升性能</a></li><li><a href="http://gityuan.com/2018/05/19/android-process-adj/">解读 Android 进程优先级 ADJ 算法</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653580105&idx=1&sn=e2845d214f71ba46116b7ab5eff02f53&chksm=84b3b94eb3c43058fced637158b728ea14063d5b00234fda187620ac01df06b9be61a7828ef9&mpshare=1&scene=1&srcid=#rd">杂谈 Android 线程优先级</a></li></ol><h1 id="IO"><a href="#IO" class="headerlink" title="IO"></a>IO</h1><ol><li><a href="https://sharrychoo.github.io/blog/android-performance-opt/io">Android 性能优化 —— IO 的监控与优化 </a></li><li><a href="https://www.cnblogs.com/huxiao-tee/p/4657851.html">从内核文件系统看文件读写过程</a></li><li><a href="https://www.cnblogs.com/huxiao-tee/p/4660352.html">认真分析 mmap：是什么 为什么 怎么用</a></li><li><a href="https://mp.weixin.qq.com/s/HNgoXqlXQC-lGU_yjxYixA">Android IO 监控 | 性能监控系列</a></li></ol><h1 id="调试工具"><a href="#调试工具" class="headerlink" title="调试工具"></a>调试工具</h1><ol><li><a href="https://zhuanlan.zhihu.com/p/25277481">另一个 Android 性能剖析工具——simpleperf</a></li><li><a href="https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md">Simpleperf</a></li><li><a href="http://weishu.me/2016/05/30/how-to-debug-android-framework/">如何调试 Android Framework</a></li><li><a href="http://weishu.me/2017/01/14/how-to-debug-android-native-framework-source/">如何调试 Android Native Framework</a></li><li><a href="https://catapult.gsrc.io/README.md">Catapult 项目</a></li><li><a href="https://zhuanlan.zhihu.com/p/27331842">手把手教你使用 Systrace（一）</a></li><li><a href="https://zhuanlan.zhihu.com/p/27535205">手把手教你使用 Systrace（二）——锁优化</a></li><li><a href="https://zhuanlan.zhihu.com/p/27593816">使用 Android Studio 和 MAT 进行内存泄漏分析</a></li><li><a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 简介</a></li><li><a href="https://www.androidperformance.com/2019/07/23/Android-Systrace-Pre/">Systrace 基础知识 - Systrace 预备知识</a></li><li><a href="https://www.androidperformance.com/2019/05/27/why-60-fps/">Systrace 基础知识 -  Why 60 fps ？</a></li><li><a href="https://androidperformance.com/2019/06/29/Android-Systrace-SystemServer/">Systrace 基础知识 - SystemServer 解读</a></li><li><a href="https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/">Systrace 基础知识 - Input 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/01/Android-Systrace-Vsync/">Systrace 基础知识 - Vsync 产生与工作机制解读</a></li><li><a href="https://androidperformance.com/2019/10/22/Android-Choreographer/">Systrace 基础知识 - Vsync-App ：基于 Choreographer 的渲染机制详解</a></li><li><a href="https://www.androidperformance.com/2019/11/06/Android-Systrace-MainThread-And-RenderThread/">Systrace 基础知识 - MainThread 和 RenderThread 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/06/Android-Systrace-Binder/">Systrace 基础知识 - Binder 和锁竞争解读</a></li><li><a href="https://www.androidperformance.com/2019/12/15/Android-Systrace-Triple-Buffer">Systrace 基础知识 - Triple Buffer 解读</a></li><li><a href="https://www.androidperformance.com/2019/12/21/Android-Systrace-CPU">Systrace 基础知识 - CPU Info 解读</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/">Systrace 流畅性实战 1 ：了解卡顿原理</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-2/">Systrace 流畅性实战 2 ：案例分析: MIUI 桌面滑动卡顿分析</a></li><li><a href="https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-3/">Systrace 流畅性实战 3 ：卡顿分析过程中的一些疑问</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-1/">Systrace 响应速度实战 1 ：了解响应速度原理</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-2/">Systrace 响应速度实战 2 ：响应速度实战分析-以启动速度为例</a></li><li><a href="https://www.androidperformance.com/2021/09/13/android-systrace-Responsiveness-in-action-3/">Systrace 响应速度实战 3 ：响应速度延伸知识</a>  </li><li><a href="https://www.androidperformance.com/2022/01/21/android-systrace-cpu-state-runnable/">Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-running/">Systrace 线程 CPU 运行状态分析技巧 - Running 篇</a></li><li><a href="https://www.androidperformance.com/2022/03/13/android-systrace-cpu-state-sleep/">Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇</a></li><li><a href="https://source.android.google.cn/devices/graphics/tracing-win-transitions">Tracing Window Transitions</a></li><li><a href="https://zhuanlan.zhihu.com/p/108260089">Android 性能问题分析之 bugreport</a></li><li><a href="https://www.jianshu.com/p/95dfe1f41971">腾讯 Apm 框架 Matrix 源码阅读 - gradle插件</a></li><li><a href="https://www.jianshu.com/p/768b14dc0ffe">腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析</a></li><li><a href="https://www.jianshu.com/p/6dad2f9be403">腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 AnrTracer</a></li><li><a href="https://www.jianshu.com/p/5721f5d11ae5">腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 StartupTracer</a></li><li><a href="https://www.jianshu.com/p/731a5f4b4cd3">腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 FrameTracer</a></li><li><a href="https://www.jianshu.com/p/d0f2164dd053">腾讯 Apm 框架 Matrix 源码阅读 - 架构解析</a></li><li><a href="https://ui.perfetto.dev/#!/viewer">PerfettoViewer</a></li><li><a href="https://mp.weixin.qq.com/s/zT5bEYBbXd6U1E9bFd26RA">Android 性能分析工具介绍 | 开发者说·DTalk</a></li></ol><h1 id="硬件相关"><a href="#硬件相关" class="headerlink" title="硬件相关"></a>硬件相关</h1><ol><li><a href="https://source.android.com/devices/tech/perf/flash-wear">Flash Wear Management in Android Automotive </a></li><li><a href="http://www.10tiao.com/html/431/201706/2650236929/1.html">Cortex-A75 和 Cortex-A55</a></li><li><a href="http://www.brendangregg.com/blog/2017-05-09/cpu-utilization-is-wrong.html">CPU Utilization is Wrong</a></li></ol><h1 id="编程语言"><a href="#编程语言" class="headerlink" title="编程语言"></a>编程语言</h1><ol><li><a href="https://academy.realm.io/cn/posts/360andev-jake-wharton-java-hidden-costs-android/">探索 Java 隐藏的开销</a></li><li><a href="https://github.com/LyndonChin/kotlin-docs-zh">Kotlin 中文文档</a></li><li><a href="https://zhuanlan.zhihu.com/p/112082107">Java 多态在 android 中的实现</a></li><li>Cancellation and Exceptions in Coroutines<ol><li><a href="https://medium.com/androiddevelopers/coroutines-first-things-first-e6187bf3bb21">Coroutines: First things first</a></li><li><a href="https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629">Cancellation in coroutines</a></li><li><a href="https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c">Exceptions in Coroutines</a></li><li><a href="https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad">Coroutines &amp; Patterns for work that shouldn’t be cancelled</a></li></ol></li><li><a href="https://zhuanlan.zhihu.com/p/24344559">彻底理解引用在 Android 和 Java 中的工作原理</a></li><li><a href="https://juejin.cn/post/7068901166456766472">Kotlin 中的协程、上下文和作用域</a></li><li><a href="https://juejin.cn/post/7064541449332719647">抽丝剥茧聊协程之深入理解 Continuation 原理</a></li><li><a href="https://medium.com/team-pratilipi/android-architecture-pattern-82aa0cf7e236">Android Architecture Pattern</a> </li><li><a href="https://magdamiu.medium.com/high-performance-with-idiomatic-kotlin-d52e099d0df0">High performance with idiomatic Kotlin</a></li></ol><h1 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h1><ol><li><a href="http://www.tinylab.cn/kernel-explore-regmap-framework/">内核探索：Regmap 框架：简化慢速 I&#x2F;O 接口优化性能 </a></li><li><a href="http://www.tinylab.cn/elinux-org-boot-time-optimization/">嵌入式 Linux 启动时间优化 </a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664066&idx=1&sn=f5bcfbb40c8b1672e64625190b795598&chksm=810f379fb678be89aa169bc8b9ba0ddfdedb88fe191522f71330a5b394c5ff7869284643608c&mpshare=1&scene=1&srcid=0406LVWw9jQVgsGymk0i3gDf%23rd">Linux 文件系统预读的情景分析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652663878&idx=1&sn=0b4118e88de5eb5b38c4ebcfa242a442&chksm=810f36dbb678bfcd3314ec8f6da1fd1caeb588a513fb307880ff228ca89d7a99d962b64739ba&mpshare=1&scene=1&srcid=0316x6fEMYpnFompZLcxdIy3%23rd">使用 blktrace 统计磁盘块 I&#x2F;O 访问频率</a></li><li><a href="http://gityuan.com/2016/05/21/syscall/">Linux 系统调用(syscall)原理</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652665068&idx=1&sn=6255885e49a552e14a89fa5f94263f4b&chksm=810f3271b678bb67f7cb02b6624e079952fda5f157cfd1079eb76315bde70f31fd25743db98f&mpshare=1&scene=1&srcid=0903PU0uqOmk4BvoBtfiK7MN%23rd">浅墨: 聊聊 Linux IO（上）</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652665073&idx=1&sn=479f6947d845ab3823bf35fd3a74d8fe&chksm=810f326cb678bb7a6f91a63134712e1951d9d93299a2fb10a7a09779599f161b9a3bac9bf7f5&mpshare=1&scene=1&srcid=0903obSJikgzRQkacWruVHxM%23rd">浅墨: 聊聊 Linux IO(中)——Linux 内核中的 IO 栈</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652665103&idx=1&sn=ffffddab091250808ec273c0fb441c4c&chksm=810f3392b678ba8430b6e46b8b61e114dee92df997793657fa28e91944e37043edaf35e7a948&mpshare=1&scene=1&srcid=09036uYavdTQwV2xoxT415kt%23rd">浅墨: 聊聊 Linux IO(下)</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664862&idx=1&sn=ff5b5e3ff12eff9933b02d056090bb64&chksm=810f3283b678bb95601a679fe633acbe8a8e5458ed3b0a172a8abf7e9748569fa2543576e9e1&mpshare=1&scene=1&srcid=0710GsjvDmQvomT82BnqUtc2%23rd">郭健： deadline 调度器之（一）：原理</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664880&idx=1&sn=b1e97835b702359e20e0fb2c1218f55f&chksm=810f32adb678bbbbb8efea4902a42ae66187785b4483de4f743ea8daaee2d0d59eb73ea69747&mpshare=1&scene=1&srcid=07126zFuAiF9Bt8GHRRfDVQH%23rd">郭健： Deadline 调度器之（二）：细节和使用方法</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652664678&idx=1&sn=4dd68ee13fcea183ddcd6576cf7d8245&chksm=810f35fbb678bcedc592e820663fd02a0f829c6a99173fb327b0a2016256d94f5a922c2436a8&mpshare=1&scene=1&srcid=0611Qm6agWbldRTrn8E6EdA2%23rd">郭健： Linux 内存模型——平坦、非连续与稀疏</a></li><li><a href="https://www.cnblogs.com/tianguiyu/articles/6091378.html">linux内核分析——CFS（完全公平调度算法）</a></li><li><a href="http://linuxperf.com/?p=42">从几个问题开始理解 CFS 调度器</a></li><li><a href="http://www.wowotech.net/process_management/447.html">CFS 调度器（1）-基本原理</a></li><li><a href="http://www.wowotech.net/process_management/448.html">CFS 调度器（2）-源码解析</a></li><li><a href="http://www.wowotech.net/process_management/449.html">CFS 调度器（3）-组调度</a></li><li><a href="http://www.wowotech.net/process_management/450.html">CFS 调度器（4）-PELT(per entity load tracking)</a></li><li><a href="http://www.wowotech.net/process_management/451.html">CFS 调度器（5）-带宽控制</a></li><li><a href="http://www.wowotech.net/process_management/452.html">CFS 调度器（6）-总结</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzU3MTkwMDU1NQ==&mid=2247483748&idx=1&sn=d8308e7138decad8340900db3494e98d&chksm=fcd85680cbafdf96f9ff3be4a8b13b6b174f5372c760527fec90ca7feba1cc22f1d0f3aa28b5&mpshare=1&scene=1&srcid=&sharer_sharetime=1569745222548&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">关于线程和 I&#x2F;O 模型的极简知识</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&amp;mid=404234343&amp;idx=1&amp;sn=b297b01ee7c656b900417f14a2a0ccae&amp;scene=1&amp;srcid=0303naEsx6F7zikbq8zK2REV#rd">TRIM：提升磁盘性能，缓解 Android 卡顿</a></li><li><a href="https://tinylab.org/lwn-456904/?from=groupmessage&isappinstalled=0">LWN 456904: 避免磁盘回写（writeback），抑制（throttling）缓存（page cache）写入</a></li><li><a href="https://tinylab.org/lwn-384093/?from=groupmessage&isappinstalled=0">LWN 384093: 有关 “回写”（writeback）的问题讨论</a></li><li><a href="https://tinylab.org/lwn-211505/?from=groupmessage&isappinstalled=0">LWN 211505: 避免和解决内存碎片化</a></li><li><a href="https://developer.android.google.cn/training/articles/smp#more">SMP Primer for Android</a></li><li><a href="https://zhuanlan.zhihu.com/p/73468738">内存分配1 - 空闲链表和内存池</a></li><li><a href="https://zhuanlan.zhihu.com/p/73562347">内存分配2 - Buddy 系统的原理</a></li><li><a href="https://zhuanlan.zhihu.com/p/105589621">内存分配3 - Linux 中 Buddy 系统的实现</a></li><li><a href="https://zhuanlan.zhihu.com/p/83945844">Linux 中的内存压缩</a></li><li><a href="https://zhuanlan.zhihu.com/p/97319402">研究 Linux 内核的乐趣</a></li><li><a href="https://mp.weixin.qq.com/s/j2fTXYX0-IDTeD-LhHecAw">linux IO Block layer 解析</a></li><li><a href="https://mp.weixin.qq.com/s/pLAFcD23qBP4iZxvek7xlg">CFS 任务的负载均衡（框架篇）</a></li><li><a href="https://mp.weixin.qq.com/s/c9Sg68iSvOPRJItFQgfZyg">一张图读懂内存反碎片化</a></li><li><a href="https://mp.weixin.qq.com/s/ppKf8nfOBjKwosieEOezYA">浅谈新型非易失存储</a></li><li><a href="https://mp.weixin.qq.com/s/4xY19jzL0tPYsf5fsoR4Bw">Linux devfreq framework 剖析</a></li><li><a href="https://mp.weixin.qq.com/s/SuKRPYTMUbC5PRw9NnvUTg">内存泄漏（增长）火焰图</a></li><li><a href="https://mp.weixin.qq.com/s/nkiE4CEo_zSN35I_qITAvQ">Linux 系统性能评测基准系统配置及其原理</a></li><li><a href="https://mp.weixin.qq.com/s/38ki8Rx0LLtHUAURJgUJpw">Cgroups 与 Systemd</a></li><li><a href="https://mp.weixin.qq.com/s/zUPcVOyP6mCtTMub5imf1w">Devfreq Bus Dcvs</a></li><li><a href="https://mp.weixin.qq.com/s/sO1MBsDyYyDOtA_aGq-YXw">基于 eBPF 的 CPU 利用率精准计算小工具开发</a></li><li><a href="https://mp.weixin.qq.com/s/udpHAaB27DpVPm1ynvUxuA">关于 eBPF 的一些粗浅理解</a></li><li><a href="https://mp.weixin.qq.com/s/yEMp70FmFYn6qL8kCZgS8A">使用 ftrace 分析函数性能</a></li><li><a href="http://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652681492&idx=1&sn=008c3ed95cf1c5985700d52c47f68c42">eBPF是什么？为什么对可观测性很重要</a></li></ol><h1 id="Flutter"><a href="#Flutter" class="headerlink" title="Flutter"></a>Flutter</h1><ol><li><a href="https://zhuanlan.zhihu.com/p/62335468">深入 Flutter 的高性能图形渲染</a></li><li><a href="https://juejin.im/post/5db29887e51d452a39002adb">跨平台技术演进及 Flutter 未来</a></li><li><a href="https://mp.weixin.qq.com/s/IZ6rUfg3_-zvopc7jZZobg">跨平台技术趋势及字节跳动 Flutter 架构实践</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzUyMjg5NTI3NQ==&mid=2247484105&idx=1&sn=7cb3d5816507290caa6338ac9b593440&chksm=f9c5a80dceb2211bd5bc130158fa1f009d927548aa9b6b7b1452b4066b7e8ddab5c4b8a70cc2&mpshare=1&scene=1&srcid=&sharer_sharetime=1565751189915&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">Flutter 的性能测试和理论</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzAwODY4OTk2Mg==&mid=2652049970&idx=2&sn=1160c3e2df1a2659e8f8a921c7978ebc&chksm=808cb277b7fb3b61c420cfd6e5b9be7acbecb57a794029665ac4baa6d7db649e6d34e0656182&mpshare=1&scene=1&srcid=&sharer_sharetime=1563846371830&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">深入理解 Flutter 多线程</a></li><li><a href="http://gityuan.com/2019/06/22/flutter_booting/">深入理解 Flutter 引擎启动</a></li><li><a href="http://gityuan.com/2019/06/15/flutter_ui_draw/">Flutter 渲染机制— UI 线程</a></li><li><a href="http://gityuan.com/2019/06/16/flutter_gpu_draw/">Flutter 渲染机制— GPU 线程</a></li><li><a href="https://mp.weixin.qq.com/s/p3epzBC-ZRr2V9Yh1GH7tw">深入探索 Flutter 性能优化</a></li></ol><h1 id="Fuchsia"><a href="#Fuchsia" class="headerlink" title="Fuchsia"></a>Fuchsia</h1><ol><li><a href="https://zhuanlan.zhihu.com/p/55690708?utm_source=ZHShareTargetIDMore&utm_medium=social&utm_oi=27871238160384">许中兴博士演讲：Fuchsia OS 简介</a></li></ol><h1 id="技术之外的思考"><a href="#技术之外的思考" class="headerlink" title="技术之外的思考"></a>技术之外的思考</h1><ol><li><a href="https://ichn.xyz/blog/how-to-start-learning-in-a-new-area">如何在一个全新的领域展开学习</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzU3ODAxNDcwNQ==&mid=2247484147&idx=1&sn=cd5f8fead3bcaac2d22a3dd699d2e79f&chksm=fd7a9e6dca0d177b27095d3d12720e83ba1638028799a89a8879929c1ad442529a62a46c5fe3&mpshare=1&scene=1&srcid=1027hi7FsUIG3AirEiJg198C#rd">我到底有多么努力</a></li><li><a href="https://zhuanlan.zhihu.com/zmywly8866/20711335">工作以来的一些感悟</a></li><li><a href="https://zhuanlan.zhihu.com/p/20708611">如何自学 Android？</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MjM5ODQ2MDIyMA==&mid=2650714160&idx=1&sn=5ea68a672a3b4df567951a09a264dfd3&chksm=bec07e6389b7f7758f438618c5dfb91e193a39446e0864bca2ed00fcbf363ea4caa5f8391d99&mpshare=1&scene=1&srcid=0507RZEnGNhAlCveVh01K3db%23rd">技术人最重要的能力是什么？</a></li><li><a href="https://blog.csdn.net/wetest_tencent/article/details/80361126">浅谈软件工程师的代码素养</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid=2652262721&idx=1&sn=978d9d94b5417ef4841d54d64fcf2776&chksm=84dc6ed6b3abe7c07990422648df4617458d50818968b247ef1aea5b077fa114c532adf79f85&mpshare=1&scene=1&srcid=0803qDDSrV6EIlW5aMuzUfv4%23rd">陆奇：除了好代码，工程师怎样才算优秀？</a></li><li><a href="https://coolshell.cn/articles/20276.html">别让自己“墙”了自己</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzIwMTAzMTMxMg==&mid=2649494737&idx=1&sn=8e1f94c24768deb3ddd926c4fd5df986&chksm=8eec9f2eb99b16386cbd677e0545d71f0863cc77f21dd9250dda8f7a31c4a206167b510118c0&mpshare=1&scene=1&srcid=&sharer_sharetime=1568602769162&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">100% 成功率的 offer 收割机是怎样练成的？</a></li><li><a href="https://coolshell.cn/articles/20533.html">使用简单的逻辑方法进行独立思考</a></li><li><a href="https://mp.weixin.qq.com/s/RWdiiYC423ocH-iuaiqr2w">这一年炼就的底层内功修养</a></li><li><a href="https://xiao.do/issues/084-1059071">084 创造自己的领域，乔布斯的人生规则，发声与退出，失眠的好处</a></li><li><a href="https://mp.weixin.qq.com/s/aA6xf6deR20QlhJxXNJVgw">我掌握到了如何无障碍观看英文技术视频</a></li><li><a href="https://www.bilibili.com/video/BV1vW411Z7Qf">视频 - 泰勒·本-沙哈尔《正向领导力》</a></li><li><a href="https://medium.com/@codecungtrung/why-did-i-choose-to-be-an-android-developer-not-an-ios-developer-eb67809fa704">Why did I choose to be an Android developer, not an IOS developer ?</a></li><li><a href="https://mp.weixin.qq.com/s/5lpb9ZASj7ioBXWaUaEOQg">技术负责人要停止写代码吗？</a></li><li><a href="https://www.kenshinji.me/wo-shi-ru-he-cong-ya-ba-ying-yu-dao-wu-zhang-ai-ying-wen-gong-zuo-gou-tong-de/">我是如何从哑巴英语到无障碍英文工作沟通的</a></li><li><a href="https://www.cnblogs.com/zhoujg/archive/2011/03/01/1968366.html">英语：漏屋-英语学习的真实方法及误区分析</a></li><li><a href="https://mp.weixin.qq.com/s/G2ib7f87dCsRyEejaYk-6Q">拆掉知识的墙</a></li><li><a href="http://mp.weixin.qq.com/s?__biz=MzIxNDQ3NDI5OQ==&mid=2247486068&idx=1&sn=934b4db38a852bf5cde0cee972e59885">5000字详解性能需求</a></li></ol><h1 id="面试题"><a href="#面试题" class="headerlink" title="面试题"></a>面试题</h1><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzAxMTg2MjA2OA==&mid=2649842075&idx=1&sn=668f0ddceab52c961f1ac20a77165429">Android 2018 最新面试题</a></li><li><a href="https://zhuanlan.zhihu.com/p/34878265">如何衡量一个 Android 应用开发人员的能力</a></li><li><a href="https://juejin.im/post/5b97ab465188255c865e030a">2018 Android 面试总结</a></li><li><a href="https://www.jianshu.com/p/8608b172b103">Android 2017-2018 最新面试题（3-5年经验个人面试经历）</a></li><li><a href="https://www.diycode.cc/wiki/androidinterview">Android 开发工程师面试指南</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzA3NzMxODEyMQ==&mid=2666454354&idx=2&sn=b08cfa0ba5d403c92bce5e7fb57f233f&chksm=8449afd4b33e26c2b04adad207e9bf93fa7af46be75cfb860aeba30bed5bc93340ca8965d2bc&mpshare=1&scene=1&srcid=0815726HCm2RAyWr0mKdKiHL%23rd">阿里电话面试面试题总结，附答案！</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/Alibaba.md">阿里巴巴面试题</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/%E7%BE%8E%E5%9B%A2.md">美团面试题</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/%E8%B1%8C%E8%B1%86%E8%8D%9A.md">豌豆荚三面试题</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/%E8%9C%BB%E8%9C%93FM.md">蜻蜓FM面试题</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A.md">新浪微博面试题</a></li><li><a href="https://github.com/francistao/LearningNotes/blob/master/Part6/InterviewExperience/%E7%BD%91%E6%98%93%E6%9D%AD%E7%A0%94.md">网易杭研面试题</a></li><li><a href="https://mp.weixin.qq.com/s/A2RzPsdkfHNGlsnFsJbe-g">为什么想来我们公司工作？- 面试常见问题解析</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzUzMjUyMDQ5Mw==&mid=2247484057&idx=1&sn=ca225ba9b647206d391624f9f7f9300a&chksm=fab349dbcdc4c0cd0ede64a8dc956e6fe1b6a7cd370a638010792185826c55264d3db1bcdac5&mpshare=1&scene=1&srcid=0918xJfW9762pd5y9E56E4Bh%23rd">今日头条大佬十年面试了 2000 人，总结了这 5 点</a></li><li><a href="https://mp.weixin.qq.com/s/4qA_FrPKUQrgDu_ITVYt-A">2019 年美团点评高级 Android 开发寒冬跳槽涨薪经验掏心分享</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247492338&idx=1&sn=1b261f2eda75163e0878d3b5e4373834&chksm=e92adffdde5d56eb4720f9ee0b3b81795ee31bedb2ebe6a3112e0e1b538242e69076ff5aa715&mpshare=1&scene=1&srcid=&sharer_sharetime=1574306357287&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">如何回答性能优化的问题，才能打动阿里面试官？</a></li><li><a href="https://juejin.im/post/5d4e40a5f265da03ef7a02a1">2019.07 Android 面试真题集锦</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI3NzE1NDcyNQ==&mid=2247485232&idx=1&sn=b7abd39999034bed76a8b849b968faa3&chksm=eb6bd9fadc1c50ec24d8dccf80d94d8cc745d2e2c69b7add3b36a12aafd5b87b2f5e7480317f&mpshare=1&scene=1&srcid=&sharer_sharetime=1570589981034&sharer_shareid=60bd7acea7881a97fbf9a6126d3e88d3#rd">技术面试中面试官怎么考察候选人？</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI3Mzc4ODQ3Mw==&mid=2247484333&idx=1&sn=03271692d0f713272baa16270f11b8d0&chksm=eb1cb8fbdc6b31edbffb8a7c01fb886e3689f5e0204f71b75908cfdfadef92333bb3c91df281&mpshare=1&scene=1&srcid=11297lZR9ijR5xUFAyStvw65#rd">给扔物线 HenCoder Plus 学员的一次分享文字版</a></li><li><a href="https://juejin.im/post/5e9102f9e51d4546d6357fde">快手，字节跳动，百度，美团Offer之旅</a></li><li><a href="https://zhuanlan.zhihu.com/p/31209029">美帝面试二三事</a></li><li><a href="https://www.tsaiggo.life/posts/thinking-in-interview/">面试随想</a></li></ol><h1 id="开源库"><a href="#开源库" class="headerlink" title="开源库"></a>开源库</h1><ol><li><a href="https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ==&mid=2247487720&idx=1&sn=4c1dc31b6201e420dcac49250b81055f&chksm=e9d0db0adea7521c9337aacc22877d4e6f33a774499c650f3bfb12ad23ffdc0b8ab6ef40a9a5&scene=21#wechat_redirect">抖音 Android 性能优化系列：新一代全能型性能分析工具 Rhea</a></li><li><a href="https://github.com/bytedance/memory-leak-detector">Raphael 开源地址</a></li><li><a href="https://github.com/bytedance/tailor">Tailor 开源地址</a></li><li><a href="https://github.com/iqiyi/xHook">xHook</a></li><li><a href="https://github.com/hexhacking/xDL">xDL 链接</a></li><li><a href="https://github.com/ele7enxxh/Android-Inline-Hook">Android-Inline-Hook 链接</a></li><li><a href="https://github.com/Rprop/And64InlineHook">And64InlineHook 链接</a></li><li><a href="https://android.googlesource.com/platform/bionic/+/master/libc/malloc_debug/README.md">malloc debug 链接</a></li><li><a href="http://www.andreasen.org/LeakTracer/">LeakTracer 链接</a></li><li><a href="https://github.com/uber/nanoscope">Nanoscope</a></li><li><a href="https://github.com/facebookincubator/profilo">Profilo</a></li><li><a href="https://gpeal.medium.com/lottie-android-5-0-7d468e36d882">Lottie Android 5.0</a></li></ol><h1 id="技术-Weekly"><a href="#技术-Weekly" class="headerlink" title="技术 Weekly"></a>技术 Weekly</h1><ol><li><a href="http://www.kotlinweekly.net/">Kotlin Weekly</a></li><li><a href="http://androidweekly.net/">Android Weekly</a></li><li><a href="https://androidweekly.zhubai.love/">Android Weekly - CN</a></li><li><a href="https://www.oncreatedigest.com/">onCreate Digest</a></li><li><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5NTMzNzQxMw==&action=getalbum&album_id=1788609729372438534&scene=173&from_msgid=2650976227&from_itemidx=1&count=3&nolastread=1#wechat_redirect">软件测试周刊</a></li><li><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzAwMjgwMTEzNw==&action=getalbum&album_id=2226497928947367940&scene=173&from_msgid=2652227850&from_itemidx=1&count=3&nolastread=1#wechat_redirect">CodeDump 的网络日志</a></li><li><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIwNDQ2NjUyMQ==&action=getalbum&album_id=1340386474226958336&scene=173&from_msgid=2247490138&from_itemidx=1&count=3&nolastread=1#wechat_redirect">体验碎周报</a></li></ol><h1 id="技术团队和个人"><a href="#技术团队和个人" class="headerlink" title="技术团队和个人"></a>技术团队和个人</h1><h1 id="微信公众号"><a href="#微信公众号" class="headerlink" title="微信公众号"></a>微信公众号</h1><h1 id="本文其他地址"><a href="#本文其他地址" class="headerlink" title="本文其他地址"></a>本文其他地址</h1><p>由于博客留言交流不方便，点赞或者交流，可以移步本文的知乎或者掘金页面<br><a href="https://zhuanlan.zhihu.com/p/30691789">知乎 - Android 性能优化必知必会</a><br><a href="https://juejin.im/post/5ebf4f21f265da7bad355036">掘金 - Android 性能优化必知必会</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这篇文章记录了 Android 性能优化所必须掌握的知识（主要是对应的优秀文章、公众号、博客、技术团队等），涵盖性能优化相关的方方面面。本文会持续更新，欢迎各位自荐或者推荐。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>2017 年度好物推荐 - 给辛勤工作的自己一点奖励</title>
    <link href="https://androidperformance.com/2018/01/06/2017%E5%B9%B4%E5%BA%A6%E6%9C%80%E6%8E%A8%E8%8D%90/"/>
    <id>https://androidperformance.com/2018/01/06/2017%E5%B9%B4%E5%BA%A6%E6%9C%80%E6%8E%A8%E8%8D%90/</id>
    <published>2018-01-06T07:40:01.000Z</published>
    <updated>2026-02-07T05:17:47.817Z</updated>
    
    <content type="html"><![CDATA[<p>2017 不知不觉已经走过，博客已经许久没有更新了，一个原因是越来越觉得自己的学识浅薄，怕写了技术误了大家，另一个原因是自己太懒了。鉴于上面两个原因，我决定此博客的更新，不再非要更新技术文章，一些我觉得有意义的事情、思考等，也会更新上来。一方面自己做个记录，如果能顺带给读者带来一点帮助，那自然是最好的(虽然没几个读者…)</p><p>2017 既然才刚刚过去，我觉得有必要把 2017 年里面我觉得体验很棒或者对工作生活很有帮助的东西推荐给大家，或许 2018 你会需要他们。推荐的内容包含了 App、硬件、书籍、器材等，需要说明的是，这些东西是我觉得 2017 年给我带来很大帮助的，适合我不一定适合你。话不多说，直接上内容吧！</p><span id="more"></span><h1 id="2017-年度最推荐-iOS-App"><a href="#2017-年度最推荐-iOS-App" class="headerlink" title="2017 年度最推荐 iOS App"></a>2017 年度最推荐 iOS App</h1><p><strong>关键词 ：减肥 学习 见识</strong></p><h2 id="Keep"><a href="#Keep" class="headerlink" title="Keep"></a>Keep</h2><p><img src="/images/media/15152276589894.jpg" alt="Keep"></p><p>如果你是一个健身爱好者、计划减肥者，想找一款能记录运动的 App，那么我推荐 Keep 给你。Keep 那句很出名的 slogan 不知道大家有没有听说过：<strong>自律给我自由!<strong>，我同样喜欢另外一句：</strong>不要让工作毁掉你的生活，总有比你更忙的人在运动</strong>。</p><p>软件只是一个辅助，最重要的是要自律，生活需要自律，运动需要自律，饮食需要自律。自律并不意味着生活会失去乐趣，相反，自律给我自由和自信，人总是向往完美的自己，Keep It！</p><p>2017 在 Keep 的帮助下，我的体重从最重时候的 78 kg 到了现在的 70 kg，可能数值上没有什么大不了的，但是回过头来看照片对比，现在真的要自信很多。</p><h2 id="得到"><a href="#得到" class="headerlink" title="得到"></a>得到</h2><p><img src="/images/media/1_9Z-KgmC_qnkI3YqbLbrjuQ.webp" alt="得到"></p><p>正如我们家领导所说，有些人你知道他很厉害，仅此而已。</p><p>得到这个 App 确给了我们一个途径，近距离地接近那些很厉害的人，知道他们的学习方式、思考方式。你会发现，比你聪明的人比你还要努力，你还有什么理由去偷懒呢？</p><p>2017 我听了吴军的《硅谷来信》，听了薛兆丰的《北大经济学课》，听了万维钢老师的《精英日课》，听老罗唠叨了一年的《逻辑思维》，听了许多书。</p><p>从长见识和思考两个方面来说，我获得的就已经远远超出这些专栏的订阅价了。</p><h2 id="微信读书"><a href="#微信读书" class="headerlink" title="微信读书"></a>微信读书</h2><p><img src="/images/media/15152618857230.jpg" alt="微信读书"></p><p>读书的平台很多，选中一个坚持下去是最重要的，微信读书、多看阅读、Kindle 三个平台我都有读书，没有谁好谁坏，重要的是真正获取到了知识。</p><p>现在的低头族被大家吐槽，与其在地铁上刷微博、知乎、即刻，不如打开一本书，静下心来好好阅读。</p><h1 id="2017-年度最推荐-Mac-App"><a href="#2017-年度最推荐-Mac-App" class="headerlink" title="2017 年度最推荐 Mac App"></a>2017 年度最推荐 Mac App</h1><p><strong>关键词 ：工作安排 工作记录</strong></p><h2 id="Things-3"><a href="#Things-3" class="headerlink" title="Things 3"></a>Things 3</h2><p><img src="/images/media/appicon-ios.webp" alt="Things 3"></p><p>这东西价格是贵了一些，但是是真的好用，各种功能都很齐全，各个平台版本（iPhone、iPad、Mac）数据也都是互通的.</p><p>Things 主要完成我工作三环中的第一环：任务安排。 很多时候会有很多任务，有时候会忘记做一些重要的事情，现在我会把所有要做的事情都记录在 Things 里面，每天早上会根据优先级，筛选出今天要做的事情。如果有其他突发的事情，也会记录下来，这样不会顾此失彼。</p><p>另外 Things 的重复任务、任务优先级、任务时间等都比较好用，之前用网页端 Tower 的我果断吧数据都迁移到 Things 了。</p><h2 id="MWeb"><a href="#MWeb" class="headerlink" title="MWeb"></a>MWeb</h2><p><img src="/images/media/512-1.webp" alt="MWeb"></p><p>Mweb 是一款 MarkDown 软件，我也是换了好几个才换到这个，功能齐全，界面美观，MarkDown 写起来很舒服，对图片的支持也很很。</p><p>MWeb 主要完成我工作三环中的第二环：任务记录，包括每日的任务完成情况记录。Things 用来安排任务，但是任务完成后要做记录和总结，这就是每日任务记录，Things 记录的是流水，而 MWeb 则记录的是思想。</p><p>另外我还使用 MWeb 来写 Blog、公司文档、总结等，言而总之，就是一款好用的 MarkDown 软件。</p><h2 id="Evernote"><a href="#Evernote" class="headerlink" title="Evernote"></a>Evernote</h2><p><img src="/images/media/15152276977516.jpg" alt="EverNote"></p><p>印象笔记就不用多介绍了，其强大的浏览器剪切插件，帮助我存储了不少非常好的技术文章；团队共享功能，帮助我在团队中分享笔记。</p><p>印象笔记 主要完成我工作三环中的第三环：存档。不管是技术文章，还是会议纪要，还是项目规划，还是 PDF，如果要找一个长期存放的地方，那么印象笔记是一个相当可靠的软件；有时候开会不做 PPT，直接用印象笔记的演示功能也可以胜任。</p><h1 id="2017-年度最推荐-Android-App"><a href="#2017-年度最推荐-Android-App" class="headerlink" title="2017 年度最推荐 Android App"></a>2017 年度最推荐 Android App</h1><p><strong>关键词 ：墙</strong></p><h2 id="影梭"><a href="#影梭" class="headerlink" title="影梭"></a>影梭</h2><p><img src="/images/media/15152275725397.jpg" alt="影梭"></p><p>作为一个资深的 Google 全家桶爱好者，影梭是 Android 完整体验的强有力的一个保证；作为一个 Android 开发者，我没法想象如果没了影梭，我会失去多少获取知识的渠道。</p><p>爬梯需谨慎，感谢国家还没有赶尽杀绝，大家别问我是怎么搞的，低调才能生存。</p><h2 id="绿色守护"><a href="#绿色守护" class="headerlink" title="绿色守护"></a>绿色守护</h2><p><img src="/images/media/15152613218534.jpg" alt="绿色守护"></p><p>国内的 Android App 的流氓程度大家是有目共睹的，尤其是 BAT 全家桶，管不好这些应用，Android 的体验会大打折扣，尽管现在很多厂商都针对流氓软件做了限制，绿色守护仍然是你拿到新机后必须要安装的一个软件。</p><p>我有幸跟绿色守护作者冯老师在微信群里讨论过问题，冯老师对技术的深度和热情，我等实在是望尘莫及。我们都欠冯老师一个捐赠版。</p><blockquote><p>再也不用嫉妒朋友的iPhone手机，即使安装大量应用，也不会变得迟缓和耗电。有了『绿色守护』，你的Android设备也能永葆第一天拥有它时的爽滑持久！</p></blockquote><blockquote><p>『知乎』上用户认同度最高的Android省电软件：<a href="http://www.zhihu.com/question/21007772">http://www.zhihu.com/question/21007772</a></p></blockquote><blockquote><p>『绿色守护』帮助你甄别那些对系统全局性能和耗电量有不良影响的应用程序，并通过独特的『绿色化』技术，阻止它们消耗您的电池电量，占用您的宝贵内存。经过『绿色化』工艺处理的应用，在您没有主动启动它们的时候，无法『偷偷』运行，而在您正常启动它们时仍然拥有完整的功能和体验，正如iPhone应用那样！</p></blockquote><h1 id="2017-年度最推荐非技术书"><a href="#2017-年度最推荐非技术书" class="headerlink" title="2017 年度最推荐非技术书"></a>2017 年度最推荐非技术书</h1><p><strong>关键词 ：自我提升提升</strong></p><h2 id="深度工作"><a href="#深度工作" class="headerlink" title="深度工作"></a>深度工作</h2><p><img src="/images/media/15152579958065.jpg" alt="深度工作"></p><p>强烈推荐此书，尤其是需要终生学习的程序员们，可能每个人读完后的感想都会不一样，但是我绝对是受到了里面一些做事方法的启发，结合自身的能力和工作，针对性地进行提高，而且得到了较好的效果。个人认为是我 2017 年读的最好的书。在这个碎片化严重的互联网时代，快知识的消费和满足，让自己产生了惰性，认为自己知道的东西很多，其实没有经过深加工，吸收的那些知识只是碎片而已，随着时间就消逝了。</p><blockquote><p>随时随地收发电子邮件、一个接一个地参加大小会议、在即时通讯软件的尖叫中手忙脚乱、在繁杂的多线程工作中不断地切换注意力……你看起来非常忙碌，甚至在不自觉地享受这种忙碌，但你的忙碌真的能转化为生产能力吗？</p></blockquote><blockquote><p>本书作者、麻省理工学院计算机博士卡尔·纽波特，尖锐地道破了信息经济时代的惊人真相——知识工作者60%以上的工作时间都花费在处理此类浮浅事务上，而这些工作不仅产出的价值有限，还会永久性地损害人们深度工作的能力！</p></blockquote><blockquote><p>作者创立的“深度工作”概念，其含义为在无干扰的状态下进行专注的职业活动，使个人的认知能力达到极限。而正因为当前社会深度工作能力的日益稀缺，其与经济成功的关系也变得日渐紧密起来。本书的所有讨论也围绕 “深度工作”而展开，全书共分为两部分：在第一部分中，作者从神经科学、心理学、哲学等角度，客观地分析了在新经济形势下实现深度工作的重要性。第二部分则系统地传授了在日常生活中践行深度工作的具体策略，如将深度工作纳入日常工作进程、提高大脑的深度思维能力、远离社交网络等。</p></blockquote><blockquote><p>作者还强调，深度工作不是一项过时的技能，而是将人们从技术垄断导致的精神异化状态中解救出来的良药。在当前这个以网络为中心的浮浅信息时代，倡导深度工作无异于呼唤一种匠人精神的回归。</p></blockquote><p>豆瓣：<a href="https://book.douban.com/subject/27056409/">https://book.douban.com/subject/27056409/</a></p><h2 id="刻意练习-如何从新手到大师"><a href="#刻意练习-如何从新手到大师" class="headerlink" title="刻意练习-如何从新手到大师"></a>刻意练习-如何从新手到大师</h2><p><img src="/images/media/15152576721372.jpg" alt="刻意练习"></p><p>这本书，也算是声名在外。其实这本书告诉我们一个很简单的道理，想要成为任何一个行业的专家，你都需要进行大量的刻意练习，而不是单纯的堆积时间。</p><blockquote><p>对于在任何行业或领域中希望提升自己的每个人，刻意练习是黄金标准，是迄今为止发现的最强大的学习方法。</p></blockquote><p>豆瓣：<a href="https://book.douban.com/subject/26895993/">https://book.douban.com/subject/26895993/</a></p><h1 id="2017-年度最推荐技术书"><a href="#2017-年度最推荐技术书" class="headerlink" title="2017 年度最推荐技术书"></a>2017 年度最推荐技术书</h1><p><strong>关键词 ：</strong><br>说来惭愧，2017年没有认认真真读完一本技术书，但从我阅读过的那么多技术书中，还是有两本可以推荐给大家，不过我阅读的技术书，跟我自己的工作相关的比较多，不一定适合你。</p><h2 id="深入理解Android内核设计思想（第二版）"><a href="#深入理解Android内核设计思想（第二版）" class="headerlink" title="深入理解Android内核设计思想（第二版）"></a>深入理解Android内核设计思想（第二版）</h2><p><img src="/images/media/15152587790243.jpg" alt="深入理解Android内核设计思想"></p><p>算是系统开发的经典书了，这书今年出了第二版，加了一些新的内容。不管是应用开发工程师，还是系统开发工程师，多了解 Android 系统的架构和设计，对自己知识的深度是很有帮助的。</p><p>不过 Android 的版本发展实在是太快了，阅读此书建议配合最新的 Android 源代码。梳理流程的同时，也要深度思考设计思想。</p><blockquote><p>《深入理解Android内核设计思想》适用于 Android 4.3以上的版本。全书从操作系统的基础知识入手，全面剖析进程&#x2F;线程、内存管理、Binderv机制、GUIv显示系统、多媒体管理、输入系统等核心技术在 Android 中的实现原理。书中讲述的知识点大部分来源于工程项目研发，因而具有较强的实用性，希望可以让读者“知其然，更知其所以然”。全书分为编译篇、系统原理篇、应用原理篇、系统工具篇共4篇22章，基本涵盖了参与Android开发所需具备的知识，并通过大量图片与实例来引导读者学习，以求尽量在源代码分析外为读者提供更易于理解的思维方式。</p></blockquote><blockquote><p>《深入理解Android内核设计思想》既适合 Android 系统工程师，也适合于应用开发工程师来阅读提升Android开发能力。读者可以在《深入理解vAndroidv内核设计思想》潜移默化的学习过程中更深刻地理解Android系统，并将所学知识自然地应用到实际开发难题的解决中。</p></blockquote><p>豆瓣 ： <a href="https://book.douban.com/subject/25921329/">https://book.douban.com/subject/25921329/</a></p><h2 id="奔跑吧-Linux内核"><a href="#奔跑吧-Linux内核" class="headerlink" title="奔跑吧 Linux内核"></a>奔跑吧 Linux内核</h2><p><img src="/images/media/15152591841672.jpg" alt="奔跑吧 Linux内核"></p><p>这本书我还在读，由于缺乏相关的知识，所以进度有点慢。Android 系统工程师必备。</p><blockquote><p>本书内容基于Linux4.x内核，主要选取了Linux内核中比较基本和常用的内存管理、进程管理、并发与同步，以及中断管理这4个内核模块进行讲述。全书共分为6章，依次介绍了ARM体系结构、Linux内存管理、进程调度管理、并发与同步、中断管理、内核调试技巧等内容。本书的每节内容都是一个Linux内核的话题或者技术点，读者可以根据每小节前的问题进行思考，进而围绕问题进行内核源代码的分析。</p></blockquote><blockquote><p>本书内容丰富，讲解清晰透彻，不仅适合有一定Linux相关基础的人员，包括从事与Linux相关的开发人员、操作系统的研究人员、嵌入式开发人员及Android底层开发人员等学习和使用，而且适合作为对Linux感兴趣的程序员的学习用书，也可以作为大专院校相关专业师生的学习用书和培训学校的教材。</p></blockquote><p>豆瓣 ： <a href="https://book.douban.com/subject/27108677/">https://book.douban.com/subject/27108677/</a></p><h1 id="2017-年度最推荐手机-–-Meizu-Pro7-Plus"><a href="#2017-年度最推荐手机-–-Meizu-Pro7-Plus" class="headerlink" title="2017 年度最推荐手机 – Meizu Pro7 Plus"></a>2017 年度最推荐手机 – Meizu Pro7 Plus</h1><p><strong>关键词 ：旗舰</strong></p><p><img src="/images/media/15152597921570.jpg" alt="Meizu Pro7 Plus"></p><p>并不是我不想推荐 iPhone X，是因为买不起……</p><p>尽管魅族的 Pro7 Plus 被网友各种吐槽，但你不能否认这是一款非常优秀的旗舰机，舒心的 flyme 系统加上不错的硬件搭配，创新的小窗逼格满满，配不配得上 Pro + Plus 的称号，仁者见仁智者见智吧。</p><h1 id="2017-年度最推荐路由器-–-小米路由器-Pro"><a href="#2017-年度最推荐路由器-–-小米路由器-Pro" class="headerlink" title="2017 年度最推荐路由器 – 小米路由器 Pro"></a>2017 年度最推荐路由器 – 小米路由器 Pro</h1><p><strong>关键词 ：美剧</strong></p><p><img src="/images/media/15152601645070.jpg" alt="小米路由器"></p><p>小米路由器+小米家庭影院，最大的好处是方便。现在有很多美剧网站，资源下载都会有个<strong>下载到小米路由器</strong>的链接，点击就可以下载到自家的路由器上，回家就可以看，突出一个方便。</p><h1 id="2017-年度最推荐平板-–-iPad-Pro"><a href="#2017-年度最推荐平板-–-iPad-Pro" class="headerlink" title="2017 年度最推荐平板 – iPad Pro"></a>2017 年度最推荐平板 – iPad Pro</h1><p><strong>关键词 ：完美</strong></p><p><img src="/images/media/verge-10.0.jpg" alt="iPad Pro"></p><p>在我眼里，iPad Pro 既具有 iPhone 的优点，又具有 Mac 的优点。从使用场景来看，开会、收发邮件、看电子书、看技术文档、看 PDF等，都可以胜任。</p><p>iPad Pro 搭配键盘（别买笔，没用），完全就是一个缩小版本的 Mac，除了写代码，其他的都可以满足我的需求，有了这货之后，我那用了好几年快退役的 Mac 使用率更低了。</p><p>前面我提到的那些软件：Keep、得到、微信读书、Things、MWeb、Evernote 都有 iPad 版本，提要要比 iOS 版本好太多。</p><p>苹果出品，必出精品，两个字送给 iPad Pro ：完美！</p><h1 id="2017-年度最推荐穿戴设备-–-iWatch"><a href="#2017-年度最推荐穿戴设备-–-iWatch" class="headerlink" title="2017 年度最推荐穿戴设备 – iWatch"></a>2017 年度最推荐穿戴设备 – iWatch</h1><p><strong>关键词 ：记录</strong></p><p><img src="/images/media/il_570xN.1294603892_67xk.jpg" alt="iWatch"></p><p>iWatch 是苹果四件套里面存在感最弱的，个人认为最大的问题是续航，一代一天一冲，二代两天一冲，实在是有点无力吐槽。主力机从 iOS 切换到 Android 后，电话短信闹钟提醒这几个功能完全没用了，不过这货解决了一个我的痛点：运动记录。</p><p>跑步机 + iWatch，简直是减肥利器，事无巨细的运动记录也 push 我不要偷懒，言而总之，iWatch 是我生活中不可或缺的。</p><h1 id="2017-年度最推荐耳机-–-MDR-1000X"><a href="#2017-年度最推荐耳机-–-MDR-1000X" class="headerlink" title="2017 年度最推荐耳机 – MDR-1000X"></a>2017 年度最推荐耳机 – MDR-1000X</h1><p><strong>关键词 ：降噪</strong></p><p><img src="/images/media/Sony_MDR1000X_Photo_Main.jpg" alt="Sony_MDR1000X"></p><p>在嘈杂的办公室，拥有一个带降噪的无线耳机真的是非常重要。降噪能给你一个安静的环境，不管是思考还是写代码还是看书，不受外界打扰的感觉真的很棒；无线带来简洁，没了线的束缚会方便很多，也会少了凳子绕线的苦恼。</p><p>MDR-1000X 我用了这么久，降噪、易用性、音质这三个方面都很满意。据说戴耳机会降低 50% 的被打扰率，推荐你也入手一个降噪耳机。</p><h1 id="2017-年度最推荐减肥伴侣-–-跑步机"><a href="#2017-年度最推荐减肥伴侣-–-跑步机" class="headerlink" title="2017 年度最推荐减肥伴侣 – 跑步机"></a>2017 年度最推荐减肥伴侣 – 跑步机</h1><p><strong>关键词 ：坚持</strong></p><p>人总是会有惰性，大家都知道跑步减肥，但是就是坚持不下来，而且外出跑步受环境的影响太大，太冷太热下雨下雪。</p><p>我在本来就拥挤的家里强行放了一台跑步机，跑步机的好处是，随时都可以跑，而且对跑步的抗拒心理没那么严重。每天下班后，打开 Keep，开跑，每天五公里并不难，重要的是要坚持。</p><h1 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h1><p>当然还有一些能显著提高幸福感的东西，就不一一列举了，我想起来就会更新到下面</p><ol><li>肉麒麟家的牛肉酱，选<strong>三观正的牛</strong>这个，包你会爱上</li><li>洗碗机，谁用谁知道，再也不会抵制在家里做饭了</li><li>扫地机器人，谁不想每天回去家里的地上很干净呢？尤其是在家里有一个长毛怪和两只猫的情况下</li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;2017 不知不觉已经走过，博客已经许久没有更新了，一个原因是越来越觉得自己的学识浅薄，怕写了技术误了大家，另一个原因是自己太懒了。鉴于上面两个原因，我决定此博客的更新，不再非要更新技术文章，一些我觉得有意义的事情、思考等，也会更新上来。一方面自己做个记录，如果能顺带给读者带来一点帮助，那自然是最好的(虽然没几个读者…)&lt;/p&gt;
&lt;p&gt;2017 既然才刚刚过去，我觉得有必要把 2017 年里面我觉得体验很棒或者对工作生活很有帮助的东西推荐给大家，或许 2018 你会需要他们。推荐的内容包含了 App、硬件、书籍、器材等，需要说明的是，这些东西是我觉得 2017 年给我带来很大帮助的，适合我不一定适合你。话不多说，直接上内容吧！&lt;/p&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://androidperformance.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="年度推荐" scheme="https://androidperformance.com/tags/%E5%B9%B4%E5%BA%A6%E6%8E%A8%E8%8D%90/"/>
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>2017</title>
    <link href="https://androidperformance.com/2017/04/23/About-work/"/>
    <id>https://androidperformance.com/2017/04/23/About-work/</id>
    <published>2017-04-23T14:02:29.000Z</published>
    <updated>2026-02-07T05:17:47.819Z</updated>
    
    <content type="html"><![CDATA[<p>一转眼就 2017 年了，算起来自己已经工作了快四年了，09年到威海上学，12年去上海实习，13年毕业后还是去了上海，14年进了珠海魅族，直到现在。</p><p>这博客从我毕业开始写东西，写写删删，也算是记录了一些东西，自己的工作内容也从 App 开发换到了系统 App 开发，在换到系统开发，也算是走了一圈，一些路程，记录下来，几年后再看看，怀念一下也是不错的。</p><p>这篇文章我记录了自己的博客、自己的工作、自己的工作内容、自己的工作习惯、还有对 2017 年的期望，有迷茫，也有奋斗。</p><p>17年已经过了 30% 了，希望还不算晚。</p><span id="more"></span><h2 id="关于博客"><a href="#关于博客" class="headerlink" title="关于博客"></a>关于博客</h2><p>看到上一篇博客文章的更新时间，已经是去年的这个月了，想想还真是惭愧，每次想动笔重新开始写一些东西的时候，总是由于这样或者那样的原因，没有动手去写。</p><p>记得刚毕业的时候，我是很喜欢写东西的，学习笔记也有，工具教程也有，什么都敢写，什么都敢往博客上面放，后来随着工作的深入，懂的东西变多之后，写东西反而不是很多了，我想一个原因是，随着技术水平的慢慢提高，我意识到自己的技术深度还远远不够，很多东西自己都知其然不知其所以然，这样的状态，写出来的东西，会不会误导人呢？</p><p>不过，现在技术有了一点点沉淀之后，我觉得有必要把自己总结的一些经验和技巧分享给大家，另外一方面也算是一个记录。很多思路和想法，如果不去记录下来，很容易就会忘记，</p><h2 id="关于工作"><a href="#关于工作" class="headerlink" title="关于工作"></a>关于工作</h2><p>从 HTC 的实习，到火花乐蛙的短暂停留，再到魅族，算起来工作了也好几年了，这几年一直在做 Android 相关的开发，从 App 开发到系统开发，一路下来，越发觉得，做软件开发是一件很有趣的事，尤其是当你做的功能，被几百万几千万的用户在使用的时候，那份激动和责任，总会驱动我们要做得更好，我推测未来五年之内， Android 在手机界的统治地位，暂时没有其他的系统可以撼动，所以值得花时间在这上面。</p><p>当然除了具体的 Android 技术点，学习 Android 的设计思想，软件架构，培养自己解决通用问题的能力，是更重要的事情，这正是我现阶段需要去努力的方向。</p><p>关于具体的技术田，我暂时还没有想好具体要深入哪一块，目前对 Android 和 Linux 的进程管理和 CPU 调度比较感兴趣，性能方面则偏重于流畅度、响应速度的分析和调优，不得不说这几块就够我钻研好久了。</p><p>前几天去总部，看到 2016 年我司出了 15 台手机，每一台手机在做的时候，我们都会对其做性能调优，那一大堆性能 Feature ，和每个机型都莫名其妙的性能问题，搞得团队根本没有时间去做一些更有深度的事情，有点故步自封的感觉，2017 年希望能改变这一点，起码自己要先做改变。</p><h2 id="关于工作内容"><a href="#关于工作内容" class="headerlink" title="关于工作内容"></a>关于工作内容</h2><p>工作内容主要是系统层面的一些优化工作，涉及到的点比较多，自己则是全而不精，今年要寻找一个点深挖：</p><ol><li>负责 Android 系统级别的性能优化，主要是 Framework 层以及 App 层的优化</li><li>负责优化内容包括 响应速度、流畅度、内存、启动速度、过度绘制、HWUI、SurfaceFlinger 等</li><li>参与制定性能部分测试项和测试标准</li><li>负责寻找和挖掘性能优化点，并负责实施和推广</li><li>负责系统关键项目的开发，比如智能系统调频器、进程优先级优化等</li><li>负责老机型与新机型的性能问题分析与解决</li><li>负责新员工性能方面的培训，以及内部技术分享</li><li>竞品分析，挖掘竞品的优点</li><li>负责性能部分文档编写与总结</li></ol><h2 id="关于习惯"><a href="#关于习惯" class="headerlink" title="关于习惯"></a>关于习惯</h2><p>2016 年起，我养成了一套自己的工作习惯，不过有的时候都没有严格去执行，2017 年则需要严格去执行这些，</p><h3 id="工作安排"><a href="#工作安排" class="headerlink" title="工作安排"></a>工作安排</h3><p>每天早上起来后，会安排一下今天一天的工作，安排的依据是昨天的工作记录情况和邮件记录，主要包含下面几点</p><ul><li>优先级：确定每个任务的优先级，重要的事情放在前面，优先做</li><li>预估时间：确定每个任务的预估时间，精确到半个小时</li></ul><p>当然会预留一定的时间，去应付突发的事情，比如有人来找我分析很重要的问题，就会打乱我的计划，所以工作安排也是一门技术，以我的经验和公司的情况，我一般会如下安排时间：</p><ul><li>一般来说，上午的时候，来找的人比较少，可以安排做一些重要 Feature 的开发，或者重要技术的预言</li><li>下午的时候可以安排处理 Bug ，处理邮件，处理非重要的 Feature 等</li><li>晚上的时候可以安排做一些技术的研究</li></ul><p>当然理想是丰满的，现实是骨干的，鉴于软件开发的不确定性，上面的安排也经常会失效，有人建议用桌子上放一个番茄钟的方式来避免别人的干扰，我个人的经验是，戴个耳机！</p><p>我安排自己工作的软件是 Tower ， Tower 本来是一个团队软件，不过我们团队不是很适应这个软件，所以在我安利了一段时间后，他们就放弃了，所以我还是自己一个人用， 优先级和时间都会以标签的方式显示在每个任务之前：</p><p><img src="/images/media/14929591578368.jpg"></p><h3 id="工作记录"><a href="#工作记录" class="headerlink" title="工作记录"></a>工作记录</h3><p>每日回家之后，会把每天的工作记录下来，Tower 适合安排工作，但是不太适合记录工作，一是不太方便每日查看，二是自己的一些思路和想法，记录到 Tower 上很容易找不到。</p><p>所以我记录工作的软件是 MWeb ，会记录每天所完成的各个项，不论大小，比较重要的工作项，解题思路和想法都会记录在后面，每周的总结也会记录，来源包括 Redmine、邮件、Tower 等，这样不会漏掉一些重要的事情和数据</p><h3 id="文章记录"><a href="#文章记录" class="headerlink" title="文章记录"></a>文章记录</h3><p>文章记录主要的印象笔记，遇到好文章或者比较重要的事情，我都会记录到印象笔记中，定时去整理和查看</p><h2 id="关于-2017"><a href="#关于-2017" class="headerlink" title="关于 2017"></a>关于 2017</h2><p>今年公司比较动荡，身边好几个小伙伴都走人了，公司也在转型，阵痛期。自己也比较犹豫，不过目前没有花太多的时间去想这事，做好目前手上每一件事，该做的去做，机会总是青睐有准备的人。</p><p>作为一个软件工程师，Coding 能力永远是要放在第一位的，这一点需要向我偶像百万学习（下图，一周Coding 的时间是51个小时）！<br><img src="/images/media/14929935518939.jpg" alt="百万"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;一转眼就 2017 年了，算起来自己已经工作了快四年了，09年到威海上学，12年去上海实习，13年毕业后还是去了上海，14年进了珠海魅族，直到现在。&lt;/p&gt;
&lt;p&gt;这博客从我毕业开始写东西，写写删删，也算是记录了一些东西，自己的工作内容也从 App 开发换到了系统 App 开发，在换到系统开发，也算是走了一圈，一些路程，记录下来，几年后再看看，怀念一下也是不错的。&lt;/p&gt;
&lt;p&gt;这篇文章我记录了自己的博客、自己的工作、自己的工作内容、自己的工作习惯、还有对 2017 年的期望，有迷茫，也有奋斗。&lt;/p&gt;
&lt;p&gt;17年已经过了 30% 了，希望还不算晚。&lt;/p&gt;</summary>
    
    
    
    <category term="随笔" scheme="https://androidperformance.com/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://androidperformance.com/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>Android Bottom navigation 规范二：样式、行为与规格</title>
    <link href="https://androidperformance.com/2016/04/05/android-bottom-bar-2/"/>
    <id>https://androidperformance.com/2016/04/05/android-bottom-bar-2/</id>
    <published>2016-04-05T15:10:25.000Z</published>
    <updated>2026-02-07T05:17:47.858Z</updated>
    
    <content type="html"><![CDATA[<p>Android 官方在三月的某一天更新了一个新的设计规范，所谓设计规范就是告诉开发者和设计师要如何去设计和使用某一个组件。不过这次 Bottom navigation 的发布，让许多人大跌眼镜，毕竟 Bottom navigation 这样的组件，在之前的 MD 设计语言中可是只字未提，Android 开发者与 iOS 开发中最大的不同也是由于 Bottom navigation 是 iOS 应用的必备，而遵循 MD 设计规范的 Android 应用，则对 Bottom navigation 敬而远之。</p><p>本文是 Android Bottom navigation 的第二篇文章，主要介绍样式、行为与规格。</p><span id="more"></span><h1 id="1-样式-Style"><a href="#1-样式-Style" class="headerlink" title="1. 样式-Style"></a>1. 样式-Style</h1><h2 id="1-1-图标和文字-Icons-and-text"><a href="#1-1-图标和文字-Icons-and-text" class="headerlink" title="1.1 图标和文字-Icons and text"></a>1.1 图标和文字-Icons and text</h2><p>Because bottom navigation actions are presented as icons, they should be used for content that can be suitably communicated with icons.<br>由于底部导航操作显示为图标，它应该使用与其内容相符合的图标。根据以下条件来为每个操作设定样式：</p><ul><li>当 Item 是 focus 状态的时候，显示这个 Item View 的图标和文字。</li><li>当 bottom navigation 只有三个 Item 的时候，他们的图标和文字都应该被显示。</li><li>当 bottom navigation 有四个或者五个 Item 的时候，在非激活状态的时候只显示他们的图标即可。</li></ul><h2 id="1-2颜色"><a href="#1-2颜色" class="headerlink" title="1.2颜色"></a>1.2颜色</h2><p>Tint the current bottom navigation action (including the icon and any text label present) with the app’s primary color<br>用应用的主色调给底部导航操作（包括图标与当前标签文字）上色。</p><p><img src="/images/bottombar2/1.webp" alt="Do. Use the app’s primary color to indicate the view in focus."></p><p><img src="/images/bottombar2/2.webp" alt="Don&#39;t. Avoid using different colored  icons and text labels."><br>如果底部导航条已着色，将底部导航操作图标和文字设置为白色或黑色。</p><p><img src="/images/bottombar2/3.webp" alt="Do. Use black or white iconography if the bottom navigation bar is colored."></p><p><img src="/images/bottombar2/4.webp" alt="Don&#39;t. Avoid pairing colored icons with a colored bottom navigation bar."></p><h2 id="1-3文本标签"><a href="#1-3文本标签" class="headerlink" title="1.3文本标签"></a>1.3文本标签</h2><p>文本标签为导航图标提供简明的定义。应避免使用较长的文本而造成文本被裁截或遮挡。<br><img src="/images/bottombar2/5.webp" alt="Do. Use short labels"></p><p><img src="/images/bottombar2/6.webp" alt="Don&#39;t. Avoid labels with wrapping text"></p><p><img src="/images/bottombar2/7.webp" alt="Don&#39;t. Avoid truncating text labels as doing so may prevent comprehension."></p><p><img src="/images/bottombar2/8.webp" alt="Don&#39;t. Avoid shrinking text labels to fit on a single line."></p><h1 id="2-操作"><a href="#2-操作" class="headerlink" title="2.操作"></a>2.操作</h1><p>点击底部导航图标将直接跳转至相关的界面或刷新当前的界面。</p><p>每一个底部导航图标都必须指向一个目的，不应打开主菜单或跳转至其他窗口。</p><p>每一个底部导航图标都会随着界面的滚动而动态的显示或隐藏。</p><ul><li>界面向下滚动时隐藏底部导航栏</li><li>界面向上滚动时显示底部导航栏</li></ul><p>在内容区域使用滑动手势不能进行界面的跳转。<br>在当前界面与未激活界面的跳转过程中使用淡入淡出的动画效果。</p><h1 id="3-空间"><a href="#3-空间" class="headerlink" title="3.空间"></a>3.空间</h1><h2 id="3-1确定底部导航栏"><a href="#3-1确定底部导航栏" class="headerlink" title="3.1确定底部导航栏"></a>3.1确定底部导航栏</h2><p>用底部导航栏的总长度除以图标的个数，计算出每个图标的宽度。也就是说，要使得每个底部导航图标占有最充足的空间。<br><img src="/images/bottombar2/9.webp" alt="Fixed bottom navigation bar on mobile"><br>宽度的最大及最小值（这些数据包含边距）：</p><ul><li>最大值：168dp</li><li>最小值：较大界面为 120dp，较小界面为 104dp</li></ul><p>高度：<br>56dp</p><p>图标：<br>24*24dp</p><p><img src="/images/bottombar2/11.webp" alt="168dp max width"><br>内容对齐：<br>文本与图标需居中且水平。</p><p>边距：</p><ul><li>距图标 6dp（当前界面），距图标 8dp（未被激活界面）</li><li>距文本 10dp</li><li>距文本左右各 12dp</li></ul><p>文本标签：</p><ul><li>常规 Roboto字体： 14sp（当前界面）</li><li>常规 Roboto字体：12sp（未激活界面）</li></ul><p><img src="/images/bottombar2/12.webp" alt="12dp left and right of text"></p><p><img src="/images/bottombar2/13.webp" alt="12dp left and right of text"></p><p><img src="/images/bottombar2/14.webp" alt="Fixed bottom navigation bar on landscape mobile"></p><p><img src="/images/bottombar2/15.webp" alt="Fixed bottom navigation bar on tablet"></p><h2 id="3-2切换底部导航栏"><a href="#3-2切换底部导航栏" class="headerlink" title="3.2切换底部导航栏"></a>3.2切换底部导航栏</h2><p>用底部导航栏的总长度除以图标的个数，计算出每个图标的宽度。</p><p><img src="/images/bottombar2/16.webp" alt="Shifting bottom navigation bar on mobile"></p><p>宽度的最大及最小值（这些数据包含边距）：<br>当前界面</p><ul><li>最大值：168 dp</li><li>最小值：96 dp</li></ul><p>未激活界面</p><ul><li>最大值：96 dp</li><li>最小值：64 dp</li></ul><p><img src="/images/bottombar2/17.webp" alt="Active view: 96dp min width"></p><p><img src="/images/bottombar2/18.webp" alt="Active view: 168dp max width"></p><p><img src="/images/bottombar2/19.webp" alt="Inactive view: 64dp min width"></p><p><img src="/images/bottombar2/20.webp" alt="Inactive view: 96dp max width"><br>高度：<br>56dp</p><p>图标：<br>24*24 dp</p><p>内容对齐：<br>文本与图标需居中且水平。</p><p>边距：</p><ul><li>距图标上 6dp（当前界面），距图标上下各 16dp（未被激活界面）</li><li>距文本下 10dp</li></ul><p>文本标签：<br>常规 Roboto 字体： 14sp（当前界面）</p><p><img src="/images/bottombar2/21.webp" alt="Shifting bottom navigation bar on landscape mobile"></p><p><img src="/images/bottombar2/22.webp" alt="Shifting bottom navigation bar on tablet"></p><h1 id="4-层级"><a href="#4-层级" class="headerlink" title="4. 层级"></a>4. 层级</h1><p>由于 snackbars  的层级高度(elevation) 为6dp，而 navigation bar 的层级高度为 8dp，所以 snackbars 显示在 navigation bar 的后面。而 Bottom sheets, navigation drawers 和 keyboards 都显示在 navigation bar 的前面，完全覆盖 navigation bar 。</p><p><img src="/images/bottombar2/23.webp" alt="Snackbars appear behind the bottom navigation bar."></p><p><img src="/images/bottombar2/24.webp" alt="Orthographic view of app structure"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 官方在三月的某一天更新了一个新的设计规范，所谓设计规范就是告诉开发者和设计师要如何去设计和使用某一个组件。不过这次 Bottom navigation 的发布，让许多人大跌眼镜，毕竟 Bottom navigation 这样的组件，在之前的 MD 设计语言中可是只字未提，Android 开发者与 iOS 开发中最大的不同也是由于 Bottom navigation 是 iOS 应用的必备，而遵循 MD 设计规范的 Android 应用，则对 Bottom navigation 敬而远之。&lt;/p&gt;
&lt;p&gt;本文是 Android Bottom navigation 的第二篇文章，主要介绍样式、行为与规格。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="设计规范" scheme="https://androidperformance.com/tags/%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83/"/>
    
  </entry>
  
  <entry>
    <title>Android Bottom Navigation 规范一：使用方法</title>
    <link href="https://androidperformance.com/2016/04/05/android-bottom-bar-1/"/>
    <id>https://androidperformance.com/2016/04/05/android-bottom-bar-1/</id>
    <published>2016-04-05T14:57:58.000Z</published>
    <updated>2026-02-07T05:17:47.858Z</updated>
    
    <content type="html"><![CDATA[<p>Android 官方在三月的某一天更新了一个新的设计规范，所谓设计规范就是告诉开发者和设计师要如何去设计和使用某一个组件。不过这次 Bottom navigation 的发布，让许多人大跌眼镜，毕竟 Bottom navigation 这样的组件，在之前的 MD 设计语言中可是只字未提，Android 开发者与 iOS 开发中最大的不同也是由于 Bottom navigation 是 iOS 应用的必备，而遵循 MD 设计规范的 Android 应用，则对 Bottom navigation 敬而远之。</p><p>本文是 Android Bottom navigation 的第一篇文章，主要介绍 Bottom navigation 的使用，以及  Bottom navigation 小变迁。</p><span id="more"></span><h2 id="1-Android-之前的关于底栏的设计规范"><a href="#1-Android-之前的关于底栏的设计规范" class="headerlink" title="1 Android 之前的关于底栏的设计规范"></a>1 Android 之前的关于底栏的设计规范</h2><p><img src="/images/bottombar1/14598674457055.jpg" alt="Google 之前对于底栏的设计规范"></p><h2 id="2-微信的改版"><a href="#2-微信的改版" class="headerlink" title="2. 微信的改版"></a>2. 微信的改版</h2><p>上上一次 Bottom navigation 在 Android 圈引起轩然大波还是微信的改版（5.2版本），在试错之后迅速换回了 Bottom navigation 。</p><p><img src="/images/bottombar1/14598674884766.jpg" alt="测试改版的微信"></p><p><img src="/images/bottombar1/14598675383016.jpg" alt="改版后的微信"></p><h2 id="3-Google-的改版"><a href="#3-Google-的改版" class="headerlink" title="3 Google + 的改版"></a>3 Google + 的改版</h2><p>而上一次 Bottom navigation 在 Android 圈引起轩然大波的就是 Google Plus 的改版：</p><p><img src="/images/bottombar1/14598675580793.jpg" alt="Google + 的改版"></p><p>反正打脸啪啪啪，不过规则是用来遵守的，你看微信没有遵守，活的比 Google + 好几百倍了。</p><h2 id="4-Bottom-navigation-设计规范"><a href="#4-Bottom-navigation-设计规范" class="headerlink" title="4. Bottom navigation 设计规范"></a>4. Bottom navigation 设计规范</h2><p>闲话说完了我们就来看看官方发布的  <a href="https://www.google.com/design/spec/components/bottom-navigation.html#">Bottom navigation 的设计规范</a>，毕竟对于我们广大程序员来说，设计方面的能力还是没有专业的设计师强的，有一些规范我们做出来的东西不至于太难看。</p><h3 id="4-1-Bottom-navigation-使用"><a href="#4-1-Bottom-navigation-使用" class="headerlink" title="4.1 Bottom navigation 使用"></a>4.1 Bottom navigation 使用</h3><p>Bottom navigation 主要为手机应用设计，它提供了应用内顶层视图的快速导航功能。一些大的显示设备，比如桌面设备，可以使用侧面导航达到类似的效果。</p><p><img src="/images/bottombar1/14598675822179.jpg" alt="The bottom navigation bar on mobile"><br><img src="/images/bottombar1/14598676008074.jpg" alt="Left navigation on a larger display, such as tablet or desktop"></p><h4 id="4-1-1-使用时机"><a href="#4-1-1-使用时机" class="headerlink" title="4.1.1 使用时机"></a>4.1.1 使用时机</h4><p>Bottom navigation 的使用时机：</p><ul><li>三到五个同样重要的顶级功能界面</li><li>需要从应用程序的任意位置访问的功能界面</li></ul><h5 id="Do-and-Don’t-1-当底栏的-Item-太少的时候，不要使用-Bottom-navigation-，用-Tab-来替代。（毕竟只有两个-Item-的时候，还是蛮奇怪的看上去。"><a href="#Do-and-Don’t-1-当底栏的-Item-太少的时候，不要使用-Bottom-navigation-，用-Tab-来替代。（毕竟只有两个-Item-的时候，还是蛮奇怪的看上去。" class="headerlink" title="[Do and Don’t] 1. 当底栏的 Item 太少的时候，不要使用 Bottom navigation ，用  Tab 来替代。（毕竟只有两个 Item 的时候，还是蛮奇怪的看上去。"></a>[Do and Don’t] 1. 当底栏的 Item 太少的时候，不要使用 Bottom navigation ，用  Tab 来替代。（毕竟只有两个 Item 的时候，还是蛮奇怪的看上去。</h5><p><img src="/images/bottombar1/14598676393637.jpg" alt="Do.The bottom navigation bar exposes the three to five top-level destinations of an app."></p><p><img src="/images/bottombar1/14598676746527.jpg" alt="Don&#39;t. If there are fewer than three destinations, consider using tabs instead."></p><h5 id="Do-and-Don’t-2-如果你的底栏-Item-多于6个，可以在-Navigation-drawer-中提供访问入口，不要将底栏做成可以滑动的。"><a href="#Do-and-Don’t-2-如果你的底栏-Item-多于6个，可以在-Navigation-drawer-中提供访问入口，不要将底栏做成可以滑动的。" class="headerlink" title="[Do and Don’t] 2. 如果你的底栏 Item 多于6个，可以在 Navigation drawer 中提供访问入口，不要将底栏做成可以滑动的。"></a>[Do and Don’t] 2. 如果你的底栏 Item 多于6个，可以在 Navigation drawer 中提供访问入口，不要将底栏做成可以滑动的。</h5><p><img src="/images/bottombar1/14598676965667.jpg" alt="Do. Views are fixed in a bottom navigation bar."></p><p><img src="/images/bottombar1/14598677161097.jpg" alt="Don&#39;t.Avoid scrollable content in the bottom navigation bar."></p><h5 id="Do-and-Don’t-3-最好提供3-5个底栏-Item-，当底栏-Item-超过五个的时候，会显得每个-Item-过于紧凑，影响美观"><a href="#Do-and-Don’t-3-最好提供3-5个底栏-Item-，当底栏-Item-超过五个的时候，会显得每个-Item-过于紧凑，影响美观" class="headerlink" title="[Do and Don’t] 3. 最好提供3-5个底栏 Item ，当底栏 Item 超过五个的时候，会显得每个 Item 过于紧凑，影响美观"></a>[Do and Don’t] 3. 最好提供3-5个底栏 Item ，当底栏 Item 超过五个的时候，会显得每个 Item 过于紧凑，影响美观</h5><p><img src="/images/bottombar1/14598677362029.jpg" alt="Do. Use up to five top-level destinations in a bottom navigation bar."></p><p><img src="/images/bottombar1/14598677540938.jpg" alt="Don&#39;t. Avoid using more than five destinations in bottom navigation as tap targets will be situated too close to one another."></p><h2 id="5-Bottom-navigation-与-tabs"><a href="#5-Bottom-navigation-与-tabs" class="headerlink" title="5. Bottom navigation 与 tabs"></a>5. Bottom navigation 与 tabs</h2><p>当 Bottom navigation 与 tabs 混合使用的时候，可能会造成使用上的混乱。 举个栗子，点击 tab 和点击 Bottom navigation 可以显示不同的内容组合，这些功能的组合会给用户带来使用上的混乱。<br>另外 Tabs 在体验上和 Bottom navigation 也有很大的不同，Tabs 更依赖手势操作带来的便利，而 Bottom navigation 则由于位置比较靠近手指，使得点击更加方便。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 官方在三月的某一天更新了一个新的设计规范，所谓设计规范就是告诉开发者和设计师要如何去设计和使用某一个组件。不过这次 Bottom navigation 的发布，让许多人大跌眼镜，毕竟 Bottom navigation 这样的组件，在之前的 MD 设计语言中可是只字未提，Android 开发者与 iOS 开发中最大的不同也是由于 Bottom navigation 是 iOS 应用的必备，而遵循 MD 设计规范的 Android 应用，则对 Bottom navigation 敬而远之。&lt;/p&gt;
&lt;p&gt;本文是 Android Bottom navigation 的第一篇文章，主要介绍 Bottom navigation 的使用，以及  Bottom navigation 小变迁。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="设计规范" scheme="https://androidperformance.com/tags/%E8%AE%BE%E8%AE%A1%E8%A7%84%E8%8C%83/"/>
    
  </entry>
  
  <entry>
    <title>Android 中如何计算 App 的启动时间？</title>
    <link href="https://androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/"/>
    <id>https://androidperformance.com/2015/12/31/How-to-calculation-android-app-lunch-time/</id>
    <published>2015-12-31T05:52:30.000Z</published>
    <updated>2026-02-07T05:17:47.854Z</updated>
    
    <content type="html"><![CDATA[<p>之前有人在知乎提问：<a href="https://www.zhihu.com/question/35487841/answer/63011462">“怎么计算apk的启动时间？”</a> :</p><blockquote><p>利用python或者直接用adb命令怎么计算apk的启动时间呢？就是计算从点击图标到apk完全启动所花费的时间。比如，对游戏来说就是点击游戏图标到进入到登录界面的这段时间。<br>已知的两种方法貌似可以获取，但是感觉结果不准确：一种是，adb shell am start -w packagename&#x2F;activity,这个可以得到两个值，ThisTime和TotalTime，不知道两个有什么区别，而且与实际启动时间不匹配，两者相加都可能比实际启动时间小（测试游戏的时候差别更大）；另外一种是通过adb logcat的方式，感觉获取的结果也与实际有差别。</p></blockquote><p>我和另外一个同事<a href="https://www.zhihu.com/people/guo-qi-fa">郭启发</a> 针对两个方面进行了回答，不过毕竟知乎上看的人会比较少，所以我在征得他的同意之后，将这两个答案整理了一下，记录到博客中，一来算是一个小的总结，之后自己看得时候比较方便，二来给需要的同学一个更加方便的途径。</p><span id="more"></span><h1 id="1-应用启动场景"><a href="#1-应用启动场景" class="headerlink" title="1 应用启动场景"></a>1 应用启动场景</h1><p>事实上 Android 中一个 App 的启动时间可以准确计算的.但是要分场景.也就是说要分开游戏和应用. 大家都知道,在Android中,游戏开发和应用开发是两码事.所以我们需要分开来说.</p><h2 id="1-1-应用启动"><a href="#1-1-应用启动" class="headerlink" title="1.1 应用启动"></a>1.1 应用启动</h2><p>我们平时在写应用的时候,一般会指定一个 mainActivity ,用户在桌面上点击这个 Activity 的时候,系统会直接起这个 Activity. 我们知道 Activity 在启动的时候会走 onCreate&#x2F;onStart&#x2F;onResume .这几个回调函数.</p><p>许多书里讲过,当执行完 onResume 函数之后,应用就显示出来了…其实这是一种不准确的说法,因为从系统层面来看,一个 Activity 走完 onCreate&#x2F;onStart&#x2F;onResume 这几个生命周期之后,只是完成了应用自身的一些配置,比如 window 的一些属性的设置&#x2F; View 树的建立(只是建立,并没有显示,也就是说只是调用了 inflate 而已) . 后面 ViewRootImpl 还会调用两次performTraversals ,初始化 Egl 以及 measure&#x2F;layout&#x2F;draw. 等.<br>所以我们定义一个 Android 应用的启动时间, 肯定不能在 Activity 的回调函数上下手.而是以用户在手机屏幕上看到你在 onCreate 的 setContentView 中设置的 layout 完全显示为准,也就是我们常说的应用第一帧.</p><p>上面扯得有点远,不感兴趣的话可以不看,下面直接说方法.<br>题主说的 adb shell am start -w packagename&#x2F;activity,是可以完全应用的启动时间的.不过也要分场景.</p><h2 id="1-2-应用第一次启动"><a href="#1-2-应用第一次启动" class="headerlink" title="1.2 应用第一次启动"></a>1.2 应用第一次启动</h2><p>也就是我们常说的冷启动,这时候你的应用程序的进程是没有创建的. 这也是大部分应用的使用场景.用户在桌面上点击你应用的 icon 之后,首先要创建进程,然后才启动 MainActivity.<br>这时候adb shell am start -w packagename&#x2F;MainActivity 返回的结果,就是标准的应用程序的启动时间（注意 Android 5.0 之前的手机是没有 WaitTime 这个值的）:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">➜ adb shell am start -W com.media.painter/com.media.painter.PainterMainActivity</span><br><span class="line">Starting: Intent &#123; act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.media.painter/.PainterMainActivity &#125;</span><br><span class="line">Status: ok</span><br><span class="line">Activity: com.media.painter/.PainterMainActivity</span><br><span class="line">ThisTime: 355</span><br><span class="line">TotalTime: 355</span><br><span class="line">WaitTime: 365</span><br><span class="line">Complete</span><br></pre></td></tr></table></figure><p>总共返回了三个结果,我们以 WaitTime 为准. </p><p>关于ThisTime&#x2F;TotalTime&#x2F;WaitTime的区别,下面是其解释：</p><p>“adb shell am start -W ”的实现在 frameworks\base\cmds\am\src\com\android\commands\am\Am.java 文件中。其实就是跨Binder调用ActivityManagerService.startActivityAndWait() 接口（后面将ActivityManagerService简称为AMS），这个接口返回的结果包含上面打印的ThisTime、TotalTime时间.</p><p><img src="/images/applunch3/1.webp" alt="ThisTime/TotalTime"></p><ul><li>startTime记录的刚准备调用startActivityAndWait()的时间点</li><li>endTime记录的是startActivityAndWait()函数调用返回的时间点</li><li>WaitTime &#x3D; startActivityAndWait()调用耗时。</li></ul><p>ThisTime、TotalTime 的计算在 frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java 文件的 reportLaunchTimeLocked() 函数中。</p><p><img src="/images/applunch3/2.webp" alt="curTime/displayStartTime/mLaunchStartTime"></p><p>我们来解释下代码里curTime、displayStartTime、mLaunchStartTime三个时间变量.</p><ul><li>curTime表示该函数调用的时间点.</li><li>displayStartTime表示一连串启动Activity中的最后一个Activity的启动时间点.</li><li>mLaunchStartTime表示一连串启动Activity中第一个Activity的启动时间点.</li></ul><p>正常情况下点击桌面图标只启动一个有界面的 Activity，此时 displayStartTime 与mLaunchStartTime 便指向同一时间点，此时 ThisTime&#x3D;TotalTime。另一种情况是点击桌面图标应用会先启动一个无界面的 Activity 做逻辑处理，接着又启动一个有界面的Activity，在这种启动一连串 Activity 的情况下（知乎的启动就是属于这种情况），displayStartTime 便指向最后一个 Activity 的开始启动时间点，mLaunchStartTime 指向第一个无界面Activity的开始启动时间点，此时 ThisTime！&#x3D;TotalTime。这两种情况如下图：</p><p><img src="/images/applunch3/3.webp" alt="curTime/displayStartTime/mLaunchStartTime"></p><p>在上面的图中，我用①②③分别标注了三个时间段，在这三个时间段内分别干了什么事呢？           </p><ul><li>在第①个时间段内，AMS 创建 ActivityRecord 记录块和选择合理的 Task、将当前Resume 的 Activity 进行 pause</li><li>在第②个时间段内，启动进程、调用无界面 Activity 的 onCreate() 等、 pause&#x2F;finish 无界面的 Activity</li><li>在第③个时间段内，调用有界面 Activity 的 onCreate、onResume</li></ul><p>看到这里应该清楚 ThisTime、TotalTime、WaitTime 三个时间的关系了吧：</p><ul><li>WaitTime 就是总的耗时，包括前一个应用 Activity pause 的时间和新应用启动的时间；</li><li>ThisTime 表示一连串启动 Activity 的最后一个 Activity 的启动耗时；</li><li>TotalTime 表示新应用启动的耗时，包括新进程的启动和 Activity 的启动，但不包括前</li></ul><p>一个应用 Activity pause 的耗时。也就是说，开发者一般只要关心 TotalTime 即可，这个时间才是自己应用真正启动的耗时。    </p><p>Event log中 TAG&#x3D;am_activity_launch_time 中的两个值分表表示 ThisTime、TotalTime，跟通过 “adb shell am start -W ” 得到的值是一致的。    </p><p>最后再说下系统根据什么来判断应用启动结束。我们知道应用启动包括进程启动、走 Activity生命周期 onCreate&#x2F;onResume 等。在第一次 onResume 时添加窗口到WMS中，然后measure&#x2F;layout&#x2F;draw，窗口绘制完成后通知 WMS，WMS 在合适的时机控制界面开始显示(夹杂了界面切换动画逻辑)。记住是窗口界面显示出来后，WMS 才调用reportLaunchTimeLocked() 通知 AMS Activity 启动完成。    </p><p><em><strong>最后总结一下，如果只关心某个应用自身启动耗时，参考TotalTime；如果关心系统启动应用耗时，参考WaitTime；如果关心应用有界面Activity启动耗时，参考ThisTime。</strong></em></p><h2 id="1-2-应用非第一次启动"><a href="#1-2-应用非第一次启动" class="headerlink" title="1.2 应用非第一次启动"></a>1.2 应用非第一次启动</h2><p>如果是你按Back键，并没有将应用进程杀掉的话，那么执行上述命令就会快一些，因为不用创建进程了，只需要启动一个Activity即可。这也就是我们说的应用热启动。</p><h1 id="2-游戏启动场景"><a href="#2-游戏启动场景" class="headerlink" title="2 游戏启动场景"></a>2 游戏启动场景</h1><p>游戏启动的话，就不适用用命令行的方法来启动了，因为从用户点击桌面图标到登录界面，既有系统的部分也有游戏自己的部分。</p><h2 id="2-1-系统部分"><a href="#2-1-系统部分" class="headerlink" title="2.1 系统部分"></a>2.1 系统部分</h2><p>游戏也有一个 Activity，所以启动的时候还是会去启动这个 Activity，所以系统启动部分也就是用户点击桌面桌面响应到这个Activity启动。</p><h2 id="2-2-游戏部分"><a href="#2-2-游戏部分" class="headerlink" title="2.2 游戏部分"></a>2.2 游戏部分</h2><p>一般游戏的主 Activity 启动后，还会做一些比较耗时的事情，这时候你看到的界面是不能操作的，比如：加载游戏数据、联网更新数据、读取和更新配置文件、游戏引擎初始化等操作。从游戏开发的角度来看，到了真正用户能操作的界面才算是一个游戏真正加载完成的时间。<br>那么这个时间，就得使用 Log 来记录了，因为加载游戏数据、联网更新数据、读取和更新配置文件、游戏引擎初始化这些操作，都是游戏自己的逻辑，与系统无关，所以得由游戏自己定义加载完成的点。</p><p>对于游戏的启动时间，我们更倾向于计算从<strong>点击桌面图标</strong>到<strong>用户可以与游戏进行交互</strong>这个时间段作为一个游戏的启动时间。</p><h1 id="3-总结"><a href="#3-总结" class="headerlink" title="3 总结"></a>3 总结</h1><p>计算机最让人着迷的一点就是其准确性，1+1 永远等于 2，启动耗时多久就是多久，每一次可能不一样，但每一次的时间都是这一次的准确时间。</p><p>不过每个公司由于对应用的定位不同，所以对应用启动的要求也不一样。比如有的做 ROM 的公司，其内置应用的启动时间一定是要非常快的，这样给用户的第一感觉就是快、流畅；互联网公司的 App 则不是很关心启动速度，大部分互联网公司的应用都有一个启动页，用来展示广告或者功能介绍之类的，然后才会进入到主界面。需求不一样，这么做也无可厚非，不过从消费者的角度来看，越早见到主界面当然越好。</p><p>所以在做一个 Android App 的时候，一定要记得将应用的启动时间作为一个性能指标，毕竟：</p><blockquote><p>天下武功，唯快不破！</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;之前有人在知乎提问：&lt;a href=&quot;https://www.zhihu.com/question/35487841/answer/63011462&quot;&gt;“怎么计算apk的启动时间？”&lt;/a&gt; :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;利用python或者直接用adb命令怎么计算apk的启动时间呢？就是计算从点击图标到apk完全启动所花费的时间。比如，对游戏来说就是点击游戏图标到进入到登录界面的这段时间。&lt;br&gt;已知的两种方法貌似可以获取，但是感觉结果不准确：一种是，adb shell am start -w packagename&amp;#x2F;activity,这个可以得到两个值，ThisTime和TotalTime，不知道两个有什么区别，而且与实际启动时间不匹配，两者相加都可能比实际启动时间小（测试游戏的时候差别更大）；另外一种是通过adb logcat的方式，感觉获取的结果也与实际有差别。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我和另外一个同事&lt;a href=&quot;https://www.zhihu.com/people/guo-qi-fa&quot;&gt;郭启发&lt;/a&gt; 针对两个方面进行了回答，不过毕竟知乎上看的人会比较少，所以我在征得他的同意之后，将这两个答案整理了一下，记录到博客中，一来算是一个小的总结，之后自己看得时候比较方便，二来给需要的同学一个更加方便的途径。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇)</title>
    <link href="https://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87/"/>
    <id>https://androidperformance.com/2015/12/29/Android%E5%BA%94%E7%94%A8%E5%90%AF%E5%8A%A8%E4%BC%98%E5%8C%96-%E4%B8%80%E7%A7%8DDelayLoad%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E5%8E%9F%E7%90%86-%E4%B8%8B%E7%AF%87/</id>
    <published>2015-12-29T08:34:06.000Z</published>
    <updated>2026-02-07T05:17:47.852Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/">上一篇文章</a>我们使用第三种方法来实现延迟加载。不过上一篇写的比较简单，只是讲解了如何去实现，这一篇就来讲一下为何要这么做，以及这么做后面的原理。<br>其中会涉及到一些 Android 中的比较重要的类，以及 Activity 生命周期中比较重要的几个函数。<br>其实这个其中的原理比较简单，不过要弄清楚其实现的过程，还是一件蛮好玩的事情，其中会用到一些工具，自己加调试代码等，一步一步下来，自己对 Activity 的启动的理解又深了一层，希望大家读完之后也会对大家有一定的帮助。</p><span id="more"></span><p>上一篇中我们最终使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函数中加入下面的方法 ：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">getWindow().getDecorView().post(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        myHandler.post(mLoadingRunnable);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>我们一一来看涉及到的类和方法</p><h2 id="1-Activity-getWindow-及-PhoneWindow-的初始化时机"><a href="#1-Activity-getWindow-及-PhoneWindow-的初始化时机" class="headerlink" title="1. Activity.getWindow 及 PhoneWindow 的初始化时机"></a>1. Activity.getWindow 及 PhoneWindow 的初始化时机</h2><p>Activity 的 getWindow 方法获取到的是一个 PhoneWindow 对象：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Window <span class="title function_">getWindow</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> mWindow;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 这个 mWindow 就是一个 PhoneWindow 对象，其初始化的时机为这个 Activity attach 的时候：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">attach</span><span class="params">(Context context, ActivityThread aThread,</span></span><br><span class="line"><span class="params">          Instrumentation instr, IBinder token, <span class="type">int</span> ident,</span></span><br><span class="line"><span class="params">          Application application, Intent intent, ActivityInfo info,</span></span><br><span class="line"><span class="params">          CharSequence title, Activity parent, String id,</span></span><br><span class="line"><span class="params">          NonConfigurationInstances lastNonConfigurationInstances,</span></span><br><span class="line"><span class="params">          Configuration config, String referrer, IVoiceInteractor voiceInteractor)</span> &#123;</span><br><span class="line">      attachBaseContext(context);</span><br><span class="line"></span><br><span class="line">      mFragments.attachActivity(<span class="built_in">this</span>, mContainer, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">      mWindow = PolicyManager.makeNewWindow(<span class="built_in">this</span>);</span><br><span class="line">      mWindow.setCallback(<span class="built_in">this</span>);</span><br><span class="line">      mWindow.setOnWindowDismissedCallback(<span class="built_in">this</span>);</span><br><span class="line">      mWindow.getLayoutInflater().setPrivateFactory(<span class="built_in">this</span>);</span><br><span class="line">      ........</span><br><span class="line"></span><br><span class="line">  <span class="comment">// PolicyManager.makeNewWindow(this) 最终会调用 Policy 的 makeNewWindow 方法</span></span><br><span class="line">  <span class="keyword">public</span> Window <span class="title function_">makeNewWindow</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PhoneWindow</span>(context);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里需要注意 Activity 的 attach 方法很早就会调用的，是要早于 Activity 的 onCreate 方法的。 </p><h3 id="总结："><a href="#总结：" class="headerlink" title="总结："></a>总结：</h3><ul><li>PhoneWindow 与 Activity 是一对一的关系，通过上面的初始化过程你应该更加清楚这个概念</li><li>Android 中对 PhoneWindow 的注释是 ：Android-specific Window ，可见其重要性</li><li>PhoneWindow 中有很多大家比较熟悉的方法，比如 setContentView &#x2F; addContentView 等 ； 也有几个重要的内部类，比如：DecorView ;</li></ul><h2 id="2-PhoneWindow-getDecorView-及-DecorView-的初始化时机"><a href="#2-PhoneWindow-getDecorView-及-DecorView-的初始化时机" class="headerlink" title="2. PhoneWindow.getDecorView 及 DecorView 的初始化时机"></a>2. PhoneWindow.getDecorView 及 DecorView 的初始化时机</h2><p>上面我们说到 DecorView是 PhoneWindow 的一个内部类，其定义如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">DecorView</span> <span class="keyword">extends</span> <span class="title class_">FrameLayout</span> <span class="keyword">implements</span> <span class="title class_">RootViewSurfaceTaker</span></span><br></pre></td></tr></table></figure><p>那么 DecorView 是什么时候初始化的呢？DecorView 是在 Activity 的父类的 onCreate 方法中被初始化的，比如我例子中的 MainActivity 是继承自 android.support.v7.app.AppCompatActivity ，当我们调用 MainActivity 的 super.onCreate(savedInstanceState); 的时候，就会调用下面的</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(<span class="meta">@Nullable</span> Bundle savedInstanceState)</span> &#123;</span><br><span class="line">    getDelegate().installViewFactory();</span><br><span class="line">    getDelegate().onCreate(savedInstanceState);</span><br><span class="line">    <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于我们导入的是 support.v7 包里面的AppCompatActivity， getDelegate() 得到的就是AppCompatDelegateImplV7 ，其 onCreate 方法如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line"></span><br><span class="line">    mWindowDecor = (ViewGroup) mWindow.getDecorView();</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>就是这里的 mWindow.getDecorView() ，对 DecorView 进行了实例化：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> View <span class="title function_">getDecorView</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mDecor == <span class="literal">null</span>) &#123;</span><br><span class="line">        installDecor();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> mDecor;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第一次调用 getDecorView 的时候，会进入 installDecor 方法，这个方法对 DecorView 进行了一系列的初始化 ，其中比较重要的几个方法有：generateDecor &#x2F; generateLayout 等，generateLayout 会从当前的 Activity 的 Theme 提取相关的属性，设置给 Window，同时还会初始化一个 startingView，添加到 DecorView上，也就是我们所说的 startingWindow。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li>Decor 有装饰的意思，DecorView 官方注释为 “This is the top-level view of the window, containing the window decor” , 我们可以理解为 DecorView 是我们当前 Activity 的最下面的布局。所以我们打开 DDMS 查看 Tree Overview 的时候，可以发现最根部的那个 View 就是 DecorView：<br>  <img src="/images/applunch2/1.webp" alt="DelayLoad"></li><li>应用从桌面启动的时候，在主 Activity 还没有显示的时候，如果主题没有设置窗口的背景，那么我们就会看到白色（这个和手机的Rom也有关系），如果应用启动很慢，那么用户得看好一会白色。如果要避免这个，则可以在 Application 或者 Activity 的 Theme 中设置 WindowBackground , 这样就可以避免白色（当然现在各种大厂都是SplashActivity+广告我也是可以理解的）</li></ul><h2 id="3-Post"><a href="#3-Post" class="headerlink" title="3. Post"></a>3. Post</h2><p>当我们调用 DecorView 的 Post 的时候，其实最终会调用 View 的 Post ，因为 DecorView 最终是继承 View 的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">post</span><span class="params">(Runnable action)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">AttachInfo</span> <span class="variable">attachInfo</span> <span class="operator">=</span> mAttachInfo;</span><br><span class="line">    <span class="keyword">if</span> (attachInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> attachInfo.mHandler.post(action);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Assume that post will succeed later</span></span><br><span class="line">    ViewRootImpl.getRunQueue().post(action);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这里的 mAttachInfo ，我们调用 post 是在 Activity 的 onCreate 中调用的，那么此时 mAttachInfo 是否为空呢？答案是 mAttachInfo 此时为空。</p><p>这里有一个点就是 Activity 的各个回调函数都是干嘛的？是不是平时自己写应用的时候，貌似在 onCreate 里面搞定一切就OK了， onResume ？ onStart？没怎么涉及到嘛，其实不然。<br>onCreate 顾名思义就是 Create ，我们在前面看到，Activity 的 onCreate 函数做了很多初始化的操作，包括 PhoneWindow&#x2F;DecorView&#x2F;StartingView&#x2F;setContentView等，但是 onCreate 只是初始化了这些对象.<br>真正要设置为显示则在 Resume 的时候，不过这些对开发者是透明了，具体可以看 ActivityThread 的 handleResumeActivity 函数，handleResumeActivity 中除了调用 Activity 的 onResume 回调之外，还初始化了几个比较重要的类：ViewRootImpl &#x2F; ThreadedRenderer。</p><p>ActivityThread.handleResumeActivity:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (r.window == <span class="literal">null</span> &amp;&amp; !a.mFinished &amp;&amp; willBeVisible) &#123;</span><br><span class="line">    r.window = r.activity.getWindow();</span><br><span class="line">    <span class="type">View</span> <span class="variable">decor</span> <span class="operator">=</span> r.window.getDecorView();</span><br><span class="line">    decor.setVisibility(View.INVISIBLE);</span><br><span class="line">    <span class="type">ViewManager</span> <span class="variable">wm</span> <span class="operator">=</span> a.getWindowManager();</span><br><span class="line">    WindowManager.<span class="type">LayoutParams</span> <span class="variable">l</span> <span class="operator">=</span> r.window.getAttributes();</span><br><span class="line">    a.mDecor = decor;</span><br><span class="line">    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;</span><br><span class="line">    l.softInputMode |= forwardBit;</span><br><span class="line">    <span class="keyword">if</span> (a.mVisibleFromClient) &#123;</span><br><span class="line">        a.mWindowAdded = <span class="literal">true</span>;</span><br><span class="line">        wm.addView(decor, l);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>主要是 wm.addView(decor, l); 这句，将 decorView 与 WindowManagerImpl联系起来，这句最终会调用到 WindowManagerGlobal 的 addView 函数，</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addView</span><span class="params">(View view, ViewGroup.LayoutParams params,</span></span><br><span class="line"><span class="params">        Display display, Window parentWindow)</span> &#123;</span><br><span class="line">    ......</span><br><span class="line">    ViewRootImpl root;</span><br><span class="line">    <span class="type">View</span> <span class="variable">panelParentView</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    ......</span><br><span class="line">        root = <span class="keyword">new</span> <span class="title class_">ViewRootImpl</span>(view.getContext(), display);</span><br><span class="line">        view.setLayoutParams(wparams);</span><br><span class="line"></span><br><span class="line">        mViews.add(view);</span><br><span class="line">        mRoots.add(root);</span><br><span class="line">        mParams.add(wparams);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// do this last because it fires off messages to start doing things</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        root.setView(view, wparams, panelParentView);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">      ......</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们知道 ViewRootImpl 是 View 系统的一个核心类，其定义如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ViewRootImpl</span> <span class="keyword">implements</span> <span class="title class_">ViewParent</span>,</span><br><span class="line">        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks</span><br></pre></td></tr></table></figure><p>ViewRootImpl 初始化的时候会对 AttachInfo 进行初始化，这就是为什么之前的在 onCreate 的时候 attachInfo 为空。ViewRootImpl 里面有很多我们比较熟悉也非常重要的方法，比如 performTraversals &#x2F; performLayout &#x2F; performMeasure &#x2F; performDraw &#x2F; draw 等。<br>我们继续 addView 中的root.setView(view, wparams, panelParentView); 传入的 view 为 decorView，root 为 ViewRootImpl ，这个函数中将 ViewRootImpl 的mView 变量 设置为传入的view，也就是 decorView。<br>这样来看，ViewRootImpl 与 DecorView 的关系我们也清楚了。</p><p>扯了一圈，我们再回到大标题的 Post 函数上，前面有说这个 Post 走的是 View 的Post 函数，由于 在 onCreate 的时候 attachInfo 为空，所以会走下面的分支：ViewRootImpl.getRunQueue().post(action);<br>注意这里的 getRunQueue 得到的并不是 Looper 里面的那个 MessageQueue，而是由 ViewRootImpl 维持的一个 RunQueue 对象，其核心为一个 ArrayList ：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ArrayList&lt;HandlerAction&gt; mActions = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;HandlerAction&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">post</span><span class="params">(Runnable action)</span> &#123;</span><br><span class="line">            postDelayed(action, <span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">postDelayed</span><span class="params">(Runnable action, <span class="type">long</span> delayMillis)</span> &#123;</span><br><span class="line">            <span class="type">HandlerAction</span> <span class="variable">handlerAction</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerAction</span>();</span><br><span class="line">            handlerAction.action = action;</span><br><span class="line">            handlerAction.delay = delayMillis;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">synchronized</span> (mActions) &#123;</span><br><span class="line">                mActions.add(handlerAction);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">executeActions</span><span class="params">(Handler handler)</span> &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (mActions) &#123;</span><br><span class="line">                <span class="keyword">final</span> ArrayList&lt;HandlerAction&gt; actions = mActions;</span><br><span class="line">                <span class="keyword">final</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> actions.size();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">                    <span class="keyword">final</span> <span class="type">HandlerAction</span> <span class="variable">handlerAction</span> <span class="operator">=</span> actions.get(i);</span><br><span class="line">                    handler.postDelayed(handlerAction.action, handlerAction.delay);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                actions.clear();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当我们执行了 Post 之后 ，其实只是把 Runnable 封装成一个 HandlerAction 对象存入到 ArrayList 中，当执行到 executeActions 方法的时候，将存在这里的 HandlerAction 再通过 executeActions 方法传入的 Handler 对象重新进行 Post。<br>那么 executeActions 方法是什么时候执行的呢？传入的 Handler 又是哪个 Handler 呢？</p><h2 id="4-PerformTraversals"><a href="#4-PerformTraversals" class="headerlink" title="4. PerformTraversals"></a>4. PerformTraversals</h2><p>我们之前讲过，ViewRootImpl 的 performTraversals 方法是一个很核心的方法，每一帧绘制都会走一遍，调用各种 measure &#x2F; layout &#x2F; draw 等 ，最终将要显示的数据交给 hwui 去进行绘制。<br>我们上一节讲到的 executeActions ，就是在 performTraversals 中执行的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Execute enqueued actions on every traversal in case a detached view enqueued an action</span></span><br><span class="line">getRunQueue().executeActions(mAttachInfo.mHandler);</span><br></pre></td></tr></table></figure><p>可以看到这里传入的 Handler 是 mAttachInfo.mHandler ，上一节讲到 mAttachInfo 是在 ViewRootImpl 初始化的时候一起初始化的：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mAttachInfo = <span class="keyword">new</span> <span class="title class_">View</span>.AttachInfo(mWindowSession, mWindow, display, <span class="built_in">this</span>, mHandler, <span class="built_in">this</span>);</span><br></pre></td></tr></table></figure><p>这里的 mHandler 是一个 ViewRootHandler 对象：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ViewRootHandler</span> <span class="keyword">extends</span> <span class="title class_">Handler</span>&#123;</span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br><span class="line">......</span><br><span class="line"><span class="keyword">final</span> <span class="type">ViewRootHandler</span> <span class="variable">mHandler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ViewRootHandler</span>();</span><br></pre></td></tr></table></figure><p>我们注意到 ViewRootHandler 在创建的时候并没有传入一个 Looper 对象，这意味着此 ViewRootHandler 的 Looper 就是 mainLooper。</p><p><em><strong>这下我们就清楚了，我们在 onCreate 中 Post 的 runnable 对象，最终还是在第一个 performTraversals 方法执行的时候，加入到了 MainLooper 的 MessageQueue 里面了。</strong></em></p><p>绕了一圈终于我们终于把文章最前面的那句话解释清楚了，当然中间还有很多的废话，不过我估计能耐着性子看到这里的人会很少，所以如果你看到了这里，可以在底下的评论里面将 index ++ ；这里 index &#x3D; 0 ；就是看看几个人是真正认真看了这篇文章的。</p><h2 id="5-UpdateText"><a href="#5-UpdateText" class="headerlink" title="5. UpdateText"></a>5. UpdateText</h2><p>接着 performTraversals 我们继续说，话说在<a href="https://www.androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/">第一篇文章</a> 我们有讲到，Activity 在启动时，会在第二次执行 performTraversals 才会去真正的绘制，原因在于第一次执行 performTraversals 的时候，会走到 Egl 初始化的逻辑，然后会重新执行一次 performTraversals 。<br>所以前一篇文章的评论区有人问为何在 run 方法里面还要 post 一次，如果在 run 方法里面直接执行 updateText 方法 ，那么 updateText 就会在第一个 performTraversals 之后就执行，而不是在第一帧绘制完成后才去执行，所以我们又 Post 了一次 。所以大概的处理步骤如下：</p><blockquote><p>第一步：Activity.onCreate –&gt; Activity.onStart –&gt; Activity.onResume</p></blockquote><blockquote><p>第二步：ViewRootImpl.performTraversals –&gt;Runnable</p></blockquote><blockquote><p>第三步：Runnable –&gt; ViewRootImpl.performTraversals</p></blockquote><blockquote><p>第四步：ViewRootImpl.performTraversals –&gt; UpdateText</p></blockquote><blockquote><p>第五步：UpdateText</p></blockquote><h2 id="6-总结"><a href="#6-总结" class="headerlink" title="6. 总结"></a>6. 总结</h2><p>其实一路跟下来发现其实原理很简单，其实 DelayLoad 其实只是一个很小的点，关键是教大家如何去跟踪一个自己不认识的知识点或者优化，这里面主要用到了两个工具：Systrace 和 Method Trace， 以及源码编译和调试。<br>关于 Systrace 和 Method Trace 的使用，之后会有详细的文章去介绍，这两个工具非常有助于理解源码和一些技术的实现。</p><h3 id="Systrace"><a href="#Systrace" class="headerlink" title="Systrace"></a>Systrace</h3><p><img src="/images/applunch2/2.webp" alt="Systrace"></p><h3 id="Method-Trace"><a href="#Method-Trace" class="headerlink" title="Method Trace"></a>Method Trace</h3><p><img src="/images/applunch2/3.webp" alt="Method Trace"></p><h3 id="源码编译与调试"><a href="#源码编译与调试" class="headerlink" title="源码编译与调试"></a>源码编译与调试</h3><p><img src="/images/applunch2/4.webp" alt="源码编译与调试"></p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>本文章所所涉及到的代码我放到了Github上：<br><a href="https://github.com/Gracker/DelayLoadSample">https://github.com/Gracker/DelayLoadSample</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/&quot;&gt;上一篇文章&lt;/a&gt;我们使用第三种方法来实现延迟加载。不过上一篇写的比较简单，只是讲解了如何去实现，这一篇就来讲一下为何要这么做，以及这么做后面的原理。&lt;br&gt;其中会涉及到一些 Android 中的比较重要的类，以及 Activity 生命周期中比较重要的几个函数。&lt;br&gt;其实这个其中的原理比较简单，不过要弄清楚其实现的过程，还是一件蛮好玩的事情，其中会用到一些工具，自己加调试代码等，一步一步下来，自己对 Activity 的启动的理解又深了一层，希望大家读完之后也会对大家有一定的帮助。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="DelayLoad" scheme="https://androidperformance.com/tags/DelayLoad/"/>
    
  </entry>
  
  <entry>
    <title>Android 应用启动优化 - 一种 DelayLoad 的实现和原理(上篇)</title>
    <link href="https://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/"/>
    <id>https://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load/</id>
    <published>2015-11-18T12:23:14.000Z</published>
    <updated>2026-02-07T05:17:47.848Z</updated>
    
    <content type="html"><![CDATA[<p>在 Android 开发中,应用启动速度是一个非常重要的点,应用启动优化也是一个非常重要的过程.对于应用启动优化,其实核心思想就是在启动过程中少做事情,具体实践的时候无非就是下面几种:</p><ol><li>异步加载</li><li>延时加载</li><li>懒加载</li></ol><p>不用一一去解释,做过启动优化的估计都使用过,本篇文章将详细讲解一下一种延时加载的实现以及其原理.<br>其实这种加载的实现是非常简单的,但是其中的原理可能比较复杂,还涉及到Looper&#x2F;Handler&#x2F;MessageQueue&#x2F;VSYNC等.以及其中碰到的一些问题,还会有一些我自己额外的思考.</p><span id="more"></span><h1 id="1-优化后的DelayLoad的实现"><a href="#1-优化后的DelayLoad的实现" class="headerlink" title="1. 优化后的DelayLoad的实现"></a>1. 优化后的DelayLoad的实现</h1><p>一提到DelayLoad,大家可能第一时间想到的就是在 onCreate 里面调用 Handler.postDelayed方法, 将需要 Delay 加载的东西放到这里面去初始化, 这个也是一个比较方便的方法. Delay一段时间再去执行,这时候应用已经加载完成,界面已经显示出来了, 不过这个方法有一个致命的问题: 延迟多久?<br>大家都知道,在 Android 的高端机型上,应用的启动是非常快的 , 这时候只需要 Delay 很短的时间就可以了, 但是在低端机型上,应用的启动就没有那么快了,而且现在应用为了兼容旧的机型,往往需要 Delay 较长的时间,这样带来体验上的差异是很明显的.</p><p>这里先说优化方案:</p><ol><li>首先 , 创建 Handler 和 Runnable 对象, 其中 Runnable 对象的 run方法里面去更新 UI 线程.</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">Handler</span> <span class="variable">myHandler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Handler</span>();</span><br><span class="line"><span class="keyword">private</span> <span class="type">Runnable</span> <span class="variable">mLoadingRunnable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    updateText(); <span class="comment">//更新UI线程</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ol start="2"><li>在主 Activity 的 onCreate 中加入下面的代码</li></ol><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">getWindow</span>()<span class="selector-class">.getDecorView</span>()<span class="selector-class">.post</span>(new Runnable() &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">@Override</span></span><br><span class="line">  public void run() &#123;</span><br><span class="line">    myHandler<span class="selector-class">.post</span>(mLoadingRunnable);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>其实实现的话非常简单,我们来对比一下三种方案的效果.</p><h1 id="2-三种写法的差异对比"><a href="#2-三种写法的差异对比" class="headerlink" title="2. 三种写法的差异对比"></a>2. 三种写法的差异对比</h1><p>为了验证我们优化的 DelayLoad的效果,我们写了一个简单的app , 这个 App 中包含三张不同大小的图片,每张图片下面都会有一个 TextView , 来标记图片的显示高度和宽度. MainActivity的代码如下:</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">class</span> MainActivity extends AppCompatActivity &#123;</span><br><span class="line">  <span class="keyword">private</span> static final <span class="built_in">int</span> DEALY_TIME = <span class="number">300</span> ;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> ImageView imageView1;</span><br><span class="line">  <span class="keyword">private</span> ImageView imageView2;</span><br><span class="line">  <span class="keyword">private</span> ImageView imageView3;</span><br><span class="line">  <span class="keyword">private</span> TextView textView1;</span><br><span class="line">  <span class="keyword">private</span> TextView textView2;</span><br><span class="line">  <span class="keyword">private</span> TextView textView3;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> Handler myHandler = <span class="keyword">new</span> <span class="constructor">Handler()</span>;</span><br><span class="line">  <span class="keyword">private</span> Runnable mLoadingRunnable = <span class="keyword">new</span> <span class="constructor">Runnable()</span> &#123;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public void run<span class="literal">()</span> &#123;</span><br><span class="line">      update<span class="constructor">Text()</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  @Override</span><br><span class="line">  protected void on<span class="constructor">Create(Bundle <span class="params">savedInstanceState</span>)</span> &#123;</span><br><span class="line">    super.on<span class="constructor">Create(<span class="params">savedInstanceState</span>)</span>;</span><br><span class="line">    set<span class="constructor">ContentView(R.<span class="params">layout</span>.<span class="params">activity_main</span>)</span>;</span><br><span class="line"></span><br><span class="line">    imageView1 = (ImageView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">image1</span>)</span>;</span><br><span class="line">    imageView2 = (ImageView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">image2</span>)</span>;</span><br><span class="line">    imageView3 = (ImageView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">image3</span>)</span>;</span><br><span class="line">    </span><br><span class="line">    textView1 = (TextView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">text1</span>)</span>;</span><br><span class="line">    textView2 = (TextView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">text2</span>)</span>;</span><br><span class="line">    textView3 = (TextView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">text3</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//  第一种写法:直接Post</span></span><br><span class="line">    myHandler.post(mLoadingRunnable);</span><br><span class="line"></span><br><span class="line"><span class="comment">//  第二种写法:直接PostDelay 300ms.</span></span><br><span class="line"><span class="comment">//  myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//  第三种写法:优化的DelayLoad</span></span><br><span class="line"><span class="comment">//  getWindow().getDecorView().post(new Runnable() &#123;</span></span><br><span class="line"><span class="comment">//    @Override</span></span><br><span class="line"><span class="comment">//    public void run() &#123;</span></span><br><span class="line"><span class="comment">//      myHandler.post(mLoadingRunnable);</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line"><span class="comment">//  &#125;);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Dump当前的MessageQueue信息.</span></span><br><span class="line">    get<span class="constructor">MainLooper()</span>.dump(<span class="keyword">new</span> <span class="constructor">Printer()</span> &#123;</span><br><span class="line">    </span><br><span class="line">      @Override</span><br><span class="line">      public void println(String x) &#123;</span><br><span class="line">        <span class="module-access"><span class="module"><span class="identifier">Log</span>.</span></span>i(<span class="string">&quot;Gracker&quot;</span>,x);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,<span class="string">&quot;onCreate&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> void update<span class="constructor">Text()</span> &#123;</span><br><span class="line">    <span class="module-access"><span class="module"><span class="identifier">TraceCompat</span>.</span></span><span class="keyword">begin</span><span class="constructor">Section(<span class="string">&quot;updateText&quot;</span>)</span>;</span><br><span class="line">    textView1.set<span class="constructor">Text(<span class="string">&quot;image1 : w=&quot;</span> + <span class="params">imageView1</span>.<span class="params">getWidth</span>()</span> +</span><br><span class="line">      <span class="string">&quot; h =&quot;</span> + imageView1.get<span class="constructor">Height()</span>);</span><br><span class="line">    textView2.set<span class="constructor">Text(<span class="string">&quot;image2 : w=&quot;</span> + <span class="params">imageView2</span>.<span class="params">getWidth</span>()</span> +</span><br><span class="line">      <span class="string">&quot; h =&quot;</span> + imageView2.get<span class="constructor">Height()</span>);</span><br><span class="line">    textView3.set<span class="constructor">Text(<span class="string">&quot;image3 : w=&quot;</span> + <span class="params">imageView3</span>.<span class="params">getWidth</span>()</span> +</span><br><span class="line">      <span class="string">&quot; h =&quot;</span> + imageView3.get<span class="constructor">Height()</span>);</span><br><span class="line">    <span class="module-access"><span class="module"><span class="identifier">TraceCompat</span>.</span></span><span class="keyword">end</span><span class="constructor">Section()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们需要关注两个点:</p><ul><li>updateText 这个函数是什么时候被执行的?</li><li>App 启动后,三个图片的长宽是否可以被正确地显示出来?</li><li>是否有 Delay Load 的效果?</li></ul><h2 id="2-1-第一种写法"><a href="#2-1-第一种写法" class="headerlink" title="2.1 第一种写法"></a>2.1 第一种写法</h2><ol><li><p>updateText执行的时机?<br>下面是第一种写法的Trace图:<br><img src="/images/app-lunch/1.webp" alt="第一种写法"><br>可以看到 updateText 是在 Activity 的 onCreate&#x2F;onStart&#x2F;onResume三个回调执行完成后才去执行的.</p></li><li><p>图片的宽高是否正确显示?<br><img src="/images/app-lunch/2.webp" alt="第一种写法"></p></li></ol><p>  从图片看一看到,宽高并没有显示. 这是为什么呢? 这个问题就要从Activity 的 onCreate&#x2F;onStart&#x2F;onResume三个回调说起了. 其实Activity 的 onCreate&#x2F;onStart&#x2F;onResume三个回调中,并没有执行Measure和Layout操作, 这个是在后面的performTraversals中才执行的. 所以在这之前宽高都是0.</p><ol start="3"><li>是否有 Delay Load 的效果?<br>并没有. 因为我们知道, 应用启动的时候,要等两次 performTraversals 都执行完成之后才会显示第一帧, 而 updateText 这个方法在第一个 performTraversals 执行之前就执行了. 所以 updateText 方法的执行时间是算在应用启动的时间里面的.</li></ol><h2 id="2-2-第二种写法"><a href="#2-2-第二种写法" class="headerlink" title="2.2 第二种写法"></a>2.2 第二种写法</h2><p>第二种写法我们Delay了300ms .我们来看一下表现.</p><ol><li>updateText执行的时机?<br>  <img src="/images/app-lunch/3.webp" alt="第二种写法"></li></ol><p>  可以看到,这种写法的话,updateText是在两个performTraversals 执行完成之后(这时候 APP 的第一帧才显示出来)才去执行的, 执行完成之后又调用了一次 performTraversals 将 TextView 的内容进行更新.</p><ol start="2"><li>图片的宽高是否正确显示?<br><img src="/images/app-lunch/4.webp" alt="第二种写法"></li></ol><p>  从上图可以看到,图片的宽高是正确显示了出来. 原因上面已经说了,measure&#x2F;layout执行完成后,宽高的数据就可以获取了.</p><ol start="3"><li>是否有 Delay Load 的效果?<br>不一定,取决于 Delay的时长.<br>从前面的 Trace 图上我们可以看到 , updateText 方法由于 Delay 了300ms, 所以在应用第一帧显示出来170ms之后, 图片的文字信息才进行了更新. 这个是有 Delay Load 的效果的.<br>但是这里只是一个简单的TextView的更新, 如果是较大模块的加载 , 用户视觉上会有很明显的 “ 空白-&gt;内容填充” 这个过程, 或者会附加”闪一下”特效…这显然是我们不想看到的.</li></ol><p>  有人会说:可以把Delay的时间减小一点嘛,这样就不会闪了. 话是这么说,但是由于 Android 机器的多元性(其实就是有很多高端机器,也有很多低端机器) , 在这个机子上300ms的延迟算是快,在另外一个机子上300ms算是很慢.</p><p>  我们将Delay时间调整为50ms, 其Trace图如下:</p><p>  <img src="/images/app-lunch/5.webp" alt="第二种写法:Delay 50ms"></p><p>  可以看到,updateText 方法在第一个 performTraversals 之后就执行了,所以也没有 Delay Load 的效果(虽然宽高是正确显示了,因为在第一个 performTraversals 方法中就执行了layout和measure).</p><h2 id="2-3-第三种写法"><a href="#2-3-第三种写法" class="headerlink" title="2.3 第三种写法"></a>2.3 第三种写法</h2><p>经过前两个方法 , 我们就会想, 如果能不使用Delay方法, updateText 方法能在 第二个performTraversals 方法执行完成后(即APP第一帧在屏幕上显示),马上就去执行,那么即起到了 Delay Load的作用,又可以正确显示图片的宽高.<br>第三种写法就是这个效果:</p><ol><li>updateText执行的时机?</li></ol><p>  <img src="/images/app-lunch/6.webp" alt="第三种写法"></p><p>  可以看到这种写法. updateText 在第二个 performTraversals 方法执行完成后马上就执行了, 然后下一个 VSYNC 信号来了之后, TextView就更新了.</p><ol start="2"><li>图片的宽高是否正确显示?<br>当然是正确显示的.如图:<br><img src="/images/app-lunch/7.webp" alt="第三种写法"></li><li>是否有 Delay Load 的效果?<br>从 Trace 图上看, 是有 Delay Load的效果的, 而且可以在应用第一帧显示后马上进行数据 Load , 不用考虑 Delay时间的长短.</li></ol><h1 id="3-一些思考"><a href="#3-一些思考" class="headerlink" title="3. 一些思考"></a>3. 一些思考</h1><p>关于优化的 Delay Load 的实现,从代码层面来看其实是非常简单的.其带来的效果也是很赞的.<br>但是实现之后我们还需要思考一下,为何这么做就可以实现这种功能呢?很显然要回答这个问题,我们需要知道更底层的一些东西.这个还涉及到 Handler&#x2F;Message&#x2F;MessageQueue&#x2F;Looper&#x2F;VSYNC&#x2F;ViewRootImpl等知识. 往大里说应该还涉及到AMS&#x2F;WMS等.由于涉及到的东西比较多,我就不在这一篇里面阐述了, 下一篇文章将会从从原理上讲解一下为何优化的 Delay Load 会起作用.</p><h1 id="4-代码"><a href="#4-代码" class="headerlink" title="4. 代码"></a>4. 代码</h1><p>本文章所所涉及到的代码我放到了Github上：<br><a href="https://github.com/Gracker/DelayLoadSample">https://github.com/Gracker/DelayLoadSample</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 Android 开发中,应用启动速度是一个非常重要的点,应用启动优化也是一个非常重要的过程.对于应用启动优化,其实核心思想就是在启动过程中少做事情,具体实践的时候无非就是下面几种:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;异步加载&lt;/li&gt;
&lt;li&gt;延时加载&lt;/li&gt;
&lt;li&gt;懒加载&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不用一一去解释,做过启动优化的估计都使用过,本篇文章将详细讲解一下一种延时加载的实现以及其原理.&lt;br&gt;其实这种加载的实现是非常简单的,但是其中的原理可能比较复杂,还涉及到Looper&amp;#x2F;Handler&amp;#x2F;MessageQueue&amp;#x2F;VSYNC等.以及其中碰到的一些问题,还会有一些我自己额外的思考.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="DelayLoad" scheme="https://androidperformance.com/tags/DelayLoad/"/>
    
  </entry>
  
  <entry>
    <title>Android hwui 中 RenderThread 工作流程</title>
    <link href="https://androidperformance.com/2015/08/12/AndroidL-hwui-RenderThread-workflow/"/>
    <id>https://androidperformance.com/2015/08/12/AndroidL-hwui-RenderThread-workflow/</id>
    <published>2015-08-12T14:38:01.000Z</published>
    <updated>2026-02-07T05:17:47.850Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本篇文章是自己的一个学习笔记，记录了 Android 5.0 中 hwui 中的 RenderThread 的简单工作流程。由于是学习笔记，所以其中一些细节不会太详细，我只是将大概的流程走一遍，将其工作流标注出来，下次遇到问题的时候就可以知道去哪里查。</p><p>下图是我用 Systrace 抓取的一个应用启动的时候 RenderThread 的第一次 Draw 的 Trace 图，从这里面的顺序来看 RenderThread 的流程。熟悉应用启动流程的话应该知道，只有当第一次 DrawFrame 完成之后，整个应用的界面才会显示在手机上，在这之前，用户看到的是应用的 StartingWindow 的界面。</p><span id="more"></span><p><img src="/images/hwui/renderthread/1.webp" alt="RenderThread Draw first frame"></p><h2 id="从Java层说起"><a href="#从Java层说起" class="headerlink" title="从Java层说起"></a>从Java层说起</h2><p>应用程序的每一帧是从接收到 VSYNC 信号开始进行计算和绘制的,这要从 Choreographer 这个类说起了，不过由于篇幅原因，我们直接看一帧的绘制调用关系链即可：</p><p><img src="/images/hwui/renderthread/2.webp" alt="绘制关系链"></p><p>Choreographer 的 drawFrame 会调用到 ViewRootImpl 的 performTraversals 方法，而 performTraversals 方法最终会调用到performDraw() 方法， performDraw 又会调用到 draw(boolean fullRedrawNeeded) 方法，这个 draw 方法是 ViewRootImpl 的私有方法，和我们熟知的那个draw并不是同一个方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">            <span class="keyword">if</span> (mAttachInfo.mHardwareRenderer != <span class="literal">null</span> &amp;&amp; mAttachInfo.mHardwareRenderer.isEnabled()) &#123;</span><br><span class="line">                mIsAnimating = <span class="literal">false</span>;</span><br><span class="line">                <span class="type">boolean</span> <span class="variable">invalidateRoot</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">if</span> (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) &#123;</span><br><span class="line">                    mHardwareYOffset = yOffset;</span><br><span class="line">                    mHardwareXOffset = xOffset;</span><br><span class="line">                    mAttachInfo.mHardwareRenderer.invalidateRoot();</span><br><span class="line">                &#125;</span><br><span class="line">                mResizeAlpha = resizeAlpha;</span><br><span class="line"></span><br><span class="line">                dirty.setEmpty();</span><br><span class="line"></span><br><span class="line">                mBlockResizeBuffer = <span class="literal">false</span>;</span><br><span class="line">                mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, <span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果是走硬件绘制路线的话，则会走这一条先，之后就会调用 mHardwareRenderer 的 draw 方法,这里的 mHardwareRenderer 指的是 ThreadedRenderer ，其 Draw 函数如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">draw</span><span class="params">(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks)</span> &#123;</span><br><span class="line">    attachInfo.mIgnoreDirtyState = <span class="literal">true</span>;</span><br><span class="line">    <span class="type">long</span> <span class="variable">frameTimeNanos</span> <span class="operator">=</span> mChoreographer.getFrameTimeNanos();</span><br><span class="line">    attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">recordDuration</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (mProfilingEnabled) &#123;</span><br><span class="line">        recordDuration = System.nanoTime();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    updateRootDisplayList(view, callbacks);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mProfilingEnabled) &#123;</span><br><span class="line">        recordDuration = System.nanoTime() - recordDuration;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    attachInfo.mIgnoreDirtyState = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// register animating rendernodes which started animating prior to renderer</span></span><br><span class="line">    <span class="comment">// creation, which is typical for animators started prior to first draw</span></span><br><span class="line">    <span class="keyword">if</span> (attachInfo.mPendingAnimatingRenderNodes != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> attachInfo.mPendingAnimatingRenderNodes.size();</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">            registerAnimatingRenderNode(</span><br><span class="line">                    attachInfo.mPendingAnimatingRenderNodes.get(i));</span><br><span class="line">        &#125;</span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes.clear();</span><br><span class="line">        <span class="comment">// We don&#x27;t need this anymore as subsequent calls to</span></span><br><span class="line">        <span class="comment">// ViewRootImpl#attachRenderNodeAnimator will go directly to us.</span></span><br><span class="line">        attachInfo.mPendingAnimatingRenderNodes = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">syncResult</span> <span class="operator">=</span> nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,</span><br><span class="line">            recordDuration, view.getResources().getDisplayMetrics().density);</span><br><span class="line">    <span class="keyword">if</span> ((syncResult &amp; SYNC_INVALIDATE_REQUIRED) != <span class="number">0</span>) &#123;</span><br><span class="line">        attachInfo.mViewRootImpl.invalidate();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个函数里面的 updateRootDisplayList(view, callbacks) ;即 getDisplayList 操作。接下来就是比较重要的一个操作：</p><figure class="highlight lisp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">int syncResult = nSyncAndDrawFrame(<span class="name">mNativeProxy</span>, frameTimeNanos,</span><br><span class="line">        recordDuration, view.getResources().getDisplayMetrics().density)<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>可以看出这是一个阻塞操作，等Native层完成后，拿到返回值后才会进行下一步的操作。</p><h2 id="Native层"><a href="#Native层" class="headerlink" title="Native层"></a>Native层</h2><p>其Native代码在android_view_ThreadedRenderer.cpp中，对应的实现代码如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">int</span> <span class="title">android_view_ThreadedRenderer_syncAndDrawFrame</span><span class="params">(JNIEnv* env, jobject clazz,</span></span></span><br><span class="line"><span class="params"><span class="function">        jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density)</span> </span>&#123;</span><br><span class="line">    RenderProxy* proxy = <span class="built_in">reinterpret_cast</span>&lt;RenderProxy*&gt;(proxyPtr);</span><br><span class="line">    <span class="keyword">return</span> proxy-&gt;<span class="built_in">syncAndDrawFrame</span>(frameTimeNanos, recordDuration, density);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RenderProxy的路径位于frameworks&#x2F;base&#x2F;libs&#x2F;hwui&#x2F;renderthread&#x2F;RenderProxy.cpp</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">RenderProxy::syncAndDrawFrame</span><span class="params">(<span class="type">nsecs_t</span> frameTimeNanos, <span class="type">nsecs_t</span> recordDurationNanos,</span></span></span><br><span class="line"><span class="params"><span class="function">        <span class="type">float</span> density)</span> </span>&#123;</span><br><span class="line">    mDrawFrameTask.<span class="built_in">setDensity</span>(density);</span><br><span class="line">    <span class="keyword">return</span> mDrawFrameTask.<span class="built_in">drawFrame</span>(frameTimeNanos, recordDurationNanos);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 mDrawFrameTask 是一个 DrawFrameTask 对象，其路径位于frameworks&#x2F;base&#x2F;libs&#x2F;hwui&#x2F;renderthread&#x2F;DrawFrameTask.cpp，其中drawFrame代码：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">int</span> DrawFrameTask::draw<span class="constructor">Frame(<span class="params">nsecs_t</span> <span class="params">frameTimeNanos</span>, <span class="params">nsecs_t</span> <span class="params">recordDurationNanos</span>)</span> &#123;</span><br><span class="line">    mSyncResult = kSync_OK;</span><br><span class="line">    mFrameTimeNanos = frameTimeNanos;</span><br><span class="line">    mRecordDurationNanos = recordDurationNanos;</span><br><span class="line">    post<span class="constructor">AndWait()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Reset the single-frame data</span></span><br><span class="line">    mFrameTimeNanos = <span class="number">0</span>;</span><br><span class="line">    mRecordDurationNanos = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    return mSyncResult;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 postAndWait() 的实现如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">DrawFrameTask::postAndWait</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    AutoMutex _lock(mLock);</span><br><span class="line">    mRenderThread-&gt;<span class="built_in">queue</span>(<span class="keyword">this</span>);</span><br><span class="line">    mSignal.<span class="built_in">wait</span>(mLock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>就是将一个 DrawFrameTask 放入到了 mRenderThread 中,其中 queue 方法实现如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">RenderThread::queue</span><span class="params">(RenderTask* task)</span> </span>&#123;</span><br><span class="line">    AutoMutex _lock(mLock);</span><br><span class="line">    mQueue.<span class="built_in">queue</span>(task);</span><br><span class="line">    <span class="keyword">if</span> (mNextWakeup &amp;&amp; task-&gt;mRunAt &lt; mNextWakeup) &#123;</span><br><span class="line">        mNextWakeup = <span class="number">0</span>;</span><br><span class="line">        mLooper-&gt;<span class="built_in">wake</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 mQueue 是一个 TaskQueue 对象，其</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> TaskQueue::queue(RenderTask* <span class="keyword">task</span>) &#123;</span><br><span class="line">    <span class="comment">// Since the RenderTask itself forms the linked list it is not allowed</span></span><br><span class="line">    <span class="comment">// to have the same task queued twice</span></span><br><span class="line">    LOG_ALWAYS_FATAL_IF(<span class="keyword">task</span>-&gt;mNext || mTail == <span class="keyword">task</span>, <span class="string">&quot;Task is already in the queue!&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (mTail) &#123;</span><br><span class="line">        <span class="comment">// Fast path if we can just append</span></span><br><span class="line">        <span class="keyword">if</span> (mTail-&gt;mRunAt &lt;= <span class="keyword">task</span>-&gt;mRunAt) &#123;</span><br><span class="line">            mTail-&gt;mNext = <span class="keyword">task</span>;</span><br><span class="line">            mTail = <span class="keyword">task</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Need to find the proper insertion point</span></span><br><span class="line">            RenderTask* <span class="keyword">previous</span> = <span class="number">0</span>;</span><br><span class="line">            RenderTask* <span class="keyword">next</span> = mHead;</span><br><span class="line">            <span class="keyword">while</span> (<span class="keyword">next</span> &amp;&amp; <span class="keyword">next</span>-&gt;mRunAt &lt;= <span class="keyword">task</span>-&gt;mRunAt) &#123;</span><br><span class="line">                <span class="keyword">previous</span> = <span class="keyword">next</span>;</span><br><span class="line">                <span class="keyword">next</span> = <span class="keyword">next</span>-&gt;mNext;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!<span class="keyword">previous</span>) &#123;</span><br><span class="line">                <span class="keyword">task</span>-&gt;mNext = mHead;</span><br><span class="line">                mHead = <span class="keyword">task</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">previous</span>-&gt;mNext = <span class="keyword">task</span>;</span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">next</span>) &#123;</span><br><span class="line">                    <span class="keyword">task</span>-&gt;mNext = <span class="keyword">next</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    mTail = <span class="keyword">task</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mTail = mHead = <span class="keyword">task</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接着看 RenderThread 之前的 queue 方法，</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Looper::wake</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">ssize_t</span> nWrite;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        nWrite = <span class="built_in">write</span>(mWakeWritePipeFd, <span class="string">&quot;W&quot;</span>, <span class="number">1</span>);</span><br><span class="line">    &#125; <span class="keyword">while</span> (nWrite == <span class="number">-1</span> &amp;&amp; errno == EINTR);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (nWrite != <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (errno != EAGAIN) &#123;</span><br><span class="line">            <span class="built_in">ALOGW</span>(<span class="string">&quot;Could not write wake signal, errno=%d&quot;</span>, errno);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>wake 函数则更为简单，仅仅向管道的写端写入一个字符“W”，这样管道的读端就会因为有数据可读而从等待状态中醒来。</p><h2 id="HWUI-RenderThread"><a href="#HWUI-RenderThread" class="headerlink" title="HWUI-RenderThread"></a>HWUI-RenderThread</h2><p>接下来会到哪里去，我们首先要熟悉一下RenderThread，RenderThread是继承自Thread的，这个Thread是utils&#x2F;Thread.h,RenderThread的初始化函数</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">RenderThread::<span class="constructor">RenderThread()</span> : <span class="constructor">Thread(<span class="params">true</span>)</span>, Singleton&lt;RenderThread&gt;<span class="literal">()</span></span><br><span class="line">        , m<span class="constructor">NextWakeup(LLONG_MAX)</span></span><br><span class="line">        , m<span class="constructor">DisplayEventReceiver(0)</span></span><br><span class="line">        , m<span class="constructor">VsyncRequested(<span class="params">false</span>)</span></span><br><span class="line">        , m<span class="constructor">FrameCallbackTaskPending(<span class="params">false</span>)</span></span><br><span class="line">        , m<span class="constructor">FrameCallbackTask(0)</span></span><br><span class="line">        , m<span class="constructor">RenderState(NULL)</span></span><br><span class="line">        , m<span class="constructor">EglManager(NULL)</span> &#123;</span><br><span class="line">    mFrameCallbackTask = <span class="keyword">new</span> <span class="constructor">DispatchFrameCallbacks(<span class="params">this</span>)</span>;</span><br><span class="line">    mLooper = <span class="keyword">new</span> <span class="constructor">Looper(<span class="params">false</span>)</span>;</span><br><span class="line">    run(<span class="string">&quot;RenderThread&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其 run 方法在 Thread 中有说明：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Start the thread in threadLoop() which needs to be implemented.</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">status_t</span>    <span class="title">run</span><span class="params">(    <span class="type">const</span> <span class="type">char</span>* name = <span class="number">0</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">                            <span class="type">int32_t</span> priority = PRIORITY_DEFAULT,</span></span></span><br><span class="line"><span class="params"><span class="function">                            <span class="type">size_t</span> stack = <span class="number">0</span>)</span></span>;</span><br></pre></td></tr></table></figure><p>即启动 threadLoop 函数，我们来看 RenderThread 的 threadLoop 函数，这个函数比较重要：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">bool</span> <span class="title">RenderThread::threadLoop</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(HAVE_PTHREADS)</span></span><br><span class="line">    <span class="built_in">setpriority</span>(PRIO_PROCESS, <span class="number">0</span>, PRIORITY_DISPLAY);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">    <span class="built_in">initThreadLocals</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> timeoutMillis = <span class="number">-1</span>;</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">int</span> result = mLooper-&gt;<span class="built_in">pollOnce</span>(timeoutMillis);</span><br><span class="line">        <span class="built_in">LOG_ALWAYS_FATAL_IF</span>(result == Looper::POLL_ERROR,</span><br><span class="line">                <span class="string">&quot;RenderThread Looper POLL_ERROR!&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">nsecs_t</span> nextWakeup;</span><br><span class="line">        <span class="comment">// Process our queue, if we have anything</span></span><br><span class="line">        <span class="keyword">while</span> (RenderTask* task = <span class="built_in">nextTask</span>(&amp;nextWakeup)) &#123;</span><br><span class="line">            task-&gt;<span class="built_in">run</span>();</span><br><span class="line">            <span class="comment">// task may have deleted itself, do not reference it again</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (nextWakeup == LLONG_MAX) &#123;</span><br><span class="line">            timeoutMillis = <span class="number">-1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">nsecs_t</span> timeoutNanos = nextWakeup - <span class="built_in">systemTime</span>(SYSTEM_TIME_MONOTONIC);</span><br><span class="line">            timeoutMillis = <span class="built_in">nanoseconds_to_milliseconds</span>(timeoutNanos);</span><br><span class="line">            <span class="keyword">if</span> (timeoutMillis &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                timeoutMillis = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (mPendingRegistrationFrameCallbacks.<span class="built_in">size</span>() &amp;&amp; !mFrameCallbackTaskPending) &#123;</span><br><span class="line">            <span class="built_in">drainDisplayEventQueue</span>(<span class="literal">true</span>);</span><br><span class="line">            mFrameCallbacks.<span class="built_in">insert</span>(</span><br><span class="line">                    mPendingRegistrationFrameCallbacks.<span class="built_in">begin</span>(), mPendingRegistrationFrameCallbacks.<span class="built_in">end</span>());</span><br><span class="line">            mPendingRegistrationFrameCallbacks.<span class="built_in">clear</span>();</span><br><span class="line">            <span class="built_in">requestVsync</span>();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，一个 for 循环是一个无限循环，而其中 pollOnce 是一个阻塞函数，直到我们上面调用了 mLooper-&gt;wake() 之后，会继续往下走，走到 while 循环中：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (RenderTask* task = <span class="built_in">nextTask</span>(&amp;nextWakeup)) &#123;</span><br><span class="line">            task-&gt;<span class="built_in">run</span>();</span><br><span class="line">            <span class="comment">// task may have deleted itself, do not reference it again</span></span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure><p>会将 RenderTask 取出来执行其 run 方法，经过前面的流程我们知道这个 RenderTask 是一个 DrawFrameTask ，其run方法如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">DrawFrameTask::run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">ATRACE_NAME</span>(<span class="string">&quot;DrawFrame&quot;</span>);</span><br><span class="line"></span><br><span class="line">    mContext-&gt;<span class="built_in">profiler</span>().<span class="built_in">setDensity</span>(mDensity);</span><br><span class="line">    mContext-&gt;<span class="built_in">profiler</span>().<span class="built_in">startFrame</span>(mRecordDurationNanos);</span><br><span class="line"></span><br><span class="line">    <span class="type">bool</span> canUnblockUiThread;</span><br><span class="line">    <span class="type">bool</span> canDrawThisFrame;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="function">TreeInfo <span class="title">info</span><span class="params">(TreeInfo::MODE_FULL, mRenderThread-&gt;renderState())</span></span>;</span><br><span class="line">        canUnblockUiThread = <span class="built_in">syncFrameState</span>(info);</span><br><span class="line">        canDrawThisFrame = info.out.canDrawThisFrame;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Grab a copy of everything we need</span></span><br><span class="line">    CanvasContext* context = mContext;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// From this point on anything in &quot;this&quot; is *UNSAFE TO ACCESS*</span></span><br><span class="line">    <span class="keyword">if</span> (canUnblockUiThread) &#123;</span><br><span class="line">        <span class="built_in">unblockUiThread</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">CC_LIKELY</span>(canDrawThisFrame)) &#123;</span><br><span class="line">        context-&gt;<span class="built_in">draw</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!canUnblockUiThread) &#123;</span><br><span class="line">        <span class="built_in">unblockUiThread</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="RenderThread-DrawFrame"><a href="#RenderThread-DrawFrame" class="headerlink" title="RenderThread.DrawFrame"></a>RenderThread.DrawFrame</h2><p>上面说到了 DrawFrameTask 的 run 方法，这里 run 方法中的执行的方法即我们在最前面那张图中所示的部分（即文章最前面那张图），下面的流程就是那张图中的函数调用，我们结合代码和图，一部分一部分来走整个 DrawFrame 的流程：</p><h3 id="1-syncFrameState"><a href="#1-syncFrameState" class="headerlink" title="1. syncFrameState"></a>1. syncFrameState</h3><p>第一个比较重要的函数是 syncFrameState ，从函数名就可以知道， syncFrameState 的作用就是同步 frame 信息，将 Java 层维护的 frame 信息同步到 RenderThread中。</p><blockquote><p>Main Thread 和Render Thread 都各自维护了一份应用程序窗口视图信息。各自维护了一份应用程序窗口视图信息的目的，就是为了可以互不干扰，进而实现最大程度的并行。其中，Render Thread维护的应用程序窗口视图信息是来自于 Main Thread 的。因此，当Main Thread 维护的应用程序窗口信息发生了变化时，就需要同步到 Render Thread 去。</p></blockquote><p>所以查看代码就可以知道有两个 RenderNode，一个在 hwui 中，一个在 View 中。简单来说，同步信息就是将 Java 层的 RenderNode 中的信息同步到 hwui 中的 RenderNode 中。 注意syncFrameState的返回值赋给了 canUnblockUiThread ，从名字可以看出这个 canUnblockUiThread 的作用是判断是否唤醒 Main Thread ，也就是说如果返回为 true 的话，会提前唤醒主线程来执行其他的事情，而不用等到 draw 完成后再去唤醒 Main Thread。 这也是 Android 5.0 和 Android 4.x 最大的区别了。</p><p><img src="/images/hwui/renderthread/3.webp" alt="syncFrameState"></p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">bool</span> DrawFrameTask::sync<span class="constructor">FrameState(TreeInfo&amp; <span class="params">info</span>)</span> &#123;</span><br><span class="line">    mRenderThread-&gt;time<span class="constructor">Lord()</span>.vsync<span class="constructor">Received(<span class="params">mFrameTimeNanos</span>)</span>;</span><br><span class="line">    mContext-&gt;make<span class="constructor">Current()</span>;</span><br><span class="line">    Caches::get<span class="constructor">Instance()</span>.textureCache.reset<span class="constructor">MarkInUse()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (size_t i = <span class="number">0</span>; i &lt; mLayers.size<span class="literal">()</span>; i++) &#123;</span><br><span class="line">        mContext-&gt;process<span class="constructor">LayerUpdate(<span class="params">mLayers</span>[<span class="params">i</span>].<span class="params">get</span>()</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    mLayers.clear<span class="literal">()</span>;</span><br><span class="line">    mContext-&gt;prepare<span class="constructor">Tree(<span class="params">info</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (info.out.hasAnimations) &#123;</span><br><span class="line">        <span class="keyword">if</span> (info.out.requiresUiRedraw) &#123;</span><br><span class="line">            mSyncResult <span class="pattern-match">|= k<span class="constructor">Sync_UIRedrawRequired</span>;</span></span><br><span class="line"><span class="pattern-match">        &#125;</span></span><br><span class="line"><span class="pattern-match">    &#125;</span></span><br><span class="line"><span class="pattern-match">    <span class="operator">/</span><span class="operator">/</span> <span class="constructor">If</span> prepare<span class="constructor">Textures</span> is <span class="literal">false</span>, we ran out <span class="keyword">of</span> texture cache space</span></span><br><span class="line"><span class="pattern-match">    return info.prepare<span class="constructor">Textures</span>;</span></span><br><span class="line"><span class="pattern-match">&#125;</span></span><br></pre></td></tr></table></figure><p>首先是makeCurrent，这里的mContext是一个CanvasContext对象，其makeCurrent实现如下：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title class_">CanvasContext</span>::<span class="title function_">makeCurrent</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// In the meantime this matches the behavior of GLRenderer, so it is not a regression</span></span><br><span class="line">    mHaveNewSurface |= mEglManager.<span class="title function_">makeCurrent</span>(mEglSurface);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>mEglManager是一个EglManager对象，其实现为：</p><figure class="highlight maxima"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">bool EglManager::makeCurrent(EGLSurface <span class="built_in">surface</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (isCurrent(<span class="built_in">surface</span>)) <span class="built_in">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">surface</span> == EGL_NO_SURFACE) &#123;</span><br><span class="line">        // If we are setting EGL_NO_SURFACE we don&#x27;t care about any of the <span class="built_in">potential</span></span><br><span class="line">        // <span class="built_in">return</span> <span class="built_in">errors</span>, which would only happen <span class="keyword">if</span> mEglDisplay had already been</span><br><span class="line">        // destroyed <span class="keyword">in</span> which case the current <span class="built_in">context</span> <span class="built_in">is</span> already NO_CONTEXT</span><br><span class="line">        TIME_LOG(<span class="string">&quot;eglMakeCurrent&quot;</span>, eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        EGLBoolean success;</span><br><span class="line">        TIME_LOG(<span class="string">&quot;eglMakeCurrent&quot;</span>, success = eglMakeCurrent(mEglDisplay, <span class="built_in">surface</span>, <span class="built_in">surface</span>, mEglContext));</span><br><span class="line">        <span class="keyword">if</span> (!success) &#123;</span><br><span class="line">            LOG_ALWAYS_FATAL(<span class="string">&quot;Failed to make current on surface %p, error=%s&quot;</span>,</span><br><span class="line">                (void*)<span class="built_in">surface</span>, egl_error_str());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    mCurrentSurface = <span class="built_in">surface</span>;</span><br><span class="line">    <span class="built_in">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里会判断mCurrentSurface &#x3D;&#x3D; surface，如果成立，则不用再初始化操作，如果是另外一个surface。，则会执行eglMakeCurrent，来重新创建上下文。</p><p>makeCurrent之后，会调用mContext-&gt;prepareTree(info)，其实现如下：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">CanvasContext::prepareTree</span><span class="params">(TreeInfo&amp; info)</span> </span>&#123;</span><br><span class="line">    mRenderThread.<span class="built_in">removeFrameCallback</span>(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">    info.damageAccumulator = &amp;mDamageAccumulator;</span><br><span class="line">    info.renderer = mCanvas;</span><br><span class="line">    <span class="keyword">if</span> (mPrefetechedLayers.<span class="built_in">size</span>() &amp;&amp; info.mode == TreeInfo::MODE_FULL) &#123;</span><br><span class="line">        info.canvasContext = <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    mAnimationContext-&gt;<span class="built_in">startFrame</span>(info.mode);</span><br><span class="line">    mRootRenderNode-&gt;<span class="built_in">prepareTree</span>(info);</span><br><span class="line">    mAnimationContext-&gt;<span class="built_in">runRemainingAnimations</span>(info);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (info.canvasContext) &#123;</span><br><span class="line">        <span class="built_in">freePrefetechedLayers</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> runningBehind = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// <span class="doctag">TODO:</span> This query is moderately expensive, investigate adding some sort</span></span><br><span class="line">    <span class="comment">// of fast-path based off when we last called eglSwapBuffers() as well as</span></span><br><span class="line">    <span class="comment">// last vsync time. Or something.</span></span><br><span class="line">    <span class="built_in">TIME_LOG</span>(<span class="string">&quot;nativeWindowQuery&quot;</span>, mNativeWindow-&gt;<span class="built_in">query</span>(mNativeWindow.<span class="built_in">get</span>(),</span><br><span class="line">            NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &amp;runningBehind));</span><br><span class="line">    info.out.canDrawThisFrame = !runningBehind;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (info.out.hasAnimations || !info.out.canDrawThisFrame) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!info.out.requiresUiRedraw) &#123;</span><br><span class="line">            <span class="comment">// If animationsNeedsRedraw is set don&#x27;t bother posting for an RT anim</span></span><br><span class="line">            <span class="comment">// as we will just end up fighting the UI thread.</span></span><br><span class="line">            mRenderThread.<span class="built_in">postFrameCallback</span>(<span class="keyword">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中 mRootRenderNode-&gt;prepareTree(info) 又是最重要的。回到Java层，我们知道 ThreadedRenderer 在初始化时，初始化了一个指针</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">rootNodePtr</span> <span class="operator">=</span> nCreateRootRenderNode();</span><br></pre></td></tr></table></figure><p>这个RootRenderNode也就是一个根Node，</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">mRootNode</span> <span class="operator">=</span> RenderNode.adopt(rootNodePtr)<span class="comment">;</span></span><br></pre></td></tr></table></figure><p>然后会创建一个 mNativeProxy 指针，在 Native 层初始化一个 RenderProxy 对象，将 rootNodePtr 传给 RenderProxy 对象，这样在 RenderProxy 我们就可以得到这个对象的指针了。其中 CanvasContext 也是在 RenderProxy 对象初始化的时候被初始化的，初始化的时候将 rootNodePtr 传给了 CanvasContext 对象。</p><p>我们之前提到 ThreadedRenderer 的 draw 方法中首先会调用updateRootDisplayList，即我们熟悉的 getDisplayList 。这个方法中，其实也分为两个步骤，第一个步骤是 updateViewTreeDisplayList，第二个步骤是将根 Node 加入到 DrawOp 中：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">canvas.insert<span class="constructor">ReorderBarrier()</span>;</span><br><span class="line">canvas.draw<span class="constructor">RenderNode(<span class="params">view</span>.<span class="params">getDisplayList</span>()</span>);</span><br><span class="line">canvas.insert<span class="constructor">InorderBarrier()</span>;</span><br></pre></td></tr></table></figure><p>其最终实现在</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">status_t DisplayListRenderer::draw<span class="constructor">RenderNode(RenderNode<span class="operator">*</span> <span class="params">renderNode</span>, Rect&amp; <span class="params">dirty</span>, <span class="params">int32_t</span> <span class="params">flags</span>)</span> &#123;</span><br><span class="line">    <span class="constructor">LOG_ALWAYS_FATAL_IF(!<span class="params">renderNode</span>, <span class="string">&quot;missing rendernode&quot;</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// dirty is an out parameter and should not be recorded,</span></span><br><span class="line">    <span class="comment">// it matters only when replaying the display list</span></span><br><span class="line">    DrawRenderNodeOp* op = <span class="keyword">new</span> (alloc<span class="literal">()</span>) <span class="constructor">DrawRenderNodeOp(<span class="params">renderNode</span>, <span class="params">flags</span>, <span class="operator">*</span><span class="params">currentTransform</span>()</span>);</span><br><span class="line">    add<span class="constructor">RenderNodeOp(<span class="params">op</span>)</span>;</span><br><span class="line"></span><br><span class="line">    return DrawGlInfo::kStatusDone;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再回到我们之前的 CanvasContext.prepareTree 中提到的 mRootRenderNode-&gt;prepareTree(info)，这时候这里的 mRootRenderNode 就是 CanvasContext 初始化是传进来的。</p><p>其实现在 RenderNode.cpp 中：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">void RenderNode::prepare<span class="constructor">Tree(TreeInfo&amp; <span class="params">info</span>)</span> &#123;</span><br><span class="line">    prepare<span class="constructor">TreeImpl(<span class="params">info</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void RenderNode::prepare<span class="constructor">TreeImpl(TreeInfo&amp; <span class="params">info</span>)</span> &#123;</span><br><span class="line">    <span class="constructor">TT_START_MARK(<span class="params">getName</span>()</span>);</span><br><span class="line">    info.damageAccumulator-&gt;push<span class="constructor">Transform(<span class="params">this</span>)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (info.mode<span class="operator"> == </span>TreeInfo::MODE_FULL) &#123;</span><br><span class="line">        push<span class="constructor">StagingPropertiesChanges(<span class="params">info</span>)</span>; <span class="comment">//同步当前正在处理的Render Node的Property</span></span><br><span class="line">    &#125;</span><br><span class="line">    uint32_t animatorDirtyMask = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="constructor">CC_LIKELY(<span class="params">info</span>.<span class="params">runAnimations</span>)</span>) &#123;</span><br><span class="line">        animatorDirtyMask = mAnimatorManager.animate(info);<span class="comment">//执行动画相关的操作</span></span><br><span class="line">    &#125;</span><br><span class="line">    prepare<span class="constructor">Layer(<span class="params">info</span>, <span class="params">animatorDirtyMask</span>)</span>;</span><br><span class="line">    <span class="keyword">if</span> (info.mode<span class="operator"> == </span>TreeInfo::MODE_FULL) &#123;</span><br><span class="line">        push<span class="constructor">StagingDisplayListChanges(<span class="params">info</span>)</span>;  <span class="comment">//同步当前正在处理的Render Node的Display List</span></span><br><span class="line">    &#125;</span><br><span class="line">    prepare<span class="constructor">SubTree(<span class="params">info</span>, <span class="params">mDisplayListData</span>)</span>; <span class="comment">//同步当前正在处理的Render Node的Display List引用的Bitmap，以及当前正在处理的Render Node的子Render Node的Display List等信息</span></span><br><span class="line">    push<span class="constructor">LayerUpdate(<span class="params">info</span>)</span>; <span class="comment">//检查当前正在处理的Render Node是否设置了Layer。如果设置了的话，就对这些Layer进行处理</span></span><br><span class="line"></span><br><span class="line">    info.damageAccumulator-&gt;pop<span class="constructor">Transform()</span>;</span><br><span class="line">    <span class="constructor">TT_END_MARK()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里所涉及到的进一步的具体操作大家可以自行去看代码。</p><h2 id="2-draw"><a href="#2-draw" class="headerlink" title="2. draw"></a>2. draw</h2><p><img src="/images/hwui/renderthread/4.webp" alt="Draw"></p><p>执行完syncFrameState之后，接下来就是执行draw</p><figure class="highlight erlang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="title">if</span> <span class="params">(CC_LIKELY(canDrawThisFrame)</span>) &#123;</span></span><br><span class="line"><span class="function">    <span class="title">context</span>-&gt;</span>draw();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>CanvasContext的draw函数是一个核心函数，其位置在 frameworks&#x2F;base&#x2F;libs&#x2F;hwui&#x2F;OpenGLRenderer.cpp ，其实现如下：</p><figure class="highlight xl"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">void CanvasContext::draw() &#123;</span><br><span class="line">    profiler().markPlaybackStart();</span><br><span class="line"></span><br><span class="line">    SkRect dirty;</span><br><span class="line">    mDamageAccumulator.finish(&amp;dirty);</span><br><span class="line"></span><br><span class="line">    ......</span><br><span class="line"></span><br><span class="line">    status_t status;</span><br><span class="line">    <span class="keyword">if</span> (!dirty.isEmpty()) &#123;</span><br><span class="line">        <span class="function"><span class="title">status</span> = mCanvas-&gt;</span>prepareDirty(dirty.fLeft, dirty.fTop,</span><br><span class="line">                dirty.fRight, dirty.fBottom, mOpaque);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="function"><span class="title">status</span> = mCanvas-&gt;</span>prepare(mOpaque);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Rect outBounds;</span><br><span class="line">    <span class="function"><span class="title">status</span> |= mCanvas-&gt;</span>drawRenderNode(mRootRenderNode.get(), outBounds);</span><br><span class="line"></span><br><span class="line">    profiler().draw(mCanvas);</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="title">mCanvas</span>-&gt;</span>finish();</span><br><span class="line"></span><br><span class="line">    profiler().markPlaybackEnd();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (status &amp; DrawGlInfo::kStatusDrew) &#123;</span><br><span class="line">        swapBuffers();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    profiler().finishFrame();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/// M: enable to get overdraw count</span></span><br><span class="line">    <span class="keyword">if</span> (CC_UNLIKELY(g_HWUI_debug_overdraw)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mDebugOverdrawLayer) &#123;</span><br><span class="line">            mDebugOverdrawLayer = LayerRenderer::createRenderLayer(mRenderThread.renderState(),</span><br><span class="line">                <span class="function"><span class="title">mCanvas</span>-&gt;</span><span class="function"><span class="title">getWidth</span>(), mCanvas-&gt;</span>getHeight());</span><br><span class="line">        &#125; <span class="function"><span class="title">else</span> <span class="keyword">if</span> (mDebugOverdrawLayer-&gt;</span><span class="function"><span class="title">layer</span>.getWidth() != mCanvas-&gt;</span>getWidth() ||</span><br><span class="line">                   <span class="function"><span class="title">mDebugOverdrawLayer</span>-&gt;</span><span class="function"><span class="title">layer</span>.getHeight() != mCanvas-&gt;</span>getHeight()) &#123;</span><br><span class="line">            <span class="function"><span class="title">if</span> (!LayerRenderer::resizeLayer(mDebugOverdrawLayer, mCanvas-&gt;</span><span class="function"><span class="title">getWidth</span>(), mCanvas-&gt;</span>getHeight())) &#123;</span><br><span class="line">                LayerRenderer::destroyLayer(mDebugOverdrawLayer);</span><br><span class="line">                mDebugOverdrawLayer = NULL;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-1-eglBeginFrame"><a href="#2-1-eglBeginFrame" class="headerlink" title="2.1 eglBeginFrame"></a>2.1 eglBeginFrame</h4><p>首先来看eglBeginFrame的实现</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void EglManager::<span class="keyword">begin</span><span class="constructor">Frame(EGLSurface <span class="params">surface</span>, EGLint<span class="operator">*</span> <span class="params">width</span>, EGLint<span class="operator">*</span> <span class="params">height</span>)</span> &#123;</span><br><span class="line">    make<span class="constructor">Current(<span class="params">surface</span>)</span>;</span><br><span class="line">    <span class="keyword">if</span> (width) &#123;</span><br><span class="line">        egl<span class="constructor">QuerySurface(<span class="params">mEglDisplay</span>, <span class="params">surface</span>, EGL_WIDTH, <span class="params">width</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (height) &#123;</span><br><span class="line">        egl<span class="constructor">QuerySurface(<span class="params">mEglDisplay</span>, <span class="params">surface</span>, EGL_HEIGHT, <span class="params">height</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    egl<span class="constructor">BeginFrame(<span class="params">mEglDisplay</span>, <span class="params">surface</span>)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>makeCurrent是用来管理上下文，eglBeginFrame主要是校验参数的合法性。</p><h4 id="2-2-prepareDirty"><a href="#2-2-prepareDirty" class="headerlink" title="2.2 prepareDirty"></a>2.2 prepareDirty</h4><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">status_t status;</span><br><span class="line">if (!<span class="keyword">dirty.isEmpty()) </span>&#123;</span><br><span class="line">    status = mCanvas-&gt;prepareDirty(<span class="keyword">dirty.fLeft, </span><span class="keyword">dirty.fTop,</span></span><br><span class="line"><span class="keyword"></span>            <span class="keyword">dirty.fRight, </span><span class="keyword">dirty.fBottom, </span>mOpaque);</span><br><span class="line">&#125; else &#123;</span><br><span class="line">    status = mCanvas-&gt;prepare(mOpaque);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这里的mCanvas是一个OpenGLRenderer对象，其prepareDirty实现</p><figure class="highlight zephir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//<span class="doctag">TODO:</span>增加函数功能描述</span></span><br><span class="line">status_t OpenGLRenderer::prepareDirty(<span class="keyword">float</span> left, <span class="keyword">float</span> top,</span><br><span class="line">        <span class="keyword">float</span> right, <span class="keyword">float</span> bottom, <span class="keyword">bool</span> opaque) &#123;</span><br><span class="line">    setupFrameState(left, top, right, bottom, opaque);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Layer renderers will start the frame immediately</span></span><br><span class="line">    <span class="comment">// The framebuffer renderer will first defer the display list</span></span><br><span class="line">    <span class="comment">// for each layer and wait until the first drawing command</span></span><br><span class="line">    <span class="comment">// to start the frame</span></span><br><span class="line">    <span class="keyword">if</span> (currentSnapshot()-&gt;fbo == <span class="number">0</span>) &#123;</span><br><span class="line">        syncState();</span><br><span class="line">        updateLayers();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> startFrame();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> DrawGlInfo::kStatusDone;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-3-drawRenderNode"><a href="#2-3-drawRenderNode" class="headerlink" title="2.3 drawRenderNode"></a>2.3 drawRenderNode</h4><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Rect outBounds;</span><br><span class="line">status <span class="pattern-match">|= m<span class="constructor">Canvas</span>-&gt;draw<span class="constructor">RenderNode(<span class="params">mRootRenderNode</span>.<span class="params">get</span>()</span>, out<span class="constructor">Bounds</span>);</span></span><br></pre></td></tr></table></figure><p>接下来就是调用OpenGLRenderer的drawRenderNode方法进行绘制</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">status_t OpenGLRenderer::draw<span class="constructor">RenderNode(RenderNode<span class="operator">*</span> <span class="params">renderNode</span>, Rect&amp; <span class="params">dirty</span>, <span class="params">int32_t</span> <span class="params">replayFlags</span>)</span> &#123;</span><br><span class="line">    status_t status;</span><br><span class="line">    <span class="comment">// All the usual checks and setup operations (quickReject, setupDraw, etc.)</span></span><br><span class="line">    <span class="comment">// will be performed by the display list itself</span></span><br><span class="line">    <span class="keyword">if</span> (renderNode<span class="operator"> &amp;&amp; </span>renderNode-&gt;is<span class="constructor">Renderable()</span>) &#123;</span><br><span class="line">        <span class="comment">// compute 3d ordering</span></span><br><span class="line">        renderNode-&gt;compute<span class="constructor">Ordering()</span>;</span><br><span class="line">        <span class="keyword">if</span> (<span class="constructor">CC_UNLIKELY(<span class="params">mCaches</span>.<span class="params">drawDeferDisabled</span>)</span>) &#123; <span class="comment">//判断是否不重排序</span></span><br><span class="line">            status = start<span class="constructor">Frame()</span>;</span><br><span class="line">            ReplayStateStruct replay<span class="constructor">Struct(<span class="operator">*</span><span class="params">this</span>, <span class="params">dirty</span>, <span class="params">replayFlags</span>)</span>;</span><br><span class="line">            renderNode-&gt;replay(replayStruct, <span class="number">0</span>);</span><br><span class="line">            return status <span class="pattern-match">| replay<span class="constructor">Struct</span>.m<span class="constructor">DrawGlStatus</span>;</span></span><br><span class="line"><span class="pattern-match">        &#125;</span></span><br><span class="line"><span class="pattern-match"></span></span><br><span class="line"><span class="pattern-match">        <span class="operator">/</span><span class="operator">/</span> 需要重新排序</span></span><br><span class="line"><span class="pattern-match">        <span class="built_in">bool</span> avoid<span class="constructor">Overdraw</span> = !m<span class="constructor">Caches</span>.debug<span class="constructor">Overdraw</span> <span class="operator">&amp;&amp;</span> !m<span class="constructor">CountOverdraw</span>; <span class="operator">/</span><span class="operator">/</span> shh, don&#x27;t tell devs!</span></span><br><span class="line"><span class="pattern-match">        <span class="constructor">DeferredDisplayList</span> deferred<span class="constructor">List(<span class="operator">*</span><span class="params">currentClipRect</span>()</span>, avoid<span class="constructor">Overdraw</span>);</span></span><br><span class="line"><span class="pattern-match">        <span class="constructor">DeferStateStruct</span> defer<span class="constructor">Struct(<span class="params">deferredList</span>, <span class="operator">*</span><span class="params">this</span>, <span class="params">replayFlags</span>)</span>;</span></span><br><span class="line"><span class="pattern-match">        render<span class="constructor">Node</span>-&gt;defer(defer<span class="constructor">Struct</span>, 0); <span class="operator">/</span><span class="operator">/</span>递归进行重排操作</span></span><br><span class="line"><span class="pattern-match"></span></span><br><span class="line"><span class="pattern-match">        flush<span class="constructor">Layers()</span>; <span class="operator">/</span><span class="operator">/</span> 首先执行设置了 <span class="constructor">Layer</span> 的子 <span class="constructor">Render</span> <span class="constructor">Node</span> 的绘制命令，以便得到一个对应的<span class="constructor">FBO</span></span></span><br><span class="line"><span class="pattern-match">        status = start<span class="constructor">Frame()</span>; <span class="operator">/</span><span class="operator">/</span>执行一些诸如清理颜色绘冲区等基本操作</span></span><br><span class="line"><span class="pattern-match">        status = deferred<span class="constructor">List</span>.flush(<span class="operator">*</span>this, dirty) | status;</span></span><br><span class="line"><span class="pattern-match">        return status;</span></span><br><span class="line"><span class="pattern-match">    &#125;</span></span><br><span class="line"><span class="pattern-match"></span></span><br><span class="line"><span class="pattern-match">    <span class="operator">/</span><span class="operator">/</span> <span class="constructor">Even</span> <span class="keyword">if</span> there is no drawing command(<span class="constructor">Ex</span>: invisible),</span></span><br><span class="line"><span class="pattern-match">    <span class="operator">/</span><span class="operator">/</span> it still needs start<span class="constructor">Frame</span> <span class="keyword">to</span> clear buffer <span class="keyword">and</span> start tiling.</span></span><br><span class="line"><span class="pattern-match">    return start<span class="constructor">Frame()</span>;</span></span><br><span class="line"><span class="pattern-match">&#125;</span></span><br><span class="line"><span class="pattern-match"></span></span><br></pre></td></tr></table></figure><p>这里的 renderNode 是一个 Root Render Node，</p><p>可以看到，到了这里虽然只是开始，但是其实已经结束了，这个函数里面最重要的几步:</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">renderNode-&gt;defer(deferStruct, <span class="number">0</span>); <span class="regexp">//</span>进行重排序</span><br><span class="line"></span><br><span class="line">flushLayers(); 首先执行设置了 Layer 的子 Render Node 的绘制命令，以便得到一个对应的FBO</span><br><span class="line"></span><br><span class="line">status = deferredList.flush(*this, dirty) | status;   <span class="regexp">//</span>对deferredList中的绘制命令进行真正的绘制操作</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这几个是渲染部分真正的核心部分，其中的代码细节需要自己去研究。老罗在这部分讲的很细，有空可以去看看他的文章<a href="http://blog.csdn.net/Luoshengyang/article/details/46281499"><a href="http://blog.csdn.net/luoshengyang/article/details/46281499">Android应用程序UI硬件加速渲染的Display List渲染过程分析</a></a>.</p><h4 id="2-4-swapBuffers"><a href="#2-4-swapBuffers" class="headerlink" title="2.4 swapBuffers"></a>2.4 swapBuffers</h4><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">if (status &amp; DrawGlInfo::kStatusDrew) &#123;</span><br><span class="line">    <span class="built_in">swapBuffers</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其核心就是调用EGL的 eglSwapBuffers(mEglDisplay, surface), duration)函数。</p><h4 id="2-5-FinishFrame"><a href="#2-5-FinishFrame" class="headerlink" title="2.5  FinishFrame"></a>2.5  FinishFrame</h4><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">profiler</span>()<span class="selector-class">.finishFrame</span>();</span><br></pre></td></tr></table></figure><p>主要是记录时间信息。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>鉴于我比较懒，而且总结能力不如老罗，就直接把他的总结贴过来了。<br>RenderThread的总的流程如下：</p><blockquote><ol><li>将Main Thread维护的Display List同步到Render Thread维护的Display List去。这个同步过程由Render Thread执行，但是Main Thread会被阻塞住。</li></ol></blockquote><blockquote><ol start="2"><li>如果能够完全地将Main Thread维护的Display List同步到Render Thread维护的Display List去，那么Main Thread就会被唤醒，此后Main Thread和Render Thread就互不干扰，各自操作各自内部维护的Display List；否则的话，Main Thread就会继续阻塞，直到Render Thread完成应用程序窗口当前帧的渲染为止。</li></ol></blockquote><blockquote><ol start="3"><li>Render Thread在渲染应用程序窗口的Root Render Node的Display List之前，首先将那些设置了Layer的子Render Node的Display List渲染在各自的一个FBO上，接下来再一起将这些FBO以及那些没有设置Layer的子Render Node的Display List一起渲染在Frame Buffer之上，也就是渲染在从Surface Flinger请求回来的一个图形缓冲区上。这个图形缓冲区最终会被提交给Surface Flinger合并以及显示在屏幕上。</li></ol></blockquote><blockquote><p>第2步能够完全将Main Thread维护的Display List同步到Render Thread维护的Display List去很关键，它使得Main Thread和Render Thread可以并行执行，这意味着Render Thread在渲染应用程序窗口当前帧的Display List的同时，Main Thread可以去准备应用程序窗口下一帧的Display List，这样就使得应用程序窗口的UI更流畅。</p></blockquote><p>注意最后一段，在 Android 4.x 时代，没有RenderThread的时代，只有 Main Thread ，也就是说 必须要等到 Draw 完成后，才会去准备下一帧的数据，如下图：</p><p><img src="/images/hwui/renderthread/5.webp" alt="Paste_Image.png"></p><p>Android5.0 之后，如老罗所说，有两种情况，</p><p><img src="/images/hwui/renderthread/6.webp" alt="Main Thread 和 Render Thread"></p><p><img src="/images/hwui/renderthread/7.webp" alt="Render Thread 提前唤醒了 Main Thread"></p><p>可以看到第二张图中，Render Thread 并没有绘制完成，但是由于其提前唤醒了 Main Thread ，所以 Main Thread 在下一个Vsync信号到来的时候，响应了Vsync事件，开始准备下一帧。<br>此时虽然由于第一帧绘制时间过长，导致掉了一帧，但是第二帧没有收到任何影响。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;本篇文章是自己的一个学习笔记，记录了 Android 5.0 中 hwui 中的 RenderThread 的简单工作流程。由于是学习笔记，所以其中一些细节不会太详细，我只是将大概的流程走一遍，将其工作流标注出来，下次遇到问题的时候就可以知道去哪里查。&lt;/p&gt;
&lt;p&gt;下图是我用 Systrace 抓取的一个应用启动的时候 RenderThread 的第一次 Draw 的 Trace 图，从这里面的顺序来看 RenderThread 的流程。熟悉应用启动流程的话应该知道，只有当第一次 DrawFrame 完成之后，整个应用的界面才会显示在手机上，在这之前，用户看到的是应用的 StartingWindow 的界面。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Java7 HashMap 源码分析</title>
    <link href="https://androidperformance.com/2015/08/05/HashMap/"/>
    <id>https://androidperformance.com/2015/08/05/HashMap/</id>
    <published>2015-08-05T12:27:13.000Z</published>
    <updated>2026-02-07T05:17:47.853Z</updated>
    
    <content type="html"><![CDATA[<p>链表和数组可以按照人们意愿排列元素的次序，但是，如果想要查看某个指定的元素，却又忘记了它的位置，就需要访问所有的元素，直到找到为止。如果集合中元素很多，将会消耗很多时间。有一种数据结构可以快速查找所需要查找的对象，这个就是哈希表（hash table）.</p><p>HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作，并允许使用 null 值和 null 键。（除了非同步和允许使用 null 之外，HashMap 类与 Hashtable 大致相同。）此类不保证映射的顺序，特别是它不保证该顺序恒久不变。</p><span id="more"></span><h3 id="1-HashMap的数据结构："><a href="#1-HashMap的数据结构：" class="headerlink" title="1. HashMap的数据结构："></a>1. HashMap的数据结构：</h3><p>HashMap使用数组和链表来共同组成的。可以看出底层是一个数组，而数组的每个元素都是一个链表头。</p><p><img src="/images/hashMapSourceAnalysis/hs.20150729.01.jpg" alt="enter image description here"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span>&lt;K,V&gt; <span class="keyword">implements</span> <span class="title class_">Map</span>.Entry&lt;K,V&gt; &#123;</span><br><span class="line">        <span class="keyword">final</span> K key;</span><br><span class="line">        V value;</span><br><span class="line">        Entry&lt;K,V&gt; next;</span><br><span class="line">        <span class="type">int</span> hash;</span><br><span class="line">        ...</span><br><span class="line">     &#125;</span><br></pre></td></tr></table></figure><p>Entry是HashMap中的一个内部静态类，包级私有，实现了Map中的接口Entey&lt;K,V&gt;。可以看出来它内部含有一个指向下一个元素的指针。</p><h3 id="2-构造函数"><a href="#2-构造函数" class="headerlink" title="2.构造函数"></a>2.构造函数</h3><p>HashMap的构造函数有四个：</p><ol><li>HashMap() — 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 </li><li>HashMap(int initialCapacity)  — 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。 </li><li>HashMap(int initialCapacity, float loadFactor) — 构造一个带指定初始容量和加载因子的空 HashMap。 </li><li>HashMap(Map&lt;? extends K,? extends V&gt; m) — 构造一个映射关系与指定 Map 相同的新 HashMap</li></ol><p>实际上就两种，一个是指定初始容量和加载因子，一个是用一个给定的映射关系生成一个新的HashMap。说一下第一种。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Constructs an empty &lt;tt&gt;HashMap &lt;/tt&gt; with the specified initial</span></span><br><span class="line"><span class="comment">    * capacity and load factor.</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span>  initialCapacity the initial capacity</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span>  loadFactor      the load factor</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@throws</span> IllegalArgumentException if the initial capacity is negative</span></span><br><span class="line"><span class="comment">    *         or the load factor is nonpositive</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">( <span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> &#123;</span><br><span class="line">       <span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>( <span class="string">&quot;Illegal initial capacity: &quot;</span> +</span><br><span class="line">                                              initialCapacity);</span><br><span class="line">       <span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">           initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">       <span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float. isNaN(loadFactor))</span><br><span class="line">           <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>( <span class="string">&quot;Illegal load factor: &quot;</span> +</span><br><span class="line">                                              loadFactor);</span><br><span class="line"></span><br><span class="line">       <span class="comment">// Find a power of 2 &gt;= initialCapacity</span></span><br><span class="line">       <span class="type">int</span> <span class="variable">capacity</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">       <span class="keyword">while</span> (capacity &lt; initialCapacity)</span><br><span class="line">           capacity &lt;&lt;= <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">       <span class="built_in">this</span>.loadFactor = loadFactor;</span><br><span class="line">       threshold = (<span class="type">int</span>)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">       table = <span class="keyword">new</span> <span class="title class_">Entry</span>[capacity];</span><br><span class="line">       useAltHashing = sun.misc.VM. isBooted() &amp;&amp;</span><br><span class="line">               (capacity &gt;= Holder. ALTERNATIVE_HASHING_THRESHOLD);</span><br><span class="line">       init();</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>参数很简单，初始容量，和加载因子。初始容量定义了初识数组的大小，加载因子和初始容量的乘积确定了一个阈值。阈值最大是(1&lt;&lt;30) + 1。初始容量一定是2的N次方，而且刚刚比要设置的值大。默认初始容量是16，默认加载因子是0.75。当表中的元素数量大于等于阈值时，数组的容量会翻倍，并重新插入元素到新的数组中，所以HashMap不保证顺序恒久不变。</p><p>当输入的加载因子小于零或者不是浮点数时会抛出异常（IllegalArgumentException）。</p><h3 id="3-put操作"><a href="#3-put操作" class="headerlink" title="3.put操作"></a>3.put操作</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Associates the specified value with the specified key in this map.</span></span><br><span class="line"><span class="comment">    * If the map previously contained a mapping for the key, the old</span></span><br><span class="line"><span class="comment">    * value is replaced.</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> key key with which the specified value is to be associated</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> value value to be associated with the specified key</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@return</span> the previous value associated with &lt;tt&gt;key &lt;/tt&gt;, or</span></span><br><span class="line"><span class="comment">    *         &lt;tt&gt;null &lt;/tt&gt; if there was no mapping for &lt;tt&gt; key&lt;/tt&gt; .</span></span><br><span class="line"><span class="comment">    *         (A &lt;tt&gt;null &lt;/tt&gt; return can also indicate that the map</span></span><br><span class="line"><span class="comment">    *         previously associated &lt;tt&gt;null &lt;/tt&gt; with &lt;tt&gt; key&lt;/tt&gt; .)</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">public</span> V <span class="title function_">put</span><span class="params">(K key, V value)</span> &#123;</span><br><span class="line">       <span class="keyword">if</span> (key == <span class="literal">null</span>)</span><br><span class="line">           <span class="keyword">return</span> putForNullKey(value);</span><br><span class="line">       <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> hash(key);</span><br><span class="line">       <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(hash, table .length );</span><br><span class="line">       <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[i]; e != <span class="literal">null</span>; e = e. next) &#123;</span><br><span class="line">           Object k;</span><br><span class="line">           <span class="keyword">if</span> (e. hash == hash &amp;&amp; ((k = e. key) == key || key.equals(k))) &#123;</span><br><span class="line">               <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e. value;</span><br><span class="line">               e. value = value;</span><br><span class="line">               e.recordAccess( <span class="built_in">this</span>);</span><br><span class="line">               <span class="keyword">return</span> oldValue;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       modCount++;</span><br><span class="line">       addEntry(hash, key, value, i);</span><br><span class="line">       <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>由于HashMap只是key值为null，所以首先要判断key值是不是为null，是则进行特殊处理。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Offloaded version of put for null keys</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">private</span> V <span class="title function_">putForNullKey</span><span class="params">(V value)</span> &#123;</span><br><span class="line">       <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[<span class="number">0</span>]; e != <span class="literal">null</span>; e = e. next) &#123;</span><br><span class="line">           <span class="keyword">if</span> (e. key == <span class="literal">null</span>) &#123;</span><br><span class="line">               <span class="type">V</span> <span class="variable">oldValue</span> <span class="operator">=</span> e. value;</span><br><span class="line">               e. value = value;</span><br><span class="line">               e.recordAccess( <span class="built_in">this</span>);</span><br><span class="line">               <span class="keyword">return</span> oldValue;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">       modCount++;</span><br><span class="line">       addEntry(<span class="number">0</span>, <span class="literal">null</span>, value, <span class="number">0</span>);</span><br><span class="line">       <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>可以看出key值为null则会插入到数组的第一个位置。如果第一个位置存在，则替代，不存在则添加一个新的。稍后会看到addEntry函数。</p><p>** PS：考虑一个问题，key值为null会插入到table[0]，那为什么还要遍历整个链表呢？**</p><p>回到put函数中。在判断key不为null后，会求key的hash值，并通过indexFor函数找出这个key应该存在table中的位置。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Returns index for hash code h.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">indexFor</span> <span class="params">(<span class="type">int</span> h, <span class="type">int</span> length)</span> &#123;</span><br><span class="line">       <span class="keyword">return</span> h &amp; (length-<span class="number">1</span>);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>indexFor函数很简短，但是却实现的很巧妙。一般来说我们把一个数映射到一个固定的长度会用取余（%）运算，也就是h % length，但里巧妙地运用了table.length的特性。还记得前面说了数组的容量都是很特殊的数，是2的N次方。用二进制表示也就是一个1后面N个0，（length-1）就是N个1了。这里直接用与运算，运算速度快，效率高。但是这是是利用了length的特殊性，如果length不是2的N次方的话可能会增加冲突。</p><p>前面的问题在这里就有答案了。因为indexFor函数返回值的范围是0到（length-1），所以可能会有key值不是null的Entry存到table[0]中，所以前面还是需要遍历链表的。</p><p>得到key值对应在table中的位置，就可以对链表进行遍历，如果存在该key则，替换value，并把旧的value返回，modCount++代表操作数加1。这个属性用于Fail-Fast机制，后面讲到。如果遍历链表后发现key不存在，则要插入一个新的Entry到链表中。这时就会调用addEntry函数</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Adds a new entry with the specified key, value and hash code to</span></span><br><span class="line"><span class="comment"> * the specified bucket.  It is the responsibility of this</span></span><br><span class="line"><span class="comment"> * method to resize the table if appropriate.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Subclass overrides this to alter the behavior of put method.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">addEntry</span> <span class="params">(<span class="type">int</span> hash, K key, V value, <span class="type">int</span> bucketIndex)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> ((size &gt;= threshold) &amp;&amp; ( <span class="literal">null</span> != table[bucketIndex])) &#123;</span><br><span class="line">        resize(<span class="number">2</span> * table. length);</span><br><span class="line">        hash = ( <span class="literal">null</span> != key) ? hash(key) : <span class="number">0</span>;</span><br><span class="line">        bucketIndex = indexFor(hash, table.length);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    createEntry(hash, key, value, bucketIndex);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个函数有四个参数，第一个是key的hash值，第二个第三个分别是key和value，最后一个是这个key在table中的位置，也就是indexFor(hash(key), table.length-1)。首先会判断size（当前表中的元素个数）是不是大于或等于阈值。并且判断数组这个位置是不是空。如果条件满足则要resize(2 * table. length)，等下我们来看这个操作。超过阈值要resize是为了减少冲突，提高访问效率。判断当前位置不是空时才resize是为了尽可能减少resize次数，因为这个位置是空，放一个元素在这也没有冲突，所以不影响效率，就先不进行resize了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Rehashes the contents of this map into a new array with a</span></span><br><span class="line"><span class="comment"> * larger capacity.  This method is called automatically when the</span></span><br><span class="line"><span class="comment"> * number of keys in this map reaches its threshold.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * If current capacity is MAXIMUM_CAPACITY, this method does not</span></span><br><span class="line"><span class="comment"> * resize the map, but sets threshold to Integer.MAX_VALUE.</span></span><br><span class="line"><span class="comment"> * This has the effect of preventing future calls.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> newCapacity the new capacity, MUST be a power of two;</span></span><br><span class="line"><span class="comment"> *        must be greater than current capacity unless current</span></span><br><span class="line"><span class="comment"> *        capacity is MAXIMUM_CAPACITY (in which case value</span></span><br><span class="line"><span class="comment"> *        is irrelevant).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">resize</span><span class="params">(<span class="type">int</span> newCapacity)</span> &#123;</span><br><span class="line">    Entry[] oldTable = table;</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> oldTable. length;</span><br><span class="line">    <span class="keyword">if</span> (oldCapacity == MAXIMUM_CAPACITY) &#123;</span><br><span class="line">        threshold = Integer. MAX_VALUE;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Entry[] newTable = <span class="keyword">new</span> <span class="title class_">Entry</span>[newCapacity];</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">oldAltHashing</span> <span class="operator">=</span> useAltHashing;</span><br><span class="line">    useAltHashing |= sun.misc.VM. isBooted() &amp;&amp;</span><br><span class="line">            (newCapacity &gt;= Holder. ALTERNATIVE_HASHING_THRESHOLD);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">rehash</span> <span class="operator">=</span> oldAltHashing ^ useAltHashing;</span><br><span class="line">    transfer(newTable, rehash);</span><br><span class="line">    table = newTable;</span><br><span class="line">    threshold = (<span class="type">int</span>)Math.min(newCapacity * loadFactor , MAXIMUM_CAPACITY + <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>resize操作先要判断当前table的长度是不是已经等于最大容量（1&lt;&lt;30）了，如果是则把阈值调到整数的最大值（(1&lt;&lt;31) - 1），就没有再拓展table的必要了。如果没有到达最大容量，就要生成一个新的空数组，长度是原来的两倍。这时候可能要问了，如果oldTable. length不等于MAXIMUM_CAPACITY，但是（2 * oldTable. length）也就是newCapacity大于MAXIMUM_CAPACITY怎么办？这个是不可能的，因为数组长度是2的N次方，而MAXIMUM_CAPACITY &#x3D; 1&lt;&lt;30。<br>生成新的数组后要执行transfer函数。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Transfers all entries from current table to newTable.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Entry[] newTable, <span class="type">boolean</span> rehash)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> newTable. length;</span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">        <span class="keyword">while</span>( <span class="literal">null</span> != e) &#123;</span><br><span class="line">            Entry&lt;K,V&gt; next = e. next;</span><br><span class="line">            <span class="keyword">if</span> ( rehash) &#123;</span><br><span class="line">                e. hash = <span class="literal">null</span> == e. key ? <span class="number">0</span> : hash(e. key);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(e.hash, newCapacity);</span><br><span class="line">            e. next = newTable[i];</span><br><span class="line">            newTable[i] = e;</span><br><span class="line">            e = next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个函数要做的就是把原来table中的值挨个拿出来插到新数组中，由于数组长度发生了改变，所以元素的位置肯定发生变化，所以HashMap不能保证该顺序恒久不变。回到resize函数，这时新的数组已经生成了，只需要替换原来数组就好了。并且要更新一下阈值。可以看出来resize是个比较消耗资源的函数，所以能减少resize的次数就尽量减少。</p><p>回到函数addEntry 中，判断完是不是需要resize后就需要创建一个新的Entry了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Like addEntry except that this version is used when creating entries</span></span><br><span class="line"><span class="comment"> * as part of Map construction or &quot;pseudo -construction&quot; (cloning,</span></span><br><span class="line"><span class="comment"> * deserialization).  This version needn&#x27;t worry about resizing the table.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * Subclass overrides this to alter the behavior of HashMap(Map),</span></span><br><span class="line"><span class="comment"> * clone, and readObject.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">void</span> <span class="title function_">createEntry</span><span class="params">( <span class="type">int</span> hash, K key, V value, <span class="type">int</span> bucketIndex)</span> &#123;</span><br><span class="line">    Entry&lt;K,V&gt; e = table[bucketIndex];</span><br><span class="line">    table[bucketIndex] = <span class="keyword">new</span> <span class="title class_">Entry</span>&lt;&gt;(hash, key, value, e);</span><br><span class="line">    size++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用createEntry函数，参数跟addEntry一样，第一个是key的hash值，第二个第三个分别是key和value，最后一个是这个key在table中的位置。这里的操作与Entry的构造函数有关系。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates new entry.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">Entry (<span class="type">int</span> h, K k, V v, Entry&lt;K,V&gt; n) &#123;</span><br><span class="line">    value = v;</span><br><span class="line">    next = n;</span><br><span class="line">    key = k;</span><br><span class="line">    hash = h;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构造函数中传入一个Entry对象，并把它当做这个新生成的Entry的next。所以createEntry函数中的操作相当于把table[bucketIndex]上的链表拿下来，放在新的Entry后面，然后再把新的Entry放到table[bucketIndex]上。</p><p><img src="/images/hashMapSourceAnalysis/hs.20150729.02.jpg" alt="enter image description here"></p><p>到这里整个put函数算是结束了。如果新插入的K，V则会返回null。</p><h3 id="4-get操作"><a href="#4-get操作" class="headerlink" title="4.get操作"></a>4.get操作</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the value to which the specified key is mapped,</span></span><br><span class="line"><span class="comment"> * or &#123;<span class="doctag">@code</span> null&#125; if this map contains no mapping for the key.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;More formally, if this map contains a mapping from a key</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> k&#125; to a value &#123;<span class="doctag">@code</span> v&#125; such that &#123;<span class="doctag">@code</span> (key==null ? k==null :</span></span><br><span class="line"><span class="comment"> * key.equals(k))&#125;, then this method returns &#123;<span class="doctag">@code</span> v&#125;; otherwise</span></span><br><span class="line"><span class="comment"> * it returns &#123;<span class="doctag">@code</span> null&#125;.  (There can be at most one such mapping.)</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;A return value of &#123;<span class="doctag">@code</span> null&#125; does not &lt;i&gt;necessarily &lt;/i&gt;</span></span><br><span class="line"><span class="comment"> * indicate that the map contains no mapping for the key; it&#x27;s also</span></span><br><span class="line"><span class="comment"> * possible that the map explicitly maps the key to &#123;<span class="doctag">@code</span> null&#125;.</span></span><br><span class="line"><span class="comment"> * The &#123;<span class="doctag">@link</span> #containsKey containsKey&#125; operation may be used to</span></span><br><span class="line"><span class="comment"> * distinguish these two cases.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@see</span> #put(Object, Object)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (key == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">return</span> getForNullKey();</span><br><span class="line">    Entry&lt;K,V&gt; entry = getEntry(key);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span> == entry ? <span class="literal">null</span> : entry.getValue();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也是先判断key是不是null，做特殊处理。直接上代码，不赘述。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Offloaded version of get() to look up null keys.  Null keys map</span></span><br><span class="line"><span class="comment"> * to index 0.  This null case is split out into separate methods</span></span><br><span class="line"><span class="comment"> * for the sake of performance in the two most commonly used</span></span><br><span class="line"><span class="comment"> * operations (get and put), but incorporated with conditionals in</span></span><br><span class="line"><span class="comment"> * others.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> V <span class="title function_">getForNullKey</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[<span class="number">0</span>]; e != <span class="literal">null</span>; e = e. next) &#123;</span><br><span class="line">        <span class="keyword">if</span> (e. key == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> e. value;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>key不是null则会调用getEntry函数，并返回一个Entry对象，如果不是null，就返回entry的value。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns the entry associated with the specified key in the</span></span><br><span class="line"><span class="comment"> * HashMap.  Returns null if the HashMap contains no mapping</span></span><br><span class="line"><span class="comment"> * for the key.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> Entry&lt;K,V&gt; <span class="title function_">getEntry</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">hash</span> <span class="operator">=</span> (key == <span class="literal">null</span>) ? <span class="number">0</span> : hash(key);</span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e = table[ indexFor(hash, table.length)];</span><br><span class="line">         e != <span class="literal">null</span>;</span><br><span class="line">         e = e. next) &#123;</span><br><span class="line">        Object k;</span><br><span class="line">        <span class="keyword">if</span> (e. hash == hash &amp;&amp;</span><br><span class="line">            ((k = e. key) == key || (key != <span class="literal">null</span> &amp;&amp; key.equals(k))))</span><br><span class="line">            <span class="keyword">return</span> e;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接求key值hash值，然后求table中的位置，遍历链表。有返回entry对象，没有返回null。</p><h3 id="5-Fail-Fast机制"><a href="#5-Fail-Fast机制" class="headerlink" title="5. Fail-Fast机制"></a>5. Fail-Fast机制</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * The number of times this HashMap has been structurally modified</span></span><br><span class="line"><span class="comment"> * Structural modifications are those that change the number of mappings in</span></span><br><span class="line"><span class="comment"> * the HashMap or otherwise modify its internal structure (e.g.,</span></span><br><span class="line"><span class="comment"> * rehash).  This field is used to make iterators on Collection-views of</span></span><br><span class="line"><span class="comment"> * the HashMap fail -fast.  (See ConcurrentModificationException).</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">transient</span> <span class="type">int</span> modCount;</span><br></pre></td></tr></table></figure><p>我们知道java.util.HashMap不是线程安全的，因此如果在使用迭代器的过程中有其他线程修改了map，那么将抛出ConcurrentModificationException，这就是所谓fail-fast策略。</p><p>这一策略在源码中的实现是通过modCount域，保证线程之间修改的可见性。，modCount顾名思义就是修改次数，对HashMap内容的修改都将增加这个值，那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。</p><p><strong>注意</strong>，迭代器的快速失败行为不能得到保证，一般来说，存在非同步的并发修改时，不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此，编写依赖于此异常的程序的做法是错误的，正确做法是：迭代器的快速失败行为应该仅用于检测程序错误。</p><h3 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h3><ul><li>《Core JAVA》</li><li>《JAVA API》</li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;链表和数组可以按照人们意愿排列元素的次序，但是，如果想要查看某个指定的元素，却又忘记了它的位置，就需要访问所有的元素，直到找到为止。如果集合中元素很多，将会消耗很多时间。有一种数据结构可以快速查找所需要查找的对象，这个就是哈希表（hash table）.&lt;/p&gt;
&lt;p&gt;HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作，并允许使用 null 值和 null 键。（除了非同步和允许使用 null 之外，HashMap 类与 Hashtable 大致相同。）此类不保证映射的顺序，特别是它不保证该顺序恒久不变。&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="HashMap" scheme="https://androidperformance.com/tags/HashMap/"/>
    
  </entry>
  
  <entry>
    <title>Android 代码内存优化建议 - OnTrimMemory 优化</title>
    <link href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/"/>
    <id>https://androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/</id>
    <published>2015-07-20T04:42:10.000Z</published>
    <updated>2026-02-07T05:17:47.840Z</updated>
    
    <content type="html"><![CDATA[<p>Android 内存优化系列文章：</p><ol><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android代码内存优化建议-Android官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android代码内存优化建议-Java官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android代码内存优化建议-Android资源篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android代码内存优化建议-OnTrimMemory优化</a></li></ol><hr><p>OnTrimMemory 回调是 Android 4.0 之后提供的一个API，这个 API 是提供给开发者的，它的主要作用是提示开发者在系统内存不足的时候，通过处理部分资源来释放内存，从而避免被 Android 系统杀死。这样应用在下一次启动的时候，速度就会比较快。</p><p>本文通过问答的方式，从各个方面来讲解 OnTrimMemory 回调的使用过程和效果。想要开发高性能且用户体验良好的 Android 应用，那么这篇文章你不应该错过。</p><span id="more"></span><h2 id="0-OnTrimMemory回调的作用？"><a href="#0-OnTrimMemory回调的作用？" class="headerlink" title="0. OnTrimMemory回调的作用？"></a>0. OnTrimMemory回调的作用？</h2><p>OnTrimMemory是Android在4.0之后加入的一个回调，任何实现了ComponentCallbacks2接口的类都可以重写实现这个回调方法．OnTrimMemory的主要作用就是<strong>指导应用程序在不同的情况下进行自身的内存释放，以避免被系统直接杀掉，提高应用程序的用户体验.</strong></p><p>Android系统会根据不同等级的内存使用情况，调用这个函数，并传入对应的等级：</p><ul><li>TRIM_MEMORY_UI_HIDDEN 表示应用程序的<strong>所有UI界面</strong>被隐藏了，即用户点击了Home键或者Back键导致应用的UI界面不可见．这时候应该释放一些资源．<br>TRIM_MEMORY_UI_HIDDEN这个等级比较常用，和下面六个的关系不是很强，所以单独说．</li></ul><p>下面三个等级是当我们的应用程序真正运行时的回调：</p><ul><li>TRIM_MEMORY_RUNNING_MODERATE    表示应用程序正常运行，并且不会被杀掉。但是目前手机的内存已经有点低了，系统可能会开始根据LRU缓存规则来去杀死进程了。</li><li>TRIM_MEMORY_RUNNING_LOW    表示应用程序正常运行，并且不会被杀掉。但是目前手机的内存已经非常低了，我们应该去释放掉一些不必要的资源以提升系统的性能，同时这也会直接影响到我们应用程序的性能。</li><li>TRIM_MEMORY_RUNNING_CRITICAL    表示应用程序仍然正常运行，但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源，不然的话系统可能会继续杀掉所有缓存中的进程，并且开始杀掉一些本来应当保持运行的进程，比如说后台运行的服务。</li></ul><p>当应用程序是缓存的，则会收到以下几种类型的回调：</p><ul><li>TRIM_MEMORY_BACKGROUND    表示手机目前内存已经很低了，系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置，是不太可能被清理掉的，但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足，从而让我们的程序更长时间地保留在缓存当中，这样当用户返回我们的程序时会感觉非常顺畅，而不是经历了一次重新启动的过程。</li><li>TRIM_MEMORY_MODERATE    表示手机目前内存已经很低了，并且我们的程序处于LRU缓存列表的中间位置，如果手机内存还得不到进一步释放的话，那么我们的程序就有被系统杀掉的风险了。</li><li>TRIM_MEMORY_COMPLETE    表示手机目前内存已经很低了，并且我们的程序处于LRU缓存列表的最边缘位置，系统会最优先考虑杀掉我们的应用程序，在这个时候应当尽可能地把一切可以释放的东西都进行释放。</li></ul><h2 id="1-哪些组件可以实现OnTrimMemory回调？"><a href="#1-哪些组件可以实现OnTrimMemory回调？" class="headerlink" title="1. 哪些组件可以实现OnTrimMemory回调？"></a>1. 哪些组件可以实现OnTrimMemory回调？</h2><ul><li>Application.onTrimMemory()</li><li>Activity.onTrimMemory()</li><li>Fragment.OnTrimMemory()</li><li>Service.onTrimMemory()</li><li>ContentProvider.OnTrimMemory()</li></ul><h2 id="2-OnTrimMemory回调中可以释放哪些资源？"><a href="#2-OnTrimMemory回调中可以释放哪些资源？" class="headerlink" title="2. OnTrimMemory回调中可以释放哪些资源？"></a>2. OnTrimMemory回调中可以释放哪些资源？</h2><p>通常在架构阶段就要考虑清楚，我们有哪些东西是要常驻内存的，有哪些是伴随界面存在的．一般情况下，有下面几种资源需要进行释放：</p><ul><li>缓存  缓存包括一些文件缓存，图片缓存等，在用户正常使用的时候这些缓存很有作用，但当你的应用程序UI不可见的时候，这些缓存就可以被清除以减少内存的使用．比如第三方图片库的缓存．</li><li>一些动态生成动态添加的View．　这些动态生成和添加的View且少数情况下才使用到的View，这时候可以被释放，下次使用的时候再进行动态生成即可．比如原生桌面中，会在OnTrimMemory的TRIM_MEMORY_MODERATE等级中，释放所有AppsCustomizePagedView的资源，来保证在低内存的时候，桌面不会轻易被杀掉．</li></ul><h4 id="2-1-例子：释放不常用到的View．"><a href="#2-1-例子：释放不常用到的View．" class="headerlink" title="2.1 例子：释放不常用到的View．"></a>2.1 例子：释放不常用到的View．</h4><p>代码出处：Launcher</p><p>Launcher.java:</p><figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">onTrimMemory</span><span class="params">(<span class="keyword">int</span> level)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>.onTrimMemory(level);</span><br><span class="line">    <span class="keyword">if</span> (level &gt;= ComponentCallbacks2.TRIM_MEMORY_MODERATE) &#123;</span><br><span class="line">        mAppsCustomizeTabHost.onTrimMemory();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>AppsCustomizeTabHost.java:</p><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onTrimMemory</span>()</span> &#123;</span><br><span class="line">    mContent.setVisibility(GONE);</span><br><span class="line">    <span class="comment">// Clear the widget pages of all their subviews - this will trigger the widget previews</span></span><br><span class="line">    <span class="comment">// to delete their bitmaps</span></span><br><span class="line">    mPagedView.clearAllWidgetPages();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>AppsCustomizePagedView.java:</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public void clear<span class="constructor">AllWidgetPages()</span> &#123;</span><br><span class="line">    cancel<span class="constructor">AllTasks()</span>;</span><br><span class="line">    <span class="built_in">int</span> count = get<span class="constructor">ChildCount()</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="built_in">int</span> i = <span class="number">0</span>; i &lt; count; i++) &#123;</span><br><span class="line">        View v = get<span class="constructor">PageAt(<span class="params">i</span>)</span>;</span><br><span class="line">        <span class="keyword">if</span> (v instanceof PagedViewGridLayout) &#123;</span><br><span class="line">            ((PagedViewGridLayout) v).remove<span class="constructor">AllViewsOnPage()</span>;</span><br><span class="line">            mDirtyPageContent.set(i, <span class="literal">true</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>PagedViewGridLayout.java</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">removeAllViewsOnPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">removeAllViews</span>();</span><br><span class="line">    mOnLayoutListener = <span class="literal">null</span>;</span><br><span class="line">    <span class="title function_">setLayerType</span>(<span class="variable constant_">LAYER_TYPE_NONE</span>, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-2-例子-清除缓存"><a href="#2-2-例子-清除缓存" class="headerlink" title="2.2 例子: 清除缓存"></a>2.2 例子: 清除缓存</h4><p>代码出处：Contact</p><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">@Override</span></span><br><span class="line">public void onTrimMemory(int level) &#123;</span><br><span class="line">    if (level &gt;= ComponentCallbacks2.TRIM_MEMORY_MODERATE) &#123;</span><br><span class="line">        <span class="comment">// Clear the caches.  Note all pending requests will be removed too.</span></span><br><span class="line">        <span class="attribute">clear</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">public void <span class="attribute">clear</span>() &#123;</span><br><span class="line">    mPendingRequests<span class="selector-class">.clear</span>();</span><br><span class="line">    mBitmapHolderCache<span class="selector-class">.evictAll</span>();</span><br><span class="line">    mBitmapCache<span class="selector-class">.evictAll</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-OnTrimMemory和onStop的关系？"><a href="#3-OnTrimMemory和onStop的关系？" class="headerlink" title="3. OnTrimMemory和onStop的关系？"></a>3. OnTrimMemory和onStop的关系？</h2><p>onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回调只有当我们程序中的所有UI组件全部不可见的时候才会触发，这和onStop()方法还是有很大区别的，因为onStop()方法只是当一个Activity完全不可见的时候就会调用，比如说用户打开了我们程序中的另一个Activity。</p><p>因此，我们可以在onStop()方法中去释放一些Activity相关的资源，比如说取消网络连接或者注销广播接收器等，但是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调之后才去释放，这样可以保证如果用户只是从我们程序的一个Activity回到了另外一个Activity，界面相关的资源都不需要重新加载，从而提升响应速度。</p><p>需要注意的是，onTrimMemory的TRIM_MEMORY_UI_HIDDEN 等级是在onStop方法之前调用的．</p><h2 id="4-OnTrimMemory和OnLowMemory的关系？"><a href="#4-OnTrimMemory和OnLowMemory的关系？" class="headerlink" title="4. OnTrimMemory和OnLowMemory的关系？"></a>4. OnTrimMemory和OnLowMemory的关系？</h2><p>在引入OnTrimMemory之前都是使用OnLowMemory回调，需要知道的是，OnLowMemory大概和OnTrimMemory中的TRIM_MEMORY_COMPLETE级别相同，如果你想兼容api&lt;14的机器，那么可以用OnLowMemory来实现，否则你可以忽略OnLowMemory，直接使用OnTrimMemory即可．</p><h2 id="5-为什么要调用OnTrimMemory？"><a href="#5-为什么要调用OnTrimMemory？" class="headerlink" title="5. 为什么要调用OnTrimMemory？"></a>5. 为什么要调用OnTrimMemory？</h2><p>尽管系统在内存不足的时候杀进程的顺序是按照LRU Cache中从低到高来的，但是它同时也会考虑杀掉那些占用内存较高的应用来让系统更快地获得更多的内存。</p><p>所以如果你的应用占用内存较小，就可以增加不被杀掉的几率，从而快速地恢复（如果不被杀掉，启动的时候就是热启动，否则就是冷启动，其速度差在2~3倍）。</p><p>所以说在几个不同的OnTrimMemory回调中释放自己的UI资源，可以有效地提高用户体验。</p><h2 id="6-有哪些典型的使用场景？"><a href="#6-有哪些典型的使用场景？" class="headerlink" title="6. 有哪些典型的使用场景？"></a>6. 有哪些典型的使用场景？</h2><h3 id="6-1-常驻内存的应用"><a href="#6-1-常驻内存的应用" class="headerlink" title="6.1 常驻内存的应用"></a>6.1 常驻内存的应用</h3><p>一些常驻内存的应用，比如Launcher、安全中心、电话等，在用户使用过要退出的时候，需要调用OnTrimMemory来及时释放用户使用的时候所产生的多余的内存资源：比如动态生成的View、图片缓存、Fragment等。</p><h3 id="6-2-有后台Service运行的应用"><a href="#6-2-有后台Service运行的应用" class="headerlink" title="6.2 有后台Service运行的应用"></a>6.2 有后台Service运行的应用</h3><p>这些应用不是常驻内存的，意味着可以被任务管理器杀掉，但是在某些场景下用户不会去杀。<br>这类应用包括：音乐、下载等。用户退出UI界面后，音乐还在继续播放，下载程序还在运行。这时候音乐应该释放部分UI资源和Cache。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 内存优化系列文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/&quot;&gt;Android代码内存优化建议-Android官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/&quot;&gt;Android代码内存优化建议-Java官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/&quot;&gt;Android代码内存优化建议-Android资源篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/&quot;&gt;Android代码内存优化建议-OnTrimMemory优化&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;OnTrimMemory 回调是 Android 4.0 之后提供的一个API，这个 API 是提供给开发者的，它的主要作用是提示开发者在系统内存不足的时候，通过处理部分资源来释放内存，从而避免被 Android 系统杀死。这样应用在下一次启动的时候，速度就会比较快。&lt;/p&gt;
&lt;p&gt;本文通过问答的方式，从各个方面来讲解 OnTrimMemory 回调的使用过程和效果。想要开发高性能且用户体验良好的 Android 应用，那么这篇文章你不应该错过。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 代码内存优化建议 - Android 资源篇</title>
    <link href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/"/>
    <id>https://androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/</id>
    <published>2015-07-20T03:48:30.000Z</published>
    <updated>2026-02-07T05:17:47.839Z</updated>
    
    <content type="html"><![CDATA[<p>Android 内存优化系列文章：</p><ol><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android代码内存优化建议-Android官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android代码内存优化建议-Java官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android代码内存优化建议-Android资源篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android代码内存优化建议-OnTrimMemory优化</a></li></ol><hr><p>这篇文章主要介绍在实际Android应用程序的开发中，容易导致内存泄露的一些情况。开发人员如果在进行代码编写之前就有内存泄露方面的基础知识，那么写出来的代码会强壮许多，写这篇文章也是这个初衷。本文从Android开发中的资源使用情况入手，介绍了如何在Bitmap、数据库查询、9-patch、过渡绘制等方面优化内存的使用。</p><span id="more"></span><h1 id="Android资源优化"><a href="#Android资源优化" class="headerlink" title="Android资源优化"></a>Android资源优化</h1><h2 id="1-Bitmap优化"><a href="#1-Bitmap优化" class="headerlink" title="1. Bitmap优化"></a>1. Bitmap优化</h2><p>Android中的大部分内存问题归根结底都是Bitmap的问题，如果打开MAT(Memory analyzer tool)来看，实际占用内存大的都是一些Bitmap(以byte数组的形式存储)。所以Bitmap的优化应该是我们着重去解决的。Google在其官方有针对Bitmap的使用专门写了一个专题 : <a href="http://developer.android.com/training/displaying-bitmaps/index.html">Displaying Bitmaps Efficiently</a>, 对应的中文翻译在 ：<a href="https://github.com/kesenhoo/android-training-course-in-chinese/tree/master/graphics/displaying-bitmaps">displaying-bitmaps</a> , 在优化Bitmap资源之前，请先看看这个系列的文档，以确保自己正确地使用了Bitmap。</p><p>Bitmap如果没有被释放，那么一般只有两个问题：</p><ul><li>用户在使用完这个Bitmap之后，没有主动去释放Bitmap资源。</li><li>这个Bitmap资源被引用所以无法被释放 。</li></ul><h3 id="1-1-主动释放Bitmap资源"><a href="#1-1-主动释放Bitmap资源" class="headerlink" title="1.1 主动释放Bitmap资源"></a>1.1 主动释放Bitmap资源</h3><p>当你确定这个Bitmap资源不会再被使用的时候(当然这个Bitmap不释放可能会让程序下一次启动或者resume快一些，但是其占用的内存资源太大，可能导致程序在后台的时候被杀掉，反而得不偿失)，我们建议手动调用recycle()方法，释放其Native内存：</p><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">if(<span class="keyword">bitmap </span>!= null &amp;&amp; !<span class="keyword">bitmap.isRecycled())&#123; </span> </span><br><span class="line">    <span class="keyword">bitmap.recycle(); </span></span><br><span class="line">    <span class="keyword">bitmap </span>= null; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们也可以看一下Bitmap.java中recycle()方法的说明：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Free the native object associated with this bitmap, and clear the</span></span><br><span class="line"><span class="comment">     * reference to the pixel data. This will not free the pixel data synchronously;</span></span><br><span class="line"><span class="comment">     * it simply allows it to be garbage collected if there are no other references.</span></span><br><span class="line"><span class="comment">     * The bitmap is marked as &quot;dead&quot;, meaning it will throw an exception if</span></span><br><span class="line"><span class="comment">     * getPixels() or setPixels() is called, and will draw nothing. This operation</span></span><br><span class="line"><span class="comment">     * cannot be reversed, so it should only be called if you are sure there are no</span></span><br><span class="line"><span class="comment">     * further uses for the bitmap. This is an advanced call, and normally need</span></span><br><span class="line"><span class="comment">     * not be called, since the normal GC process will free up this memory when</span></span><br><span class="line"><span class="comment">     * there are no more references to this bitmap.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recycle</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mRecycled) &#123;</span><br><span class="line">            <span class="keyword">if</span> (nativeRecycle(mNativeBitmap)) &#123;</span><br><span class="line">                <span class="comment">// return value indicates whether native pixel object was actually recycled.</span></span><br><span class="line">                <span class="comment">// false indicates that it is still in use at the native level and these</span></span><br><span class="line">                <span class="comment">// objects should not be collected now. They will be collected later when the</span></span><br><span class="line">                <span class="comment">// Bitmap itself is collected.</span></span><br><span class="line">                mBuffer = <span class="literal">null</span>;</span><br><span class="line">                mNinePatchChunk = <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            mRecycled = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">......</span><br><span class="line"><span class="comment">//如果使用过程中抛出异常的判断</span></span><br><span class="line"><span class="keyword">if</span> (bitmap.isRecycled()) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Canvas: trying to use a recycled bitmap &quot;</span> + bitmap);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用bitmap.recycle之后，这个Bitmap如果没有被引用到，那么就会被垃圾回收器回收。如果不主动调用这个方法，垃圾回收器也会进行回收工作，只不过垃圾回收器的不确定性太大，依赖其自动回收不靠谱(比如垃圾回收器一次性要回收好多Bitmap，那么需要的时间就会很多，导致回收的时候会卡顿)。所以我们需要主动调用recycle。</p><h3 id="1-2-主动释放ImageView的图片资源"><a href="#1-2-主动释放ImageView的图片资源" class="headerlink" title="1.2 主动释放ImageView的图片资源"></a>1.2 主动释放ImageView的图片资源</h3><p>由于我们在实际开发中，很多情况是在xml布局文件中设置ImageView的src或者在代码中调用ImageView.setImageResource&#x2F;setImageURI&#x2F;setImageDrawable等方法设置图像，下面代码可以回收这个ImageView所对应的资源：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">recycleImageViewBitMap</span>(<span class="params">ImageView imageView</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (imageView != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="title class_">BitmapDrawable</span> bd = (<span class="title class_">BitmapDrawable</span>) imageView.<span class="title function_">getDrawable</span>();</span><br><span class="line">        <span class="title function_">rceycleBitmapDrawable</span>(bd);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">rceycleBitmapDrawable</span>(<span class="params">BitmapDrawable bitmapDrawable</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (bitmapDrawable != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="title class_">Bitmap</span> bitmap = bitmapDrawable.<span class="title function_">getBitmap</span>();</span><br><span class="line">        <span class="title function_">rceycleBitmap</span>(bitmap);</span><br><span class="line">    &#125;</span><br><span class="line">    bitmapDrawable = <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="built_in">void</span> <span class="title function_">rceycleBitmap</span>(<span class="params">Bitmap bitmap</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (bitmap != <span class="literal">null</span> &amp;&amp; !bitmap.<span class="title function_">isRecycled</span>()) &#123;</span><br><span class="line">        bitmap.<span class="title function_">recycle</span>();</span><br><span class="line">        bitmap = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-3-主动释放ImageView的背景资源"><a href="#1-3-主动释放ImageView的背景资源" class="headerlink" title="1.3 主动释放ImageView的背景资源"></a>1.3 主动释放ImageView的背景资源</h3><p>如果你的ImageView是有Background，那么下面的代码可以释放他：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">recycleBackgroundBitMap</span><span class="params">(ImageView view)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (view != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">BitmapDrawable</span> <span class="variable">bd</span> <span class="operator">=</span> (BitmapDrawable) view.getBackground();</span><br><span class="line">        rceycleBitmapDrawable(bd);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">recycleImageViewBitMap</span><span class="params">(ImageView imageView)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (imageView != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">BitmapDrawable</span> <span class="variable">bd</span> <span class="operator">=</span> (BitmapDrawable) imageView.getDrawable();</span><br><span class="line">        rceycleBitmapDrawable(bd);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">rceycleBitmapDrawable</span><span class="params">(BitmapDrawable bitmapDrawable)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (bitmapDrawable != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> bitmapDrawable.getBitmap();</span><br><span class="line">        rceycleBitmap(bitmap);</span><br><span class="line">    &#125;</span><br><span class="line">    bitmapDrawable = <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="1-4-尽量少用Png图，多用NinePatch的图"><a href="#1-4-尽量少用Png图，多用NinePatch的图" class="headerlink" title="1.4 尽量少用Png图，多用NinePatch的图"></a>1.4 尽量少用Png图，多用NinePatch的图</h3><p>现在手机的分辨率越来越高，图片资源在被加载后所占用的内存也越来越大，所以要尽量避免使用大的PNG图，在产品设计的时候就要尽量避免用一张大图来进行展示，尽量多用NinePatch资源。</p><p>Android中的NinePatch指的是一种拉伸后不会变形的特殊png图，NinePatch的拉伸区域可以自己定义。这种图的优点是体积小，拉伸不变形，可以适配多机型。Android SDK中有自带NinePatch资源制作工具，Android-Studio中在普通png图片点击右键可以将其转换为NinePatch资源，使用起来非常方便。</p><p><img src="/images/Memory_AndroidSoruce/1.webp" alt="左边是原图，右边是拉伸后的效果"></p><h3 id="1-5-使用大图之前，尽量先对其进行压缩"><a href="#1-5-使用大图之前，尽量先对其进行压缩" class="headerlink" title="1.5 使用大图之前，尽量先对其进行压缩"></a>1.5 使用大图之前，尽量先对其进行压缩</h3><p>图片有不同的形状与大小。在大多数情况下它们的实际大小都比需要呈现出来的要大很多。例如，系统的Gallery程序会显示那些你使用设备camera拍摄的图片，但是那些图片的分辨率通常都比你的设备屏幕分辨率要高很多。</p><p>考虑到程序是在有限的内存下工作，理想情况是你只需要在内存中加载一个低分辨率的版本即可。这个低分辨率的版本应该是与你的UI大小所匹配的，这样才便于显示。一个高分辨率的图片不会提供任何可见的好处，却会占用宝贵的(precious)的内存资源，并且会在快速滑动图片时导致(incurs)附加的效率问题。</p><p>Google官网的Training中，有一篇文章专门介绍如何有效地加载大图，里面提到了两个比较重要的技术：</p><ul><li>在图片加载前获取其宽高和类型</li><li>加载一个按比例缩小的版本到内存中</li></ul><p>原文地址：<a href="http://developer.android.com/training/displaying-bitmaps/load-bitmap.html#read-bitmap">Loading Large Bitmaps Efficiently</a>,中文翻译地址：<a href="https://github.com/kesenhoo/android-training-course-in-chinese/blob/master/graphics/displaying-bitmaps/load-bitmap.md">有效地加载大尺寸位图</a>，强烈建议每一位Android开发者都去看一下，并在自己的实际项目中使用到。</p><p>更多关于Bitmap的使用和优化，可以参考Android官方Training专题的<a href="http://developer.android.com/training/displaying-bitmaps/index.html">displaying-bitmaps</a></p><h2 id="2-查询数据库没有关闭游标"><a href="#2-查询数据库没有关闭游标" class="headerlink" title="2 查询数据库没有关闭游标"></a>2 查询数据库没有关闭游标</h2><p>程序中经常会进行查询数据库的操作，但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小，对内存的消耗不容易被发现，只有在常时间大量操作的情况下才会复现内存问题，这样就会给以后的测试和问题排查带来困难和风险。<br>示例代码：</p><figure class="highlight jboss-cli"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Cursor cursor = getContentResolver<span class="params">()</span><span class="string">.query</span><span class="params">(uri ...)</span>;</span><br><span class="line"><span class="keyword">if</span> <span class="params">(cursor.moveToNext()</span>) &#123;</span><br><span class="line"> <span class="string">...</span> <span class="string">...</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修正示例代码:</p><figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Cursor <span class="built_in">cursor</span> = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="built_in">cursor</span> = <span class="title function_">getContentResolver</span>().<span class="property">query</span>(uri ...);</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">cursor</span> != <span class="literal">null</span> &amp;&amp; <span class="built_in">cursor</span>.<span class="property">moveToNext</span>()) &#123;</span><br><span class="line">... ... </span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">cursor</span> != <span class="literal">null</span>) &#123;</span><br><span class="line"><span class="keyword">try</span> &#123; </span><br><span class="line"><span class="built_in">cursor</span>.<span class="property">close</span>();</span><br><span class="line">&#125; <span class="title function_">catch</span> (Exception e) &#123;</span><br><span class="line"><span class="comment">//ignore this</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>&#96;</p><h2 id="3-构造Adapter时，没有使用缓存的convertView"><a href="#3-构造Adapter时，没有使用缓存的convertView" class="headerlink" title="3 构造Adapter时，没有使用缓存的convertView"></a>3 构造Adapter时，没有使用缓存的convertView</h2><p>以构造ListView的BaseAdapter为例，在BaseAdapter中提供了方法：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public View get<span class="constructor">View(<span class="params">int</span> <span class="params">position</span>, View <span class="params">convertView</span>, ViewGroup <span class="params">parent</span>)</span></span><br></pre></td></tr></table></figure><p>来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象，同时ListView会将这些view对象缓存起来。当向上滚动ListView时，原先位于最上面的list item的view对象会被回收，然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的，getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。<br>由此可以看出，如果我们不去使用convertView，而是每次都在getView()中重新实例化一个View对象的话，即浪费资源也浪费时间，也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:android.widget.AbsListView.java –&gt; void addScrapView(View scrap) 方法。</p><p><img src="/images/Memory_AndroidSoruce/2.webp" alt="ListView的getView"></p><p>示例代码：</p><figure class="highlight cos"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">View</span> getView(int position, <span class="keyword">View</span> convertView, ViewGroup parent) &#123;</span><br><span class="line"> <span class="keyword">View</span> <span class="keyword">view</span> = <span class="keyword">new</span> Xxx(...)<span class="comment">;</span></span><br><span class="line"> ... ...</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">view</span><span class="comment">;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&#96;<br>示例修正代码：</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public <span class="keyword">View</span> getView(int position, <span class="keyword">View</span> convertView, ViewGroup parent) &#123;</span><br><span class="line"> <span class="keyword">View</span> <span class="keyword">view</span> = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (convertView != <span class="keyword">null</span>) &#123;</span><br><span class="line"> <span class="keyword">view</span> = convertView;</span><br><span class="line"> populate(<span class="keyword">view</span>, getItem(position));</span><br><span class="line"> ...</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> <span class="keyword">view</span> = new Xxx(...);</span><br><span class="line"> ...</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">view</span>;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>关于ListView的使用和优化，可以参考这两篇文章：</p><ul><li><a href="http://www.vogella.com/tutorials/AndroidListView/article.html">Using lists in Android (ListView) - Tutorial  </a></li><li><a href="http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder">Making ListView Scrolling Smooth</a></li></ul><h2 id="4-释放对象的引用"><a href="#4-释放对象的引用" class="headerlink" title="4 释放对象的引用"></a>4 释放对象的引用</h2><p>前面有说过，一个对象的内存没有被释放是因为他被其他的对象所引用，系统不回去释放这些有GC Root的对象。</p><p>示例A：<br>假设有如下操作</p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">public <span class="class"><span class="keyword">class</span> <span class="title">DemoActivity</span> <span class="keyword">extends</span> <span class="title">Activity</span> </span>&#123;</span><br><span class="line">... ...</span><br><span class="line"><span class="keyword">private</span> <span class="type">Handler</span> mHandler = ...</span><br><span class="line"><span class="keyword">private</span> <span class="type">Object</span> obj;</span><br><span class="line">public void operation() &#123;</span><br><span class="line"> obj = initObj();</span><br><span class="line"> ...</span><br><span class="line"> [<span class="type">Mark</span>]</span><br><span class="line"> mHandler.post(<span class="keyword">new</span> <span class="type">Runnable</span>() &#123;</span><br><span class="line">        public void run() &#123;</span><br><span class="line">         useObj(obj);</span><br><span class="line">        &#125;</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们有一个成员变量 obj，在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码中，即便是mHandler所在的线程使用完了obj所引用的对象，但这个对象仍然不会被垃圾回收掉，因为DemoActivity.obj还保有这个对象的引用。所以如果在DemoActivity中不再使用这个对象了，可以在[Mark]的位置释放对象的引用，而代码可以修改为：</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">operation</span>(<span class="params"></span>) &#123;</span><br><span class="line">obj = <span class="title function_">initObj</span>();</span><br><span class="line">...</span><br><span class="line">final <span class="title class_">Object</span> o = obj;</span><br><span class="line">obj = <span class="literal">null</span>;</span><br><span class="line">mHandler.<span class="title function_">post</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="built_in">void</span> <span class="title function_">run</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="title function_">useObj</span>(o);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>示例B：<br>假设我们希望在锁屏界面(LockScreen)中，监听系统中的电话服务以获取一些信息(如信号强度等)，则可以在LockScreen中定义一个PhoneStateListener的对象，同时将它注册到TelephonyManager服务中。对于LockScreen对象，当需要显示锁屏界面的时候就会创建一个LockScreen对象，而当锁屏界面消失的时候LockScreen对象就会被释放掉。  </p><p>但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象，则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失，则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_ui进程挂掉。</p><p>总之当一个生命周期较短的对象A，被一个生命周期较长的对象B保有其引用的情况下，在A的生命周期结束时，要在B中清除掉对A的引用。</p><p>使用MAT可以很方便地查看对象之间的引用，</p><h2 id="5-在Activity的生命周期中释放资源"><a href="#5-在Activity的生命周期中释放资源" class="headerlink" title="5 在Activity的生命周期中释放资源"></a>5 在Activity的生命周期中释放资源</h2><p>Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中，在onPause()、onStop()、onDestroy()方法中需要适当的释放资源的情况。由于此情况很基础，在此不详细说明，具体可以查看官方文档对Activity生命周期的介绍，以明确何时应该释放哪些资源。</p><h2 id="6-消除过渡绘制"><a href="#6-消除过渡绘制" class="headerlink" title="6 消除过渡绘制"></a>6 消除过渡绘制</h2><p>过渡绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景，那么显示文本的像素至少绘了两次，一次是背景，一次是文本。GPU过度绘制或多或少对性能有些影响,设备的内存带宽是有限的，当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的限制每个设备都可能是不一样的。</p><p>过渡绘制的原因:</p><ol><li>同一层级的View叠加</li><li>复杂的层级叠加</li></ol><p>减少过渡绘制能去掉一些无用的View，能有效减少GPU的负载，也可以减轻一部分内存压力。关于过渡绘制我专门写了一篇文章来介绍：<a href="https://www.androidperformance.com/android-performance-optimization-overdraw-1.html">过渡绘制及其优化</a></p><h2 id="7-使用Android系统自带的资源"><a href="#7-使用Android系统自带的资源" class="headerlink" title="7 使用Android系统自带的资源"></a>7 使用Android系统自带的资源</h2><p>在Android应用开发过程中，屏幕上控件的布局代码和程序的逻辑代码通常是分开的。界面的布局代码是放在一个独立的xml文件中的，这个文件里面是树型组织的，控制着页面的布局。通常，在这个页面中会用到很多控件，控件会用到很多的资源。Android系统本身有很多的资源，包括各种各样的字符串、图片、动画、样式和布局等等，这些都可以在应用程序中直接使用。这样做的好处很多，既可以减少内存的使用，又可以减少部分工作量，也可以缩减程序安装包的大小。</p><p>比如下面的代码就是使用系统的ListView：</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;ListView </span><br><span class="line">    android:<span class="attribute">id</span>=<span class="string">&quot;@android:id/list&quot;</span></span><br><span class="line">    android:<span class="attribute">layout_width</span>=<span class="string">&quot;fill_parent&quot;</span></span><br><span class="line">    android:<span class="attribute">layout_height</span>=<span class="string">&quot;fill_parent&quot;</span>/&gt;</span><br></pre></td></tr></table></figure><h2 id="8-使用内存相关工具检测"><a href="#8-使用内存相关工具检测" class="headerlink" title="8 使用内存相关工具检测"></a>8 使用内存相关工具检测</h2><p>在开发中，不可能保证一次就开发出一个内存管理非常棒的应用，所以在开发的每一个阶段，都要有意识地去针对内存进行专门的检查。目前Android提供了许多布局、内存相关的工具，比如Lint、MAT等。学会这些工具的使用是一个Android开发者必不可少的技能。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 内存优化系列文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/&quot;&gt;Android代码内存优化建议-Android官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/&quot;&gt;Android代码内存优化建议-Java官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/&quot;&gt;Android代码内存优化建议-Android资源篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/&quot;&gt;Android代码内存优化建议-OnTrimMemory优化&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;这篇文章主要介绍在实际Android应用程序的开发中，容易导致内存泄露的一些情况。开发人员如果在进行代码编写之前就有内存泄露方面的基础知识，那么写出来的代码会强壮许多，写这篇文章也是这个初衷。本文从Android开发中的资源使用情况入手，介绍了如何在Bitmap、数据库查询、9-patch、过渡绘制等方面优化内存的使用。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Android 代码内存优化建议 - Android 官方篇</title>
    <link href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Google/"/>
    <id>https://androidperformance.com/2015/07/20/Android-Performance-Memory-Google/</id>
    <published>2015-07-20T03:29:32.000Z</published>
    <updated>2026-02-07T05:17:47.839Z</updated>
    
    <content type="html"><![CDATA[<p>Android 内存优化系列文章：</p><ol><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android代码内存优化建议-Android官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android代码内存优化建议-Java官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android代码内存优化建议-Android资源篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android代码内存优化建议-OnTrimMemory优化</a></li></ol><hr><p>为了使垃圾回收器可以正常释放程序所占用的内存，在编写代码的时候就一定要注意尽量避免出现内存泄漏的情况（通常都是由于全局成员变量持有对象引用所导致的），并且在适当的时候去释放对象引用。对于大多数的应用程序而言，后面其它的事情就可以都交给垃圾回收器去完成了，如果一个对象的引用不再被其它对象所持有，那么系统就会将这个对象所分配的内存进行回收。</p><p>我们在开发软件的时候应当自始至终都把内存的问题充分考虑进去，这样的话才能开发出更加高性能的软件。而内存问题也并不是无规律可行的，Android系统给我们提出了很多内存优化的建议技巧，只要按照这些技巧来编写程序，就可以让我们的程序在内存性能发面表现得相当不错。</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>本文原文来自Android开发者官网<a href="http://developer.android.com/training/articles/memory.html">Managing Your App’s Memory</a>章节中的<br><a href="http://developer.android.com/training/articles/memory.html#YourApp">How Your App Should Manage Memory</a>部分。是Android官方帮助应用开发者更好地管理应用的内存而写的。作为一个应用程序开发者，你需要在你开发应用程序的时时刻刻都考虑内存问题。</p><h3 id="1-节制地使用Service"><a href="#1-节制地使用Service" class="headerlink" title="1. 节制地使用Service"></a>1. 节制地使用Service</h3><p>如果应用程序当中需要使用Service来执行后台任务的话，请一定要注意只有当任务正在执行的时候才应该让Service运行起来。另外，当任务执行完之后去停止Service的时候，要小心Service停止失败导致内存泄漏的情况。</p><p>当我们启动一个Service时，系统会倾向于将这个Service所依赖的进程进行保留，这样就会导致这个进程变得非常消耗内存。并且，系统可以在LRU cache当中缓存的进程数量也会减少，导致切换应用程序的时候耗费更多性能。严重的话，甚至有可能会导致崩溃，因为系统在内存非常吃紧的时候可能已无法维护所有正在运行的Service所依赖的进程了。</p><p>为了能够控制Service的生命周期，Android官方推荐的最佳解决方案就是使用<a href="http://developer.android.com/reference/android/app/IntentService.html"><strong>IntentService</strong></a>，这种Service的最大特点就是当后台任务执行结束后会自动停止，从而极大程度上避免了Service内存泄漏的可能性。</p><p>让一个Service在后台一直保持运行，即使它并不执行任何工作，这是编写Android程序时最糟糕的做法之一。所以Android官方极度建议开发人员们不要过于贪婪，让Service在后台一直运行，这不仅可能会导致手机和程序的性能非常低下，而且被用户发现了之后也有可能直接导致我们的软件被卸载</p><h3 id="2-当界面不可见时释放内存"><a href="#2-当界面不可见时释放内存" class="headerlink" title="2. 当界面不可见时释放内存"></a>2. 当界面不可见时释放内存</h3><p>当用户打开了另外一个程序，我们的程序界面已经不再可见的时候，我们应当将所有和界面相关的资源进行释放。在这种场景下释放资源可以让系统缓存后台进程的能力显著增加，因此也会让用户体验变得更好。<br>那么我们如何才能知道程序界面是不是已经不可见了呢？其实很简单，只需要在Activity中重写onTrimMemory()方法，然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别，一旦触发了之后就说明用户已经离开了我们的程序，那么此时就可以进行资源释放操作了，如下所示：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onTrimMemory</span><span class="params">(<span class="type">int</span> level)</span> &#123;  </span><br><span class="line">    <span class="built_in">super</span>.onTrimMemory(level);  </span><br><span class="line">    <span class="keyword">switch</span> (level) &#123;  </span><br><span class="line">    <span class="keyword">case</span> TRIM_MEMORY_UI_HIDDEN:  </span><br><span class="line">        <span class="comment">// 进行资源释放操作  </span></span><br><span class="line">        <span class="keyword">break</span>;  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>注意onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回调只有当我们程序中的所有UI组件全部不可见的时候才会触发，这和onStop()方法还是有很大区别的，因为onStop()方法只是当一个Activity完全不可见的时候就会调用，比如说用户打开了我们程序中的另一个Activity。因此，我们可以在onStop()方法中去释放一些Activity相关的资源，比如说取消网络连接或者注销广播接收器等，但是像UI相关的资源应该一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)这个回调之后才去释放，这样可以保证如果用户只是从我们程序的一个Activity回到了另外一个Activity，界面相关的资源都不需要重新加载，从而提升响应速度。</p><h3 id="3-当内存紧张时释放内存"><a href="#3-当内存紧张时释放内存" class="headerlink" title="3.当内存紧张时释放内存"></a>3.当内存紧张时释放内存</h3><p>除了刚才讲的<strong>TRIM_MEMORY_UI_HIDDEN</strong>这个回调，onTrimMemory()方法还有很多种其它类型的回调，可以在手机内存降低的时候及时通知我们。我们应该根据回调中传入的级别来去决定如何释放应用程序的资源：</p><h4 id="3-1-应用程序正在运行时"><a href="#3-1-应用程序正在运行时" class="headerlink" title="3.1 应用程序正在运行时"></a>3.1 应用程序正在运行时</h4><p><strong>TRIM_MEMORY_RUNNING_MODERATE</strong> 表示应用程序正常运行，并且不会被杀掉。但是目前手机的内存已经有点低了，系统可能会开始根据LRU缓存规则来去杀死进程了。<br><strong>TRIM_MEMORY_RUNNING_LOW</strong> 表示应用程序正常运行，并且不会被杀掉。但是目前手机的内存已经非常低了，我们应该去释放掉一些不必要的资源以提升系统的性能，同时这也会直接影响到我们应用程序的性能。<br><strong>TRIM_MEMORY_RUNNING_CRITICAL</strong> 表示应用程序仍然正常运行，但是系统已经根据LRU缓存规则杀掉了大部分缓存的进程了。这个时候我们应当尽可能地去释放任何不必要的资源，不然的话系统可能会继续杀掉所有缓存中的进程，并且开始杀掉一些本来应当保持运行的进程，比如说后台运行的服务。</p><h4 id="3-2-应用程序被缓存"><a href="#3-2-应用程序被缓存" class="headerlink" title="3.2 应用程序被缓存"></a>3.2 应用程序被缓存</h4><p><strong>TRIM_MEMORY_BACKGROUND</strong> 表示手机目前内存已经很低了，系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置，是不太可能被清理掉的，但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足，从而让我们的程序更长时间地保留在缓存当中，这样当用户返回我们的程序时会感觉非常顺畅，而不是经历了一次重新启动的过程。<br><strong>TRIM_MEMORY_MODERATE</strong> 表示手机目前内存已经很低了，并且我们的程序处于LRU缓存列表的中间位置，如果手机内存还得不到进一步释放的话，那么我们的程序就有被系统杀掉的风险了。<br><strong>TRIM_MEMORY_COMPLETE</strong> 表示手机目前内存已经很低了，并且我们的程序处于LRU缓存列表的最边缘位置，系统会最优先考虑杀掉我们的应用程序，在这个时候应当尽可能地把一切可以释放的东西都进行释放。</p><p>因为onTrimMemory()是在API14才加进来的，所以如果要支持API14之前的话，则可以考虑 <a href="http://developer.android.com/reference/android/content/ComponentCallbacks.html#onLowMemory()">onLowMemory()</a>这个方法，它粗略的相等于onTrimMemory()回调的TRIM_MEMORY_COMPLETE事件。</p><blockquote><p>注意：当系统安装LRU cache杀进程的时候，尽管大部分时间是从下往上按顺序杀，有时候系统也会将占用内存比较大的进程纳入被杀范围，以尽快得到足够的内存。所以你的应用在LRU list中占用的内存越少，你就越能避免被杀掉，当你恢复的时候也会更快。</p></blockquote><h3 id="4-检查你应该使用多少的内存"><a href="#4-检查你应该使用多少的内存" class="headerlink" title="4. 检查你应该使用多少的内存"></a>4. 检查你应该使用多少的内存</h3><p>正如前面提到的，每一个Android设备都会有不同的RAM总大小与可用空间，因此不同设备为app提供了不同大小的heap限制。你可以通过调用<a href="http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass()">getMemoryClass()</a>来获取你的app的可用heap大小。如果你的app尝试申请更多的内存，会出现OutOfMemory的错误。</p><p>在一些特殊的情景下，你可以通过在manifest的application标签下添加largeHeap&#x3D;true的属性来声明一个更大的heap空间。如果你这样做，你可以通过<a href="http://developer.android.com/reference/android/app/ActivityManager.html#getLargeMemoryClass()">getLargeMemoryClass()</a>来获取到一个更大的heap size。</p><p>然而，能够获取更大heap的设计本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。<strong>不要轻易的因为你需要使用大量的内存而去请求一个大的heap size。</strong>只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap. 因此请尽量少使用large heap。使用额外的内存会影响系统整体的用户体验，并且会使得GC的每次运行时间更长。在任务切换时，系统的性能会变得大打折扣。</p><p>另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上，large heap的大小和通常的heap size是一样的。因此即使你申请了large heap，你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。</p><h3 id="5-避免在Bitmap上浪费内存"><a href="#5-避免在Bitmap上浪费内存" class="headerlink" title="5. 避免在Bitmap上浪费内存"></a>5. 避免在Bitmap上浪费内存</h3><p>当我们读取一个Bitmap图片的时候，有一点一定要注意，就是千万不要去加载不需要的分辨率。在一个很小的ImageView上显示一张高分辨率的图片不会带来任何视觉上的好处，但却会占用我们相当多宝贵的内存。需要仅记的一点是，将一张图片解析成一个Bitmap对象时所占用的内存并不是这个图片在硬盘中的大小，可能一张图片只有100k你觉得它并不大，但是读取到内存当中是按照像素点来算的，比如这张图片是1500<em>1000像素，使用的ARGB_8888颜色类型，那么每个像素点就会占用4个字节，总内存就是1500</em>1000*4字节，也就是5.7M，这个数据看起来就比较恐怖了。</p><h3 id="6-使用优化过的数据集合"><a href="#6-使用优化过的数据集合" class="headerlink" title="6. 使用优化过的数据集合"></a>6. 使用优化过的数据集合</h3><p>利用Android Framework里面优化过的容器类，例如<a href="http://developer.android.com/reference/android/util/SparseArray.html">SparseArray</a>, <a href="http://developer.android.com/reference/android/util/SparseBooleanArray.html">SparseBooleanArray</a>, 与 <a href="http://developer.android.com/reference/android/support/v4/util/LongSparseArray.html">LongSparseArray</a>。 通常的HashMap的实现方式更加消耗内存，因为它需要一个额外的实例对象来记录Mapping操作。另外，SparseArray更加高效在于他们避免了对key与value的autobox自动装箱，并且避免了装箱后的解箱。</p><h3 id="7-知晓内存的开支情况"><a href="#7-知晓内存的开支情况" class="headerlink" title="7. 知晓内存的开支情况"></a>7. 知晓内存的开支情况</h3><p>我们还应当清楚我们所使用语言的内存开支和消耗情况，并且在整个软件的设计和开发当中都应该将这些信息考虑在内。可能有一些看起来无关痛痒的写法，结果却会导致很大一部分的内存开支，例如：</p><ul><li>使用枚举通常会比使用静态常量要消耗两倍以上的内存，在Android开发当中我们应当尽可能地不使用枚举。</li><li>任何一个Java类，包括内部类、匿名类，都要占用大概500字节的内存空间。</li><li>任何一个类的实例要消耗12-16字节的内存开支，因此频繁创建实例也是会一定程序上影响内存的。</li><li>在使用HashMap时，即使你只设置了一个基本数据类型的键，比如说int，但是也会按照对象的大小来分配内存，大概是32字节，而不是4字节。因此最好的办法就是像上面所说的一样，使用优化过的数据集合。</li></ul><h3 id="8-谨慎使用抽象编程"><a href="#8-谨慎使用抽象编程" class="headerlink" title="8. 谨慎使用抽象编程"></a>8. 谨慎使用抽象编程</h3><p>许多程序员都喜欢各种使用抽象来编程，认为这是一种很好的编程习惯。当然，这一点不可否认，因为的抽象的编程方法更加面向对象，而且在代码的维护和可扩展性方面都会有所提高。但是，在Android上使用抽象会带来额外的内存开支，因为抽象的编程方法需要编写额外的代码，虽然这些代码根本执行不到，但是却也要映射到内存当中，不仅占用了更多的内存，在执行效率方面也会有所降低。当然这里我并不是提倡大家完全不使用抽象编程，而是谨慎使用抽象编程，不要认为这是一种很酷的编程方式而去肆意使用它，只在你认为有必要的情况下才去使用。</p><h3 id="9-为序列化的数据使用nano-protobufs"><a href="#9-为序列化的数据使用nano-protobufs" class="headerlink" title="9. 为序列化的数据使用nano protobufs"></a>9. 为序列化的数据使用nano protobufs</h3><p><a href="https://developers.google.com/protocol-buffers/docs/overview">Protocol buffers</a>是由Google为序列化结构数据而设计的，一种语言无关，平台无关，具有良好扩展性的协议。类似XML，却比XML更加轻量，快速，简单。如果你需要为你的数据实现协议化，你应该在客户端的代码中总是使用nano protobufs。通常的协议化操作会生成大量繁琐的代码，这容易给你的app带来许多问题：增加RAM的使用量，显著增加APK的大小，更慢的执行速度，更容易达到DEX的字符限制。</p><p>关于更多细节，请参考<a href="https://android.googlesource.com/platform/external/protobuf/+/master/java/README.txt">protobuf readme</a>的”Nano version”章节。</p><h3 id="10-尽量避免使用依赖注入框架"><a href="#10-尽量避免使用依赖注入框架" class="headerlink" title="10. 尽量避免使用依赖注入框架"></a>10. 尽量避免使用依赖注入框架</h3><p>现在有很多人都喜欢在Android工程当中使用依赖注入框架，比如说像Guice或者RoboGuice等，因为它们可以简化一些复杂的编码操作，比如可以将下面的一段代码：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> AndroidWay extends Activity &#123;   </span><br><span class="line">    TextView name;   </span><br><span class="line">    ImageView thumbnail;   </span><br><span class="line">    LocationManager loc;   </span><br><span class="line">    Drawable icon;   </span><br><span class="line">    String myName;   </span><br><span class="line">  </span><br><span class="line">    public void on<span class="constructor">Create(Bundle <span class="params">savedInstanceState</span>)</span> &#123;   </span><br><span class="line">        super.on<span class="constructor">Create(<span class="params">savedInstanceState</span>)</span>;   </span><br><span class="line">        set<span class="constructor">ContentView(R.<span class="params">layout</span>.<span class="params">main</span>)</span>;  </span><br><span class="line">        name      = (TextView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">name</span>)</span>;   </span><br><span class="line">        thumbnail = (ImageView) find<span class="constructor">ViewById(R.<span class="params">id</span>.<span class="params">thumbnail</span>)</span>;   </span><br><span class="line">        loc       = (LocationManager) get<span class="constructor">SystemService(Activity.LOCATION_SERVICE)</span>;   </span><br><span class="line">        icon      = get<span class="constructor">Resources()</span>.get<span class="constructor">Drawable(R.<span class="params">drawable</span>.<span class="params">icon</span>)</span>;   </span><br><span class="line">        myName    = get<span class="constructor">String(R.<span class="params">string</span>.<span class="params">app_name</span>)</span>;   </span><br><span class="line">        name.set<span class="constructor">Text( <span class="string">&quot;Hello, &quot;</span> + <span class="params">myName</span> )</span>;   </span><br><span class="line">    &#125;   </span><br><span class="line">&#125;   </span><br></pre></td></tr></table></figure><p>简化成这样的一种写法：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">@<span class="constructor">ContentView(R.<span class="params">layout</span>.<span class="params">main</span>)</span>  </span><br><span class="line"><span class="keyword">class</span> RoboWay extends RoboActivity &#123;   </span><br><span class="line">    @<span class="constructor">InjectView(R.<span class="params">id</span>.<span class="params">name</span>)</span>             TextView name;   </span><br><span class="line">    @<span class="constructor">InjectView(R.<span class="params">id</span>.<span class="params">thumbnail</span>)</span>        ImageView thumbnail;   </span><br><span class="line">    @<span class="constructor">InjectResource(R.<span class="params">drawable</span>.<span class="params">icon</span>)</span>   Drawable icon;   </span><br><span class="line">    @<span class="constructor">InjectResource(R.<span class="params">string</span>.<span class="params">app_name</span>)</span> String myName;   </span><br><span class="line">    @Inject                            LocationManager loc;   </span><br><span class="line">  </span><br><span class="line">    public void on<span class="constructor">Create(Bundle <span class="params">savedInstanceState</span>)</span> &#123;   </span><br><span class="line">        super.on<span class="constructor">Create(<span class="params">savedInstanceState</span>)</span>;   </span><br><span class="line">        name.set<span class="constructor">Text( <span class="string">&quot;Hello, &quot;</span> + <span class="params">myName</span> )</span>;   </span><br><span class="line">    &#125;   </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><p>看上去确实十分诱人，我们甚至可以将findViewById()这一类的繁琐操作全部省去了。但是这些框架为了要搜寻代码中的注解，通常都需要经历较长的初始化过程，并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间，可能要过很久之后才会得到释放，相较之下，也许多敲几行看似繁琐的代码才是更好的选择。</p><h3 id="11-谨慎使用external-libraries"><a href="#11-谨慎使用external-libraries" class="headerlink" title="11. 谨慎使用external libraries"></a>11. 谨慎使用external libraries</h3><p>很多External library的代码都不是为移动网络环境而编写的，在移动客户端则显示的效率不高。至少，当你决定使用一个external library的时候，你应该针对移动网络做繁琐的porting与maintenance的工作。</p><p>即使是针对Android而设计的library，也可能是很危险的，因为每一个library所做的事情都是不一样的。例如，其中一个lib使用的是nano protobufs, 而另外一个使用的是micro protobufs。那么这样，在你的app里面就有2种protobuf的实现方式。这样的冲突同样可能发生在输出日志，加载图片，缓存等等模块里面。</p><p>同样不要陷入为了1个或者2个功能而导入整个library的陷阱。如果没有一个合适的库与你的需求相吻合，你应该考虑自己去实现，而不是导入一个大而全的解决方案。</p><h3 id="12-优化整体性能"><a href="#12-优化整体性能" class="headerlink" title="12. 优化整体性能"></a>12. 优化整体性能</h3><p>官方有列出许多优化整个app性能的文章：<a href="http://developer.android.com/training/best-performance.html">Best Practices for Performance</a>. 这篇文章就是其中之一。有些文章是讲解如何优化app的CPU使用效率，有些是如何优化app的内存使用效率。</p><p>你还应该阅读<a href="http://developer.android.com/tools/debugging/debugging-ui.html">optimizing your UI</a>来为layout进行优化。同样还应该关注lint工具所提出的建议，进行优化。</p><h3 id="13-使用ProGuard来剔除不需要的代码"><a href="#13-使用ProGuard来剔除不需要的代码" class="headerlink" title="13. 使用ProGuard来剔除不需要的代码"></a>13. 使用ProGuard来剔除不需要的代码</h3><p><a href="http://developer.android.com/tools/help/proguard.html">ProGuard</a>能够通过移除不需要的代码，重命名类，域与方法等方对代码进行压缩，优化与混淆。使用ProGuard可以是的你的代码更加紧凑，这样能够使用更少mapped代码所需要的RAM。</p><h3 id="14-对最终的APK使用zipalign"><a href="#14-对最终的APK使用zipalign" class="headerlink" title="14. 对最终的APK使用zipalign"></a>14. 对最终的APK使用zipalign</h3><p>在编写完所有代码，并通过编译系统生成APK之后，你需要使用<a href="http://developer.android.com/tools/help/zipalign.html">zipalign</a>对APK进行重新校准。如果你不做这个步骤，会导致你的APK需要更多的RAM，因为一些类似图片资源的东西不能被mapped。</p><p>**Notes::**Google Play不接受没有经过zipalign的APK。</p><h3 id="15-分析你的RAM使用情况"><a href="#15-分析你的RAM使用情况" class="headerlink" title="15. 分析你的RAM使用情况"></a>15. 分析你的RAM使用情况</h3><p>一旦你获取到一个相对稳定的版本后，需要分析你的app整个生命周期内使用的内存情况，并进行优化，更多细节请参考<a href="http://developer.android.com/tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a>.</p><h3 id="16-使用多进程"><a href="#16-使用多进程" class="headerlink" title="16. 使用多进程"></a>16. 使用多进程</h3><p>如果合适的话，有一个更高级的技术可以帮助你的app管理内存使用：通过把你的app组件切分成多个组件，运行在不同的进程中。这个技术必须谨慎使用，大多数app都不应该运行在多个进程中。因为如果使用不当，它会显著增加内存的使用，而不是减少。当你的app需要在后台运行与前台一样的大量的任务的时候，可以考虑使用这个技术。</p><p>一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个app运行在一个进程中，当后台播放的时候，前台的那些UI资源也没有办法得到释放。类似这样的app可以切分成2个进程：一个用来操作UI，另外一个用来后台的Service.</p><p>你可以通过在manifest文件中声明’android:process’属性来实现某个组件运行在另外一个进程的操作。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">service</span> <span class="attr">android:name</span>=<span class="string">&quot;.PlaybackService&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">android:process</span>=<span class="string">&quot;:background&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>更多关于使用这个技术的细节，请参考原文，链接如下。<br><a href="http://developer.android.com/training/articles/memory.html">http://developer.android.com/training/articles/memory.html</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 内存优化系列文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/&quot;&gt;Android代码内存优化建议-Android官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/&quot;&gt;Android代码内存优化建议-Java官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/&quot;&gt;Android代码内存优化建议-Android资源篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/&quot;&gt;Android代码内存优化建议-OnTrimMemory优化&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;为了使垃圾回收器可以正常释放程序所占用的内存，在编写代码的时候就一定要注意尽量避免出现内存泄漏的情况（通常都是由于全局成员变量持有对象引用所导致的），并且在适当的时候去释放对象引用。对于大多数的应用程序而言，后面其它的事情就可以都交给垃圾回收器去完成了，如果一个对象的引用不再被其它对象所持有，那么系统就会将这个对象所分配的内存进行回收。&lt;/p&gt;
&lt;p&gt;我们在开发软件的时候应当自始至终都把内存的问题充分考虑进去，这样的话才能开发出更加高性能的软件。而内存问题也并不是无规律可行的，Android系统给我们提出了很多内存优化的建议技巧，只要按照这些技巧来编写程序，就可以让我们的程序在内存性能发面表现得相当不错。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 代码内存优化建议 - Java 官方篇</title>
    <link href="https://androidperformance.com/2015/07/20/Android-Performance-Memory-Java/"/>
    <id>https://androidperformance.com/2015/07/20/Android-Performance-Memory-Java/</id>
    <published>2015-07-20T03:26:09.000Z</published>
    <updated>2026-02-07T05:17:47.840Z</updated>
    
    <content type="html"><![CDATA[<p>Android 内存优化系列文章：</p><ol><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/">Android代码内存优化建议-Android官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/">Android代码内存优化建议-Java官方篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/">Android代码内存优化建议-Android资源篇</a></li><li><a href="https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/">Android代码内存优化建议-OnTrimMemory优化</a></li></ol><hr><p>这篇文章主要是介绍了一些小细节的优化技巧，当这些小技巧综合使用起来的时候，对于整个App的性能提升还是有作用的，只是不能较大幅度的提升性能而已。选择合适的算法与数据结构才应该是你首要考虑的因素，在这篇文章中不会涉及这方面。你应该使用这篇文章中的小技巧作为平时写代码的习惯，这样能够提升代码的效率。</p><p>本文的原文为Google官方Training的性能优化部分，这一章节主要讲解的是高性能Android代码优化建议,建议所有Android应用开发者都仔细阅读这份文档，并将所提到的编码思想运用到实际的Android开发中。</p><span id="more"></span><blockquote><p>原文地址：<a href="http://developer.android.com/training/articles/perf-tips.html">http://developer.android.com/training/articles/perf-tips.html</a></p></blockquote><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p>通常来说，高效的代码需要满足下面两个规则：</p><ul><li>不要做冗余的动作</li><li>如果能避免，尽量不要分配内存</li></ul><p>你会面临最棘手的一个问题是当你优化一个肯定会在多种类型的硬件上运行的应用程序。不同版本的VM在不同的处理器上运行速度不同。它甚至不是你可以简单地说“设备X因为F原因比设备Y快&#x2F;慢”那么简单,而且也不能简单地从一个设备拓展到另一个设备。特别提醒的是模拟器在性能方面和其他的设备没有可比性。通常有JIT优化和没有JIT优化的设备之间存在巨大差异:经过JIT代码优化的设备并不一定比没有经过JIT代码优化的设备好。</p><p>代码的执行效果会受到设备CPU,设备内存,系统版本等诸多因素的影响。为了确保代码能够在不同设备上都运行良好，需要最大化代码的效率。</p><h2 id="1-避免创建不必要的对象"><a href="#1-避免创建不必要的对象" class="headerlink" title="1)避免创建不必要的对象"></a>1)避免创建不必要的对象</h2><p>虽然GC可以回收不用的对象，可是为这些对象分配内存，并回收它们同样是需要耗费资源的。<br>因此请尽量避免创建不必要的对象，有下面一些例子来说明这个问题：</p><ul><li>如果你需要返回一个String对象，并且你知道它最终会需要连接到一个StringBuffer，请修改你的实现方式，避免直接进行连接操作，应该采用创建一个临时对象来做这个操作.</li><li>当从输入的数据集中抽取出Strings的时候，尝试返回原数据的substring对象，而不是创建一个重复的对象。</li></ul><p>一个稍微激进点的做法是把所有多维的数据分解成1维的数组:</p><ul><li>一组int数据要比一组Integer对象要好很多。可以得知，两组1维数组要比一个2维数组更加的有效率。同样的，这个道理可以推广至其他原始数据类型。</li><li>如果你需要实现一个数组用来存放(Foo,Bar)的对象，尝试分解为Foo[]与Bar[]要比(Foo,Bar)好很多。(当然，为了某些好的API的设计，可以适当做一些妥协。但是在自己的代码内部，你应该多多使用分解后的容易。</li></ul><p>通常来说，需要避免创建更多的对象。更少的对象意味者更少的GC动作，GC会对用户体验有比较直接的影响。</p><h2 id="2-选择Static而不是Virtual"><a href="#2-选择Static而不是Virtual" class="headerlink" title="2)选择Static而不是Virtual"></a>2)选择Static而不是Virtual</h2><p>如果你不需要访问一个对象的值域,请保证这个方法是static类型的,这样方法调用将快15%-20%。这是一个好的习惯，因为你可以从方法声明中得知调用无法改变这个对象的状态。</p><h2 id="3-常量声明为Static-Final"><a href="#3-常量声明为Static-Final" class="headerlink" title="3)常量声明为Static Final"></a>3)常量声明为Static Final</h2><p>先看下面这种声明的方式</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="variable">intVal</span> <span class="operator">=</span> <span class="number">42</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="type">String</span> <span class="variable">strVal</span> <span class="operator">=</span> <span class="string">&quot;Hello, world!&quot;</span>;</span><br></pre></td></tr></table></figure><p>编译器会在类首次被使用到的时候，使用初始化<code>&lt;clinit&gt;</code>方法来初始化上面的值，之后访问的时候会需要先到它那里查找，然后才返回数据。我们可以使用static final来提升性能：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">intVal</span> <span class="operator">=</span> <span class="number">42</span>;</span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">strVal</span> <span class="operator">=</span> <span class="string">&quot;Hello, world!&quot;</span>;</span><br></pre></td></tr></table></figure><p>这时再也不需要上面的那个方法来做多余的查找动作了。<br>** 所以，请尽可能的为常量声明为static final类型的。**</p><h2 id="4-避免内部的Getters-x2F-Setters"><a href="#4-避免内部的Getters-x2F-Setters" class="headerlink" title="4)避免内部的Getters&#x2F;Setters"></a>4)避免内部的Getters&#x2F;Setters</h2><p>像C++等native language,通常使用getters(i &#x3D; getCount())而不是直接访问变量(i &#x3D; mCount).这是编写C++的一种优秀习惯，而且通常也被其他面向对象的语言所采用，例如C#与Java，因为编译器通常会做inline访问，而且你需要限制或者调试变量，你可以在任何时候在getter&#x2F;setter里面添加代码。<br><strong>然而，在Android上，这是一个糟糕的写法</strong>。Virtual method的调用比起直接访问变量要耗费更多。那么合理的做法是：<strong>在面向对象的设计当中应该使用getter&#x2F;setter，但是在类的内部你应该直接访问变量。</strong><br>没有<code>JIT(Just In Time Compiler)</code>时，直接访问变量的速度是调用getter的3倍。有JIT时,直接访问变量的速度是通过getter访问的7倍。<br>请注意，如果你使用<a href="http://developer.android.com/tools/help/proguard.html">ProGuard</a>, 你可以获得同样的效果，因为ProGuard可以为你inline accessors.</p><h2 id="5-使用增强的For循环写法"><a href="#5-使用增强的For循环写法" class="headerlink" title="5)使用增强的For循环写法"></a>5)使用增强的For循环写法</h2><p>请比较下面三种循环的方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Foo</span> &#123;</span><br><span class="line">    <span class="type">int</span> mSplat;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Foo[] mArray = ...</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">zero</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; mArray.length; ++i) &#123;</span><br><span class="line">        sum += mArray[i].mSplat;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">one</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    Foo[] localArray = mArray;</span><br><span class="line">    <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> localArray.length;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; len; ++i) &#123;</span><br><span class="line">        sum += localArray[i].mSplat;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">two</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (Foo a : mArray) &#123;</span><br><span class="line">        sum += a.mSplat;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>zero()是最慢的，因为JIT没有办法对它进行优化。</li><li>one()稍微快些。</li><li>two() 在没有做JIT时是最快的，可是如果经过JIT之后，与方法one()是差不多一样快的。它使用了增强的循环方法for-each。</li></ul><p>所以请尽量使用for-each的方法，但是对于ArrayList，请使用方法one()。</p><h2 id="6-使用包级访问而不是内部类的私有访问"><a href="#6-使用包级访问而不是内部类的私有访问" class="headerlink" title="6)使用包级访问而不是内部类的私有访问"></a>6)使用包级访问而不是内部类的私有访问</h2><p>参考下面一段代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Foo</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">Inner</span> &#123;</span><br><span class="line">        <span class="keyword">void</span> <span class="title function_">stuff</span><span class="params">()</span> &#123;</span><br><span class="line">            Foo.<span class="built_in">this</span>.doStuff(Foo.<span class="built_in">this</span>.mValue);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> mValue;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">Inner</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Inner</span>();</span><br><span class="line">        mValue = <span class="number">27</span>;</span><br><span class="line">        in.stuff();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doStuff</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Value is &quot;</span> + value);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里重要的是，我们定义了一个私有的内部类（Foo$Inner），它直接访问了外部类中的私有方法以及私有成员对象。这是合法的，这段代码也会如同预期一样打印出”Value is 27”。</p><p>问题是，VM因为Foo和Foo$Inner是不同的类，会认为在Foo$Inner中直接访问Foo类的私有成员是不合法的。即使Java语言允许内部类访问外部类的私有成员。为了去除这种差异，编译器会产生一些仿造函数：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*package*/</span> <span class="keyword">static</span> <span class="type">int</span> Foo.access$<span class="number">100</span>(Foo foo) &#123;</span><br><span class="line">    <span class="keyword">return</span> foo.mValue;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*package*/</span> <span class="keyword">static</span> <span class="keyword">void</span> Foo.access$<span class="number">200</span>(Foo foo, <span class="type">int</span> value) &#123;</span><br><span class="line">    foo.doStuff(value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每当内部类需要访问外部类中的mValue成员或需要调用doStuff()函数时，它都会调用这些静态方法。这意味着，上面的代码可以归结为，通过accessor函数来访问成员变量。早些时候我们说过，通过accessor会比直接访问域要慢。所以，这是一个特定语言用法造成性能降低的例子。</p><p>如果你正在性能热区（hotspot:高频率、重复执行的代码段）使用像这样的代码，你可以把内部类需要访问的域和方法声明为包级访问，而不是私有访问权限。不幸的是，这意味着在相同包中的其他类也可以直接访问这些域，所以在公开的API中你不能这样做。</p><h2 id="7-避免使用float类型"><a href="#7-避免使用float类型" class="headerlink" title="7)避免使用float类型"></a>7)避免使用float类型</h2><p>Android系统中float类型的数据存取速度是int类型的一半，尽量优先采用int类型。</p><h2 id="8-使用库函数"><a href="#8-使用库函数" class="headerlink" title="8)使用库函数"></a>8)使用库函数</h2><p>尽量使用System.arraycopy()等一些封装好的库函数，它的效率是手动编写copy实现的9倍多。</p><p>** Tip: Also see Josh Bloch’s Effective Java, item 47. **</p><h2 id="9-谨慎使用native函数"><a href="#9-谨慎使用native函数" class="headerlink" title="9)谨慎使用native函数"></a>9)谨慎使用native函数</h2><p>当你需要把已经存在的native code迁移到Android，请谨慎使用JNI。如果你要使用JNI,请学习<a href="http://developer.android.com/guide/practices/jni.html">JNI Tips</a></p><h2 id="10-关于性能的误区"><a href="#10-关于性能的误区" class="headerlink" title="10)关于性能的误区"></a>10)关于性能的误区</h2><p>在没有做JIT之前，使用一种确切的数据类型确实要比抽象的数据类型速度要更有效率。(例如，使用HashMap要比Map效率更高。) 有误传效率要高一倍，实际上只是6%左右。而且，在JIT之后，他们直接并没有大多差异。</p><h2 id="11-关于测量"><a href="#11-关于测量" class="headerlink" title="11)关于测量"></a>11)关于测量</h2><p>上面文档中出现的数据是Android的实际运行效果。我们可以用<a href="http://developer.android.com/tools/debugging/debugging-tracing.html">Traceview</a> 来测量，但是测量的数据是没有经过JIT优化的，所以实际的效果应该是要比测量的数据稍微好些。</p><p>关于如何测量与调试，还可以参考下面两篇文章：</p><ul><li><a href="http://developer.android.com/tools/debugging/debugging-tracing.html">Profiling with Traceview and dmtracedump</a></li><li><a href="http://developer.android.com/tools/debugging/systrace.html">Analysing Display and Performance with Systrace</a></li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Android 内存优化系列文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Google/&quot;&gt;Android代码内存优化建议-Android官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-Java/&quot;&gt;Android代码内存优化建议-Java官方篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-AndroidResource/&quot;&gt;Android代码内存优化建议-Android资源篇&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/07/20/Android-Performance-Memory-onTrimMemory/&quot;&gt;Android代码内存优化建议-OnTrimMemory优化&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;这篇文章主要是介绍了一些小细节的优化技巧，当这些小技巧综合使用起来的时候，对于整个App的性能提升还是有作用的，只是不能较大幅度的提升性能而已。选择合适的算法与数据结构才应该是你首要考虑的因素，在这篇文章中不会涉及这方面。你应该使用这篇文章中的小技巧作为平时写代码的习惯，这样能够提升代码的效率。&lt;/p&gt;
&lt;p&gt;本文的原文为Google官方Training的性能优化部分，这一章节主要讲解的是高性能Android代码优化建议,建议所有Android应用开发者都仔细阅读这份文档，并将所提到的编码思想运用到实际的Android开发中。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
  </entry>
  
  <entry>
    <title>Nexus6 with Android M 开启多窗口模式</title>
    <link href="https://androidperformance.com/2015/05/29/Nexus6-with-Android-M/"/>
    <id>https://androidperformance.com/2015/05/29/Nexus6-with-Android-M/</id>
    <published>2015-05-29T12:01:06.000Z</published>
    <updated>2026-02-07T05:17:47.855Z</updated>
    
    <content type="html"><![CDATA[<p>昨天的Google IO之后，Google放出了Android M Preview for Nexus6. 固件大家可以去Google的官网去下，下好了刷完之后，就可以体验一下最新的Android M了。下面是设置 里面的主界面和彩蛋界面:</p><span id="more"></span><p><img src="/images/nexus6/0.webp" alt="主界面"></p><p><img src="/images/nexus6/1.webp" alt="彩蛋"></p><h3 id="变化"><a href="#变化" class="headerlink" title="变化"></a>变化</h3><ol><li>Android M给人最大的不一样就是其抽屉的变化：</li></ol><p><img src="/images/nexus6/2.webp" alt="抽屉"></p><p>从图中可以看出,抽屉中的APP是使用字母进行索引的.最上面一排是最常用的的APP,目前是不可以定制的.</p><ol start="2"><li><p>另外一个感觉变化比较大的地方就是动画,文件夹动画和应用启动动画都变了,这个不好描述,大家回头可以自己去感受.</p></li><li><p>设置里面可以设置主题了,不过目前也就light和dark可以选择.</p></li></ol><p><img src="/images/nexus6/3.webp" alt="模式选择"></p><h2 id="多窗口"><a href="#多窗口" class="headerlink" title="多窗口"></a>多窗口</h2><p>Android M Preview官方固件里面默认是没有多窗口模式这个选项的.因为官方的固件是user版本的,而这个多窗口的模式是在userdebug中才能打开的. 所以必须要手动进行开启.</p><h3 id="刷入第三方Recovery"><a href="#刷入第三方Recovery" class="headerlink" title="刷入第三方Recovery"></a>刷入第三方Recovery</h3><h4 id="1-进入bootloader模式"><a href="#1-进入bootloader模式" class="headerlink" title="1. 进入bootloader模式"></a>1. 进入bootloader模式</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb reboot bootloader</span><br></pre></td></tr></table></figure><h4 id="2-刷入twrp-img"><a href="#2-刷入twrp-img" class="headerlink" title="2. 刷入twrp.img"></a>2. 刷入twrp.img</h4><p>这个twrp是从官网下载的，下载速度略慢，我把它放到了百度网盘，方便大家去下载：<a href="http://pan.baidu.com/s/1eQ5ysE2">twrp</a></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastboot flash recovery ~/Downloads/twrp-<span class="number">2.8</span><span class="number">.6</span><span class="number">.0</span>-shamu.img</span><br></pre></td></tr></table></figure><h4 id="3-Mount-System"><a href="#3-Mount-System" class="headerlink" title="3. Mount System"></a>3. Mount System</h4><p>刷入recovery之后不用重启手机，直接进入recovery模式，进入mount，将system这一项前面的对勾选上。这一步是为了可以改system目录里面的值。</p><h4 id="4-修改-x2F-system-x2F-build-prop"><a href="#4-修改-x2F-system-x2F-build-prop" class="headerlink" title="4. 修改&#x2F;system&#x2F;build.prop"></a>4. 修改&#x2F;system&#x2F;build.prop</h4><p>第三步结束后，使用adb devices查看是否可以看到手机。如果能正常看到手机，那么先进入shell：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell</span><br></pre></td></tr></table></figure><p>然后使用vi打开system&#x2F;build.prop</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi system/build.prop</span><br></pre></td></tr></table></figure><p>找到</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ro.build.type=user</span><br></pre></td></tr></table></figure><p>将其修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ro.build.type=userdebug</span><br></pre></td></tr></table></figure><p>保存后退出shell，这时候执行adb reboot重启机器就可以看到在开发者选项中多了一项：</p><p><img src="/images/nexus6/4.webp" alt="选择开启多窗口"></p><p>开启多窗口的方法是点击多任务按钮，选择一个任务窗口上面的三个框（选一个即可）<br>，第一个是在上面窗口，第二个是在下面的窗口，第三个是全屏。</p><p><img src="/images/nexus6/5.webp" alt="多窗口位置选择"></p><p>然后就可以看到效果了：</p><p><img src="/images/nexus6/6.webp" alt="多窗口效果"></p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>说是多窗口，其实目前只支持双窗口。不过双窗口的窗口利用率其实并不高，多窗口就更加呵呵呵了。这个就看Google之后如何发挥这个了。</p><p>其实多窗口三星早就实现了。而且每个窗口还可以动态调整大小，不过这并没有什么卵用。测评中提到的也不多。可见多窗口现在其实还只是一个炫技的功能。至于使用的话，可能还得要多迭代几次才可以。</p><p>另外这篇文章的简书地址在此：<a href="http://www.jianshu.com/p/8bb7b44930e3">Nexus6-with-Android-M开启多窗口模式</a> . 因为我写东西都是在简书上完成，然后才Copy到其他的地方，所以这里也给大家推荐一下简书吧。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;昨天的Google IO之后，Google放出了Android M Preview for Nexus6. 固件大家可以去Google的官网去下，下好了刷完之后，就可以体验一下最新的Android M了。下面是设置 里面的主界面和彩蛋界面:&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="Nexus" scheme="https://androidperformance.com/tags/Nexus/"/>
    
  </entry>
  
  <entry>
    <title>细说 Java 单例模式</title>
    <link href="https://androidperformance.com/2015/05/06/Java-Singleton/"/>
    <id>https://androidperformance.com/2015/05/06/Java-Singleton/</id>
    <published>2015-05-06T10:09:43.000Z</published>
    <updated>2026-02-07T05:17:47.855Z</updated>
    
    <content type="html"><![CDATA[<p>单例模式也叫单子模式，是一种常用的软件设计模式。在应用这个模式时，单例对象的类必须保证只有一个实例存在。本文就从单例模式的两种构建方式来带大家了解一下单例，最后介绍一种高级且简洁的单例模式。</p><span id="more"></span><h2 id="什么是单例模式"><a href="#什么是单例模式" class="headerlink" title="什么是单例模式"></a>什么是单例模式</h2><blockquote><p><a href="http://zh.wikipedia.org/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F">单例模式</a>，也叫单子模式，是一种常用的软件设计模式。在应用这个模式时，单例对象的类必须保证只有一个实例存在。</p></blockquote><p>中心原则就是：单例对象的类必须保证只有一个实例存在</p><h2 id="单例模式的构建"><a href="#单例模式的构建" class="headerlink" title="单例模式的构建"></a>单例模式的构建</h2><p>在java中主要有两种构建方式</p><ol><li>懒汉方式。指全局的单例实例在第一次被使用时构建。</li><li>饿汉方式。指全局的单例实例在类装载时构建。</li></ol><p>简单的说就是一个需要延迟初始化，一个则不需要。</p><p>比较简单的构建方式有：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">Singleton</span> <span class="variable">INSTANCE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Private constructor suppresses   </span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// default public constructor</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式实现简单，实例在类装载时构建，如果想要实现一种实例在第一次被使用时构建应该怎么做？</p><p>有一种叫做 双重检查锁(double-checked locking)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="type">Singleton</span> <span class="variable">INSTANCE</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Private constructor suppresses </span></span><br><span class="line">    <span class="comment">// default public constructor</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//thread safe and performance  promote </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (INSTANCE == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) &#123;</span><br><span class="line">                <span class="comment">//when more than two threads run into the first null check same time, to avoid instanced more than one time, it needs to be checked again.</span></span><br><span class="line">                <span class="keyword">if</span> (INSTANCE == <span class="literal">null</span>) &#123;</span><br><span class="line">                    INSTANCE = <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此种方法只能用在JDK5及以后版本(注意 INSTANCE 被声明为 volatile)，之前的版本使用“双重检查锁”会发生非预期行为.</p><h2 id="另一种单例模式"><a href="#另一种单例模式" class="headerlink" title="另一种单例模式"></a>另一种单例模式</h2><p>在第一条推荐阅读里提到了另一种实现单例的方式 lazy initialization holder class idiom</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Private constructor suppresses   </span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">LazyHolder</span> &#123;</span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Singleton</span> <span class="variable">INSTANCE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> LazyHolder.INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>JVM在类的初始化阶段（即在Class被加载后，且被线程使用之前），会执行类的初始化。在执行类的初始化期间，JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。相比其他实现方案（如double-checked locking等），该技术方案的实现代码较为简洁，并且在所有版本的编译器中都是可行的。</p><p>关于 static final Singleton INSTANCE 域的访问权限为什么时包级私有可以阅读: <a href="http://ifeve.com/initialization-on-demand-holder-idiom/">Initialization On Demand Holder idiom的实现探讨</a></p><h2 id="使用枚举"><a href="#使用枚举" class="headerlink" title="使用枚举"></a>使用枚举</h2><p>最后推荐实现最为简洁的一种方式: 使用枚举</p><p>代码极其简洁, 使用极其简单:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line">    INSTANCE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>回顾一下前面集中单例的实现方式, 都只考虑了常规获取类对象的手段, 然而还可以通过序列化和反射机制获取对象.上面两种方式如果实现了序列化接口 Serializable 就必须重写 readResolve() 方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Object <span class="title function_">readResolve</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> INSTANCE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>即使重写了 readResolve() 方法也会涉及某系域需要关键字 transient 的修饰, 具体讨论不再展开, 总之涉及序列化挺蛋疼. </p><p>关于防止反射暂时没有深入了解, 据了解: 因为反射的某些地方绕过了java机制的限制，private只在编译时进行权限的限制，但是在运行时是不存在这种权限的限制的, 此处仅供参考.</p><p>但是使用enum实现的单例自带防序列化与防反射功能, 详细参照枚举类反编译后代码(供参考)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> <span class="keyword">extends</span> <span class="title class_">Enum</span></span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">(String s, <span class="type">int</span> i)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">super</span>(s, i);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton[] values()</span><br><span class="line">    &#123;</span><br><span class="line">        Singleton asingleton[];</span><br><span class="line">        <span class="type">int</span> i;</span><br><span class="line">        Singleton asingleton1[];</span><br><span class="line">        System.arraycopy(asingleton = ENUM$VALUES, <span class="number">0</span>, asingleton1 = <span class="keyword">new</span> <span class="title class_">Singleton</span>[i = asingleton.length], <span class="number">0</span>, i);</span><br><span class="line">        <span class="keyword">return</span> asingleton1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">valueOf</span><span class="params">(String s)</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> (Singleton)Enum.valueOf(singleton/Singleton, s);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Singleton(String s, <span class="type">int</span> i, Singleton singleton)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">this</span>(s, i);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> Singleton INSTANCE;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Singleton ENUM$VALUES[];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> </span><br><span class="line">    &#123;</span><br><span class="line">        INSTANCE = <span class="keyword">new</span> <span class="title class_">Singleton</span>(<span class="string">&quot;INSTANCE&quot;</span>, <span class="number">0</span>) ;</span><br><span class="line">        ENUM$VALUES = (<span class="keyword">new</span> <span class="title class_">Singleton</span>[] &#123;</span><br><span class="line">            INSTANCE</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们实现的枚举都是继承了 java.lang.Enum 可以看出来单例的实现也是通过关键字 static 修饰的静态初始化块来实现.</p><p>那么为什么enum可以防御反射呢…很简单, 因为它是一个抽象类 public abstract class Singleton extends Enum 即使是反射机制也不能实例化了.</p><p>有为什么能防御序列化呢…这个要看java源码中对于对象序列化的处理.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">java.io.ObjectOutputStream</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">writeObject0</span><span class="params">(Object obj, <span class="type">boolean</span> unshared)</span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// remaining cases</span></span><br><span class="line">    <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">        writeString((String) obj, unshared);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cl.isArray()) &#123;</span><br><span class="line">        writeArray(obj, desc, unshared);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> Enum) &#123;</span><br><span class="line">        writeEnum((Enum) obj, desc, unshared);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (obj <span class="keyword">instanceof</span> Serializable) &#123;</span><br><span class="line">        writeOrdinaryObject(obj, desc, unshared);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (extendedDebugInfo) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NotSerializableException</span>(cl.getName() + <span class="string">&quot;\n&quot;</span> + debugInfoStack.toString());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NotSerializableException</span>(cl.getName());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Writes given enum constant to stream.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">writeEnum</span><span class="params">(Enum en, ObjectStreamClass desc, <span class="type">boolean</span> unshared)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    bout.writeByte(TC_ENUM);</span><br><span class="line">    <span class="type">ObjectStreamClass</span> <span class="variable">sdesc</span> <span class="operator">=</span> desc.getSuperDesc();</span><br><span class="line">    writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, <span class="literal">false</span>);</span><br><span class="line">    handles.assign(unshared ? <span class="literal">null</span> : en);</span><br><span class="line">    writeString(en.name(), <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看出enum在被序列化时时经过特殊处理的, 被序列化的仅仅是枚举的名字而已.所以可以猜测一下反序列的的代码实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"> java.io.ObjectOutputStream</span><br><span class="line"><span class="keyword">private</span> Object <span class="title function_">readObject0</span><span class="params">(<span class="type">boolean</span> unshared)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">switch</span> (tc) &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">case</span> TC_ENUM:</span><br><span class="line">        <span class="keyword">return</span> checkResolve(readEnum(unshared));</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Reads in and returns enum constant, or null if enum type is</span></span><br><span class="line"><span class="comment"> * unresolvable.  Sets passHandle to enum constant&#x27;s assigned handle.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Enum <span class="title function_">readEnum</span><span class="params">(<span class="type">boolean</span> unshared)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">if</span> (cl != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            en = Enum.valueOf(cl, name);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalArgumentException ex) &#123;</span><br><span class="line">            <span class="keyword">throw</span> (IOException) <span class="keyword">new</span> <span class="title class_">InvalidObjectException</span>(</span><br><span class="line">                <span class="string">&quot;enum constant &quot;</span> + name + <span class="string">&quot; does not exist in &quot;</span> +</span><br><span class="line">                cl).initCause(ex);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!unshared) &#123;</span><br><span class="line">            handles.setObject(enumHandle, en);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> en;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>反序列化也仅仅是通过name调用了方法 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Enum.valueOf(Class&lt;T&gt; enumType, String name)</span><br></pre></td></tr></table></figure><p>获取了一个枚举实例, 所以枚举也可以防止通过序列化产生新的单例.</p><p>友情建议: 在序列化枚举时要特别注意, 枚举的名称一定不能改变, 否则在反序列化时有可能会抛出异常!!!</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Enum</span>&lt;T&gt;&gt; T <span class="title function_">valueOf</span><span class="params">(Class&lt;T&gt; enumType,</span></span><br><span class="line"><span class="params">                                            String name)</span> &#123;</span><br><span class="line">    <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> enumType.enumConstantDirectory().get(name);</span><br><span class="line">    <span class="keyword">if</span> (result != <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    <span class="keyword">if</span> (name == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>(<span class="string">&quot;Name is null&quot;</span>);</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(</span><br><span class="line">        <span class="string">&quot;No enum constant &quot;</span> + enumType.getCanonicalName() + <span class="string">&quot;.&quot;</span> + name);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读:"></a>推荐阅读:</h2><ol><li>Effective Java 第71条 慎用延迟初始化</li><li>Core Java 第一卷 14.5.8 Volatile 域</li><li><a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4">JSL 17.4</a></li><li><a href="http://www.ibm.com/developerworks/cn/java/j-jtp06197.html">Java 理论与实践: 正确使用 Volatile 变量</a></li><li><a href="http://ifeve.com/double-checked-locking-with-delay-initialization/">双重检查锁定与延迟初始化</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;单例模式也叫单子模式，是一种常用的软件设计模式。在应用这个模式时，单例对象的类必须保证只有一个实例存在。本文就从单例模式的两种构建方式来带大家了解一下单例，最后介绍一种高级且简洁的单例模式。&lt;/p&gt;</summary>
    
    
    
    <category term="Java" scheme="https://androidperformance.com/categories/Java/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="Singleton" scheme="https://androidperformance.com/tags/Singleton/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化典范 - Profile GPU Rendering</title>
    <link href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-4/"/>
    <id>https://androidperformance.com/2015/04/19/Android-Performance-Patterns-4/</id>
    <published>2015-04-19T07:53:31.000Z</published>
    <updated>2026-02-07T05:17:47.842Z</updated>
    
    <content type="html"><![CDATA[<p>系列文章目录：</p><ol><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li></ol><hr><blockquote><p>“If you can measure it, you can optimize it” is a common term in the computing world, and for Android’s rendering system, the same thing holds true. In order to optimize your pipeline to be more efficient for rendering, you need a tool to give you feedback on where the current perf problems lie.</p></blockquote><blockquote><p>And in this video, +Colt McAnlis walks you through an on-device tool that’s built for this exact reason. “Profile GPU Rendering” will help you understand the stages of the rendering pipeline, and also get a chance to see what portions of it might be taking too long, and what you can do about it for your application.</p></blockquote><h2 id="GPU-Profile工具"><a href="#GPU-Profile工具" class="headerlink" title="GPU Profile工具"></a>GPU Profile工具</h2><p>渲染性能问题往往是偷取你宝贵帧数的罪魁祸首,这种问题很容易产生,很容易出现,而且在一个非常方便的工具的帮助下,也非常容易去追踪. 使用Peofile GPU Rendering tool,你可以在手机上就可以看到究竟是什么导致你的应用程序出现卡顿,变慢的情况.</p><span id="more"></span><p>这个工具在设置-开发者选项-Profile GPU rendering选项,打开后选择on screen as bars:</p><p><img src="/images/performance-4/1.webp" alt="Profile GPU rendering"></p><p>然后手机屏幕上就会出现三个颜色组成的小柱状图,以及一条绿线:</p><p><img src="/images/performance-4/2.webp" alt="gpu工具"></p><p>这个工具会在屏幕上显示经过分析后的图形数据,最底部的图显示的是Navigation的相关信息,最上面显示的是Notification的相关信息,中间的图显示的是当前应用程序的图.</p><h2 id="使用GPU-Profile工具"><a href="#使用GPU-Profile工具" class="headerlink" title="使用GPU Profile工具"></a>使用GPU Profile工具</h2><p>当你的应用程序在运行时,你会看到一排柱状图在屏幕上,从左到右动态地显示,<strong>每一个垂直的柱状图代表一帧的渲染,越长的垂直柱状图表示这一帧需要渲染的时间越长</strong>.随着需要渲染的帧数越来越多,他们会堆积在一起,这样你就可以观察到这段时间帧率的变化.</p><h3 id="绿线"><a href="#绿线" class="headerlink" title="绿线"></a>绿线</h3><p>下图中的绿线代表16ms,要确保一秒内打到60fps,你需要确保这些帧的每一条线都在绿色的16ms标记线之下.任何时候你看到一个竖线超过了绿色的标记现,你就会看到你的动画有卡顿现象产生.</p><p><img src="/images/performance-4/3.webp" alt="绿线"></p><h3 id="柱状图"><a href="#柱状图" class="headerlink" title="柱状图"></a>柱状图</h3><p>每一条柱状图都由三种颜色组成: 蓝-红-黄. 这些线直接和Android的渲染流水线和他实际运行帧数的时间关联:</p><p><img src="/images/performance-4/4.webp" alt="柱状图"></p><ul><li><p>蓝色代表测量绘制的时间,或者说它代表需要多长时间去创建和更新你的DisplayList.在Android中,一个视图在可以实际的进行渲染之前,它必须被转换成GPU所熟悉的格式,简单来说就是几条绘图命令,复杂点的可能是你的自定义的View嵌入了自定义的Path. 一旦完成,结果会作为一个DisplayList对象被系统送入缓存,蓝色就是记录了需要花费多长时间在屏幕上更新视图(说白了就是执行每一个View的onDraw方法,创建或者更新每一个View的Display List对象).</p><p><img src="/images/performance-4/5.webp" alt="Draw Phase"></p><p>当你看到蓝色的线很高的时候,<strong>有可能是因为你的一堆视图突然变得无效了(即需要重新绘制),或者你的几个自定义视图的onDraw函数过于复杂</strong>.</p><p><img src="/images/performance-4/6.webp" alt="自定义视图"></p></li><li><p>红色代表执行的时间,这部分是Android进行2D渲染 Display List的时间,为了绘制到屏幕上,Android需要使用OpenGl ES的API接口来绘制Display List.这些API有效地将数据发送到GPU,最总在屏幕上显示出来.</p><p><img src="/images/performance-4/7.webp" alt="红色"></p><p>记住绘制下图这样自定义的比较复杂的视图时,需要用到的OpenGl的绘制命令也会更复杂</p><p><img src="/images/performance-4/8.webp" alt="自定义的复杂View"></p><p>当你看到红色的线非常高的时候,这些复杂的自定义View就是罪魁祸首:</p><p><img src="/images/performance-4/9.webp" alt="Paste_Image.png"></p><p>值得一提的是,上面图中红色线较高的一种可能性是因为重新提交了视图而导致的.这些视图并不是失效的视图,但是有些时候发生了某些事,例如视图旋转,我们需要重新清理这个区域的视图,这样可能会影响这个视图下面的视图,因为这些视图都需要进行重新的绘制操作.</p></li><li><p>橙色部分表示的是处理时间,或者说是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复,如果柱状图很高,那就意味着你给GPU太多的工作,太多的负责视图需要OpenGL命令去绘制和处理.</p></li></ul><p>保持动画流畅的关键就在于让这些垂直的柱状条尽可能地保持在绿线下面,任何时候超过绿线,你就有可能丢失一帧的内容.</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>GPU Profile工具能够很好地帮助你找到渲染相关的问题,但是要修复这些问题就不是那么简单了. 你需要结合代码来具体分析,找到性能的瓶颈,并进行优化.</p><p>有时候你可以以这个为工具,让负责设计这个产品的人修改他的设计,以获得良好的用户体验.</p><h2 id="Perf-Matters"><a href="#Perf-Matters" class="headerlink" title="Perf Matters"></a>Perf Matters</h2><blockquote><p>keep calm, profile your code, and always remember, Perf Matters</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;系列文章目录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/&quot;&gt;Android性能优化典范综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/&quot;&gt;Android性能优化典范之Render Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/&quot;&gt;Android性能优化典范之Understanding Overdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/&quot;&gt;Android性能优化典范之Understanding VSYNC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/&quot;&gt;Android性能优化典范之Profile GPU Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;“If you can measure it, you can optimize it” is a common term in the computing world, and for Android’s rendering system, the same thing holds true. In order to optimize your pipeline to be more efficient for rendering, you need a tool to give you feedback on where the current perf problems lie.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;And in this video, +Colt McAnlis walks you through an on-device tool that’s built for this exact reason. “Profile GPU Rendering” will help you understand the stages of the rendering pipeline, and also get a chance to see what portions of it might be taking too long, and what you can do about it for your application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;GPU-Profile工具&quot;&gt;&lt;a href=&quot;#GPU-Profile工具&quot; class=&quot;headerlink&quot; title=&quot;GPU Profile工具&quot;&gt;&lt;/a&gt;GPU Profile工具&lt;/h2&gt;&lt;p&gt;渲染性能问题往往是偷取你宝贵帧数的罪魁祸首,这种问题很容易产生,很容易出现,而且在一个非常方便的工具的帮助下,也非常容易去追踪. 使用Peofile GPU Rendering tool,你可以在手机上就可以看到究竟是什么导致你的应用程序出现卡顿,变慢的情况.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化典范 - Understanding VSYNC</title>
    <link href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-3/"/>
    <id>https://androidperformance.com/2015/04/19/Android-Performance-Patterns-3/</id>
    <published>2015-04-19T07:47:25.000Z</published>
    <updated>2026-02-07T05:17:47.841Z</updated>
    
    <content type="html"><![CDATA[<p>系列文章目录：</p><ol><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li></ol><hr><blockquote><p>Unbeknown to most developers, there’s a simple hardware design that defines everything about how fast your application can draw things to the screen.</p></blockquote><blockquote><p>You may have heard the term VSYNC - VSYNC stands for vertical synchronization and it’s an event that happens every time your screen starts to refresh the content it wants to show you.</p></blockquote><blockquote><p>Effectively, VSYNC is the product of two components Refresh Rate (how fast the hardware can refresh the screen), and Frames Per Second (how fast the GPU can draw images), and in this video +Colt McAnlis walks through each of these topics, and discusses where VSYNC (and the 16ms rendering barrier) comes from, and why it’s critical to understand if you want a silky smooth application.</p></blockquote><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>想要开发一个高性能的应用程序,首先你得了解他的硬件工作原理,那么最好的办法就是去使用它,应用程序运行速度的快慢,很容易被人误解为硬件进程的控制问题,然而这最主要的根源在于渲染性能.如果你想要提高你应用程序的渲染性能,你就必须知道什么是<strong>VSYNC</strong>.</p><span id="more"></span><p>在了解<strong>VSYNC</strong>之前,我们需要了解两个概念:</p><h3 id="刷新率"><a href="#刷新率" class="headerlink" title="刷新率"></a>刷新率</h3><p>刷新率代表屏幕在一秒内刷新屏幕的次数,这个值用赫兹来表示,取决于硬件的固定参数. 这个值一般是60Hz,即每16.66ms刷新一次屏幕.</p><p><img src="/images/performance-3/1.webp" alt="刷新率"></p><h3 id="帧速率"><a href="#帧速率" class="headerlink" title="帧速率"></a>帧速率</h3><p>帧速率代表了GPU在一秒内绘制操作的帧数,比如30fps&#x2F;60fps.在这种情况下,高点的帧速率总是好的.</p><p><img src="/images/performance-3/2.webp" alt="帧速率"></p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>刷新率和帧速率需要协同工作,才能让你的应用程序的内容显示到屏幕上,GPU会获取图像数据进行绘制,然后硬件负责把内容呈现到屏幕上,这将在你的应用程序的生命周期中周而复始地发生.下面的图中每一个竖行代表了一帧的绘制和呈现工作.</p><p><img src="/images/performance-3/3.webp" alt="协同工作"></p><p>不幸的是,刷新率和帧速率并不是总能够保持相同的节奏:</p><ul><li><p>如果帧速率实际上比刷新率快,那么就会出现一些视觉上的问题,下面的图中可以看到,当帧速率在100fps而刷新率只有75Hz的时候,GPU所渲染的图像并非全都被显示出来.</p><p><img src="/images/performance-3/4.webp" alt="帧速率比刷新率快的情况"></p><p>举个例子, 你拍了一张照片,然后旋转5度再拍一张照片, 将两种图片的中间剪开并拼接在一起:</p><p><img src="/images/performance-3/5.webp" alt="拍两张照片"></p><p><img src="/images/performance-3/6.webp" alt="剪贴在一起"><br>这两张图有相似之处,但是上面和下面部分有明显的区别,这就叫Tearing(撕裂),是刷新率和帧速率不一致的结果.</p><p>上面的原因是因为,当你的显卡正在使用,一个内存区正在写入帧数据(用来显示一帧的一个Buffer),从顶部开始, 新的一帧覆盖前一帧,并立刻输出一行内容. 现在,当屏幕开始刷新时,实际上并不知道缓冲区是什么状态(即不知道缓冲区中的一帧是否绘制完毕,即存在只绘制了一半的情况,另一半还是之前的那帧),因此它从GPU中抓住的帧肯可能并不是完全完整的.</p><p><img src="/images/performance-3/7.webp" alt="图像撕裂"></p><p>目前Android的双缓冲(或者三缓冲&#x2F;四缓冲), 这是非常有效的,当GPU将一帧写入一个被成为后缓冲的存储器, 而存储器中的次级区域被称为帧缓冲,当写入下一帧时,它会开始填充后缓冲,而帧缓冲保持不变,现在我们刷新屏幕,它将使用帧缓冲(事先已经绘制好),而不是正在处于绘制状态的后缓冲, 这就是VSYNC的作用.如果在屏幕刷新中,VSYNC,即垂直同步,将会在让从后缓冲到帧缓冲的拷贝过程保持同样的复制操作:</p><p><img src="/images/performance-3/8.webp" alt="Vsync"></p><p>GPU的频率比屏幕刷新率高是正常的,因为你的GPU刷新会比屏幕刷新快,在这种情况下,当屏幕刷新成功,你的GPU将会等待VSYNC信号,直到下一个VSYNC信号到来时(即屏幕刷新时),这时你的帧速率就可以达到设备的刷新率上限. 当然这只是理想情况, 当fps达到60的时候,GPU需要在16.66ms内准备好一帧,这对应用程序的要求是非常高的.更不用说100fps了…</p></li><li><p>屏幕刷新率比帧速率快的情况<br>如果屏幕刷新率比帧速率快,屏幕会在两帧中显示同一个画面,当这种断断续续的情况发生时,你就遇到麻烦了.比如你的帧速率比屏幕刷新率高的时候,用户看到的是非常流畅的画面, 但是帧速率降下来的时候(GPU绘制太多东西的时候),用户将会很明显地察觉到动画卡住了或者掉帧,然后又恢复了流畅.这通常会被描述为闪屏, 跳帧,延迟.</p><p><img src="/images/performance-3/9.webp" alt="屏幕刷新率比帧速率快"></p><p>你的应用程序应该避免这些帧率突降的情况.以确保GPU迅速获取数据,并在屏幕再次刷新之前写录内容.</p></li></ul><h2 id="Perf-Matters"><a href="#Perf-Matters" class="headerlink" title="Perf Matters"></a>Perf Matters</h2><blockquote><p>keep calm, profile your code, and always remember, Perf Matters</p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;系列文章目录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/&quot;&gt;Android性能优化典范综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/&quot;&gt;Android性能优化典范之Render Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/&quot;&gt;Android性能优化典范之Understanding Overdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/&quot;&gt;Android性能优化典范之Understanding VSYNC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/&quot;&gt;Android性能优化典范之Profile GPU Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;Unbeknown to most developers, there’s a simple hardware design that defines everything about how fast your application can draw things to the screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;You may have heard the term VSYNC - VSYNC stands for vertical synchronization and it’s an event that happens every time your screen starts to refresh the content it wants to show you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Effectively, VSYNC is the product of two components Refresh Rate (how fast the hardware can refresh the screen), and Frames Per Second (how fast the GPU can draw images), and in this video +Colt McAnlis walks through each of these topics, and discusses where VSYNC (and the 16ms rendering barrier) comes from, and why it’s critical to understand if you want a silky smooth application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;基本概念&quot;&gt;&lt;a href=&quot;#基本概念&quot; class=&quot;headerlink&quot; title=&quot;基本概念&quot;&gt;&lt;/a&gt;基本概念&lt;/h2&gt;&lt;p&gt;想要开发一个高性能的应用程序,首先你得了解他的硬件工作原理,那么最好的办法就是去使用它,应用程序运行速度的快慢,很容易被人误解为硬件进程的控制问题,然而这最主要的根源在于渲染性能.如果你想要提高你应用程序的渲染性能,你就必须知道什么是&lt;strong&gt;VSYNC&lt;/strong&gt;.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化典范之 Understanding Overdraw</title>
    <link href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-2/"/>
    <id>https://androidperformance.com/2015/04/19/Android-Performance-Patterns-2/</id>
    <published>2015-04-19T07:38:48.000Z</published>
    <updated>2026-02-07T05:17:47.841Z</updated>
    
    <content type="html"><![CDATA[<p>系列文章目录：</p><ol><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li></ol><hr><blockquote><p>One of the most problematic performance problems on Android is the easiest to create; thankfully, it’s also easy to fix.</p></blockquote><blockquote><p><strong>OVERDRAW</strong> is a term used to describe how many times a pixel has been re-drawn in a single frame of rendering. It’s a troublesome issue, because in most cases, pixels that are overdrawn do not end up contributing to the final rendered image. As such, it amounts to wasted work for your GPU and CPU. </p></blockquote><blockquote><p>Fixing overdraw has everything to do with using the available on-device tools, like Show GPU Overdraw, and then adjusting your view hierarchy in order to reduce areas where it may be occurring.</p></blockquote><h2 id="OverDraw概念"><a href="#OverDraw概念" class="headerlink" title="OverDraw概念"></a>OverDraw概念</h2><p>视频开头作者举了一个例子，说如果你是一个粉刷匠，你应该会知道，给墙壁粉刷是一件工作量非常大的工作，而且如果你需要重新粉刷一遍的话(比如对颜色不满意),那么第一次的粉刷就白干了. 同样的道理,如果你的应用程序中出现了过度绘制问题,那么你之前所做的事情也就白费了.如果你想兼顾高性能和完美的设计,那么你的程序可能会出现一个性能问题:<strong>OverDraw</strong>!</p><p><strong>OverDraw</strong>是一个术语, 它表示某些组件在屏幕上的一个像素点的绘制超过1次.如下面的图所示,我们有一堆重叠的卡片,被用户激活的卡片在最上面,而那些没有激活的卡片在下面,这意味着我们画大力气绘制的那些卡片,基本都是不可见的.问题就在于次,我们像素渲染的并不全是用户最后能看打的部分, 这是在浪费GPU的时间!</p><span id="more"></span><p><img src="/images/performance-2/1.webp" alt="OverDraw"></p><p>目前流行的一些布局是一把双刃剑,带给我们漂亮的画面的同时,也带来了很大的麻烦.为了最大限度地提高应用程序的性能,你得减少<strong>OverDraw!</strong></p><p><img src="/images/performance-2/2.webp" alt="性能和界面"></p><h2 id="追踪OverDraw"><a href="#追踪OverDraw" class="headerlink" title="追踪OverDraw"></a>追踪OverDraw</h2><p>Android手机中提供了查看OverDraw情况的工具,在设置-开发者选项中,找到打开”Show GPU OverDraw”按钮即可:</p><p><img src="/images/performance-2/3.webp" alt="Show GPU OverDraw"></p><p>Android会使用不同深浅的颜色来表示OverDraw的程序,没有OverDraw的时候, 你看到的是它本来的颜色,其他颜色表示不同的过度绘制程序:</p><ul><li>蓝色: 1倍过度绘制,即一个像素点绘制了2次</li><li>绿色:2倍过度绘制,即一个像素点绘制了3次</li><li>浅红色:3倍过度绘制,即一个像素点绘制了4次</li><li>深红色:4倍过度绘制及以上,即一个像素点绘制了5次及以上</li></ul><p><img src="/images/performance-2/4.webp" alt="OverDraw"></p><p>你的应用程序的目标应该是尽可能地减少过度绘制,即更多的蓝色色块而不是红色色块:</p><p><img src="/images/performance-2/5.webp" alt="Good and Bad"></p><h2 id="OverDraw的根源"><a href="#OverDraw的根源" class="headerlink" title="OverDraw的根源"></a>OverDraw的根源</h2><p>虽然OverDraw很大程序上来自于你的视图互相重叠的问题,但是各位开发者更需要注意的是不必要的背景重叠.</p><p><img src="/images/performance-2/6.webp" alt="Bad"></p><p>比如在一个应用程序中,你的所有的View都有背景的话,就会看起来像第一张图中那样,而在去除这些不必要的背景之后(指的是Window的默认背景,Layout的背景,文字以及图片的可能存在的背景),效果就像第二张图那样,基本没有过度绘制的情况.</p><p><img src="/images/performance-2/7.webp" alt="去掉不必要的背景"></p><p>比如去除Window的默认背景:</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this.get<span class="constructor">Window()</span>.set<span class="constructor">BackgroundDrawableResource(<span class="params">android</span>.R.<span class="params">color</span>.<span class="params">transparent</span>)</span>;</span><br></pre></td></tr></table></figure><h2 id="Perf-Matters"><a href="#Perf-Matters" class="headerlink" title="Perf Matters"></a>Perf Matters</h2><blockquote><p>keep calm, profile your code, and always remember, Perf Matters</p></blockquote><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><p>关于过度绘制及其优化,我博客有两篇文章专门介绍:</p><ul><li><a href="https://androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/">Android过度绘制优化-基础篇</a></li><li><a href="https://androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/">Android过度绘制优化-实战篇</a></li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;系列文章目录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/&quot;&gt;Android性能优化典范综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/&quot;&gt;Android性能优化典范之Render Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/&quot;&gt;Android性能优化典范之Understanding Overdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/&quot;&gt;Android性能优化典范之Understanding VSYNC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/&quot;&gt;Android性能优化典范之Profile GPU Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;One of the most problematic performance problems on Android is the easiest to create; thankfully, it’s also easy to fix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;OVERDRAW&lt;/strong&gt; is a term used to describe how many times a pixel has been re-drawn in a single frame of rendering. It’s a troublesome issue, because in most cases, pixels that are overdrawn do not end up contributing to the final rendered image. As such, it amounts to wasted work for your GPU and CPU. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Fixing overdraw has everything to do with using the available on-device tools, like Show GPU Overdraw, and then adjusting your view hierarchy in order to reduce areas where it may be occurring.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;OverDraw概念&quot;&gt;&lt;a href=&quot;#OverDraw概念&quot; class=&quot;headerlink&quot; title=&quot;OverDraw概念&quot;&gt;&lt;/a&gt;OverDraw概念&lt;/h2&gt;&lt;p&gt;视频开头作者举了一个例子，说如果你是一个粉刷匠，你应该会知道，给墙壁粉刷是一件工作量非常大的工作，而且如果你需要重新粉刷一遍的话(比如对颜色不满意),那么第一次的粉刷就白干了. 同样的道理,如果你的应用程序中出现了过度绘制问题,那么你之前所做的事情也就白费了.如果你想兼顾高性能和完美的设计,那么你的程序可能会出现一个性能问题:&lt;strong&gt;OverDraw&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OverDraw&lt;/strong&gt;是一个术语, 它表示某些组件在屏幕上的一个像素点的绘制超过1次.如下面的图所示,我们有一堆重叠的卡片,被用户激活的卡片在最上面,而那些没有激活的卡片在下面,这意味着我们画大力气绘制的那些卡片,基本都是不可见的.问题就在于次,我们像素渲染的并不全是用户最后能看打的部分, 这是在浪费GPU的时间!&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化典范 - Render Performance</title>
    <link href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns-1/"/>
    <id>https://androidperformance.com/2015/04/19/Android-Performance-Patterns-1/</id>
    <published>2015-04-19T07:26:47.000Z</published>
    <updated>2026-02-07T05:17:47.841Z</updated>
    
    <content type="html"><![CDATA[<p>系列文章目录：</p><ol><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li></ol><hr><blockquote><p>Rendering performance is all about how fast you can draw your activity, and get it updated on the screen. Success here means your users feeling like your application is smooth and responsive, which means that you’ve got to get all your logic completed, <em>and</em> all your rendering done in 16ms or less, each and every frame. But that might be a bit more difficult than you think.</p></blockquote><blockquote><p>In this video, +Colt McAnlis takes a look at what “rendering performance” means to developers, alongside some of the most common pitfalls that are ran into; and let’s not forget the important stuff: the tools that help you track down, and fix these issues before they become large problems.</p></blockquote><h2 id="Android渲染知识"><a href="#Android渲染知识" class="headerlink" title="Android渲染知识"></a>Android渲染知识</h2><p>当你觉得自己开发了一个改变世界的应用的时候，你的用户可能并不会这么认为，他们认为你的应用又慢又卡，达不到他们所期望的那种顺滑，更谈不上改变这该死的世界了，回收站走你！等等！明明我这个应用在我的Nexus5上非常顺滑啊？你咋能说又慢又卡呢？如果你对Android的碎片化有一定了解的话，你就应该知道，很多低配置的手机并不像Nexus5那样有强大的处理器和GPU，以及没有被怎么污染的原生系统。</p><p>如果有大量的用户投诉说你的应用又卡又慢的时候，不要总是抱怨用户的低端手机，有时候问题就出在你的应用本身，也就意味着你的Android存在比较严重的渲染性能问题。只有真正了解问题发生的根源，才能有效的解决问题。所以了解Android渲染相关的知识，是一个Android开发者必不可少的知识。</p><span id="more"></span><h2 id="设计与性能"><a href="#设计与性能" class="headerlink" title="设计与性能"></a>设计与性能</h2><p>渲染问题是你建立一个应用程序是最经常碰到的问题，一方面，设计师希望展现给用户一个超自然的体验，另一方面，这些华丽的动画和视图并不能在所有的Android手机上都流畅地运行。所以这就是问题所在。</p><p><img src="/images/performance-1/1.webp" alt="Design vs Performance"></p><h2 id="绘制原理"><a href="#绘制原理" class="headerlink" title="绘制原理"></a>绘制原理</h2><p>Android系统每16ms都会重新绘制一次你的Activity，也就是说，你的逻辑控制画面更新要保证最多16ms一帧才能每秒达到60帧（至于为什么是60帧，这个后面会有一个专题来讲解这个）。如下图，每一帧都在16ms内绘制完成，此时的世界是顺滑的。</p><p><img src="/%5D(/images/performance-1/2.webp" alt="Draw Good"></p><p>但是如果你的应用程序没有在16ms内完成这一帧的绘制，假设你花费了24ms来绘制这一帧，那么就会出现我们称之为<strong>掉帧</strong>的情况，世界变得有延迟了。如下图：</p><p><img src="/%5D(%5D(/images/performance-1/3.webp" alt="Draw Bad"></p><p>系统准备将新的一帧绘制到屏幕上，但是这一帧并没有准备好，所有就不会有绘制操作，画面也就不会刷新。反馈到用户身上，就是用户盯着同一张图看了32ms而不是16ms，也就是说掉帧发生了。</p><h2 id="掉帧"><a href="#掉帧" class="headerlink" title="掉帧"></a>掉帧</h2><p>掉帧是用户体验中一个非常核心的问题，用户将很容易察觉到由于掉帧而产生的卡顿感，如果此时用户正在与系统进行交互，比如滑动列表，或者正在打字，那么卡顿感是非常明显的。用户会马上对你的应用进行吐槽，下一步工作肯定是回收站走你！所以弄清楚掉帧的原因是非常重要的。</p><p>不过蛋疼的是，引起掉帧发生的原因非常多，比如：</p><ul><li><strong>你花了太多的时间重新绘制你视图中的大部分东西，这样非常浪费CPU周期</strong><br><img src="/%5D(%5D(/images/performance-1/4.webp" alt="Too Much View"></li><li><strong>你有太多的对象堆叠到了一起，你在绘制用户看不到的对象上花费了太多的时间</strong><br><img src="/%5D(%5D(/images/performance-1/5.webp" alt="Draw Hidden View"></li><li><strong>你有一大堆的动画重复了一遍又一遍，导致CPU和GPU组件的大量浪费</strong><br><img src="/%5D(%5D(/images/performance-1/6.webp" alt="Too Much Animations"></li></ul><h2 id="检测和解决"><a href="#检测和解决" class="headerlink" title="检测和解决"></a>检测和解决</h2><p>检测和解决这些问题很大程度上依赖于你的应用程序架构，但是幸运的是，我们有很多开发者工具来协助我们发现和解决这些问题，有些工具甚至能追踪到具体出错的代码行数或者UI控件，这些工具包括但不限于：</p><ul><li><h4 id="Hierarchy-View"><a href="#Hierarchy-View" class="headerlink" title="Hierarchy View"></a>Hierarchy View</h4></li></ul><p><img src="/%5D(%5D(/images/performance-1/7.webp" alt="Hierarchy View"><br>你可以使用Hierarchy View 来查看你的View是否过于复杂，如果是，那么说明你有很多时间没有利用。并且浪费了许多时间进行重绘。<br>Hierarchy View 位于Android Device Monitor 中，Android Device Monitor在Eclipse和Android Studio中都有有对应的入口，依次选则Window-Open Perspective-Hierarchy View即可打开Hierarchy View视图。 Hierarchy View视图虽然比较简单，但是非常有效。花费一点了解这个工具每一个细节，对于以后排查问题来说都是事半功倍。关于Hierarchy View视图的用法，会有更详细的单独的教程来讲解。</p><ul><li><h4 id="On-Device-Tools-–-Profile-GPU-Rendering-、Show-GPU-Overdraw、GPU-View-Updates"><a href="#On-Device-Tools-–-Profile-GPU-Rendering-、Show-GPU-Overdraw、GPU-View-Updates" class="headerlink" title="On-Device Tools – Profile GPU Rendering 、Show GPU Overdraw、GPU View Updates"></a>On-Device Tools – Profile GPU Rendering 、Show GPU Overdraw、GPU View Updates</h4></li></ul><p><img src="/%5D(%5D(/images/performance-1/8.webp" alt="On-Device Tools"></p><p>这三个选项在设置-辅助功能- 开发者选项中，默认都是关闭的。Profile GPU Rendering 和 GPU Overdraw比较重要，所以系列视频后面会有专门的专题会讲解，这里简单介绍一下GPU View Updates。GPU View Updates的作用是使用GPU进行绘图时闪烁显示窗口中的视图。随着android版本的更新，越来越多的绘制操作能使用GPU来完成，详见<a href="http://developer.android.com/guide/topics/graphics/hardware-accel.html">http://developer.android.com/guide/topics/graphics/hardware-accel.html</a>，而这个工具打开之后，使用GPU绘制的区域会用红色来标注，而没有红色标注的区域，则是使用CPU绘制的。这个选项也可以用来查看redraw的区域大小。</p><ul><li><h4 id="TraceView"><a href="#TraceView" class="headerlink" title="TraceView"></a>TraceView</h4></li></ul><p><img src="/%5D(%5D(/images/performance-1/9.webp" alt="TraceView"></p><p>TraceView是一个很棒的检查是否掉帧的工具，视频中没有对此工具进行介绍，但是这个工具非常的重要，他可以找到你代码中花费时间的地方，精确到每一个函数，不论这个函数是你应用程序中的还是系统函数。另外在Android Studio中，TraceView得到了改进，其视图能非常直观的显示出每一帧所消耗的时间，函数像倒金字塔一般展现在面前，我们可以很容易地看出掉帧的地方以及那一帧里面所有的函数调用情况。鉴于此工具非常实用，所有会有更详细的单独的教程来讲解。</p><h2 id="Perf-Matters"><a href="#Perf-Matters" class="headerlink" title="Perf Matters"></a>Perf Matters</h2><blockquote><p>keep calm, profile your code, and always remember, Perf Matters</p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这是这个系列视频的第一个视频，从内容上来看，是从一个大的角度来看Render Performance，简单地讲述了一下Render Performance基本的概念，出现的原因以及排查的工具。在发现问题–定位问题–解决问题的流程上属于发现问题–定位问题，解决问题则基本没有提到。这也基本符合这一系列视频的基调：即着重于发现问题(使用工具发现问题、挖掘问题出现的原理和原因)和定位问题(使用工具定位)，如何解决问题则需要自己通过实战去进行锻炼，毕竟这种问题并没有一个通用的解决方法，每个应用都有每个应用自己的问题。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;系列文章目录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/&quot;&gt;Android性能优化典范综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/&quot;&gt;Android性能优化典范之Render Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/&quot;&gt;Android性能优化典范之Understanding Overdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/&quot;&gt;Android性能优化典范之Understanding VSYNC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/&quot;&gt;Android性能优化典范之Profile GPU Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;Rendering performance is all about how fast you can draw your activity, and get it updated on the screen. Success here means your users feeling like your application is smooth and responsive, which means that you’ve got to get all your logic completed, &lt;em&gt;and&lt;/em&gt; all your rendering done in 16ms or less, each and every frame. But that might be a bit more difficult than you think.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;In this video, +Colt McAnlis takes a look at what “rendering performance” means to developers, alongside some of the most common pitfalls that are ran into; and let’s not forget the important stuff: the tools that help you track down, and fix these issues before they become large problems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;Android渲染知识&quot;&gt;&lt;a href=&quot;#Android渲染知识&quot; class=&quot;headerlink&quot; title=&quot;Android渲染知识&quot;&gt;&lt;/a&gt;Android渲染知识&lt;/h2&gt;&lt;p&gt;当你觉得自己开发了一个改变世界的应用的时候，你的用户可能并不会这么认为，他们认为你的应用又慢又卡，达不到他们所期望的那种顺滑，更谈不上改变这该死的世界了，回收站走你！等等！明明我这个应用在我的Nexus5上非常顺滑啊？你咋能说又慢又卡呢？如果你对Android的碎片化有一定了解的话，你就应该知道，很多低配置的手机并不像Nexus5那样有强大的处理器和GPU，以及没有被怎么污染的原生系统。&lt;/p&gt;
&lt;p&gt;如果有大量的用户投诉说你的应用又卡又慢的时候，不要总是抱怨用户的低端手机，有时候问题就出在你的应用本身，也就意味着你的Android存在比较严重的渲染性能问题。只有真正了解问题发生的根源，才能有效的解决问题。所以了解Android渲染相关的知识，是一个Android开发者必不可少的知识。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Jank" scheme="https://androidperformance.com/tags/Jank/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化典范综述</title>
    <link href="https://androidperformance.com/2015/04/19/Android-Performance-Patterns/"/>
    <id>https://androidperformance.com/2015/04/19/Android-Performance-Patterns/</id>
    <published>2015-04-19T07:20:35.000Z</published>
    <updated>2026-02-07T05:17:47.842Z</updated>
    
    <content type="html"><![CDATA[<p>系列文章目录：</p><ol><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/">Android性能优化典范综述</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/">Android性能优化典范之Render Performance</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/">Android性能优化典范之Understanding Overdraw</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/">Android性能优化典范之Understanding VSYNC</a></li><li><a href="https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/">Android性能优化典范之Profile GPU Rendering</a></li></ol><hr><p>2015年1月6日，Google官方发布了一系列关于Android性能优化的小视频，将其命名为Android Performance Patterns，这一些列视频放在<a href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">YouTube</a>上，观看的话需要科学地上网。</p><p><img src="/images/performance-0/1.jpg" alt="Android性能优化典范"></p><p>官方简介：</p><blockquote><p>Android Performance Patterns is a collection of videos focused entirely on helping developers write faster, more performant Android Applications. On one side, it’s about peeling back the layers of the Android System, and exposing how things are working under the hood. On the other side, it’s about teaching you how the tools work, and what to look for in order to extract the right perf out of your app.<br>But at the end of the day, Android Performance Patterns is all giving you the right resources, at the right time to help make the fastest, smoothest, most awesome experience for your users. And that’s the whole point, right?</p></blockquote><p>总之就是一系列讲解Android性能相关的视频。这些小视频的时间非常短，在3-5分钟之内，主讲人的英文语速也非常快，初期这些视频没有翻译的时候，着实考验了一把听力。好消息是现在这些视频已经都有中英文字幕了。</p><p>这些视频的时间虽然很短，但是信息量却非常大，有些他一句话带过的内容，我们却需要花费很多的时间去研究他的原理，或者研究一个调试工具如何使用。也就是说，这一系列视频并没有真正教你如何去优化你的应用，而是告诉你关于Android性能优化你需要知道的知识，这样你去优化你的Android应用的时候，<strong>知道该用什么工具，该采取什么样的步骤，需要达到什么样的目标。</strong></p><span id="more"></span><p>由于我最近也在研究Android性能优化相关的课题，这个视频第一时间出来的时候我就看了好几遍，所以一早就有将这一系列视频翻译成中文的想法。后来看了几遍之后，我发现仅仅翻译成中文其实意义不大，他所讲述的每一个知识点，都是可以写成一篇博文甚至好几篇博文的，所以就有了这一系列文章的出现。</p><p>每一篇文章中，我都会先将视频中涉及到的知识点列出来，然后一一进行讲解。有些调试工具由于篇幅原因，可能会写到单独的博文中。</p><p>另外催生我写这一系列文章的是胡凯，他的博客 <a href="http://hukai.me/android-performance-patterns">http://hukai.me/android-performance-patterns</a> 第一时间就将这一些列视频的内容翻译成了中文，优美的排版加上过硬的翻译，让这篇博文被广泛传播，备受好评。同时他也是github上 <a href="https://github.com/kesenhoo/android-training-course-in-chinese">android-training-course-in-chinese</a> 项目的发起人，他的Github主页：<a href="https://github.com/kesenhoo">https://github.com/kesenhoo</a>。他对于分享的热情我非常敬佩。如果你并非是Android应用开发者或者对技术细节不感兴趣的话，直接看他的那篇Android性能优化典范即可，看完之后你会对这一些列视频有一个大概的认识。</p><p>下面是关于Android性能优化典范这一些列视频的一些资源信息：</p><ul><li>YouTube主页：<a href="https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE">Android Performance Patterns</a></li><li>作者Google+主页：</li><li>Android Performance Patterns的Google+社群：</li></ul><p>OK，Let us start！！！</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;系列文章目录：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns/&quot;&gt;Android性能优化典范综述&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-1/&quot;&gt;Android性能优化典范之Render Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-2/&quot;&gt;Android性能优化典范之Understanding Overdraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-3/&quot;&gt;Android性能优化典范之Understanding VSYNC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/19/Android-Performance-Patterns-4/&quot;&gt;Android性能优化典范之Profile GPU Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;2015年1月6日，Google官方发布了一系列关于Android性能优化的小视频，将其命名为Android Performance Patterns，这一些列视频放在&lt;a href=&quot;https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&quot;&gt;YouTube&lt;/a&gt;上，观看的话需要科学地上网。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/performance-0/1.jpg&quot; alt=&quot;Android性能优化典范&quot;&gt;&lt;/p&gt;
&lt;p&gt;官方简介：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Android Performance Patterns is a collection of videos focused entirely on helping developers write faster, more performant Android Applications. On one side, it’s about peeling back the layers of the Android System, and exposing how things are working under the hood. On the other side, it’s about teaching you how the tools work, and what to look for in order to extract the right perf out of your app.&lt;br&gt;But at the end of the day, Android Performance Patterns is all giving you the right resources, at the right time to help make the fastest, smoothest, most awesome experience for your users. And that’s the whole point, right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;总之就是一系列讲解Android性能相关的视频。这些小视频的时间非常短，在3-5分钟之内，主讲人的英文语速也非常快，初期这些视频没有翻译的时候，着实考验了一把听力。好消息是现在这些视频已经都有中英文字幕了。&lt;/p&gt;
&lt;p&gt;这些视频的时间虽然很短，但是信息量却非常大，有些他一句话带过的内容，我们却需要花费很多的时间去研究他的原理，或者研究一个调试工具如何使用。也就是说，这一系列视频并没有真正教你如何去优化你的应用，而是告诉你关于Android性能优化你需要知道的知识，这样你去优化你的Android应用的时候，&lt;strong&gt;知道该用什么工具，该采取什么样的步骤，需要达到什么样的目标。&lt;/strong&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
  </entry>
  
  <entry>
    <title>Android 内存优化之三 - 打开 MAT 中的 Bitmap 原图</title>
    <link href="https://androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/"/>
    <id>https://androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/</id>
    <published>2015-04-11T10:41:32.000Z</published>
    <updated>2026-02-07T05:17:47.850Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 MAT 工具使用系列的第二篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：</p><ol><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android 内存优化之一：MAT 使用入门</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android内存优化之二：MAT使用进阶</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android内存优化之三：打开MAT中的Bitmap原图</a></li></ol><p>在使用MAT查看应用程序内存使用情况的时候,我们经常会碰到Bitmap对象以及BitmapDrawable$BitmapState对象,而且在内存使用上,Bitmap所占用的内存占大多数.在这样的情况下, Bitmap所造成的内存泄露尤其严重, 需要及时发现并且及时处理.在这样的需求下, 当我们在MAT中发现和图片相关的内存泄露的时候, 如果能知道是那一张图片,对分析问题会有很大的帮助.</p><p>本文就介绍如何将MAT中的Bitmap数组对象还原成一张图片。</p><span id="more"></span><p>Bitmap对象如图:</p><p><img src="/images/MAT_OpenBitmap/Image_1.webp" alt="Bitmap对象"></p><h2 id="导出Bitmap原始数据"><a href="#导出Bitmap原始数据" class="headerlink" title="导出Bitmap原始数据"></a>导出Bitmap原始数据</h2><p>在MAT中打开Dominator Tree视图 , 选择一个Bitmap对象 , 查看此时右边的Inspector窗口,内容如下图: </p><p><img src="/images/MAT_OpenBitmap/Image_2.webp" alt="image"></p><p>这个视图中,可以看到这个Bitmap的一些基本的信息: mBuffer, mHeight, mWidth , mNativeBitmap等, 宽和高的值我们一会需要用的到 . </p><p>mBuffer的定义在Bitmap.java中:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Backing buffer for the Bitmap.</span></span><br><span class="line"><span class="comment"> * Made public for quick access from drawing methods -- do NOT modify</span></span><br><span class="line"><span class="comment"> * from outside this class</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@hide</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@SuppressWarnings(&quot;UnusedDeclaration&quot;)</span> <span class="comment">// native code only</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">byte</span>[] mBuffer;</span><br></pre></td></tr></table></figure><p>其值是保存在byte数组中的, 我们需要的就是这个byte数组中的内容. 在Inspector窗口的mBuffer这一栏或者Dominator Tree视图的Bitmap这一栏点开下一级,都可以看到这个byte数组的内容. 鼠标右键选择Copy –&gt;Save Value To File. 弹出如下对话框:</p><p><img src="/images/MAT_OpenBitmap/Image_3.webp" alt="image"></p><p>选择存储路径和文件名,这里需要注意的是,<strong>文件名一定要以 .data为后缀</strong>,否则无法正常使用,切记.</p><h2 id="打开原始资源数据"><a href="#打开原始资源数据" class="headerlink" title="打开原始资源数据"></a>打开原始资源数据</h2><h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><p>这时需要借助Linux上强大的图片应用:GIMP,没安装的可以去安装一下. 安装好之后, 打开GIMP,选择文件-打开.选择我们上一步导出的.data文件(比如image.data),然后会出现如下图的属性框:</p><p><img src="/images/MAT_OpenBitmap/Image_4.webp" alt="image"></p><p>图像类型这一栏选择RGB Alpha, 宽度和高度必填, 其值可以在MAT中查看到,第一步的时候有说到这个值的位置, 其他的选择默认即可,然后点击打开. GIMP就会把这个文件打开.</p><h3 id="Mac-amp-amp-Windows"><a href="#Mac-amp-amp-Windows" class="headerlink" title="Mac &amp;&amp; Windows"></a>Mac &amp;&amp; Windows</h3><p>Mac和Windows可以选择使用PhotoShop作为打开的工具, 和Linux唯一不同的地方在于. 保存的文件的格式需要以.raw结尾 (比如image.raw),选择深度为32位. 其余的和Linux相同.</p><p>另外GIMP也有Mac、Windows版本，建议大家在各个平台都使用GIMP，这样学习成本比较低，而且GIMP为免费软件，使用起来功能也非常多。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 MAT 工具使用系列的第二篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/&quot;&gt;Android 内存优化之一：MAT 使用入门&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/&quot;&gt;Android内存优化之二：MAT使用进阶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/&quot;&gt;Android内存优化之三：打开MAT中的Bitmap原图&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在使用MAT查看应用程序内存使用情况的时候,我们经常会碰到Bitmap对象以及BitmapDrawable$BitmapState对象,而且在内存使用上,Bitmap所占用的内存占大多数.在这样的情况下, Bitmap所造成的内存泄露尤其严重, 需要及时发现并且及时处理.在这样的需求下, 当我们在MAT中发现和图片相关的内存泄露的时候, 如果能知道是那一张图片,对分析问题会有很大的帮助.&lt;/p&gt;
&lt;p&gt;本文就介绍如何将MAT中的Bitmap数组对象还原成一张图片。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 内存优化之二 - MAT使用进阶</title>
    <link href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/"/>
    <id>https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/</id>
    <published>2015-04-11T10:26:36.000Z</published>
    <updated>2026-02-07T05:17:47.851Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 MAT 工具使用系列的第二篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：</p><ol><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android 内存优化之一：MAT 使用入门</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android内存优化之二：MAT使用进阶</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android内存优化之三：打开MAT中的Bitmap原图</a></li></ol><h1 id="Java的内存泄露的特点"><a href="#Java的内存泄露的特点" class="headerlink" title="Java的内存泄露的特点"></a>Java的内存泄露的特点</h1><ul><li>Java中的内存泄露主要特征：可达，无用</li><li>无用指的是创建了但是不再使用之后没有释放</li><li>能重用但是却创建了新的对象进行处理</li></ul><h1 id="MAT使用技巧进阶"><a href="#MAT使用技巧进阶" class="headerlink" title="MAT使用技巧进阶"></a>MAT使用技巧进阶</h1><h2 id="使用Android-Studio-Dump内存文件"><a href="#使用Android-Studio-Dump内存文件" class="headerlink" title="使用Android Studio Dump内存文件"></a>使用Android Studio Dump内存文件</h2><p>Android Studio的最新版本可以直接获取hprof文件：</p><p><img src="/images/MAT_Pro/MAT_1.webp" alt="Android-Studio"></p><span id="more"></span><p>然后选择文件，点击右键转换成标准的hprof文件，就可以在MAT中打开了。</p><blockquote><p>在使用使用Eclipse或者AndroidStudio抓内存之前，一定要手动点击 Initiate GC按钮手动触发GC，这样抓到的内存使用情况就是不包括Unreachable对象的。</p></blockquote><blockquote><p><img src="/images/MAT_Pro/MAT_2.webp" alt="手动触发GC"></p></blockquote><h2 id="Unreachable对象"><a href="#Unreachable对象" class="headerlink" title="Unreachable对象"></a>Unreachable对象</h2><p>Unreachable指的是可以被垃圾回收器回收的对象，但是由于没有GC发生，所以没有释放，这时抓的内存使用中的Unreachable就是这些对象。</p><p><img src="/images/MAT_Pro/MAT_3.webp" alt="Unreachable Objects"></p><p><img src="/images/MAT_Pro/MAT_4.webp" alt="Unreachable Objects Histogram"></p><p>点击Calculate Retained Size之后，会出现Retained Size这一列</p><p><img src="/images/MAT_Pro/MAT_5.webp" alt="Calculate Retained Size"></p><p><img src="/images/MAT_Pro/MAT_6.webp" alt="Unreachable Objects Histogram"></p><p>可以看到Unreachable Object的对象其Retained Heap值都为0.这也是正常的。</p><h2 id="Histogram"><a href="#Histogram" class="headerlink" title="Histogram"></a>Histogram</h2><p>MAT中Histogram的主要作用是查看一个instance的数量，一般用来查看自己创建的类的实例的个数。</p><ul><li>可以很容易的找出占用内存最多的几个对象，根据Percentage（百分比）来排序。</li><li>可以分不同维度来查看对象的Dominator Tree视图，Group by class、Group by class  loader、Group by package<br>和Histogram类似，时间久了，通过多次对比也可以把溢出对象找出来。</li><li>Dominator Tree和Histogram的区别是站的角度不一样，Histogram是站在类的角度上去看，Dominator Tree是站的对象实例的角度上看，Dominator Tree可以更方便的看出其引用关系。</li></ul><p><img src="/images/MAT_Pro/MAT_7.webp" alt="Histogram group by package"></p><p>通过查看Object的个数，结合代码就可以找出存在内存泄露的类（<strong>即可达但是无用的对象，或者是可以重用但是重新创建的对象</strong>）</p><p>Histogram中还可以对对象进行Group，更方便查看自己Package中的对象信息。</p><p><img src="/images/MAT_Pro/MAT_8.webp" alt="Paste_Image.png"></p><h3 id="Thread信息"><a href="#Thread信息" class="headerlink" title="Thread信息"></a>Thread信息</h3><p>MAT中可以查看当前的Thread信息：</p><p><img src="/images/MAT_Pro/MAT_9.webp" alt="Thread"></p><p>从图中可以得到的信息：</p><ol><li>可以看到可能有内存问题的Thread：</li></ol><p>  <img src="/images/MAT_Pro/MAT_10.webp" alt="内存异常"></p><ol start="2"><li>可以看到数量可能有问题的Thread</li></ol><p> <img src="/images/MAT_Pro/MAT_11.webp" alt="数量异常"></p><h2 id="帮助信息"><a href="#帮助信息" class="headerlink" title="帮助信息"></a>帮助信息</h2><p>MAT中的各个视图中，在每一个Item中点击右键会出现很多选项，很多时候我们需要依赖这些选项来进行分析：</p><p><img src="/images/MAT_Pro/MAT_12.webp" alt="右键选项"></p><p>这些选项的具体含义则可以通过右键中的Search Queries这个选项(上图中的倒数第四个选项)进行搜索和查看，非常的有用。</p><p><img src="/images/MAT_Pro/MAT_13.webp" alt="帮助信息"></p><p>可以看到，所有的命令其实就是配置不同的SQL查询语句。</p><p>比如我们最常用的：</p><ul><li><strong>List objects -&gt; with incoming references</strong>：查看这个对象持有的外部对象引用</li><li><strong>List objects -&gt; with outcoming references</strong>：查看这个对象被哪些外部对象引用</li><li><strong>Path To GC Roots -&gt; exclude all phantim&#x2F;weak&#x2F;soft etc. references</strong>：查看这个对象的GC Root，不包含虚、弱引用、软引用，剩下的就是强引用。从GC上说，除了强引用外，其他的引用在JVM需要的情况下是都可以 被GC掉的，如果一个对象始终无法被GC，就是因为强引用的存在，从而导致在GC的过程中一直得不到回收，因此就内存溢出了。</li><li><strong>Path To GC Roots  -&gt; exclude weak&#x2F;soft references</strong>：查看这个对象的GC Root，不含弱引用和软引用所有的引用.</li><li>**Merge Shortest path to GC root **：找到从GC根节点到一个对象或一组对象的共同路径</li></ul><h2 id="Debug-Bitmap"><a href="#Debug-Bitmap" class="headerlink" title="Debug Bitmap"></a>Debug Bitmap</h2><p>如果经常使用MAT分析内存，就会发现Bitmap所占用的内存是非常大的，这个和其实际显示面积是有关系的。在2K屏幕上，一张Bitmap能达到20MB的大小。</p><p>所以要是MAT提供了一种方法，可以将存储Bitmap的byte数组导出来，使用第三方工具打开。这个大大提高了我们分析内存泄露的效率。</p><p>关于这个方法的操作流程，可以参考这篇文章<a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">还原MAT中的Bitmap图像</a>.<br>I</p><h2 id="Debug-ArrayList"><a href="#Debug-ArrayList" class="headerlink" title="Debug ArrayList"></a>Debug ArrayList</h2><p>ArrayList是使用非常常用的一个数据结构，在MAT中，如果想知道ArrayList中有哪些数据，需要右键-&gt; List Objects -&gt; With outgoing references,然后可以看到下面的图：</p><p><img src="/images/MAT_Pro/MAT_14.webp" alt="Outgoing"></p><p>从上图可以看到，这个ArrayList的内容在一个array数组中，即暴漏了ArrayList的内部结构，查看的时候有点不方便，所以MAT提供了另外一种查看ArrayList内数据的方式：</p><p><img src="/images/MAT_Pro/MAT_15.webp" alt="Extrace List Values"></p><p>其结果非常直观：</p><p><img src="/images/MAT_Pro/MAT_16.webp" alt="Extrace List Values Result"></p><h2 id="Big-Drops-In-Dominator-Tree"><a href="#Big-Drops-In-Dominator-Tree" class="headerlink" title="Big Drops In Dominator Tree"></a>Big Drops In Dominator Tree</h2><p>Big Drops In Dominator Tree选项在右键-&gt;</p><blockquote><p>Displays memory accumulation points in the dominator tree. Displayed are objects with a big difference between the retained size of the parent and the children and the first “interesting” dominator of the accumulation point. These are places where the memory of many small objects is accumulated under one object.</p></blockquote><p><img src="/images/MAT_Pro/MAT_17.webp" alt="Big Drops In Dominator Tree"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 MAT 工具使用系列的第二篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/&quot;&gt;Android 内存优化之一：MAT 使用入门&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/&quot;&gt;Android内存优化之二：MAT使用进阶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/&quot;&gt;Android内存优化之三：打开MAT中的Bitmap原图&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;Java的内存泄露的特点&quot;&gt;&lt;a href=&quot;#Java的内存泄露的特点&quot; class=&quot;headerlink&quot; title=&quot;Java的内存泄露的特点&quot;&gt;&lt;/a&gt;Java的内存泄露的特点&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;Java中的内存泄露主要特征：可达，无用&lt;/li&gt;
&lt;li&gt;无用指的是创建了但是不再使用之后没有释放&lt;/li&gt;
&lt;li&gt;能重用但是却创建了新的对象进行处理&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;MAT使用技巧进阶&quot;&gt;&lt;a href=&quot;#MAT使用技巧进阶&quot; class=&quot;headerlink&quot; title=&quot;MAT使用技巧进阶&quot;&gt;&lt;/a&gt;MAT使用技巧进阶&lt;/h1&gt;&lt;h2 id=&quot;使用Android-Studio-Dump内存文件&quot;&gt;&lt;a href=&quot;#使用Android-Studio-Dump内存文件&quot; class=&quot;headerlink&quot; title=&quot;使用Android Studio Dump内存文件&quot;&gt;&lt;/a&gt;使用Android Studio Dump内存文件&lt;/h2&gt;&lt;p&gt;Android Studio的最新版本可以直接获取hprof文件：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/MAT_Pro/MAT_1.webp&quot; alt=&quot;Android-Studio&quot;&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
  </entry>
  
  <entry>
    <title>Android 内存优化(1) - MAT 使用入门</title>
    <link href="https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/"/>
    <id>https://androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/</id>
    <published>2015-04-11T10:10:29.000Z</published>
    <updated>2026-02-07T05:17:47.851Z</updated>
    
    <content type="html"><![CDATA[<p>本文是 MAT 工具使用系列的第一篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：</p><ol><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/">Android 内存优化(1) - MAT 使用入门</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/">Android 内存优化(2) - MAT使用进阶</a></li><li><a href="https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/">Android 内存优化(3) - 打开MAT中的Bitmap原图</a></li></ol><h1 id="MAT简介"><a href="#MAT简介" class="headerlink" title="MAT简介"></a>MAT简介</h1><h2 id="MAT介绍"><a href="#MAT介绍" class="headerlink" title="MAT介绍"></a>MAT介绍</h2><p>MAT(Memory Analyzer Tool)，一个基于 Eclipse 的内存分析工具，是一个快速、功能丰富的JAVA heap分析工具，它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析，快速的计算出在内存中对象的占用大小，看看是谁阻止了垃圾收集器的回收工作，并可以通过报表直观的查看到可能造成这种结果的对象。  </p><p><img src="/images/MAT/MAT_Intro.webp" alt="image"></p><p>当然MAT也有独立的不依赖Eclipse的版本，只不过这个版本在调试Android内存的时候，需要将DDMS生成的文件进行转换，才可以在独立版本的MAT上打开。不过Android SDK中已经提供了这个Tools，所以使用起来也是很方便的。</p><span id="more"></span><h2 id="MAT工具的下载安装"><a href="#MAT工具的下载安装" class="headerlink" title="MAT工具的下载安装"></a>MAT工具的下载安装</h2><p>这里是MAT的下载地址：[<a href="https://eclipse.org/mat/downloads.php]">https://eclipse.org/mat/downloads.php]</a>(<a href="https://eclipse.org/mat/">https://eclipse.org/mat/</a> downloads.php)，下载时会提供三种选择的方式：</p><p><img src="/images/MAT/MAT_Download.webp" alt="image"></p><ul><li>Update Site  这种方式后面会有一个网址：比如<a href="http://download.eclipse.org/mat/1.4/update-site/">http://download.eclipse.org/mat/1.4/update-site/</a> ，安装过Eclipse插件的同学应该知道，只要把这段网址复制到对应的Eclipse的Install New Software那里，就可以进行在线下载了。</li></ul><p><img src="/images/MAT/MAT_Eclipse_Install.webp" alt="image"></p><ul><li>Archived Update Site  这种方式安装的位置和上一种差不多，只不过第一种是在线下载，这一种是使用离线包进行更新，这种方式劣势是当这个插件更新后，需要重新下载离线包，而第一种方式则可以在线下载更新。</li><li>Stand-alone Eclipse RCP Applications 这种方式就是把MAT当成一个独立的工具使用，不再依附于Eclipse，适合不使用Eclipse而使用Android Studio的同学。这种方式有个麻烦的地方就是DDMS导出的文件，需要进行转换才可以在MAT中打开。</li></ul><p>下载安装好之后，就可以使用MAT进行实际的操作了。</p><h1 id="Android-Java-中常见的容易引起内存泄露的不良代码"><a href="#Android-Java-中常见的容易引起内存泄露的不良代码" class="headerlink" title="Android(Java)中常见的容易引起内存泄露的不良代码"></a>Android(Java)中常见的容易引起内存泄露的不良代码</h1><h2 id="Android内存"><a href="#Android内存" class="headerlink" title="Android内存"></a>Android内存</h2><p>使用MAT工具之前，要对Android的内存分配方式有基本的了解，对容易引起内存泄露的代码也要保持敏感，在代码级别对内存泄露的排查，有助于内存的使用。</p><p>Android主要应用在嵌入式设备当中，而嵌入式设备由于一些众所周知的条件限制，通常都不会有很高的配置，特别是内存是比较有限的。如果我们编写的代码当中有太多的对内存使用不当的地方，难免会使得我们的设备运行缓慢，甚至是死机。为了能够使得Android应用程序安全且快速的运行，Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行，它是由Zygote服务进程孵化出来的，也就是说每个应用程序都是在属于自己的进程中运行的。一方面，如果程序在运行过程中出现了内存泄漏的问题，仅仅会使得自己的进程被kill掉，而不会影响其他进程（如果是system_process等系统进程出问题的话，则会引起系统重启）。另一方面Android为不同类型的进程分配了不同的内存使用上限，如果应用进程使用的内存超过了这个上限，则会被系统视为内存泄漏，从而被kill掉。</p><h2 id="常见的内存使用不当的情况"><a href="#常见的内存使用不当的情况" class="headerlink" title="常见的内存使用不当的情况"></a>常见的内存使用不当的情况</h2><h3 id="查询数据库没有关闭游标"><a href="#查询数据库没有关闭游标" class="headerlink" title="查询数据库没有关闭游标"></a>查询数据库没有关闭游标</h3><p>描述：<br>程序中经常会进行查询数据库的操作，但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小，对内存的消耗不容易被发现，只有在常时间大量操作的情况下才会复现内存问题，这样就会给以后的测试和问题排查带来困难和风险。<br>示例代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Cursor</span> <span class="variable">cursor</span> <span class="operator">=</span> getContentResolver().query(uri ...);</span><br><span class="line"><span class="keyword">if</span> (cursor.moveToNext()) &#123;</span><br><span class="line"> ... ... </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修正示例代码:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Cursor</span> <span class="variable">cursor</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  cursor = getContentResolver().query(uri ...);</span><br><span class="line"><span class="keyword">if</span> (cursor != <span class="literal">null</span> &amp;&amp; cursor.moveToNext()) &#123;</span><br><span class="line">... ... </span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (cursor != <span class="literal">null</span>) &#123;</span><br><span class="line"><span class="keyword">try</span> &#123; </span><br><span class="line">cursor.close();</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line"><span class="comment">//ignore this</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>&#96;</p><h3 id="构造Adapter时，没有使用缓存的-convertView"><a href="#构造Adapter时，没有使用缓存的-convertView" class="headerlink" title="构造Adapter时，没有使用缓存的 convertView"></a>构造Adapter时，没有使用缓存的 convertView</h3><p>描述：以构造ListView的BaseAdapter为例，在BaseAdapter中提供了方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> View <span class="title function_">getView</span><span class="params">(<span class="type">int</span> position, View convertView, ViewGroup parent)</span></span><br></pre></td></tr></table></figure><p>来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象，同时ListView会将这些view对象缓存起来。当向上滚动ListView时，原先位于最上面的list item的view对象会被回收，然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的，getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。<br>由此可以看出，如果我们不去使用convertView，而是每次都在getView()中重新实例化一个View对象的话，即浪费资源也浪费时间，也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:android.widget.AbsListView.java –&gt; void addScrapView(View scrap) 方法。</p><p>示例代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> View <span class="title function_">getView</span><span class="params">(<span class="type">int</span> position, View convertView, ViewGroup parent)</span> &#123;</span><br><span class="line"> <span class="type">View</span> <span class="variable">view</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Xxx</span>(...);</span><br><span class="line"> ... ...</span><br><span class="line"> <span class="keyword">return</span> view;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&#96;<br>示例修正代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> View <span class="title function_">getView</span><span class="params">(<span class="type">int</span> position, View convertView, ViewGroup parent)</span> &#123;</span><br><span class="line"> <span class="type">View</span> <span class="variable">view</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (convertView != <span class="literal">null</span>) &#123;</span><br><span class="line"> view = convertView;</span><br><span class="line"> populate(view, getItem(position));</span><br><span class="line"> ...</span><br><span class="line"> &#125; <span class="keyword">else</span> &#123;</span><br><span class="line"> view = <span class="keyword">new</span> <span class="title class_">Xxx</span>(...);</span><br><span class="line"> ...</span><br><span class="line"> &#125;</span><br><span class="line"> <span class="keyword">return</span> view;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p>关于ListView的使用和优化，可以参考这两篇文章：</p><ul><li><a href="">Using lists in Android (ListView) - Tutorial  </a>](<a href="http://www.vogella.com/tutorials/AndroidListView/article.html">http://www.vogella.com/tutorials/AndroidListView/article.html</a>)</li><li><a href="http://developer.android.com/training/improving-layouts/smooth-scrolling.html#ViewHolder">Making ListView Scrolling Smooth</a></li></ul><h3 id="Bitmap对象不在使用时调用recycle-释放内存"><a href="#Bitmap对象不在使用时调用recycle-释放内存" class="headerlink" title="Bitmap对象不在使用时调用recycle()释放内存"></a>Bitmap对象不在使用时调用recycle()释放内存</h3><p>描述：有时我们会手工的操作Bitmap对象，如果一个Bitmap对象比较占内存，当它不在被使用的时候，可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存。<br>另外在最新版本的Android开发时，使用下面的方法也可以释放此Bitmap所占用的内存</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Bitmap bitmap ;</span><br><span class="line"> ...</span><br><span class="line"> bitmap初始化以及使用</span><br><span class="line"> ...</span><br><span class="line">bitmap = <span class="literal">null</span>;</span><br></pre></td></tr></table></figure><h3 id="释放对象的引用"><a href="#释放对象的引用" class="headerlink" title="释放对象的引用"></a>释放对象的引用</h3><p>描述：这种情况描述起来比较麻烦，举两个例子进行说明。</p><p>示例A：<br>假设有如下操作</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoActivity</span> <span class="keyword">extends</span> <span class="title class_">Activity</span> &#123;</span><br><span class="line">... ...</span><br><span class="line"><span class="keyword">private</span> <span class="type">Handler</span> <span class="variable">mHandler</span> <span class="operator">=</span> ...</span><br><span class="line"><span class="keyword">private</span> Object obj;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">operation</span><span class="params">()</span> &#123;</span><br><span class="line"> obj = initObj();</span><br><span class="line"> ...</span><br><span class="line"> [Mark]</span><br><span class="line"> mHandler.post(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">         useObj(obj);</span><br><span class="line">        &#125;</span><br><span class="line"> &#125;);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们有一个成员变量 obj，在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码中，即便是mHandler所在的线程使用完了obj所引用的对象，但这个对象仍然不会被垃圾回收掉，因为DemoActivity.obj还保有这个对象的引用。所以如果在DemoActivity中不再使用这个对象了，可以在[Mark]的位置释放对象的引用，而代码可以修改为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">operation</span><span class="params">()</span> &#123;</span><br><span class="line">obj = initObj();</span><br><span class="line">...</span><br><span class="line"><span class="keyword">final</span> <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> obj;</span><br><span class="line">obj = <span class="literal">null</span>;</span><br><span class="line">mHandler.post(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        useObj(o);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>示例B：<br>假设我们希望在锁屏界面(LockScreen)中，监听系统中的电话服务以获取一些信息(如信号强度等)，则可以在LockScreen中定义一个PhoneStateListener的对象，同时将它注册到TelephonyManager服务中。对于LockScreen对象，当需要显示锁屏界面的时候就会创建一个LockScreen对象，而当锁屏界面消失的时候LockScreen对象就会被释放掉。  </p><p>但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象，则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失，则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。</p><p>总之当一个生命周期较短的对象A，被一个生命周期较长的对象B保有其引用的情况下，在A的生命周期结束时，要在B中清除掉对A的引用。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中，在onPause()、onStop()、onDestroy()方法中需要适当的释放资源的情况。由于此情况很基础，在此不详细说明，具体可以查看官方文档对Activity生命周期的介绍，以明确何时应该释放哪些资源。</p><p>另外一些其他的例子，将会在补充版本加入。</p><h1 id="使用MAT进行内存调试"><a href="#使用MAT进行内存调试" class="headerlink" title="使用MAT进行内存调试"></a>使用MAT进行内存调试</h1><h2 id="获取HPROF文件"><a href="#获取HPROF文件" class="headerlink" title="获取HPROF文件"></a>获取HPROF文件</h2><p>HPROF文件是MAT能识别的文件，HPROF文件存储的是特定时间点，java进程的内存快照。有不同的格式来存储这些数据，总的来说包含了快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情，所以heap dump中无法包含一个对象在何时、何地（哪个方法中）被分配这样的信息。<br>这个文件可以使用DDMS导出：</p><ol><li>DDMS中在Devices上面有一排按钮，选择一个进程后（即在Devices下面列出的列表中选择你要调试的应用程序的包名），点击Dump HPROF file 按钮：</li></ol><p>  <img src="/images/MAT/MAT_DDMS_ExportFile.webp" alt="image">  </p><p>选择存储路径保存后就可以得到对应进程的HPROF文件。eclipse插件可以把上面的工作一键完成。只需要点击Dump HPROF file图标，然后MAT插件就会自动转换格式，并且在eclipse中打开分析结果。eclipse中还专门有个Memory Analysis视图  </p><ol><li>得到对应的文件后，如果安装了Eclipse插件，那么切换到Memory Analyzer视图。使用独立安装的，要使用Android SDK自带的的工具（hprof-conv 位置在sdk&#x2F;platform-tools&#x2F;hprof-conv）进行转换</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hprof-conv xxx.xxx.xxx.hprof xxx.xxx.xxx.hprof</span><br></pre></td></tr></table></figure><p>转换过后的.hprof文件即可使用MAT工具打开了。</p><h2 id="MAT主界面介绍"><a href="#MAT主界面介绍" class="headerlink" title="MAT主界面介绍"></a>MAT主界面介绍</h2><p>这里介绍的不是MAT这个工具的主界面，而是导入一个文件之后，显示OverView的界面。</p><ul><li>打开经过转换的hprof文件：<br>  <img src="/images/MAT/MAT_OpenFile.webp" alt="image"></li></ul><p>如果选择了第一个，则会生成一个报告。这个无大碍。  </p><p><img src="/images/MAT/MAT_Report.webp" alt="image"></p><ul><li><p>选择OverView界面：  </p><p><img src="/images/MAT/MAT_Overview.webp" alt="Image"></p></li></ul><p>我们需要关注的是下面的Actions区域</p><ul><li><p>Histogram：列出内存中的对象，对象的个数以及大小  </p><p><img src="/images/MAT/MAT_Histogram.webp" alt="image"></p></li><li><p>Dominator Tree：列出最大的对象以及其依赖存活的Object （大小是以Retained Heap为标准排序的）  </p><p><img src="/images/MAT/MAT_DominatorTree.webp" alt="image"></p></li><li><p>Top Consumers ： 通过图形列出最大的object  </p><p><img src="/images/MAT/MAT_TopConsumers.webp" alt="image"></p></li><li><p>Duplicate Class：通过MAT自动分析泄漏的原因</p></li></ul><p>一般Histogram和 Dominator Tree是最常用的。  </p><h2 id="MAT中一些概念介绍"><a href="#MAT中一些概念介绍" class="headerlink" title="MAT中一些概念介绍"></a>MAT中一些概念介绍</h2><p>要看懂MAT的列表信息，Shallow heap、Retained Heap、GC Root这几个概念一定要弄懂。</p><h3 id="3-3-1-Shallow-heap"><a href="#3-3-1-Shallow-heap" class="headerlink" title="3.3.1 Shallow heap"></a>3.3.1 Shallow heap</h3><p>Shallow size就是对象本身占用内存的大小，不包含其引用的对象。  </p><ul><li>常规对象（非数组）的Shallow size有其成员变量的数量和类型决定。</li><li>数组的shallow size有数组元素的类型（对象类型、基本类型）和数组长度决定</li></ul><p>因为不像c++的对象本身可以存放大量内存，java的对象成员都是些引用。真正的内存都在堆上，看起来是一堆原生的byte[], char[], int[]，所以我们如果只看对象本身的内存，那么数量都很小。所以我们看到Histogram图是以Shallow size进行排序的，排在第一位第二位的是byte，char 。</p><h3 id="3-3-2-Retained-Heap"><a href="#3-3-2-Retained-Heap" class="headerlink" title="3.3.2 Retained Heap"></a>3.3.2 Retained Heap</h3><p>Retained Heap的概念，它表示如果一个对象被释放掉，那会因为该对象的释放而减少引用进而被释放的所有的对象（包括被递归释放的）所占用的heap大小。于是，如果一个对象的某个成员new了一大块int数组，那这个int数组也可以计算到这个对象中。相对于shallow heap，Retained heap可以更精确的反映一个对象实际占用的大小（因为如果该对象释放，retained heap都可以被释放）。</p><p>这里要说一下的是，Retained Heap并不总是那么有效。例如我在A里new了一块内存，赋值给A的一个成员变量。此时我让B也指向这块内存。此时，因为A和B都引用到这块内存，所以A释放时，该内存不会被释放。所以这块内存不会被计算到A或者B的Retained Heap中。为了纠正这点，MAT中的Leading Object（例如A或者B）不一定只是一个对象，也可以是多个对象。此时，(A, B)这个组合的Retained Set就包含那块大内存了。对应到MAT的UI中，在Histogram中，可以选择Group By class, superclass or package来选择这个组。</p><p>为了计算Retained Memory，MAT引入了Dominator Tree。加入对象A引用B和C，B和C又都引用到D（一个菱形）。此时要计算Retained Memory，A的包括A本身和B，C，D。B和C因为共同引用D，所以他俩的Retained Memory都只是他们本身。D当然也只是自己。我觉得是为了加快计算的速度，MAT改变了对象引用图，而转换成一个对象引用树。在这里例子中，树根是A，而B，C，D是他的三个儿子。B，C，D不再有相互关系。把引用图变成引用树，计算Retained Heap就会非常方便，显示也非常方便。对应到MAT UI上，在dominator tree这个view中，显示了每个对象的shallow heap和retained heap。然后可以以该节点位树根，一步步的细化看看retained heap到底是用在什么地方了。要说一下的是，这种从图到树的转换确实方便了内存分析，但有时候会让人有些疑惑。本来对象B是对象A的一个成员，但因为B还被C引用，所以B在树中并不在A下面，而很可能是平级。</p><p>为了纠正这点，MAT中点击右键，可以List objects中选择with outgoing references和with incoming references。这是个真正的引用图的概念，</p><ul><li>outgoing references ：表示该对象的出节点（被该对象引用的对象）。</li><li>incoming references ：表示该对象的入节点（引用到该对象的对象）。</li></ul><p>为了更好地理解Retained Heap，下面引用一个例子来说明：  </p><p>把内存中的对象看成下图中的节点，并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots，这就是reference chain(引用链)的起点:<br><img src="/images/MAT/MAT_Retained_objects.webp" alt="image"> <img src="/images/MAT/MAT_Retained_objects_2.webp" alt="image"></p><p>从obj1入手，上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问，所以左图的obj3不是蓝色节点；而在右图却是蓝色，因为它已经被包含在retained集合内。<br>所以对于左图，obj1的retained size是obj1、obj2、obj4的shallow size总和；<br>右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。<br>obj2的retained size可以通过相同的方式计算。</p><h3 id="GC-Root"><a href="#GC-Root" class="headerlink" title="GC Root"></a>GC Root</h3><p>GC发现通过任何reference chain(引用链)无法访问某个对象的时候，该对象即被回收。名词GC Roots正是分析这一过程的起点，例如JVM自己确保了对象的可到达性(那么JVM就是GC Roots)，所以GC Roots就是这样在内存中保持对象可到达性的，一旦不可到达，即被回收。通常GC Roots是一个在current thread(当前线程)的call stack(调用栈)上的对象（例如方法参数和局部变量），或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。所以GC Roots是分析对象为何还存活于内存中的利器。</p><h2 id="MAT中的一些有用的视图"><a href="#MAT中的一些有用的视图" class="headerlink" title="MAT中的一些有用的视图"></a>MAT中的一些有用的视图</h2><h3 id="Thread-OvewView"><a href="#Thread-OvewView" class="headerlink" title="Thread OvewView"></a>Thread OvewView</h3><p>Thread OvewView可以查看这个应用的Thread信息：<br><img src="/images/MAT/MAT_ThreadOverView.webp" alt="image"></p><h3 id="Group"><a href="#Group" class="headerlink" title="Group"></a>Group</h3><p>在Histogram和Domiantor Tree界面，可以选择将结果用另一种Group的方式显示（默认是Group by Object），切换到Group by package，可以更好地查看具体是哪个包里的类占用内存大，也很容易定位到自己的应用程序。<br><img src="/images/MAT/MAT_Group.webp" alt="image"></p><h3 id="Path-to-GC-Root"><a href="#Path-to-GC-Root" class="headerlink" title="Path to GC Root"></a>Path to GC Root</h3><p>在Histogram或者Domiantor Tree的某一个条目上，右键可以查看其GC Root Path：<br><img src="/images/MAT/MAT_PathToGCRoot.webp" alt="image"></p><p>这里也要说明一下Java的引用规则：<br>从最强到最弱，不同的引用（可到达性）级别反映了对象的生命周期。  </p><ul><li>Strong Ref(强引用)：通常我们编写的代码都是Strong Ref，于此对应的是强可达性，只有去掉强可达，对象才被回收。</li><li>Soft Ref(软引用)：对应软可达性，只要有足够的内存，就一直保持对象，直到发现内存吃紧且没有Strong Ref时才回收对象。一般可用来实现缓存，通过java.lang.ref.SoftReference类实现。</li><li>Weak Ref(弱引用)：比Soft Ref更弱，当发现不存在Strong Ref时，立刻回收对象而不必等到内存吃紧的时候。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。</li><li>Phantom Ref(虚引用)：根本不会在内存中保持任何对象，你只能使用Phantom Ref本身。一般用于在进入finalize()方法后进行特殊的清理过程，通过 java.lang.ref.PhantomReference实现。</li></ul><p>点击Path To GC Roots –&gt; with all references<br><img src="/images/MAT/MAT_PathToGCRoot_Detail.webp" alt="image"></p><h1 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h1><ol><li><a href="http://www.yourkit.com/docs/java/help/sizes.jsp">Shallow and retained sizes</a></li><li>MAT的wiki：<a href="http://wiki.eclipse.org/index.php/MemoryAnalyzer">http://wiki.eclipse.org/index.php/MemoryAnalyzer</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是 MAT 工具使用系列的第一篇，这个系列共三篇，详细介绍了如何使用 MAT 来分析内存问题，既可以是 Java 应用的内存问题，也可以是 Android 应用的内存问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT/&quot;&gt;Android 内存优化(1) - MAT 使用入门&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Usage-Of-MAT-Pro/&quot;&gt;Android 内存优化(2) - MAT使用进阶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.androidperformance.com/2015/04/11/AndroidMemory-Open-Bitmap-Object-In-MAT/&quot;&gt;Android 内存优化(3) - 打开MAT中的Bitmap原图&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&quot;MAT简介&quot;&gt;&lt;a href=&quot;#MAT简介&quot; class=&quot;headerlink&quot; title=&quot;MAT简介&quot;&gt;&lt;/a&gt;MAT简介&lt;/h1&gt;&lt;h2 id=&quot;MAT介绍&quot;&gt;&lt;a href=&quot;#MAT介绍&quot; class=&quot;headerlink&quot; title=&quot;MAT介绍&quot;&gt;&lt;/a&gt;MAT介绍&lt;/h2&gt;&lt;p&gt;MAT(Memory Analyzer Tool)，一个基于 Eclipse 的内存分析工具，是一个快速、功能丰富的JAVA heap分析工具，它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析，快速的计算出在内存中对象的占用大小，看看是谁阻止了垃圾收集器的回收工作，并可以通过报表直观的查看到可能造成这种结果的对象。  &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/MAT/MAT_Intro.webp&quot; alt=&quot;image&quot;&gt;&lt;/p&gt;
&lt;p&gt;当然MAT也有独立的不依赖Eclipse的版本，只不过这个版本在调试Android内存的时候，需要将DDMS生成的文件进行转换，才可以在独立版本的MAT上打开。不过Android SDK中已经提供了这个Tools，所以使用起来也是很方便的。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
  </entry>
  
  <entry>
    <title>Android性能优化后续</title>
    <link href="https://androidperformance.com/2015/03/31/android-performance-case-study-follow-up/"/>
    <id>https://androidperformance.com/2015/03/31/android-performance-case-study-follow-up/</id>
    <published>2015-03-31T01:29:00.000Z</published>
    <updated>2026-02-07T05:17:47.861Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本文是一篇译文，原文<a href="http://www.curious-creature.com/2015/03/25/android-performance-case-study-follow-up/?utm_source=Android+Weekly&utm_campaign=0692ef161b-Android_Weekly_146&utm_medium=email&utm_term=0_4eb677ad19-0692ef161b-337850757">Android Performance Case Study Follow-up</a>的作者是大名鼎鼎的<a href="http://www.curious-creature.com/">Romain Guy</a>。本文讲述了Android性能优化的一些技巧、方法和工具。</p><h1 id="译文正文"><a href="#译文正文" class="headerlink" title="译文正文"></a>译文正文</h1><p>两年前，我发表了一篇名为<a href="http://www.curious-creature.com/2012/12/01/android-performance-case-study/">Android Performance Case Study</a> 的文章，来帮助Android开发者了解需要使用什么工具和技术手段来确定、追踪和优化性能问题。</p><p>那篇文章以一个Twitter客户端 <a href="https://play.google.com/store/apps/details?id=com.jv.materialfalcon">Falcon Pro</a>为典范，其开发人员为 Joaquim Vergès. Joaquim人不错，他允许我在我的文章中使用它的程序作为例子，并且快速处理了我发现的所有问题。一切都OK，直到Joaquim 从头开始开发Falcon Pro 3，前不久在他准备发布它的新应用的时候，他联系了我，因为他有一个和滚动相关的性能问题需要我来帮助他，这一次我依然没有源代码可以参考。</p><span id="more"></span><p>Joaquim使用了所有的工具来找出问题所在，他发现Overdraw不是问题的原因，他觉得是 <a href="http://developer.android.com/reference/android/support/v4/view/ViewPager.html">ViewPager</a> 的用法导致了这个问题。他给我发来了下面的截图：</p><p><img src="/images/android-performance-case-study-follow-up/falconpro3.webp" alt="Falcon Pro"></p><p>Joaquim使用了系统内置的GPU profiling工具来发现掉帧现象， 左边的截图是在没有ViewPager 的情况下滑动时间线，右边的截图是有ViewPager的情况下滑动（他使用的是2014年的Moto x来截的图），问题看起来很明显。</p><p>我最先想到的是查看ViewPager是不是由于滥用硬件加速导致，这个性能问题看起来像是在滑动的过程中每一帧都使用了硬件加速。系统的 <a href="http://www.curious-creature.com/2013/09/13/optimizing-hardware-layers/">hardware layers updates debugging tool</a>没有显示什么有用的信息。我反复使用HierarchyViewer 查看布局情况，令我满意的是ViewPager的表现很正确（相反，不太可能会出问题）</p><p>之后我打开了另一个强大的工具却很少用到的工具：Tracer for OpenGL 。我之前的那篇<a href="http://www.curious-creature.com/2012/12/01/android-performance-case-study">文章</a>解释了如何使用工具获得更多细节。你首先需要知道的是这个工具收集了所有UI界面发给GPU的绘制命令。</p><blockquote><p><strong>Android 4.3 and up</strong>: <em>Tracer</em> has unfortunately become a little more difficult to use since Android 4.3 when we introduced<a href="https://developers.google.com/events/io/sessions/325418001">reordering and merging of drawing commands</a>. It’s an amazingly useful optimization but it prevents <em>Tracer</em> from grouping drawing commands by view. You can restore the old behavior by disabling display lists optimization using the following command (before you start your application)(意思是说Android4.3之后，这个工具不太好用了，因为有reordering and merging 机制的引进)</p></blockquote><blockquote><p><strong>Reading OpenGL traces</strong>: Commands shown in blue are GL operations that draw pixels on screen. All other commands are used to transfer data or set state and can easily be ignored. Every time you click on one of the blue commands, Tracer will update the Details tab and show you the content of the current render target right after the command you clicked is executed. You can thus reconstruct a frame by clicking on each blue command one after another. It’s pretty much how I analyze performance issues with Tracer. Seeing how a frame is rendered gives a lot of insight on what the application is doing.(意思是说只蓝色的行是真正进行绘制的命令，点击可以看到绘制的这一帧的图像，其他的命令都是一些数据的转换)</p></blockquote><p>滑动一段时间Falcon Pro应用后，我仔细查看Gl Trace收集到的数据，我很惊奇地发现很多SaveLayer&#x2F;ComposeLayer阻塞命令。</p><p><img src="/images/android-performance-case-study-follow-up/glTrace.webp" alt="Paste_Image.png"></p><p>这些命令表明应用在生成一个临时的Hardware Layer。这些临时的Layer被不同的 [Canvas.saveLayer()](<a href="http://developer.android.com/reference/android/graphics/Canvas.html#saveLayer">http://developer.android.com/reference/android/graphics/Canvas.html#saveLayer</a>(float, float, float, float, android.graphics.Paint, int))所创建，这些UI控件在下面的情况下使用Canvas.saveLayer()方法去绘制 alpha &lt; 1 (see<a href="http://developer.android.com/reference/android/view/View.html#setAlpha(float)">View.setAlpha()</a>的View(即半透明View)：</p><ul><li><a href="http://developer.android.com/reference/android/view/View.html#getAlpha(">getAlpha()</a> returns a value &lt; 1</li><li><a href="http://developer.android.com/reference/android/view/View.html#onSetAlpha(int">onSetAlpha()</a> returns false</li><li><a href="http://developer.android.com/reference/android/view/View.html#getLayerType(">getLayerType()</a> returns LAYER_TYPE_NONE</li><li><a href="http://developer.android.com/reference/android/view/View.html#hasOverlappingRendering(">hasOverlappingRendering()</a> returns true</li></ul><p>我和Chet 在很多演示中解释过为什么你应该 <a href="https://youtu.be/vQZFaec9NpA?t=29m51s">use alpha with care</a>，每次UI控件使用一个临时的Layer，绘制命令会发送不同的渲染目标，对GPU来说，切换渲染目标是很昂贵的操作,这对于使用tiling&#x2F;deferred架构的GPU（ImaginationTech’s SGX, Qualcomm’s Adreno, etc）等是硬伤，直接渲染架构的GPU，比如 Nvidia，则会好一点。因为我和Joaquim 使用的是搭载高通处理器的Moto X 2014版本，所以使用多个临时硬件层是最有可能的性能问题的根源。</p><p>那么问题来了，是什么创建了这些临时的Layer呢？<em>Tracer</em>告诉我们了答案，如果你看了刚刚上面那张<a href="http://www.curious-creature.com/blog/wp-content/uploads/2015/03/Screen-Shot-2015-03-25-at-11.00.53-AM-950x552.webp">图</a>,你可以看到只有SaveLayer这个组中OpenGl命令绘制了一个小圆圈（图被工具放大了），我们来看一下应用截图：</p><p><img src="/images/android-performance-case-study-follow-up/before.webp" alt="Falcon Pro 3"></p><p>你看到最上面的小圆圈了么？那是ViewPager的指示器，来显示当前的位置。Joaquim 使用了一个第三方库来绘制这些指示器，有趣的是这些库如何绘制指示器的：当前的Page用一个白色的圈指示，其他的页用类似灰色的圆圈来指示。我说类似灰色因为这个圆圈其实是半透明的白色圆圈。这个库使用 setAlpha()方法来给每个圆圈设置颜色。</p><p>有下面几种方法来解决这个问题：</p><ul><li>Use a customizable “inactive” color instead of setting an opacity on the View（ 使用动态的“inactive”颜色(即根据状态来设置View的颜色)而不是设置透明度。）</li><li>Return false from hasOverlappingRendering() and the framework will set the proper alpha on the Paint<br> for you（使hasOverlappingRendering()返回false，这样系统会设置适当的alpha，关于这个的用法，这篇<a href="http://imid.me/blog/2014/01/17/best-practices-for-using-alpha/">文章</a>中有提到：同时Android提供了hasOverlappingRendering()接口，通过重写该接口可以告知系统当前View是否存在内容重叠的情况，帮助系统优化绘制流程，原理是这样的：对于有重叠内容的View，系统简单粗暴的使用 offscreen buffer来协助处理。当告知系统该View无重叠内容时，系统会分别使用合适的alpha值绘制每一层。）</li></ul><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment">* Returns whether this View has content which overlaps. This function, intended to be</span></span><br><span class="line"><span class="comment">* overridden by specific View types, is an optimization when alpha is set on a view. If</span></span><br><span class="line"><span class="comment">* rendering overlaps in a view with alpha &lt; 1, that view is drawn to an offscreen buffer</span></span><br><span class="line"><span class="comment">* and then composited it into place, which can be expensive. If the view has no overlapping</span></span><br><span class="line"><span class="comment">* rendering, the view can draw each primitive with the appropriate alpha value directly.</span></span><br><span class="line"><span class="comment">* An example of overlapping rendering is a TextView with a background image, such as a</span></span><br><span class="line"><span class="comment">* Button. An example of non-overlapping rendering is a TextView with no background, or</span></span><br><span class="line"><span class="comment">* an ImageView with only the foreground image. The default implementation returns true;</span></span><br><span class="line"><span class="comment">* subclasses should override if they have cases which can be optimized.</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* <span class="doctag">@return</span> true if the content in this view might overlap, false otherwise.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">  <span class="keyword">public</span> <span class="built_in">boolean</span> <span class="title function_">hasOverlappingRendering</span>(<span class="params"></span>) &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>Return true from onSetAlpha() and set an alpha on the Paint used to draw the “gray” circles（使onSetAlpha() 返回True并对Paint设置alpha来绘制“gray”圆圈）</li></ul><figure class="highlight mel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">paint.setAlpha((<span class="keyword">int</span>) alpha * <span class="number">255</span>);</span><br><span class="line"><span class="keyword">canvas</span>.draw*(..., paint);</span><br></pre></td></tr></table></figure><p>最简单的方法是使用第二种，但是他只能在API16以上使用，如果你要支持旧版本的Android，使用其他两个方法，我相信Joaquim 已经丢弃那个第三方库并使用自己的指示器了。</p><p>我希望这篇文章能让大家清楚如何从看似无辜的和无害的操作中寻找可能会出现性能问题。所以请记住:不要仅仅做出假设,要实际去验证、测量。</p><h1 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h1><p>更多关于Alpha的使用，可以参考这篇文章：<br><a href="http://imid.me/blog/2014/01/17/best-practices-for-using-alpha/">Android Tips: Best Practices for Using Alpha</a></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;本文是一篇译文，原文&lt;a href=&quot;http://www.curious-creature.com/2015/03/25/android-performance-case-study-follow-up/?utm_source=Android+Weekly&amp;utm_campaign=0692ef161b-Android_Weekly_146&amp;utm_medium=email&amp;utm_term=0_4eb677ad19-0692ef161b-337850757&quot;&gt;Android Performance Case Study Follow-up&lt;/a&gt;的作者是大名鼎鼎的&lt;a href=&quot;http://www.curious-creature.com/&quot;&gt;Romain Guy&lt;/a&gt;。本文讲述了Android性能优化的一些技巧、方法和工具。&lt;/p&gt;
&lt;h1 id=&quot;译文正文&quot;&gt;&lt;a href=&quot;#译文正文&quot; class=&quot;headerlink&quot; title=&quot;译文正文&quot;&gt;&lt;/a&gt;译文正文&lt;/h1&gt;&lt;p&gt;两年前，我发表了一篇名为&lt;a href=&quot;http://www.curious-creature.com/2012/12/01/android-performance-case-study/&quot;&gt;Android Performance Case Study&lt;/a&gt; 的文章，来帮助Android开发者了解需要使用什么工具和技术手段来确定、追踪和优化性能问题。&lt;/p&gt;
&lt;p&gt;那篇文章以一个Twitter客户端 &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.jv.materialfalcon&quot;&gt;Falcon Pro&lt;/a&gt;为典范，其开发人员为 Joaquim Vergès. Joaquim人不错，他允许我在我的文章中使用它的程序作为例子，并且快速处理了我发现的所有问题。一切都OK，直到Joaquim 从头开始开发Falcon Pro 3，前不久在他准备发布它的新应用的时候，他联系了我，因为他有一个和滚动相关的性能问题需要我来帮助他，这一次我依然没有源代码可以参考。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Performance" scheme="https://androidperformance.com/tags/Performance/"/>
    
    <category term="MAT" scheme="https://androidperformance.com/tags/MAT/"/>
    
    <category term="Compose" scheme="https://androidperformance.com/tags/Compose/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 - 5</title>
    <link href="https://androidperformance.com/2015/03/15/android-tips-round-up-5/"/>
    <id>https://androidperformance.com/2015/03/15/android-tips-round-up-5/</id>
    <published>2015-03-14T16:32:11.000Z</published>
    <updated>2026-02-07T05:17:47.870Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一篇译文,这篇是这个系列的第五篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.html">ViewDragHelper</a> ——视图拖动是一个比较复杂的问题。这个类可以帮助解决不少问题。如果你需要一个例子，<a href="https://developer.android.com/reference/android/support/v4/widget/DrawerLayout.html">DrawerLayout</a>就是利用它实现扫滑。Flavient Laurent 还写了一些关于这方面的<a href="http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/">优秀文章</a>。</p><p><a href="https://developer.android.com/reference/android/widget/PopupWindow.html">PopupWindow</a>——Android到处都在使用PopupWindow ，甚至你都没有意识到（标题导航条ActionBar，自动补全AutoComplete，编辑框错误提醒Edittext Errors）。这个类是创建浮层内容的主要方法。</p><p><a href="https://developer.android.com/reference/android/app/ActionBar.htmlgetThemedContext%28%29">Actionbar.getThemrContext()</a>——导航栏的主题化是很复杂的（不同于Activity其他部分的主题化）。你可以得到一个上下文（Context），用这个上下文创建的自定义组件可以得到正确的主题。</p><p><a href="https://developer.android.com/reference/android/media/ThumbnailUtils.html">ThumbnailUtils</a>——帮助创建缩略图。通常我都是用现有的图片加载库（比如，Picasso 或者 Volley），不过这个ThumbnaiUtils可以创建视频缩略图。<strong>译者注：</strong>该API从V8才开始支持。</p><p><a href="https://developer.android.com/reference/android/content/Context.htmlgetExternalFilesDir%28java.lang.String%29">Context.getExternalFilesDir()</a>———— 申请了SD卡写权限后，你可以在SD的任何地方写数据，把你的数据写在设计好的合适位置会更加有礼貌。这样数据可以及时被清理，也会有更好的用户体验。此外，Android 4.0 Kitkat中在这个文件夹下写数据是不需要权限的，每个用户有自己的独立的数据存储路径。<strong>译者注：</strong>该API从V8才开始支持。</p><p><a href="https://developer.android.com/reference/android/util/SparseArray.html">SparseArray</a>——Map的高效优化版本。推荐了解姐妹类SparseBooleanArray、SparseIntArray和SparseLongArray。</p><p><a href="https://developer.android.com/reference/android/content/pm/PackageManager.htmlsetComponentEnabledSetting%28android.content.ComponentName,%20int,%20int%29">PackageManager.setComponentEnabledSetting()</a>——可以用来启动或者禁用程序清单中的组件。对于关闭不需要的功能组件是非常赞的，比如关掉一个当前不用的广播接收器。</p><p><a href="https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.htmlyieldIfContendedSafely%28%29">SQLiteDatabase.yieldIfContendedSafely()</a>——让你暂时停止一个数据库事务， 这样你可以就不会占用太多的系统资源。</p><p><a href="https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory%28java.lang.String%29">Environment.getExternalStoragePublicDirectory()</a>——还是那句话，用户期望在SD卡上得到统一的用户体验。用这个方法可以获得在用户设备上放置指定类型文件（音乐、图片等）的正确目录。</p><p><a href="https://developer.android.com/reference/android/view/View.htmlgenerateViewId%28%29">View.generateViewId()</a>——每次我都想要推荐动态生成控件的ID。需要注意的是，不要和已经存在的控件ID或者其他已经生成的控件ID重复。</p><p><a href="https://developer.android.com/reference/android/app/ActivityManager.htmlclearApplicationUserData%28%29">ActivityManager.clearApplicationUserData()</a>—— 一键清理你的app产生的用户数据，可能是做用户退出登录功能，有史以来最简单的方式了。</p><p><a href="http://developer.android.com/reference/android/content/Context.htmlcreateConfigurationContext%28android.%E2%80%94%E2%80%94ontent.res.Configuration%29">Context.createConfigurationContext()</a> ——自定义你的配置环境信息。我通常会遇到这样的问题：强制让一部分显示在某个特定的环境下（倒不是我一直这样瞎整，说来话长，你很难理解）。用这个实现起来可以稍微简单一点。</p><p><a href="http://developer.android.com/reference/android/app/ActivityOptions.html">ActivityOptions</a> ——方便的定义两个Activity切换的动画。 使用<a href="http://developer.android.com/reference/android/support/v4/app/ActivityOptionsCompat.html">ActivityOptionsCompat</a> 可以很好解决旧版本的兼容问题。</p><p><a href="http://developer.android.com/reference/android/widget/AdapterViewFlipper.htmlfyiWillBeAdvancedByHostKThx%28%29">AdapterViewFlipper.fyiWillBeAdvancedByHostKThx()</a>——仅仅因为很好玩，没有其他原因。在整个安卓开源项目中（AOSP the Android ——pen Source Project Android开放源代码项目）中还有其他很有意思的东西（比如<br><a href="http://developer.android.com/reference/android/hardware/SensorManager.htmlGRAVITY_DEATH_STAR_I">GRAVITY_DEATH_STAR_I</a>）。不过，都不像这个这样，这个确实有用</p><p><a href="http://developer.android.com/reference/android/view/ViewParent.htmlrequestDisallowInterceptTouchEvent%28boolean%29">ViewParent.requestDisallowInterceptTouchEvent()</a> ——Android系统触摸事件机制大多时候能够默认处理，不过有时候你需要使用这个方法来剥夺父级控件的控制权（顺便说一下，如果你想对Android触摸机制了解更多，<a href="https://www.youtube.com/watch?v=EZAoJU-nUyI">这个演讲</a>会令你惊叹不已。）</p><h2 id="备忘"><a href="#备忘" class="headerlink" title="备忘"></a>备忘</h2><h3 id="More"><a href="#More" class="headerlink" title="More"></a>More</h3><ol><li><a href="https://www.androidperformance.com/android-tips-round-up.html">Part1</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-2.html">Part2</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-3.html">Part3</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-4.html">Part4</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-5.html">Part5</a></li></ol><p>原文地址:<a href="http://blog.danlew.net/2014/03/30/android-tips-round-up-part-5/">http://blog.danlew.net/2014/03/30/android-tips-round-up-part-5/</a><br>原文作者:<a href="http://blog.danlew.net/about/">http://blog.danlew.net/about/</a><br>本文地址:<a href="https://www.androidperformance.com/android-tips-round-up-5.html">https://www.androidperformance.com/android-tips-round-up-5.html</a> 转载请注明.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是一篇译文,这篇是这个系列的第五篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 - 4</title>
    <link href="https://androidperformance.com/2015/03/15/android-tips-round-up-4/"/>
    <id>https://androidperformance.com/2015/03/15/android-tips-round-up-4/</id>
    <published>2015-03-14T16:31:02.000Z</published>
    <updated>2026-02-07T05:17:47.870Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一篇译文,这篇是这个系列的第四篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="http://developer.android.com/reference/android/app/Activity.html#isChangingConfigurations%28%29">Activity.isChangingConfigurations ()</a>——如果在 Activity 中 configuration 会经常改变的话，使用这个方法就可以不用手动做保存状态的工作了。</p><p><a href="http://developer.android.com/reference/android/content/SearchRecentSuggestionsProvider.html">SearchRecentSuggestionsProvider</a>——可以创建最近提示效果的 provider，是一个简单快速的方法。</p><p><a href="http://developer.android.com/reference/android/view/ViewTreeObserver.html">ViewTreeObserver</a>——这是一个很棒的工具。可以进入到 VIew 里面，并监控 View 结构的各种状态，通常我都用来做 View 的测量操作（自定义视图中经常用到）。</p><p><a href="https://www.timroes.de/2013/09/12/speed-up-gradle/">org.gradle.daemon&#x3D;true</a>——这句话可以帮助减少 Gradle 构建的时间，仅在命令行编译的时候用到，因为 Android Studio 已经这样使用了。</p><p><a href="http://developer.android.com/reference/android/database/DatabaseUtils.html">DatabaseUtils</a>——一个包含各种数据库操作的使用工具。</p><p><a href="http://developer.android.com/reference/android/widget/LinearLayout.html#attr_android:weightSum">android:weightSum (LinearLayout)</a>——如果想使用 layout weights，但是却不想填充整个 LinearLayout 的话，就可以用 weightSum 来定义总的 weight 大小。</p><p><a href="http://developer.android.com/reference/android/view/View.html#attr_android:duplicateParentState">android:duplicateParentState (View)</a>——此方法可以使得子 View 可以复制父 View 的状态。比如如果一个 ViewGroup 是可点击的，那么可以用这个方法在它被点击的时候让它的子 View 都改变状态。</p><p><a href="http://developer.android.com/reference/android/view/ViewGroup.html#attr_android:clipChildren">android:clipChildren (ViewGroup)</a>——如果此属性设置为不可用，那么 ViewGroup 的子 View 在绘制的时候会超出它的范围，在做动画的时候需要用到。</p><p><a href="http://developer.android.com/reference/android/widget/ScrollView.html#attr_android:fillViewport">android:fillViewport (ScrollView)</a>——在这片文章中有详细介绍<a href="http://www.curious-creature.org/2010/08/15/scrollviews-handy-trick/">文章链接</a>，可以解决在 ScrollView 中当内容不足的时候填不满屏幕的问题。</p><p><a href="http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap">android:tileMode (BitmapDrawable)</a>——可以指定图片使用重复填充的模式。</p><p><a href="http://developer.android.com/reference/android/R.attr.html#exitFadeDuration">android:enterFadeDuration&#x2F;android:exitFadeDuration (Drawables)</a>——此属性在 Drawable 具有多种状态的时候，可以定义它展示前的淡入淡出效果。</p><p><a href="http://developer.android.com/reference/android/widget/ImageView.html#attr_android:scaleType">android:scaleType (ImageView)</a>——定义在 ImageView 中怎么缩放&#x2F;剪裁图片，一般用的比较多的是“centerCrop”和“centerInside”。</p><p><a href="http://developer.android.com/training/improving-layouts/reusing-layouts.html#Merge">Merge</a>——此标签可以在另一个布局文件中包含别的布局文件，而不用再新建一个 ViewGroup，对于自定义 ViewGroup 的时候也需要用到；可以通过载入一个带有标签的布局文件来自动定义它的子部件。</p><p><a href="http://developer.android.com/reference/android/util/AtomicFile.html">AtomicFile</a>——通过使用备份文件进行文件的原子化操作。这个知识点之前我也写过，不过最好还是有出一个官方的版本比较好。</p><h2 id="备忘"><a href="#备忘" class="headerlink" title="备忘"></a>备忘</h2><h3 id="More"><a href="#More" class="headerlink" title="More"></a>More</h3><ol><li><a href="https://www.androidperformance.com/android-tips-round-up.html">Part1</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-2.html">Part2</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-3.html">Part3</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-4.html">Part4</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-5.html">Part5</a></li></ol><p>原文地址:<a href="http://blog.danlew.net/2014/03/30/android-tips-round-up-part-4/">http://blog.danlew.net/2014/03/30/android-tips-round-up-part-4/</a><br>原文作者:<a href="http://blog.danlew.net/about/">http://blog.danlew.net/about/</a><br>本文地址:<a href="https://www.androidperformance.com/android-tips-round-up-4.html">https://www.androidperformance.com/android-tips-round-up-4.html</a> 转载请注明.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是一篇译文,这篇是这个系列的第四篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 -3</title>
    <link href="https://androidperformance.com/2015/03/15/android-tips-round-up-3/"/>
    <id>https://androidperformance.com/2015/03/15/android-tips-round-up-3/</id>
    <published>2015-03-14T16:29:57.000Z</published>
    <updated>2026-02-07T05:17:47.869Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一篇译文,这篇是这个系列的第三篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="http://developer.android.com/reference/android/net/UrlQuerySanitizer.html">UrlQuerySanitizer</a>——使用这个工具可以方便对 URL 进行检查。</p><p><a href="http://developer.android.com/reference/android/app/Fragment.html#setArguments%28android.os.Bundle%29">Fragment.setArguments</a>——因为在构建 Fragment 的时候不能加参数，所以这是个很好的东西，可以在创建 Fragment 之前设置参数（即使在 configuration 改变的时候仍然会导致销毁&#x2F;重建）。</p><p><a href="http://developer.android.com/reference/android/app/DialogFragment.html#setShowsDialog%28boolean%29">DialogFragment.setShowsDialog ()</a>—— 这是一个很巧妙的方式，DialogFragment 可以作为正常的 Fragment 显示！这里可以让 Fragment 承担双重任务。我通常在创建 Fragment 的时候把 onCreateView ()和 onCreateDialog ()都加上，就可以创建一个具有双重目的的 Fragment。</p><p><a href="http://developer.android.com/reference/android/app/FragmentManager.html#enableDebugLogging%28boolean%29">FragmentManager.enableDebugLogging ()</a>——在需要观察 Fragment 状态的时候会有帮助。</p><p><a href="http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html">LocalBroadcastManager</a>——这个会比全局的 broadcast 更加安全，简单，快速。像 <a href="http://square.github.io/otto/">otto</a> 这样的 Event buses 机制对你的应用场景更加有用。</p><p><a href="http://developer.android.com/reference/android/telephony/PhoneNumberUtils.html#formatNumber%28java.lang.String%29">PhoneNumberUtils.formatNumber ()</a>——顾名思义，这是对数字进行格式化操作的时候用的。</p><p><a href="http://developer.android.com/reference/android/graphics/Region.html#op%28android.graphics.Region,%20android.graphics.Region,%20android.graphics.Region.Op%29">Region.op()</a>——我发现在对比两个渲染之前的区域的时候很实用，如果你有两条路径，那么怎么知道它们是不是会重叠呢？使用这个方法就可以做到。</p><p><a href="http://developer.android.com/reference/android/app/Application.html#registerActivityLifecycleCallbacks%28android.app.Application.ActivityLifecycleCallbacks%29">Application.registerActivityLifecycleCallbacks</a>——虽然缺少官方文档解释，不过我想它就是注册 Activity 的生命周期的一些回调方法（顾名思义），就是一个方便的工具。</p><p><a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types">versionNameSuffix</a>——这个 gradle 设置可以让你在基于不同构建类型的 manifest 中修改版本名这个属性，例如，如果需要在在 debug 版本中以”-SNAPSHOT”结尾，那么就可以轻松的看出当前是 debug 版还是 release 版。</p><p><a href="http://developer.android.com/reference/android/database/CursorJoiner.html">CursorJoiner</a>——如果你是只使用一个数据库的话，使用 SQL 中的 join 就可以了，但是如果收到的数据是来自两个独立的 ContentProvider，那么 CursorJoiner 就很实用了。</p><p><a href="http://www.genymotion.com/">Genymotion</a>——一个非常快的 Android 模拟器，本人一直在用。</p><p><a href="http://developer.android.com/guide/practices/screens_support.html#qualifiers">-nodpi</a>——在没有特别定义的情况下，很多修饰符(-mdpi,-hdpi,-xdpi等等)都会默认自动缩放 assets&#x2F;dimensions，有时候我们需要保持显示一致，这种情况下就可以使用 -nodpi。</p><p><a href="http://developer.android.com/reference/android/content/BroadcastReceiver.html#setDebugUnregister%28boolean%29">BroadcastRecevier.setDebugUnregister ()</a>——又一个方便的调试工具。</p><p><a href="http://developer.android.com/reference/android/app/Activity.html#recreate%28%29">Activity.recreate ()</a>——强制让 Activity 重建。</p><p><a href="http://developer.android.com/reference/android/content/pm/PackageManager.html#checkSignatures%28java.lang.String,%20java.lang.String%29">PackageManager.checkSignatures ()</a>——如果同时安装了两个 app 的话，可以用这个方法检查。如果不进行签名检查的话，其他人可以轻易通过使用一样的包名来模仿你的 app。</p><h2 id="备忘"><a href="#备忘" class="headerlink" title="备忘"></a>备忘</h2><h3 id="More"><a href="#More" class="headerlink" title="More"></a>More</h3><ol><li><a href="https://www.androidperformance.com/android-tips-round-up.html">Part1</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-2.html">Part2</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-3.html">Part3</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-4.html">Part4</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-5.html">Part5</a></li></ol><p>原文地址:<a href="http://blog.danlew.net/2014/03/30/android-tips-round-up-part-3/">http://blog.danlew.net/2014/03/30/android-tips-round-up-part-3/</a><br>原文作者:<a href="http://blog.danlew.net/about/">http://blog.danlew.net/about/</a><br>本文地址:<a href="https://www.androidperformance.com/android-tips-round-up-3.html">https://www.androidperformance.com/android-tips-round-up-3.html</a> 转载请注明.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是一篇译文,这篇是这个系列的第三篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
  </entry>
  
  <entry>
    <title>为 Nexus5 编译 AndroidL 固件</title>
    <link href="https://androidperformance.com/2015/02/04/build-rom-for-nexus5/"/>
    <id>https://androidperformance.com/2015/02/04/build-rom-for-nexus5/</id>
    <published>2015-02-04T12:51:19.000Z</published>
    <updated>2026-02-07T05:17:47.872Z</updated>
    
    <content type="html"><![CDATA[<p>接前一篇文章<a href="https://www.androidperformance.com/view-android-source-code-with-androidstudio.html">使用Android Studio查看Android Lollipop源码</a>, 我们知道,仅仅看代码,提高是比较有限的,而且理解起来也比较肤浅,往往过目就忘. 而自己写过的代码,往往会印象比较深刻,在写的时候也会比较容易理解流程. 所以我们在看代码的同时, 如果能修改代码, 在手机上跑起来并看到修改的效果,这无疑会加快看代码的效率和积极性. 所以这篇文章,就讲解一下如何自己编译AndroidL的源码,并且在Nexus5上跑起来. 至于为何需要自己编译固件,而不是直接安装Google给出的工厂固件?原因是Google给出的工厂固件是User版本,是没法随心所欲Push东西进去的. 所以我们需要编译自己的Userdebug版本.</p><p>本文假设你已经下载了AndroidL的源码,并且有一台Nexus5手机(手机系统开发人员必备), 如果你还没有AndroidL的源码,或者你有源码但是没有配置编译的环境,那么 <a href="https://source.android.com/source/initializing.html">Initializing a Build Environment</a> 和<a href="https://source.android.com/source/downloading.html">Downloading the Source</a>这两篇文章你应该先去看一下(我又一次假设你会翻墙,如果你不会翻墙,那么下代码也是一个痛苦的事情). 这后面的教程Google官网也有教程.所以我只针对Nexus5进行讲解.</p><p>另外你需要知道AOSP,AOSP即Android Open Source Project 汉语意思是：谷歌开放源代码项目.我们通过Google官方下载的源代码,就是AOSP的代码, 其中是不包含Google开发的那些个应用的,各个厂商拿到的也是这个版本,在这个版本的基础上进行修改. 而Google发布的工厂固件则是包含全套Google服务的. 厂商如果想安装Google服务,就需要过Google的那一套认证,比较麻烦,而且价格不菲,鉴于Google在国内的尴尬地位,国内很多厂商都没有过这个认证.</p><span id="more"></span><h3 id="1-初始化编译环境"><a href="#1-初始化编译环境" class="headerlink" title="1. 初始化编译环境"></a>1. 初始化编译环境</h3><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">. </span>build/envsetup.sh</span><br></pre></td></tr></table></figure><h3 id="2-加载机型"><a href="#2-加载机型" class="headerlink" title="2. 加载机型"></a>2. 加载机型</h3><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">lunch</span></span><br></pre></td></tr></table></figure><h3 id="3-选择要编译的机器"><a href="#3-选择要编译的机器" class="headerlink" title="3. 选择要编译的机器"></a>3. 选择要编译的机器</h3><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">You&#x27;re building on Linux</span><br><span class="line"></span><br><span class="line">Lunch menu... pick a combo:</span><br><span class="line"><span class="bullet">     1.</span> aosp<span class="emphasis">_arm-eng</span></span><br><span class="line"><span class="emphasis">     2. aosp_</span>arm64-eng</span><br><span class="line"><span class="bullet">     3.</span> aosp<span class="emphasis">_mips-eng</span></span><br><span class="line"><span class="emphasis">     4. aosp_</span>mips64-eng</span><br><span class="line"><span class="bullet">     5.</span> aosp<span class="emphasis">_x86-eng</span></span><br><span class="line"><span class="emphasis">     6. aosp_</span>x86<span class="emphasis">_64-eng</span></span><br><span class="line"><span class="emphasis">     7. aosp_</span>shamu-userdebug</span><br><span class="line"><span class="bullet">     8.</span> aosp<span class="emphasis">_manta-userdebug</span></span><br><span class="line"><span class="emphasis">     9. full_</span>fugu-userdebug</span><br><span class="line"><span class="bullet">     10.</span> aosp<span class="emphasis">_fugu-userdebug</span></span><br><span class="line"><span class="emphasis">     11. aosp_</span>grouper-userdebug</span><br><span class="line"><span class="bullet">     12.</span> aosp<span class="emphasis">_tilapia-userdebug</span></span><br><span class="line"><span class="emphasis">     13. aosp_</span>deb-userdebug</span><br><span class="line"><span class="bullet">     14.</span> aosp<span class="emphasis">_flo-userdebug</span></span><br><span class="line"><span class="emphasis">     15. aosp_</span>mako-userdebug</span><br><span class="line"><span class="bullet">     16.</span> aosp<span class="emphasis">_hammerhead-userdebug</span></span><br><span class="line"><span class="emphasis">     17. aosp_</span>flounder-userdebug</span><br><span class="line"><span class="bullet">     18.</span> mini<span class="emphasis">_emulator_</span>x86<span class="emphasis">_64-userdebug</span></span><br><span class="line"><span class="emphasis">     19. mini_</span>emulator<span class="emphasis">_arm-userdebug</span></span><br><span class="line"><span class="emphasis">     20. mini_</span>emulator<span class="emphasis">_x86-userdebug</span></span><br><span class="line"><span class="emphasis">     21. mini_</span>emulator<span class="emphasis">_mips-userdebug</span></span><br><span class="line"><span class="emphasis">     22. mini_</span>emulator<span class="emphasis">_arm64-userdebug</span></span><br></pre></td></tr></table></figure><h4 id="3-1-机型对照表"><a href="#3-1-机型对照表" class="headerlink" title="3.1 机型对照表"></a>3.1 机型对照表</h4><table><thead><tr><th>DEVICE</th><th>CODE NAME</th><th>BUILD CONFIGURATION</th></tr></thead><tbody><tr><td>Nexus 6</td><td>shamu</td><td>aosp_shamu-userdebug</td></tr><tr><td>Nexus Player</td><td>fugu</td><td>aosp_fugu-userdebug</td></tr><tr><td>Nexus 9</td><td>volantis (flounder)</td><td>aosp_flounder-userdebug</td></tr><tr><td>Nexus 5 (GSM&#x2F;LTE)</td><td>hammerhead</td><td>aosp_hammerhead-userdebug</td></tr><tr><td>Nexus 7 (Wi-Fi)</td><td>razor (flo)</td><td>aosp_flo-userdebug</td></tr><tr><td>Nexus 7 (Mobile)</td><td>razorg (deb)</td><td>aosp_deb-userdebug</td></tr><tr><td>Nexus 10</td><td>mantaray (manta)</td><td>full_manta-userdebug</td></tr><tr><td>Nexus 4</td><td>occam (mako)</td><td>full_mako-userdebug</td></tr><tr><td>Nexus 7 (Wi-Fi)</td><td>nakasi (grouper)</td><td>full_grouper-userdebug</td></tr><tr><td>Nexus 7 (Mobile)</td><td>nakasig (tilapia)</td><td>full_tilapia-userdebug</td></tr><tr><td>Galaxy Nexus (GSM&#x2F;HSPA+)</td><td>yakju (maguro)</td><td>full_maguro-userdebug</td></tr><tr><td>Galaxy Nexus (Verizon)</td><td>mysid (toro)</td><td>aosp_toro-userdebug</td></tr><tr><td>Galaxy Nexus (Experimental)</td><td>mysidspr (toroplus)</td><td>aosp_toroplus-userdebug</td></tr><tr><td>PandaBoard (Archived)</td><td>panda</td><td>aosp_panda-userdebug</td></tr><tr><td>Motorola Xoom (U.S. Wi-Fi)</td><td>wingray</td><td>full_wingray-userdebug</td></tr><tr><td>Nexus S</td><td>soju (crespo)</td><td>full_crespo-userdebug</td></tr><tr><td>Nexus S 4G</td><td>sojus (crespo4g)</td><td>full_crespo4g-userdebug3.2</td></tr></tbody></table><h4 id="3-2-三个版本说明版本"><a href="#3-2-三个版本说明版本" class="headerlink" title="3.2 三个版本说明版本"></a>3.2 三个版本说明版本</h4><table><thead><tr><th>BUILDTYPE</th><th>USE</th></tr></thead><tbody><tr><td>user</td><td>limited access; suited for production</td></tr><tr><td>userdebug</td><td>like “user” but with root access and debuggability; preferred for debugging</td></tr><tr><td>eng</td><td>development configuration with additional debugging tools</td></tr></tbody></table><p>选择aosp_hammerhead-userdebug之后,会有下面的确认信息:</p><figure class="highlight abnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span></span><br><span class="line">PLATFORM_VERSION_CODENAME<span class="operator">=</span>REL</span><br><span class="line">PLATFORM_VERSION<span class="operator">=</span><span class="number">5.0</span>.<span class="number">50.50</span>.<span class="number">50.50</span></span><br><span class="line">TARGET_PRODUCT<span class="operator">=</span>aosp_hammerhead</span><br><span class="line">TARGET_BUILD_VARIANT<span class="operator">=</span>userdebug</span><br><span class="line">TARGET_BUILD_TYPE<span class="operator">=</span>release</span><br><span class="line">TARGET_BUILD_APPS<span class="operator">=</span></span><br><span class="line">TARGET_ARCH<span class="operator">=</span>arm</span><br><span class="line">TARGET_ARCH_VARIANT<span class="operator">=</span>armv7-a-neon</span><br><span class="line">TARGET_CPU_VARIANT<span class="operator">=</span>krait</span><br><span class="line">TARGET_2ND_ARCH<span class="operator">=</span></span><br><span class="line">TARGET_2ND_ARCH_VARIANT<span class="operator">=</span></span><br><span class="line">TARGET_2ND_CPU_VARIANT<span class="operator">=</span></span><br><span class="line">HOST_ARCH<span class="operator">=</span>x86_64</span><br><span class="line">HOST_OS<span class="operator">=</span>linux</span><br><span class="line">HOST_OS_EXTRA<span class="operator">=</span>Linux-<span class="number">3.13</span>.<span class="number">0</span>-<span class="number">37</span>-generic-x86_64-with-Ubuntu-<span class="number">14.10</span>-utopic</span><br><span class="line">HOST_BUILD_TYPE<span class="operator">=</span>release</span><br><span class="line">BUILD_ID<span class="operator">=</span>AOSP</span><br><span class="line">OUT_DIR<span class="operator">=</span>out</span><br><span class="line"><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span><span class="operator">=</span></span><br></pre></td></tr></table></figure><h3 id="4-生成驱动目录"><a href="#4-生成驱动目录" class="headerlink" title="4. 生成驱动目录"></a>4. 生成驱动目录</h3><p>接下来需要在<a href="https://developers.google.com/android/nexus/drivers#hammerheadlrx22c">Android官网</a>下载 Nexus5所需要的驱动:</p><table><thead><tr><th>HARDWARE COMPONENT</th><th>COMPANY</th><th>DOWNLOAD</th><th>MD5 CHECKSUM</th><th>SHA-1 CHECKSUM</th></tr></thead><tbody><tr><td>NFC, Bluetooth, Wi-Fi</td><td>Broadcom</td><td><a href="https://dl.google.com/dl/android/aosp/broadcom-hammerhead-lrx22c-964d941e.tgz">Link</a></td><td>2c398994e37093df51b105d63f0eb611</td><td>991346159c95ae75f760014a6822b8b3e8667700</td></tr><tr><td>Camera, Sensors, Audio</td><td>LG</td><td><a href="https://dl.google.com/dl/android/aosp/lge-hammerhead-lrx22c-95a9d465.tgz">Link</a></td><td>74cf8235e6bb04da28b2ff738b13eee9</td><td>175dd5bae81bb54030d072cb0f0b4ec81eb3f71f</td></tr><tr><td>Graphics, GSM, Camera, GPS, Sensors, Media, DSP, USB</td><td>Qualcomm</td><td><a href="https://dl.google.com/dl/android/aosp/lge-hammerhead-lrx22c-95a9d465.tgz">Link</a></td><td>0a43395e175d3de3dc312d8abdcb4f20</td><td>007cf9d49f0409d5c703e7f2811fd153fee22353</td></tr></tbody></table><p>下载完成后,解压出来是三个.sh文件,放到Android源码目录下面,然后执行.会将相关驱动放到vender目录下面.</p><h3 id="5-执行编译命令"><a href="#5-执行编译命令" class="headerlink" title="5. 执行编译命令"></a>5. 执行编译命令</h3><figure class="highlight gauss"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">make</span> -j8</span><br></pre></td></tr></table></figure><p>如果没有出错的话,在经过漫长的时间之后,编译成功:</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Installed <span class="keyword">file</span> list: out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/i</span>nstalled-files.txt</span><br><span class="line">Target system fs image: out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>obj<span class="regexp">/PACKAGING/</span>systemimage_intermediates/system.img</span><br><span class="line">Running:  mkuserimg.sh -s out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>system out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>obj<span class="regexp">/PACKAGING/</span>systemimage_intermediates<span class="regexp">/system.img ext4 system 1073741824 -L system out/</span>target<span class="regexp">/product/</span>hammerhead<span class="regexp">/root/</span>file_contexts</span><br><span class="line">make_ext4fs -s -T -<span class="number">1</span> -S out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>root<span class="regexp">/file_contexts -L system -l 1073741824 -a system out/</span>target<span class="regexp">/product/</span>hammerhead<span class="regexp">/obj/</span>PACKAGING<span class="regexp">/systemimage_intermediates/</span>system.img out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>system</span><br><span class="line">Creating filesystem with parameters:</span><br><span class="line">    <span class="keyword">Size</span>: <span class="number">1073741824</span></span><br><span class="line">    Block <span class="keyword">size</span>: <span class="number">4096</span></span><br><span class="line">    Blocks per <span class="keyword">group</span>: <span class="number">32768</span></span><br><span class="line">    Inodes per <span class="keyword">group</span>: <span class="number">8192</span></span><br><span class="line">    Inode <span class="keyword">size</span>: <span class="number">256</span></span><br><span class="line">    Journal blocks: <span class="number">4096</span></span><br><span class="line">    Label: system</span><br><span class="line">    Blocks: <span class="number">262144</span></span><br><span class="line">    Block groups: <span class="number">8</span></span><br><span class="line">    Reserved block <span class="keyword">group</span> <span class="keyword">size</span>: <span class="number">63</span></span><br><span class="line">Created filesystem with <span class="number">1445</span><span class="regexp">/65536 inodes and 85879/</span><span class="number">262144</span> blocks</span><br><span class="line">Install system fs image: out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>system.img</span><br><span class="line">out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>system.img+out<span class="regexp">/target/</span>product<span class="regexp">/hammerhead/</span>obj<span class="regexp">/PACKAGING/</span>recovery_patch_intermediates/recovery_from_boot.p maxsize=<span class="number">1096212480</span> blocksize=<span class="number">135168</span> total=<span class="number">336629666</span> reserve=<span class="number">11083776</span></span><br></pre></td></tr></table></figure><h3 id="6-刷机命令"><a href="#6-刷机命令" class="headerlink" title="6. 刷机命令"></a>6. 刷机命令</h3><p>Nexus5关机状态下,长按音量下+电源,即可进入recovery模式, 然后在源码根目录下执行下面命令:</p><figure class="highlight ebnf"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">fastboot -w flashall</span></span><br></pre></td></tr></table></figure><p>刷机成功后会自动重启</p><figure class="highlight gams"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">~/Android-SourceCode fastboot -w flashall</span><br><span class="line">target reported <span class="built_in">max</span> download size of <span class="number">1073741824</span> bytes</span><br><span class="line">Creating filesystem with <span class="keyword">parameters</span>:</span><br><span class="line">    Size: 13725835264</span><br><span class="line">    Block <span class="comment">size: 4096</span></span><br><span class="line">    Blocks <span class="comment">per group: 32768</span></span><br><span class="line">    Inodes <span class="comment">per group: 8144</span></span><br><span class="line">    Inode <span class="comment">size: 256</span></span><br><span class="line">    Journal <span class="comment">blocks: 32768</span></span><br><span class="line">    Label: </span><br><span class="line">    Blocks: 3351034</span><br><span class="line">    Block <span class="comment">groups: 103</span></span><br><span class="line">    Reserved <span class="comment">block group size: 823</span></span><br><span class="line">Created <span class="comment">filesystem with 11</span>/<span class="number">838832</span> inodes <span class="keyword">and</span> <span class="number">93654</span>/<span class="comment">3351034 blocks</span></span><br><span class="line">Creating <span class="comment">filesystem with parameters:</span></span><br><span class="line">    Size: 734003200</span><br><span class="line">    Block <span class="comment">size: 4096</span></span><br><span class="line">    Blocks <span class="comment">per group: 32768</span></span><br><span class="line">    Inodes <span class="comment">per group: 7472</span></span><br><span class="line">    Inode <span class="comment">size: 256</span></span><br><span class="line">    Journal <span class="comment">blocks: 2800</span></span><br><span class="line">    Label: </span><br><span class="line">    Blocks: 179200</span><br><span class="line">    Block <span class="comment">groups: 6</span></span><br><span class="line">    Reserved <span class="comment">block group size: 47</span></span><br><span class="line">Created <span class="comment">filesystem with 11</span>/<span class="number">44832</span> inodes <span class="keyword">and</span> <span class="number">5813</span>/<span class="comment">179200 blocks</span></span><br><span class="line">--------------------------------------------</span><br><span class="line">Bootloader <span class="comment">Version...: HHZ12d</span></span><br><span class="line">Baseband <span class="comment">Version.....: M8974A-2.0.50.2.22</span></span><br><span class="line">Serial <span class="comment">Number........: 08ade1a513dc087b</span></span><br><span class="line">--------------------------------------------</span><br><span class="line">checking <span class="comment">product...</span></span><br><span class="line">OKAY <span class="comment">[  0.100s]</span></span><br><span class="line">sending <span class="comment">&#x27;boot&#x27;</span> <span class="comment">(8950 KB)...</span></span><br><span class="line">OKAY <span class="comment">[  0.500s]</span></span><br><span class="line">writing <span class="comment">&#x27;boot&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  0.784s]</span></span><br><span class="line">sending <span class="comment">&#x27;recovery&#x27;</span> <span class="comment">(9672 KB)...</span></span><br><span class="line">OKAY <span class="comment">[  0.556s]</span></span><br><span class="line">writing <span class="comment">&#x27;recovery&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  0.814s]</span></span><br><span class="line">erasing <span class="comment">&#x27;system&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  1.166s]</span></span><br><span class="line">sending <span class="comment">&#x27;system&#x27;</span> <span class="comment">(328187 KB)...</span></span><br><span class="line">OKAY <span class="comment">[ 10.501s]</span></span><br><span class="line">writing <span class="comment">&#x27;system&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[ 23.623s]</span></span><br><span class="line">erasing <span class="comment">&#x27;userdata&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[ 14.657s]</span></span><br><span class="line">sending <span class="comment">&#x27;userdata&#x27;</span> <span class="comment">(137318 KB)...</span></span><br><span class="line">OKAY <span class="comment">[  4.520s]</span></span><br><span class="line">writing <span class="comment">&#x27;userdata&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  9.776s]</span></span><br><span class="line">erasing <span class="comment">&#x27;cache&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  0.618s]</span></span><br><span class="line">sending <span class="comment">&#x27;cache&#x27;</span> <span class="comment">(13348 KB)...</span></span><br><span class="line">OKAY <span class="comment">[  0.630s]</span></span><br><span class="line">writing <span class="comment">&#x27;cache&#x27;</span><span class="comment">...</span></span><br><span class="line">OKAY <span class="comment">[  1.070s]</span></span><br><span class="line">rebooting...</span><br><span class="line"></span><br><span class="line">finished. total <span class="comment">time: 70.095s</span></span><br></pre></td></tr></table></figure><h3 id="7-秀桌面"><a href="#7-秀桌面" class="headerlink" title="7.秀桌面"></a>7.秀桌面</h3><p><img src="/images/build-image-for-nexus-5/Nexus5.webp" alt="image"></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;接前一篇文章&lt;a href=&quot;https://www.androidperformance.com/view-android-source-code-with-androidstudio.html&quot;&gt;使用Android Studio查看Android Lollipop源码&lt;/a&gt;, 我们知道,仅仅看代码,提高是比较有限的,而且理解起来也比较肤浅,往往过目就忘. 而自己写过的代码,往往会印象比较深刻,在写的时候也会比较容易理解流程. 所以我们在看代码的同时, 如果能修改代码, 在手机上跑起来并看到修改的效果,这无疑会加快看代码的效率和积极性. 所以这篇文章,就讲解一下如何自己编译AndroidL的源码,并且在Nexus5上跑起来. 至于为何需要自己编译固件,而不是直接安装Google给出的工厂固件?原因是Google给出的工厂固件是User版本,是没法随心所欲Push东西进去的. 所以我们需要编译自己的Userdebug版本.&lt;/p&gt;
&lt;p&gt;本文假设你已经下载了AndroidL的源码,并且有一台Nexus5手机(手机系统开发人员必备), 如果你还没有AndroidL的源码,或者你有源码但是没有配置编译的环境,那么 &lt;a href=&quot;https://source.android.com/source/initializing.html&quot;&gt;Initializing a Build Environment&lt;/a&gt; 和&lt;a href=&quot;https://source.android.com/source/downloading.html&quot;&gt;Downloading the Source&lt;/a&gt;这两篇文章你应该先去看一下(我又一次假设你会翻墙,如果你不会翻墙,那么下代码也是一个痛苦的事情). 这后面的教程Google官网也有教程.所以我只针对Nexus5进行讲解.&lt;/p&gt;
&lt;p&gt;另外你需要知道AOSP,AOSP即Android Open Source Project 汉语意思是：谷歌开放源代码项目.我们通过Google官方下载的源代码,就是AOSP的代码, 其中是不包含Google开发的那些个应用的,各个厂商拿到的也是这个版本,在这个版本的基础上进行修改. 而Google发布的工厂固件则是包含全套Google服务的. 厂商如果想安装Google服务,就需要过Google的那一套认证,比较麻烦,而且价格不菲,鉴于Google在国内的尴尬地位,国内很多厂商都没有过这个认证.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化之 Systrace 工具介绍(一)</title>
    <link href="https://androidperformance.com/2015/01/30/android-performance-tools-systrace-1/"/>
    <id>https://androidperformance.com/2015/01/30/android-performance-tools-systrace-1/</id>
    <published>2015-01-30T12:24:31.000Z</published>
    <updated>2026-02-07T05:17:47.862Z</updated>
    
    <content type="html"><![CDATA[<p><strong>注意</strong>，本篇内容已经过时，请参考新的 <a href="https://www.androidperformance.com/2019/05/28/Android-Systrace-About/">Systrace 系列文章</a></p><p>本文是Android性能优化工具系列的第一篇，这个系列主要介绍Android性能优化过程中会使用到的一些工具，以及如何用这些工具来发现问题和解决问题。在性能优化方面，Android有不少性能工具供大家来使用，按照我们一贯地 “发现问题-解决问题”的思路来看，发现问题才是最主要的，一上来就想着如何去解决问题，反而会事倍功半。</p><p>这一篇先来简单介绍一下Systrace这个工具。</p><h2 id="Systrace简单介绍"><a href="#Systrace简单介绍" class="headerlink" title="Systrace简单介绍"></a>Systrace简单介绍</h2><p>Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统（如surfaceflinger、WindowManagerService等Framework部分关键模块、服务，View系统等）的运行信息，从而帮助开发者更直观的分析系统瓶颈，改进性能。</p><p>Systrace的功能包括跟踪系统的I&#x2F;O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中，它主要由3部分组成：</p><ul><li>内核部分：Systrace利用了Linux Kernel中的ftrace功能。所以，如果要使用Systrace的话，必须开启kernel中和ftrace相关的模块。</li><li>数据采集部分：Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时，Android还有一个atrace程序，它可以从ftrace中读取统计信息然后交给数据分析工具来处理。</li><li>数据分析工具：Android提供一个systrace.py（python脚本文件，位于Android SDK目录&#x2F;tools&#x2F;systrace中，其内部将调用atrace程序）用来配置数据采集的方式（如采集数据的标签、输出文件名等）和收集ftrace统计数据并生成一个结果网页文件供用户查看。 从本质上说，Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace.<br>关于Systrace的官方介绍和使用可以看这里：<a href="http://developer.android.com/tools/help/systrace.html" title="SysTrace官方介绍">Systrace</a></li></ul><span id="more"></span><h2 id="Systrace简单使用"><a href="#Systrace简单使用" class="headerlink" title="Systrace简单使用"></a>Systrace简单使用</h2><p>使用Systrace前，要先了解一下Systrace在各个平台上的使用方法，鉴于大家使用Eclipse和Android Studio的居多，所以直接摘抄官网关于这个的使用方法，不过不管是什么工具，流程是一样的：</p><ul><li>手机准备好你要进行抓取的界面</li><li>点击开始抓取（命令行的话就是开始执行命令）</li><li>手机上开始操作</li><li>设定好的时间到了之后，会将生成Trace文件，使用Chrome将这个文件打开进行分析</li></ul><h4 id="Using-Eclipse"><a href="#Using-Eclipse" class="headerlink" title="Using Eclipse"></a>Using <strong>Eclipse</strong></h4><ol><li><p>In Eclipse, open an Android application project.</p><ol><li>Switch to the DDMS perspective, by selecting Window &gt; Perspectives &gt; DDMS.</li><li>In the Devices tab, select the device on which to run a trace. If no devices are listed, make sure your device is connected via USB cable and that debugging is enabled on the device.</li><li>Click the Systrace icon at the top of the Devices panel to configure tracing.</li><li>Set the tracing options and click OK to start the trace.</li></ol></li></ol><h4 id="Using-Android-Studio"><a href="#Using-Android-Studio" class="headerlink" title="Using ** Android Studio**"></a>Using ** Android Studio**</h4><ol><li><p>In Android Studio, open an Android application project.</p><ol><li>Open the Device Monitor by selecting Tools &gt; Android &gt; Monitor.</li><li>In the Devices tab, select the device on which to run a trace. If no devices are listed, make sure your device is connected via USB cable and that debugging is enabled on the device.</li><li>Click the Systrace icon at the top of the Devices panel to configure tracing.</li><li>Set the tracing options and click OK to start the trace.</li></ol></li></ol><h4 id="Using-Device-Monitor"><a href="#Using-Device-Monitor" class="headerlink" title="Using Device Monitor"></a>Using <strong>Device Monitor</strong></h4><ol><li><p>Navigate to your SDK tools&#x2F; directory.</p><ol><li>Run the monitor program.</li><li>In the Devices tab, select the device on which to run a trace. If no devices are listed, make sure your device is connected via USB cable and that debugging is enabled on the device.</li><li>Click the Systrace icon at the top of the Devices panel to configure tracing.</li><li>Set the tracing options and click OK to start the trace.</li></ol></li></ol><h4 id="Command-Line-Usage"><a href="#Command-Line-Usage" class="headerlink" title="Command Line Usage"></a>Command Line Usage</h4><p>命令行形式比较灵活，速度也比较快，一次性配置好之后，以后再使用的时候就会很快就出结果（<strong>强烈推荐</strong>）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> android-sdk/platform-tools/systrace</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">python systrace.py --time=10 -o mynewtrace.html <span class="built_in">sched</span> gfx view wm</span></span><br></pre></td></tr></table></figure><p>从上面的命令可以看到Systrace工具的位置，只需要在Bash中配置好对应的路径和Alias，使用起来还是很快速的。另外User版本是不可以抓Trace的，只有ENG版本或者Userdebug版本才可以。</p><p>抓取结束后，会生成对应的Trace文件，注意这个文件只能被Chrome打开。关于如何分析Trace文件，我们下面的章节会讲。不论使用那种工具，在抓取之前都会让选择参数，下面说一下这些参数的意思：</p><ul><li><p>-h, –help Show the help message.（帮助）</p></li><li><p>-o  Write the HTML trace report to the specified file.（即输出文件名，）</p></li><li><p>-t N, –time&#x3D;N Trace activity for N seconds. The default value is 5 seconds. （Trace抓取的时间，一般是 ： -t 8）</p></li><li><p>-b N, –buf-size&#x3D;N Use a trace buffer size of N kilobytes. This option lets you limit the total size of the data collected during a trace.</p></li><li><p>-k</p></li><li><p>—ktrace&#x3D; Trace the activity of specific kernel functions, specified in a comma-separated list.</p></li><li><p>-l, –list-categories List the available tracing category tags. The available tags are(下面的参数不用翻译了估计大家也看得懂，贴官方的解释也会比较权威，后面分析的时候我们会看到这些参数的作业的):</p><ul><li><strong>gfx</strong> - Graphics</li><li><strong>input</strong> - Input</li><li><strong>view</strong> - View</li><li>webview - WebView</li><li><strong>wm</strong> - Window Manager</li><li><strong>am</strong> - Activity Manager</li><li>audio - Audio</li><li>video - Video</li><li>camera - Camera</li><li>hal - Hardware Modules</li><li>res - Resource Loading</li><li><strong>dalvik</strong> - Dalvik VM</li><li>rs - RenderScript</li><li><strong>sched</strong> - CPU Scheduling</li><li><strong>freq</strong> - CPU Frequency</li><li><strong>membus</strong> - Memory Bus Utilization</li><li><strong>idle</strong> - CPU Idle</li><li><strong>disk</strong> - Disk input and output</li><li><strong>load</strong> - CPU Load</li><li><strong>sync</strong> - Synchronization Manager</li><li><strong>workq</strong> - Kernel Workqueues Note: Some trace categories are not supported on all devices. Tip: If you want to see the names of tasks in the trace output, you must include the sched category in your command parameters.</li></ul></li><li><p>-a</p></li><li><p>—app&#x3D; Enable tracing for applications, specified as a comma-separated list of package names. The apps must contain tracing instrumentation calls from the Trace class. For more information, see Analyzing Display and Performance.</p></li><li><p>—link-assets Link to the original CSS or JavaScript resources instead of embedding them in the HTML trace report.</p></li><li><p>—from-file&#x3D; Create the interactive Systrace report from a file, instead of running a live trace.</p></li><li><p>—asset-dir&#x3D; Specify a directory for the trace report assets. This option is useful for maintaining a single set of assets for multiple Systrace reports.</p></li><li><p>-e</p></li><li><p>—serial&#x3D; Conduct the trace on a specific connected device, identified by its device serial number.<br>上面的参数虽然比较多，但使用工具的时候不需考虑这么多，在对应的项目前打钩即可，命令行的时候才会去手动加参数：</p></li></ul><p>我们一般会把这个命令配置成Alias，配置如下：</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">alias</span> st-start=<span class="string">&#x27;python /home/gaojianwu/Software/android-studio/sdk/platform-tools/systrace/systrace.py&#x27;</span>  </span><br><span class="line"><span class="keyword">alias</span> st-<span class="keyword">start</span>-gfx-trace = ‘st-<span class="keyword">start</span> -t <span class="number">8</span> gfx <span class="keyword">input</span> <span class="keyword">view</span> sched freq wm am hwui workq res dalvik sync disk <span class="keyword">load</span> perf hal rs idle mmc’</span><br></pre></td></tr></table></figure><p>这样在使用的时候，可以直接敲 <strong>st-start</strong> 即可，当然为了区分和保持各个文件，还需要加上 <strong>-o xxx.Trace</strong> .上面的命令和参数不必一次就理解，只需要记住如何简单使用即可，在分析的过程中，这些东西都会慢慢熟悉的。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;，本篇内容已经过时，请参考新的 &lt;a href=&quot;https://www.androidperformance.com/2019/05/28/Android-Systrace-About/&quot;&gt;Systrace 系列文章&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文是Android性能优化工具系列的第一篇，这个系列主要介绍Android性能优化过程中会使用到的一些工具，以及如何用这些工具来发现问题和解决问题。在性能优化方面，Android有不少性能工具供大家来使用，按照我们一贯地 “发现问题-解决问题”的思路来看，发现问题才是最主要的，一上来就想着如何去解决问题，反而会事倍功半。&lt;/p&gt;
&lt;p&gt;这一篇先来简单介绍一下Systrace这个工具。&lt;/p&gt;
&lt;h2 id=&quot;Systrace简单介绍&quot;&gt;&lt;a href=&quot;#Systrace简单介绍&quot; class=&quot;headerlink&quot; title=&quot;Systrace简单介绍&quot;&gt;&lt;/a&gt;Systrace简单介绍&lt;/h2&gt;&lt;p&gt;Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统（如surfaceflinger、WindowManagerService等Framework部分关键模块、服务，View系统等）的运行信息，从而帮助开发者更直观的分析系统瓶颈，改进性能。&lt;/p&gt;
&lt;p&gt;Systrace的功能包括跟踪系统的I&amp;#x2F;O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中，它主要由3部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内核部分：Systrace利用了Linux Kernel中的ftrace功能。所以，如果要使用Systrace的话，必须开启kernel中和ftrace相关的模块。&lt;/li&gt;
&lt;li&gt;数据采集部分：Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时，Android还有一个atrace程序，它可以从ftrace中读取统计信息然后交给数据分析工具来处理。&lt;/li&gt;
&lt;li&gt;数据分析工具：Android提供一个systrace.py（python脚本文件，位于Android SDK目录&amp;#x2F;tools&amp;#x2F;systrace中，其内部将调用atrace程序）用来配置数据采集的方式（如采集数据的标签、输出文件名等）和收集ftrace统计数据并生成一个结果网页文件供用户查看。 从本质上说，Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace.&lt;br&gt;关于Systrace的官方介绍和使用可以看这里：&lt;a href=&quot;http://developer.android.com/tools/help/systrace.html&quot; title=&quot;SysTrace官方介绍&quot;&gt;Systrace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Systrace" scheme="https://androidperformance.com/tags/Systrace/"/>
    
    <category term="RenderThread" scheme="https://androidperformance.com/tags/RenderThread/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>使用 Android Studio 查看 Android Lollipop 源码</title>
    <link href="https://androidperformance.com/2015/01/16/view-android-source-code-with-androidstudio/"/>
    <id>https://androidperformance.com/2015/01/16/view-android-source-code-with-androidstudio/</id>
    <published>2015-01-16T13:54:32.000Z</published>
    <updated>2026-02-07T05:17:47.882Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Android-Studio"><a href="#Android-Studio" class="headerlink" title="Android Studio"></a>Android Studio</h2><p>作为Google的亲儿子,Nexus手机系列所收到的待遇大家有目共睹.Android5.0出来之后,Nexus5第一时间就升级到了最新的系统.那么作为Google亲儿子的Android Studio同样备受Google的重视,我也是第一时间从Eclipse转投到了Android Studio的怀抱中,从最初的测试版本一路升级到现在的1.0正式版本(今天发布了1.1,果断已经升级了).</p><span id="more"></span><p>关于Android Studio的好处我就不用说了,下面两点就足矣让你转投Android Studio了:</p><ul><li>Android Studio是Google官方指定的,目前官网已经去掉了ADT, 大家可以在<a href="http://developer.android.com/sdk/installing/index.html">Android开发者官网</a> 中进行查看,目前只有Android Studio提供下载了.</li><li>Google也表示ADT不再进行维护了.<br>转投Android Studio时大势所趋,网上关于如何使用Android Studio的帖子也是满天飞,所以我就不再啰嗦夸奖Android Studio了.看一下下面的图,估计你就会很有欲望了.想查看详细的内容,可以查看<a href="http://developer.android.com/tools/studio/index.html">Android Studio官方文档</a></li></ul><p><img src="/images/view-android-source-code-with-androidstudio/androidstudio.webp" alt="Android Studio Preview"></p><h2 id="Android-5-0-Lollipop"><a href="#Android-5-0-Lollipop" class="headerlink" title="Android 5.0 : Lollipop"></a>Android 5.0 : Lollipop</h2><p>Android Lollipop是Google在今年推出的,关于Lollipop的详细介绍可以查看<a href="http://developer.android.com/about/versions/lollipop.html">Lollipop官方介绍</a> ,我也就不多说了.作为一个开发者,我们不应该停留在表面(即Lollipop的绚丽的界面,和新奇的设计语言<a href="http://developer.android.com/about/versions/lollipop.html#Material">Material design</a>,我们更要了解其中的原理.</p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>在使用Android Studio查看源码之前,你需要做下面几件事:</p><ul><li>下载Android Lollipop源码(当然不一定非要是Lollipop源码,其他的版本也是可以的)</li><li>进行过一次完整编译(不编译是无法进行源码导入工作的)<br>以上操作可以参考<a href="https://source.android.com/source/initializing.html">Google 官方教程</a> 以确保万无一失(呵呵).上面的几个链接需要翻墙才可以访问,不过有兴趣看这篇文章的人,大部分应该都是知道VPN的.或者没有VPN不会翻墙的,速度去卖一个VPN,能花钱解决的事情,就不要花时间.</li></ul><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><ul><li>进入Android 源码根目录</li><li>执行<br><code>mmm development/tools/idegen/</code><br>这行命令的意思是编译idegen这个项目,生成idegen.jar文件.生成成功后,会显示这个jar包的位置,并显示<br><code>make completed successfully (43 seconds)</code> ,如果编译失败了,后面会讲到这种情况(比较少见)</li><li>执行<br><code>sh ./development/tools/idegen/idegen.sh</code><br>这行命令的意思是生成对应的文件:android.iws, android.ipr, android.iml .如果生成失败,后面也会讲到这种情况.</li><li>生成对应的文件后,打开Android Studio,选择打开一个现有的Android Studio项目,选择Android源码的根目录,导入即可(起作用的是android.irp文件).在配置sdk版本之后就可以查看Android 源码了.</li><li>已经完成了,没有下一步了.</li></ul><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><ul><li><p>执行第一个命令的时候编译不过,出现这种情况的原因有好几种:</p><ul><li>Android 源码没有下载完全</li><li>没有进行全部编译</li><li>环境变量配置有问题解决方法依问题而定,网上对应的帖子很多,可以自己去找. 推荐这种问题还是去<a href="http://www.jianshu.com/p/www.google.com">Google</a> 和<a href="http://www.jianshu.com/p/www.stackoverflow.com">StackOverFlow</a> 上去问.</li></ul></li><li><p>执行第二个命令的时候,碰到下面的问题:</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Exception <span class="keyword">in</span> thread <span class="string">&quot;main&quot;</span> java.io.FileNotFoundException: ./out/target/product/hammerhead/obj/GYP/shared_intermediates/res.java (Is a directory)</span><br><span class="line">at java.io.<span class="module-access"><span class="module"><span class="identifier">FileInputStream</span>.</span></span><span class="keyword">open</span>(Native Method)</span><br><span class="line">at java.io.FileInputStream.&amp;lt;init&amp;gt;(<span class="module-access"><span class="module"><span class="identifier">FileInputStream</span>.</span></span>java:<span class="number">138</span>)</span><br><span class="line">at java.io.FileReader.&amp;lt;init&amp;gt;(<span class="module-access"><span class="module"><span class="identifier">FileReader</span>.</span></span>java:<span class="number">72</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>parse<span class="constructor">PackageName(Configuration.<span class="params">java</span>:204)</span></span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>root<span class="constructor">Of(Configuration.<span class="params">java</span>:180)</span></span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">140</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>traverse(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">167</span>)</span><br><span class="line">at Configuration.&amp;lt;init&amp;gt;(<span class="module-access"><span class="module"><span class="identifier">Configuration</span>.</span></span>java:<span class="number">72</span>)</span><br><span class="line">at <span class="module-access"><span class="module"><span class="identifier">Main</span>.</span></span>main(<span class="module-access"><span class="module"><span class="identifier">Main</span>.</span></span>java:<span class="number">37</span>)</span><br></pre></td></tr></table></figure></li></ul><p>解决办法是将<br><code>./out/target/product/hammerhead/obj/GYP/shared_intermediates/res.java</code><br>修改为:<br><code>./out/target/product/hammerhead/obj/GYP/shared_intermediates/res.j</code><br>即可.</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>使用Android Studio看Android源码很爽，下面是他们的优缺点：</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li>和Android 完美结合</li><li>代码提示很全</li><li>代码跳转很强</li><li>IDE比Eclipse好看(我说的是黑色主题)</li><li>插件很强大(底层是IDEA你懂得)</li><li>免费(SourceInsight负分,啥?你用的破解版? 当我没说)</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li>对C＋＋代码的支持不是很好</li><li>有一定的上手难度</li></ul><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;Android-Studio&quot;&gt;&lt;a href=&quot;#Android-Studio&quot; class=&quot;headerlink&quot; title=&quot;Android Studio&quot;&gt;&lt;/a&gt;Android Studio&lt;/h2&gt;&lt;p&gt;作为Google的亲儿子,Nexus手机系列所收到的待遇大家有目共睹.Android5.0出来之后,Nexus5第一时间就升级到了最新的系统.那么作为Google亲儿子的Android Studio同样备受Google的重视,我也是第一时间从Eclipse转投到了Android Studio的怀抱中,从最初的测试版本一路升级到现在的1.0正式版本(今天发布了1.1,果断已经升级了).&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化之过渡绘制 - 实战篇</title>
    <link href="https://androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/"/>
    <id>https://androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/</id>
    <published>2015-01-13T11:38:53.000Z</published>
    <updated>2026-02-07T05:17:47.862Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>上一篇<a href="https://www.androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/">文章</a>从理论的角度讲解了一下什么是过渡绘制，以及可以用来查看和确认过渡绘制的工具，还提供了一些优化过渡绘制的方法。对代码和布局比较熟悉的人，看完上一篇其实就已经可以对自己的应用进行优化了。我记得有人说过，用iphone你只需要保证苹果有节操即可，用Android你就得保证所有的Android开发者都有节操。但现实是残酷的，现在Android市场上，有很多粗制滥造的应用，其中不乏大厂之作，各位打开过渡绘制按钮，就知道我所言非虚。作为一个Android开发人员，我肯定是更希望Android能一步一步好起来，超越iphone。</p><p>这篇文章从实战的角度，讲解了一个过渡绘制的优化过程。当然这里用到的只是很少的一部分，毕竟每个应用差别很大，优化方式也各不一样。所以这篇文章仅供参考，想把这块做好还是要下功夫的。</p><p>如果没有看过前一篇，可以点这里：<a href="https://www.androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/">Android性能优化之过渡绘制(一)</a></p><hr><span id="more"></span><h2 id="定位过渡绘制区域"><a href="#定位过渡绘制区域" class="headerlink" title="定位过渡绘制区域"></a>定位过渡绘制区域</h2><h3 id="打开GPU过渡绘制选项"><a href="#打开GPU过渡绘制选项" class="headerlink" title="打开GPU过渡绘制选项:"></a>打开GPU过渡绘制选项:</h3><p>设置—辅助功能–开发人员工具–硬件加速渲染—调试GPU过渡绘制— 显示过渡绘制区域.</p><h3 id="清理后台"><a href="#清理后台" class="headerlink" title="清理后台"></a>清理后台</h3><p>Kill掉(即清后台)要测试的应用,重新打开就可以看到效果.下面以文件管理器和设置为例子,如下图</p><p><img src="/images/overdraw-1/filemanager-1.webp" alt="文件管理器"><img src="/images/overdraw-1/settings.webp" alt="设置"></p><p>从图上可以看出,按照过渡绘制从好到坏(蓝-绿-粉红-红)来看,文件管理器的过渡绘制是非常严重的,而设置界面的过渡绘制则在可以接受的范围内.下面就以文件管理器为主要分析对象,来看看如何对文件管理器的过渡绘制进行优化.</p><h3 id="从文件管理器的图-分析出过渡绘制区域"><a href="#从文件管理器的图-分析出过渡绘制区域" class="headerlink" title="从文件管理器的图,分析出过渡绘制区域:."></a>从文件管理器的图,分析出过渡绘制区域:.</h3><ul><li>首先看最上面的ActionBar和最下面的SmartBar,对比设置界面的ActionBar就可以知道,整个文件管理器存在一个不透明的背景,导致每次绘制时,都要先绘制这个看不见且不透明的背景.这个背景一般是应用的主题自带的背景,所以GPU过渡绘制显示其位蓝色,这个背景是可以进行优化的.</li><li>中间的内容部分,最底层是绿色,说明进行了2x的过渡绘制,去掉第一条我们提的那个全局背景,还有一层背景,也就是1x的过渡绘制,对比setting可知,这个背景色也是可以去掉的.</li><li>最容易看出的是这两条,我们先分析和优化这两条,然后再进行其他的优化.</li></ul><h2 id="优化过渡绘制区域"><a href="#优化过渡绘制区域" class="headerlink" title="优化过渡绘制区域"></a>优化过渡绘制区域</h2><p>在进行位置确认后，我们大概确定了过渡绘制的区域,让我们来使用工具来进行验证和View确认.</p><p>打开Monitor(Eclipse和Android Studio中都有快捷打开按钮,即DDMS,右上角选择 Hierarchy View,大概使用如图</p><p><img src="/images/overdraw-1/HierarchyView-1.webp" alt="Hierarchy View"></p><p>其中根节点:PhoneWindos$DecorView是整个视图的根节点,唯一的子节点是ActionBarOverlayLayout,这个Layout包含了ActionBar,应用程序,以及SmartBar.</p><p>下面讲述如何从Hierarchy View结合代码分析出需要进行修改的区域</p><h3 id="去除默认背景"><a href="#去除默认背景" class="headerlink" title="去除默认背景"></a>去除默认背景</h3><p>上面分析过渡绘制区域的第一条,整个window存在一个背景,所以进行了一次重绘,这个背景的重绘是系统级别的,和主题有关,即这个背景是属于ActionBarOverlayLayout的.这种类型的过渡绘制解决也比较方便,在文件管理器的主Activity的onCreate方法中,加入</p><p><code>this.getWindow().setBackgroundDrawableResource(android.R.color.transparent);</code></p><p>就可以将这个看不见的主题背景去掉.下面是去掉主题背景后的效果图(一张是划开,一张是没有划开):</p><p><img src="/images/overdraw-1/filemanager-2.webp" alt="文件管理器"><img src="/images/overdraw-1/filemanager-3.webp" alt="文件管理器"></p><p>对比优化前的图可以发现,背景被去掉之后,少了一层过渡绘制. ActionBar上的蓝色已经消失了.中间的内容由绿色变为蓝色</p><h3 id="消除子控件背景"><a href="#消除子控件背景" class="headerlink" title="消除子控件背景"></a>消除子控件背景</h3><p>上面分析的第二条说”<strong>中间的内容部分,最底层是绿色,说明进行了2x的过渡绘制</strong>”,现在中间部分变成了蓝色,但是这是一个全局的背景,导致右边的view拉过来之后,还是存在大量的红色和绿色. 继续分析Hierarchy View,找到中间view对应的视图:DragRelativeLayout,查看源码可知,DragRelativeLayout继承自公共控件:SlidingMenu ，SlidingMenu 由CustomViewAbove和CustomViewBehind组成,前者是上面可以左右拉动的那部分,后者是底部不能拉动的那部分(这个从HierarchyView中也可以看出来:如下图所示:</p><p><img src="/images/overdraw-1/HierarchyView-2.webp" alt="Hierarchy View"></p><p>点击CustomViewBehind,查看其所占的区域,就可以发现背景是这个View进行绘制的,打开CustomViewBehind的代码可以发现其构造函数中包含下面的代码:<br><code>setBackgroundColor(getResources().getColor(R.color.mz_slidingmenu_background_light));</code><br>这个背景是不需要的,查看源码可知,这个view会在SlidingMenu.setMenu的时候,被覆盖掉,还是看不到的.所以这一层view是可以去掉的.下面是去掉一层背景之后的预览图:</p><p><img src="/images/overdraw-1/filemanager-4.webp" alt="文件管理器"><img src="/images/overdraw-1/filemanager-5.webp" alt="文件管理器"></p><p>可以看到这一层背景去掉之后,过渡绘制减轻了很多.</p><h3 id="进一步优化"><a href="#进一步优化" class="headerlink" title="进一步优化"></a>进一步优化</h3><p>接着进行分析,可以看到CustomViewAbove也是存在一个过渡绘制的背景的,查看Hierarchy View的CustomViewAbove的子节点,可以看到过渡绘制是由ListView导致的.其id为:FilesList,在代码中找到它,并对他进行分析.在我将PartitionItemLayout中onDraw()函数的setBounds去掉之后,过渡绘制进一步改善了(但是ListItem的View的颜色也比之前要浅了,这一步优化需要根据具体情况进行) 下面是优化后的效果图:</p><p><img src="/images/overdraw-1/filemanager-6.webp" alt="文件管理器"><img src="/images/overdraw-1/filemanager-7.webp" alt="文件管理器"></p><p>可以看到图中的过渡绘制已经非常少了.!点个赞!</p><hr><h2 id="优化代码"><a href="#优化代码" class="headerlink" title="优化代码"></a>优化代码</h2><h3 id="Lint工具"><a href="#Lint工具" class="headerlink" title="Lint工具"></a>Lint工具</h3><p>Lint工具的使用比较简单,根据给出的提示做对应的修改即可.有时候需要工具具体情况来确定是否需要修改. 下图是一个简单地例子.箭头处提示这个Layout或者它的父Layout是不必须的.具体修改方法即去掉FrameLayout,将RelativeLayout提升为根VIew即可.</p><p><img src="/images/overdraw-1/lint-1.webp" alt="Lint"></p><p>Lint工具还会针对代码中潜在的不合理或者Bed Code做出修改意见.比较重要的提示包括</p><ul><li>声明但是没有使用的变量</li><li>可能会产生的空指针</li><li>没必要书写的return,continue</li><li>复杂代码的简化写法</li><li>for循环的简化写法:foreach</li><li>无效的判空</li><li>空if</li><li>无效或者未使用的import</li></ul><h3 id="使用Tracer-For-OpenGL-ES"><a href="#使用Tracer-For-OpenGL-ES" class="headerlink" title="使用Tracer For OpenGL ES"></a>使用Tracer For OpenGL ES</h3><p>Tracer工具也在Android Device Monitor中.点击右上角的Tracer for OpenGL ES按钮就可以进入(如果没有这个按钮,点击旁边的Open Perspective按钮,从选项中选择Tracer for OpenGL ES即可).初次打开Tracer工具,里面是没有内容的,点击右上角的两个按钮(一个是打开现有的GLTrace文件,另一个是新建GLTrace文件)。点击Trace按钮, 手机会自动启动应用程序并启动对应的Activity,当手机上的内容完全绘制出来之后,就可以点击Stop按钮,生成GlTrace文件.文件会自动打开.</p><p><img src="/images/overdraw-1/Tracer-1.webp" alt="Tracer"></p><p>分析GLTrace文件，下图是优化过后的图,对比优化前的图可以发现,优化后不会去绘制默认的背景图和CustomViewBehind的背景图.</p><p><img src="/images/overdraw-1/Tracer-2.webp" alt="Tracer"></p><p>这只是一帧的绘制,如果多操作几下生成多个帧的绘制trace文件,会发现这两个背景会被多次的重绘,去掉后不仅会减轻过渡绘制,也会加快GUP的绘制速度.</p><hr><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ol><li><a href="http://www.curious-creature.org/2012/12/01/android-performance-case-study/">优化过程</a></li><li><a href="http://blog.csdn.net/yihongyuelan/article/details/12169647">反编译并添加gpu显示</a></li><li><a href="http://developer.android.com/training/improving-layouts/optimizing-layout.html#Inspect">http://developer.android.com/training/improving-layouts/optimizing-layout.html#Inspect</a></li><li><a href="http://developer.android.com/training/improving-layouts/reusing-layouts.html">http://developer.android.com/training/improving-layouts/reusing-layouts.html </a></li><li><a href="http://developer.android.com/training/improving-layouts/loading-ondemand.html">http://developer.android.com/training/improving-layouts/loading-ondemand.html</a></li><li><a href="http://developer.android.com/training/improving-layouts/smooth-scrolling.html">http://developer.android.com/training/improving-layouts/smooth-scrolling.html </a></li><li><a href="http://developer.android.com/tools/help/hierarchy-viewer.html">http://developer.android.com/tools/help/hierarchy-viewer.html</a></li><li><a href="http://tools.android.com/tips/lint">http://tools.android.com/tips/lint</a></li></ol><hr><blockquote><p>作者：<a href="http://weibo.com/1315612820/profile?topnav=1&wvr=6">Gracker</a></p><p>出处：<a href="https://androidperformance.com/2015/01/13/android-performance-optimization-overdraw-2/">androidperformance.com</a></p><p>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</p><p>打赏一下： <a href="http://weibo.com/p/1001603801588257955883?from=page_100505_profile&wvr=6&mod=wenzhangmod">微博打赏</a></p></blockquote><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;上一篇&lt;a href=&quot;https://www.androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/&quot;&gt;文章&lt;/a&gt;从理论的角度讲解了一下什么是过渡绘制，以及可以用来查看和确认过渡绘制的工具，还提供了一些优化过渡绘制的方法。对代码和布局比较熟悉的人，看完上一篇其实就已经可以对自己的应用进行优化了。我记得有人说过，用iphone你只需要保证苹果有节操即可，用Android你就得保证所有的Android开发者都有节操。但现实是残酷的，现在Android市场上，有很多粗制滥造的应用，其中不乏大厂之作，各位打开过渡绘制按钮，就知道我所言非虚。作为一个Android开发人员，我肯定是更希望Android能一步一步好起来，超越iphone。&lt;/p&gt;
&lt;p&gt;这篇文章从实战的角度，讲解了一个过渡绘制的优化过程。当然这里用到的只是很少的一部分，毕竟每个应用差别很大，优化方式也各不一样。所以这篇文章仅供参考，想把这块做好还是要下功夫的。&lt;/p&gt;
&lt;p&gt;如果没有看过前一篇，可以点这里：&lt;a href=&quot;https://www.androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/&quot;&gt;Android性能优化之过渡绘制(一)&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
  </entry>
  
  <entry>
    <title>Android 性能优化之过渡绘制 - 理论篇</title>
    <link href="https://androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/"/>
    <id>https://androidperformance.com/2014/10/20/android-performance-optimization-overdraw-1/</id>
    <published>2014-10-20T15:19:23.000Z</published>
    <updated>2026-02-07T05:17:47.861Z</updated>
    
    <content type="html"><![CDATA[<p>博客有一段时间没有更新了，到了新公司后，一直比较忙，博客也更新地不那么频繁了，倒是利用这个VPS搭建的VPN每天都在用，速度也蛮不错的。最近一直在看Android上和性能相关的部分，也就是所谓的Android性能优化，才发现Android性能这一块，自己懂得还是太少了，所以从上层开始看，也算是一点一点入门吧。这个系列将讲解学习过程中总结的和性能相关的内容。</p><p>首先将讲解一下GPU过渡绘制，也是开发者最直接接触的部分吧，这个内容将分为两个部分来将讲，第一部分初步讲解一下gpu过渡绘制的原理，和一些优化建议，第二部分将用实际例子来讲解优化GPU过渡绘制的一般步骤。</p><h2 id="过渡绘制概念"><a href="#过渡绘制概念" class="headerlink" title="过渡绘制概念"></a>过渡绘制概念</h2><p>GPU过渡绘制的概念：GPU过度绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景，那么显示文本的像素至少绘了两次，一次是背景，一次是文本。GPU过度绘制或多或少对性能有些影响,设备的内存带宽是有限的，当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的限制每个设备都可能是不一样的。</p><span id="more"></span><h2 id="过渡绘制的原因"><a href="#过渡绘制的原因" class="headerlink" title="过渡绘制的原因"></a>过渡绘制的原因</h2><ol><li>太多的View叠加</li><li>复杂的层级叠加</li><li>更长的inflation时间</li></ol><h2 id="过渡绘制和不合理的xml布局的影响"><a href="#过渡绘制和不合理的xml布局的影响" class="headerlink" title="过渡绘制和不合理的xml布局的影响"></a>过渡绘制和不合理的xml布局的影响</h2><ol><li>布局文件是一个xml文件，inflate布局文件其实就是解析xml，根据标签信息创建相应的布局对象并做关联。xml中的标签和属性设置越多，节点树的深度越深，在解析时要执行的判断逻辑、函数的嵌套和递归就越多，所以时间消耗越多；</li><li>inflate操作只是布局影响的第一个环节，一个界面要显示出来，在requestLayout后还要执行一系列的measure、layout、draw的操作，每一步的执行时间都会受到布局本身的影响。而界面的最终显示是所有这些操作完成后才实现的，所以如果布局质量差，会增加每一步操作的时间成本，最终显示时间就会比较长。</li></ol><h2 id="过渡绘制的一些基本概念"><a href="#过渡绘制的一些基本概念" class="headerlink" title="过渡绘制的一些基本概念:"></a>过渡绘制的一些基本概念:</h2><p>Android提供了三个工具来帮助辨别和解决重绘问题：Hierachy Viewer，Tracer for OpenGL和Show GPU overdraw。前两个可以在ADT工具或者独立的monitor工具中找到，最后一个是在开发者选项的一部分.</p><ol><li>GPU过渡绘制测试:对于过度绘制的测试主要通过人工进行测试，也是发现应用过渡绘制的首选途径 .通过打开开发者选项中的 显示GPU过度绘制(魅族手机:设置—辅助功能–开发人员工具–硬件加速渲染—调试GPU过渡绘制— 显示过渡绘制区域. (魅族手机需要打开开发者模式：需要在电话界面输入: <em>#</em>#6961#<em>#</em> )) 来进行测试（PS：只有android4.2及以上的版本才具备此功能)</li><li>颜色标识: GPU过渡绘制从好到差:蓝-绿-淡红-红<ul><li>蓝色1x过度绘制</li><li>绿色2x过度绘制</li><li>淡红色3x过度绘制</li><li>红色超过4x过度绘制</li></ul></li><li>验收标准:<ul><li>控制过度绘制为2x</li><li>不允许存在4x过度绘制</li><li>不允许存在面积超过屏幕1&#x2F;4区域的3x过度绘制（淡红色区域）</li></ul></li></ol><h2 id="优化工具介绍"><a href="#优化工具介绍" class="headerlink" title="优化工具介绍"></a>优化工具介绍</h2><ol><li><p>Lint工具:</p><ul><li>Eclipse中,点击即可,下面的窗口中会出现提示,根据提示和具体解决办法消除.</li><li>Android Studio自带Lint工具,不合理或者需要优化和注意的地方,会用黄色标记出来.</li><li>Lint工具不仅对布局有很好的优化建议,对代码中不合理的活着存在潜在风险的模块也会提出优化建议,所以一个好的建议是:多使用Lint工具检查自己的应用,尽量消除所有的建议.</li><li>Lint工具可以用命令行来运行,具体使用可以参考:<a href="tools.android.com">tools.android.com</a><br> <img src="/images/overdraw-1/lint.webp" alt="Lint"></li></ul></li><li><p>Lint工具的提升例子(摘自官方文档):</p><ul><li>Use compound drawables(使用compound drawables) - A LinearLayout which contains an ImageView and a TextView can be more efficiently handled as a compound drawable.</li><li>Merge root frame(使用Merge根框架) - If a FrameLayout is the root of a layout and does not provide background or padding etc, it can be replaced with a merge tag which is slightly more efficient.</li><li>Useless leaf(去除无用的分支) - A layout that has no children or no background can often be removed (since it is invisible) for a flatter and more efficient layout hierarchy.</li><li>Useless parent (去除无用的父控件)- A layout with children that has no siblings, is not a ScrollView or a root layout, and does not have a background, can be removed and have its children moved directly into the parent for a flatter and more efficient layout hierarchy.</li><li>Deep layouts (注意Layout的深度) - Layouts with too much nesting are bad for performance. Consider using flatter layouts such as RelativeLayout or GridLayout to improve performance. The default maximum depth is</li></ul></li><li><p>Hierarchy Viewer:此工具是一个ADT工具（或者monitor,最新版本的SDK建议不使用独立的HV工具,而是直接在monitor中进行操作.）的一部分，可以被用作对视图层级进行快速解读。在处理布局问题时特别有用，对于性能问题也很适用。Hierarchy Viewer默认只能在非加密设备使用，例如工程机，工程平板或者模拟器。为了能够在任何手机上使用Hierarchy Viewer，你得在你的应用中添加ViewServer，这是一个开源库,使用方法可以参考这里。连接上设备，打开Hierarchy Viewer(定位到tools&#x2F;目录下，直接执行hierarchyviewer的命令，选定需要查看的Process，再点击Load View Hierarchy会显示出当前界面的布局Tree。在每个模块的Traffic light上有三个灯，分别代表了Measure, Layout and Draw三个步骤的性能。</p></li></ol><h2 id="布局优化建议"><a href="#布局优化建议" class="headerlink" title="布局优化建议"></a>布局优化建议</h2><p>在Android UI布局过程中，通过遵守一些惯用、有效的布局原则，我们可以制作出高效且复用性高的UI，概括来说包括如下几点：</p><ul><li><p>尽量多使用RelativeLayout和LinearLayout, 不要使用绝对布局AbsoluteLayout，</p><ul><li>在布局层次一样的情况下， 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点.</li><li>在完成相对较复杂的布局时,建议使用RelativeLayout,RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局.</li></ul></li><li><p>将可复用的组件抽取出来并通过include标签使用；</p></li><li><p>使用ViewStub标签来加载一些不常用的布局；</p></li><li><p>动态地inflation view性能要比SetVisiblity性能要好.当然用VIewStub是最好的选择.</p></li><li><p>使用merge标签减少布局的嵌套层次</p></li><li><p>去掉多余的背景颜色(查看背景颜色是否多余,可以将HierarchyView中的图导出为psd文件,然后用Photoshop查看.具体可以参考这个<a href="https://www.youtube.com/watch?v=URyoiAt8098">视频</a>)</p><ul><li>对于有多层背景颜色的Layout来说,留最上面一层的颜色即可,其他底层的颜色都可以去掉</li><li>对于使用Selector当背景的Layout(比如ListView的Item,会使用Selector来标记点击,选择等不同的状态),可以将normal状态的color设置为&quot;@android:color&#x2F;transparent”,来解决对应的问题</li></ul></li><li><p>内嵌使用包含layout_weight属性的LinearLayout会在绘制时花费昂贵的系统资源，因为每一个子组件都需要被测量两次。在使用ListView与GridView的时候，这个问题显的尤其重要，因为子组件会重复被创建.所以要尽量避免使用Layout_weight</p></li><li><p>使得Layout宽而浅，而不是窄而深（在Hierarchy Viewer的Tree视图里面体现）</p></li></ul><h2 id="源码相关"><a href="#源码相关" class="headerlink" title="源码相关"></a>源码相关</h2><p>另外有能力看源码的同学，下面是绘制OverDraw的源码位置：&#x2F;frameworks&#x2F;base&#x2F;libs&#x2F;hwui&#x2F;OpenGLRenderer.cpp,有兴趣的可以去研究研究。</p><figure class="highlight arcade"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> OpenGLRenderer::renderOverdraw() &#123;</span><br><span class="line">    <span class="keyword">if</span> (mCaches.debugOverdraw &amp;amp;&amp;amp; getTargetFbo() == <span class="number">0</span>) &#123;</span><br><span class="line">        const Rect* <span class="built_in">clip</span> = &amp;amp;mTilingClip;</span><br><span class="line"></span><br><span class="line">        mCaches.enableScissor();</span><br><span class="line">        mCaches.setScissor(<span class="built_in">clip</span>-&amp;gt;<span class="built_in">left</span>, mFirstSnapshot-&amp;gt;height - <span class="built_in">clip</span>-&amp;gt;bottom,</span><br><span class="line">                <span class="built_in">clip</span>-&amp;gt;<span class="built_in">right</span> - <span class="built_in">clip</span>-&amp;gt;<span class="built_in">left</span>, <span class="built_in">clip</span>-&amp;gt;bottom - <span class="built_in">clip</span>-&amp;gt;<span class="built_in">top</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1x overdraw</span></span><br><span class="line">        mCaches.stencil.enableDebugTest(<span class="number">2</span>);</span><br><span class="line">        drawColor(mCaches.getOverdrawColor(<span class="number">1</span>), <span class="attr">SkXfermode</span>::kSrcOver_Mode);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2x overdraw</span></span><br><span class="line">        mCaches.stencil.enableDebugTest(<span class="number">3</span>);</span><br><span class="line">        drawColor(mCaches.getOverdrawColor(<span class="number">2</span>), <span class="attr">SkXfermode</span>::kSrcOver_Mode);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3x overdraw</span></span><br><span class="line">        mCaches.stencil.enableDebugTest(<span class="number">4</span>);</span><br><span class="line">        drawColor(mCaches.getOverdrawColor(<span class="number">3</span>), <span class="attr">SkXfermode</span>::kSrcOver_Mode);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4x overdraw and higher</span></span><br><span class="line">        mCaches.stencil.enableDebugTest(<span class="number">4</span>, <span class="literal">true</span>);</span><br><span class="line">        drawColor(mCaches.getOverdrawColor(<span class="number">4</span>), <span class="attr">SkXfermode</span>::kSrcOver_Mode);</span><br><span class="line"></span><br><span class="line">        mCaches.stencil.disable();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> OpenGLRenderer::countOverdraw() &#123;</span><br><span class="line">    size_t <span class="built_in">count</span> = mWidth * mHeight;</span><br><span class="line">    uint32_t* <span class="built_in">buffer</span> = <span class="keyword">new</span> uint32_t[<span class="built_in">count</span>];</span><br><span class="line">    glReadPixels(<span class="number">0</span>, <span class="number">0</span>, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &amp;amp;<span class="built_in">buffer</span>[<span class="number">0</span>]);</span><br><span class="line"></span><br><span class="line">    size_t total = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (size_t i = <span class="number">0</span>; i &amp;lt; <span class="built_in">count</span>; i++) &#123;</span><br><span class="line">        total += <span class="built_in">buffer</span>[i] &amp;amp; <span class="number">0xff</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mOverdraw = total / float(<span class="built_in">count</span>);</span><br><span class="line"></span><br><span class="line">    delete[] <span class="built_in">buffer</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>还有QA可能用得到的一个指标：OverDraw数值，这个的源码位置在Framework&#x2F;base&#x2F;core&#x2F;java&#x2F;android&#x2F;view&#x2F;HardwareRender.java中(5.0中去掉了这个数值的显示)</p><figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">private <span class="type">void</span> debugOverdraw(<span class="keyword">View</span>.AttachInfo attachInfo, Rect dirty,</span><br><span class="line">                HardwareCanvas canvas, DisplayList displayList) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (mDebugOverdraw == OVERDRAW_TYPE_COUNT) &#123;</span><br><span class="line">                <span class="keyword">if</span> (mDebugOverdrawLayer == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, <span class="keyword">true</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (mDebugOverdrawLayer.getWidth() != mWidth ||</span><br><span class="line">                        mDebugOverdrawLayer.getHeight() != mHeight) &#123;</span><br><span class="line">                    mDebugOverdrawLayer.resize(mWidth, mHeight);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!mDebugOverdrawLayer.isValid()) &#123;</span><br><span class="line">                    mDebugOverdraw = <span class="number">-1</span>;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                HardwareCanvas layerCanvas = mDebugOverdrawLayer.<span class="keyword">start</span>(canvas, dirty);</span><br><span class="line">                countOverdraw(layerCanvas);</span><br><span class="line">                final <span class="type">int</span> restoreCount = layerCanvas.save();</span><br><span class="line">                layerCanvas.drawDisplayList(displayList, <span class="keyword">null</span>, DisplayList.FLAG_CLIP_CHILDREN);</span><br><span class="line">                layerCanvas.restoreToCount(restoreCount);</span><br><span class="line">                mDebugOverdrawLayer.<span class="keyword">end</span>(canvas);</span><br><span class="line"></span><br><span class="line">                <span class="type">float</span> overdraw = getOverdraw(layerCanvas);</span><br><span class="line">                DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics();</span><br><span class="line"></span><br><span class="line">                drawOverdrawCounter(canvas, overdraw, metrics.density);</span><br><span class="line">            &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">private <span class="type">void</span> drawOverdrawCounter(HardwareCanvas canvas, <span class="type">float</span> overdraw, <span class="type">float</span> density) &#123;</span><br><span class="line">            final String <span class="type">text</span> = String.format(\&quot;%.2fx\&quot;, overdraw);</span><br><span class="line">            final Paint paint = setupPaint(density);</span><br><span class="line">            // HSBtoColor will clamp the <span class="keyword">values</span> <span class="keyword">in</span> the <span class="number">0.</span><span class="number">.1</span> range</span><br><span class="line">            paint.setColor(Color.HSBtoColor(<span class="number">0.28</span>f - <span class="number">0.28</span>f * overdraw / <span class="number">3.5</span>f, <span class="number">0.8</span>f, <span class="number">1.0</span>f));</span><br><span class="line"></span><br><span class="line">            canvas.drawText(<span class="type">text</span>, density * <span class="number">4.0</span>f, mHeight - paint.getFontMetrics().bottom, paint);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol><li><a href="http://www.curious-creature.org/2012/12/01/android-performance-case-study/">优化过程</a></li><li><a href="http://blog.csdn.net/yihongyuelan/article/details/12169647">反编译并添加gpu显示</a></li><li><a href="http://developer.android.com/training/improving-layouts/optimizing-layout.html#Inspect">http://developer.android.com/training/improving-layouts/optimizing-layout.html#Inspect</a></li><li><a href="http://developer.android.com/training/improving-layouts/reusing-layouts.html">http://developer.android.com/training/improving-layouts/reusing-layouts.html </a></li><li><a href="http://developer.android.com/training/improving-layouts/loading-ondemand.html">http://developer.android.com/training/improving-layouts/loading-ondemand.html</a></li><li><a href="http://developer.android.com/training/improving-layouts/smooth-scrolling.html">http://developer.android.com/training/improving-layouts/smooth-scrolling.html </a></li><li><a href="http://developer.android.com/tools/help/hierarchy-viewer.html">http://developer.android.com/tools/help/hierarchy-viewer.html</a></li><li><a href="http://tools.android.com/tips/lint">http://tools.android.com/tips/lint</a></li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;博客有一段时间没有更新了，到了新公司后，一直比较忙，博客也更新地不那么频繁了，倒是利用这个VPS搭建的VPN每天都在用，速度也蛮不错的。最近一直在看Android上和性能相关的部分，也就是所谓的Android性能优化，才发现Android性能这一块，自己懂得还是太少了，所以从上层开始看，也算是一点一点入门吧。这个系列将讲解学习过程中总结的和性能相关的内容。&lt;/p&gt;
&lt;p&gt;首先将讲解一下GPU过渡绘制，也是开发者最直接接触的部分吧，这个内容将分为两个部分来将讲，第一部分初步讲解一下gpu过渡绘制的原理，和一些优化建议，第二部分将用实际例子来讲解优化GPU过渡绘制的一般步骤。&lt;/p&gt;
&lt;h2 id=&quot;过渡绘制概念&quot;&gt;&lt;a href=&quot;#过渡绘制概念&quot; class=&quot;headerlink&quot; title=&quot;过渡绘制概念&quot;&gt;&lt;/a&gt;过渡绘制概念&lt;/h2&gt;&lt;p&gt;GPU过渡绘制的概念：GPU过度绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景，那么显示文本的像素至少绘了两次，一次是背景，一次是文本。GPU过度绘制或多或少对性能有些影响,设备的内存带宽是有限的，当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的限制每个设备都可能是不一样的。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 - 如何让EditText不自动获取焦点</title>
    <link href="https://androidperformance.com/2014/06/03/android-edittext-do-not-auto-get-focus/"/>
    <id>https://androidperformance.com/2014/06/03/android-edittext-do-not-auto-get-focus/</id>
    <published>2014-06-02T16:31:49.000Z</published>
    <updated>2026-02-07T05:17:47.860Z</updated>
    
    <content type="html"><![CDATA[<p>Android中,使用EditText作为输入框很方便,但是有时候EditText会自动获取焦点,其行为:点击进入这个页面后,EditText自动获取焦点,导致软键盘直接跳出.有时候这么做很方便,但是大部分时候我们还是希望在点击EditText的时候,软键盘才弹出来.</p><p>这里有个很简单也很实用的技巧,即在EditText的父Layout中,加入下面的两个属性即可:</p><pre class="lang:java decode:true ">android:focusable="true"  android:focusableInTouchMode="true"</pre><p>这样做的原理是让用户进入到这个页面之后,EditText的父控件 获取焦点,这样的话EditText就获取不到焦点,软键盘也不会自动弹起.只有在点击EditText的时候,软键盘才会弹起.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Android中,使用EditText作为输入框很方便,但是有时候EditText会自动获取焦点,其行为:点击进入这个页面后,EditText自动获取焦点,导致软键盘直接跳出.有时候这么做很方便,但是大部分时候我们还是希望在点击EditText的时候,软键盘才弹出来.&lt;/p</summary>
      
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 -2</title>
    <link href="https://androidperformance.com/2014/05/31/android-tips-round-up-2/"/>
    <id>https://androidperformance.com/2014/05/31/android-tips-round-up-2/</id>
    <published>2014-05-31T10:20:47.000Z</published>
    <updated>2026-02-07T05:17:47.869Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一篇译文,这篇是这个系列的第二篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="http://developer.android.com/reference/android/text/format/DateUtils.html#formatDateTime%28android.content.Context,%20long,%20int%29">DateUtils.formatDateTime()</a> 用来进行区域格式化工作,输出格式化和本地化的时间或者日期.</p><p>[AlarmManager.setInexactRepeating](<a href="http://developer.android.com/reference/android/app/AlarmManager.html#setInexactRepeating">http://developer.android.com/reference/android/app/AlarmManager.html#setInexactRepeating</a>(int, long, long, android.app.PendingIntent)) 通过闹铃分组的方式省电,即使你只调用了一个闹钟,这也是一个好的选择,（可以确保在使用完毕时自动调用 AlarmManager.cancel ()。原文说的比较抽象,这里详细说一下:setInexactRepeating指的是设置非准确闹钟,使用方法:alarmManager.setInexactRepeating(AlarmManager.RTC, startTime,intervalL, pendingIntent),非准确闹钟只能保证大致的时间间隔，但是不一定准确，可能出现设置间隔为30分钟，但是实际上一次间隔20分钟，另一次间隔40分钟。它的最大的好处是可以合并闹钟事件，比如间隔设置每30分钟一次，不唤醒休眠，在休眠8小时后已经积累了16个闹钟事件，而在手机被唤醒的时候，非准时闹钟可以把16个事件合并为一个, 所以这么看来,非准时闹钟一般来说比较节约能源.</p><p>[Formatter.formatFileSize()](<a href="http://developer.android.com/reference/android/text/format/Formatter.html#formatFileSize">http://developer.android.com/reference/android/text/format/Formatter.html#formatFileSize</a>(android.content.Context, long)) 一个区域化的文件大小格式化工具。通俗来说就是把大小转换为MB,G,KB之类的字符串.</p><p><a href="http://developer.android.com/reference/android/app/ActionBar.html#hide()">ActionBar.hide()</a>&#x2F;<a href="http://developer.android.com/reference/android/app/ActionBar.html#show()">.show()</a> 顾名思义,隐藏和显示ActionBar,可以优雅地在全屏和带Actionbar之间转换.</p><p>[Linkify.addLinks()](<a href="http://developer.android.com/reference/android/text/util/Linkify.html#addLinks">http://developer.android.com/reference/android/text/util/Linkify.html#addLinks</a>(android.text.Spannable, int))  在Text上添加链接.很实用.</p><p><a href="http://developer.android.com/reference/android/text/StaticLayout.html">StaticLayout</a> 在自定义 View 中渲染文字的时候很实用。</p><p><a href="http://developer.android.com/reference/android/app/Activity.html#onBackPressed()">Activity.onBackPressed()</a> 很方便的管理back键的方法,有时候需要自己控制返回键的事件的时候,可以重写一下.比如加入 “点两下back键退出” 功能.</p><p><a href="http://developer.android.com/reference/android/view/GestureDetector.html">GestureDetector</a> 用来监听和相应对应的手势事件,比如点击,长按,慢滑动,快滑动,用起来很简单,比你自己实现要方便许多.</p><p><a href="http://developer.android.com/reference/android/graphics/DrawFilter.html">DrawFilter</a> 可以让你在不调用onDrew方法的情况下,操作canvas,比了个如,你可以在创建自定义 View 的时候设置一个 DrawFilter，给父 View 里面的所有 View 设置反别名。</p><p><a href="http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass()">ActivityManager.getMemoryClass()</a> 告诉你你的机器还有多少内存,在计算缓存大小的时候会比较有用.</p><p><a href="http://developer.android.com/reference/android/view/ViewStub.html">ViewStub</a> 它是一个初始化不做任何事情的 View，但是之后可以载入一个布局文件。在慢加载 View 中很适合做占位符。唯一的缺点就是不支持标签，所以如果你不太小心的话，可能会在视图结构中加入不需要的嵌套。</p><p><a href="http://developer.android.com/reference/android/os/SystemClock.html#sleep(long)">SystemClock.sleep()</a> 这个方法在保证一定时间的 sleep 时很方便，通常我用来进行 debug 和模拟网络延时。</p><p><a href="http://developer.android.com/reference/android/util/DisplayMetrics.html#density">DisplayMetrics.density</a> 这个方法你可以获取设备像素密度,大部分时候最好让系统来自动进行缩放资源之类的操作,但是有时候控制的效果会更好一些.(尤其是在自定义View的时候).</p><p>[Pair.create()](<a href="http://developer.android.com/reference/android/util/Pair.html#create">http://developer.android.com/reference/android/util/Pair.html#create</a>(A, B)) 方便构建类和构造器的方法。</p><h2 id="备忘"><a href="#备忘" class="headerlink" title="备忘"></a>备忘</h2><h3 id="More"><a href="#More" class="headerlink" title="More"></a>More</h3><ol><li><a href="https://www.androidperformance.com/android-tips-round-up.html">Part1</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-2.html">Part2</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-3.html">Part3</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-4.html">Part4</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-5.html">Part5</a></li></ol><p>原文地址:<a href="http://blog.danlew.net/2014/04/14/android-tips-round-up-part-2/">http://blog.danlew.net/2014/03/30/android-tips-round-up-part-2/</a><br>原文作者:<a href="http://blog.danlew.net/about/">http://blog.danlew.net/about/</a><br>本文地址:<a href="https://www.androidperformance.com/android-tips-round-up-2.html">https://www.androidperformance.com/android-tips-round-up-2.html</a> 转载请注明.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是一篇译文,这篇是这个系列的第二篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android 小技巧 - 1</title>
    <link href="https://androidperformance.com/2014/05/28/android-tips-round-up-1/"/>
    <id>https://androidperformance.com/2014/05/28/android-tips-round-up-1/</id>
    <published>2014-05-28T15:47:43.000Z</published>
    <updated>2026-02-07T05:17:47.869Z</updated>
    
    <content type="html"><![CDATA[<p>本文是一篇译文,这篇是这个系列的第一篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.</p><span id="more"></span><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><a href="http://developer.android.com/reference/android/app/Activity.html#startActivities(android.content.Intent[])">Activity.startActivities()</a> 常用于在应用程序中间启动其他的Activity.</p><p><a href="http://developer.android.com/reference/android/text/TextUtils.html#isEmpty(java.lang.CharSequence)">TextUtils.isEmpty()</a> 简单的工具类,用于检测是否为空</p><p><a href="http://developer.android.com/reference/android/text/Html.html#fromHtml(java.lang.String)">Html.fromHtml()</a> 用于生成一个Html,参数可以是一个字符串.个人认为它不是很快,所以我不怎么经常去用.（我说不经常用它是为了重点突出这句话：请多手动构建 Spannable 来替换 Html.fromHtml），但是它对渲染从 web 上获取的文字还是很不错的。</p><p><a href="http://developer.android.com/reference/android/widget/TextView.html#setError%28java.lang.CharSequence%29">TextView.setError()</a> 在验证用户输入的时候很棒</p><p><a href="http://developer.android.com/reference/android/os/Build.VERSION_CODES.html">Build.VERSION_CODES</a> 这个标明了当前的版本号,在处理兼容性问题的时候经常会用到.点进去可以看到各个版本的不同特性</p><p><a href="http://developer.android.com/reference/android/util/Log.html#getStackTraceString(java.lang.Throwable)">Log.getStackTraceString()</a> 方便的日志类工具,方法Log.v()、Log.d()、Log.i()、Log.w()和Log.e()都是将信息打印到LogCat中，有时候需要将出错的信息插入到数据库或一个自定义的日志文件中，那么这种情况就需要将出错的信息以字符串的形式返回来，也就是使用static String getStackTraceString(Throwable tr)方法的时候.</p><p><a href="http://developer.android.com/reference/android/view/LayoutInflater.html#from%28android.content.Context%29">LayoutInflater.from()</a> 顾名思义,用于Inflate一个layout,参数是layout的id.这个经常写Adapter的人会用的比较多.</p><p><a href="http://developer.android.com/reference/android/view/ViewConfiguration.html#getScaledTouchSlop%28%29">ViewConfiguration.getScaledTouchSlop()</a> 使用 ViewConfiguration 中提供的值以保证所有触摸的交互都是统一的。这个方法获取的值表示:用户的手滑动这个距离后,才判定为正在进行滑动.当然这个值也可以自己来决定.但是为了一致性,还是使用标准的值较好.</p><p><a href="http://developer.android.com/reference/android/telephony/PhoneNumberUtils.html#convertKeypadLettersToDigits%28java.lang.String%29">PhoneNumberUtils.convertKeypadLettersToDigits</a> 顾名思义.将字母转换为数字,类似于T9输入法,</p><p><a href="http://developer.android.com/reference/android/content/Context.html#getCacheDir%28%29">Context.getCacheDir()</a> 获取缓存数据文件夹的路径,很简单但是知道的人不多,这个路径通常在SD卡上(这里的SD卡指的是广义上的SD卡,包括外部存储和内部存储)Adnroid&#x2F;data&#x2F;您的应用程序包名&#x2F;cache&#x2F;  下面.测试的时候,可以去这里面看是否缓存成功.缓存在这里的好处是:不用自己再去手动创建文件夹,不用担心用户把自己创建的文件夹删掉,在应用程序卸载的时候,这里会被清空,使用第三方的清理工具的时候,这里也会被清空.</p><p><a href="http://developer.android.com/reference/android/animation/ArgbEvaluator.html">ArgbEvaluator</a> 用于处理颜色的渐变。就像 Chris Banes 说的一样，这个类会进行很多自动装箱的操作，所以最好还是去掉它的逻辑自己去实现它。这个没用过,不明其所以然,回头再补充.</p><p><a href="http://developer.android.com/reference/android/view/ContextThemeWrapper.html">ContextThemeWrapper</a> 方便在运行的时候修改主题.</p><p><a href="http://developer.android.com/reference/android/widget/Space.html">Space</a> space是Android 4.0中新增的一个控件，它实际上可以用来分隔不同的控件，其中形成一个空白的区域.这是一个轻量级的视图组件，它可以跳过Draw，对于需要占位符的任何场景来说都是很棒的。</p><p><a href="http://developer.android.com/reference/android/animation/ValueAnimator.html#reverse%28%29">ValueAnimator.reverse()</a> 这个方法可以很顺利地取消正在运行的动画.我超喜欢.</p><h2 id="备忘"><a href="#备忘" class="headerlink" title="备忘"></a>备忘</h2><h3 id="More"><a href="#More" class="headerlink" title="More"></a>More</h3><ol><li><a href="https://www.androidperformance.com/android-tips-round-up.html">Part1</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-2.html">Part2</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-3.html">Part3</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-4.html">Part4</a></li><li><a href="https://www.androidperformance.com/android-tips-round-up-5.html">Part5</a></li></ol><p>原文地址:<a href="http://blog.danlew.net/2014/03/30/android-tips-round-up-part-1/">http://blog.danlew.net/2014/03/30/android-tips-round-up-part-1/</a><br>原文作者:<a href="http://blog.danlew.net/about/">http://blog.danlew.net/about/</a><br>本文地址:<a href="https://www.androidperformance.com/android-tips-round-up-1.html">https://www.androidperformance.com/android-tips-round-up-1.html</a> 转载请注明.</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文是一篇译文,这篇是这个系列的第一篇.讲述的是Android开发中遇到的一些好用的小技巧,或者一些实用的API,很多人都知道,但也有人不知道,记录下来,如果能帮助到大家,也是极好的.由于不是严格的博文,所以翻译也不那么严格,有些工具和类我也会经常用,所以我会根据自己的想法去写.有些地方坐在并没有将这个工具的作用讲出来,我会补充上去.&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Memory" scheme="https://androidperformance.com/tags/Memory/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android Tools - Log2File</title>
    <link href="https://androidperformance.com/2014/05/02/android_log_to_file/"/>
    <id>https://androidperformance.com/2014/05/02/android_log_to_file/</id>
    <published>2014-05-02T05:25:35.000Z</published>
    <updated>2026-02-07T05:17:47.870Z</updated>
    
    <content type="html"><![CDATA[<div style="color: #000000;">Log2File工具类是用于Android程序将Log记录到文件中(如sd卡)的工具,应用场景：</div><div style="color: #000000;"><ol><li>无法连接电脑进行调试（usb线被usbotg占用）</li><li>Log不容易被抓取</li><li>Bug出现很随机，不是必出现</li><li>其他自己脑补</li></ol><h2 id="0-说明"><a href="#0-说明" class="headerlink" title="0.说明"></a>0.说明</h2><p>这篇文章最早是在我的CSDN博客上面发布了:<a href="http://blog.csdn.net/grackergao/article/details/18322749" title="Log2File类的使用">http://blog.csdn.net/grackergao/article/details/18322749</a> .现在讲他转移到了这里,代码的Github地址 :<a href="https://github.com/Gracker/Android-Utils/blob/master/Log2File.java" title="Log2File的github地址">https://github.com/Gracker/Android-Utils/blob/master/Log2File.java</a></p><span id="more"></span><h2 id="1-Log2File工具类源码"><a href="#1-Log2File工具类源码" class="headerlink" title="1.Log2File工具类源码"></a>1.Log2File工具类源码</h2><pre class="lang:java decode:true " title="Log2File工具类源码">import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.Date;import android.content.Context;import android.os.Environment;public class Log2File{    private static boolean  logInit;    private static BufferedWriter writer;    private Log2File()    {        }        /**     * 初始化Log,创建log文件     * @param ctx     * @param fileName     * @return     */    public static boolean init(Context ctx, String fileName)    {        if(!logInit)        {                       String state = Environment.getExternalStorageState();            if (Environment.MEDIA_MOUNTED.equals(state))            {                File sdDir = Environment.getExternalStorageDirectory();                File logDir = new File(sdDir.getAbsolutePath() + "/log2file/" +                         ctx.getPackageName() + "/");                    try {                    if(!logDir.exists())                    {                        logDir.mkdirs();                    }                        File logFile = new File(logDir, fileName);                    logFile.createNewFile();                        writer = new BufferedWriter(new FileWriter(logFile, true));                    logInit = true;                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                }            }            return logInit;    }        /**     * 写一条log     * @param msg     */    public static void w(String msg)    {        if(logInit)        {            try {                Date date = new Date();                writer.write("[" + date.toLocaleString() + "] " + msg);                writer.newLine();                writer.flush();            } catch (IOException e) {                // TODO Auto-generated catch block            }        }    }        /**     * 关闭log     */    public static void close()    {        if(logInit)        {            try {                writer.close();                writer = null;                } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }                logInit = false;        }    }}</pre><h2 id="2-Log2File类的使用"><a href="#2-Log2File类的使用" class="headerlink" title="2.Log2File类的使用"></a>2.Log2File类的使用</h2><div>这个工具类的使用比较简单</div><div>1.首先调用init进行初始化</div><div><pre class="lang:java decode:true " title="Log2File类的初始化">Log2File.init(context, fileName);</pre>2.调用w()进行输出<pre class="lang:java decode:true " title="调用w(String msg)函数进行输出">Log2File.w(String msg);</pre>3.使用完毕后，记得要关闭Log<pre class="lang:java decode:true  crayon-selected" title="关闭Log">Log2File.close();</pre></div></div><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;div style=&quot;color: #000000;&quot;&gt;Log2File工具类是用于Android程序将Log记录到文件中(如sd卡)的工具,应用场景：&lt;/div&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;

&lt;ol&gt;
&lt;li&gt;无法连接电脑进行调试（usb线被usbotg占用）&lt;/li&gt;
&lt;li&gt;Log不容易被抓取&lt;/li&gt;
&lt;li&gt;Bug出现很随机，不是必出现&lt;/li&gt;
&lt;li&gt;其他自己脑补&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;0-说明&quot;&gt;&lt;a href=&quot;#0-说明&quot; class=&quot;headerlink&quot; title=&quot;0.说明&quot;&gt;&lt;/a&gt;0.说明&lt;/h2&gt;&lt;p&gt;这篇文章最早是在我的CSDN博客上面发布了:&lt;a href=&quot;http://blog.csdn.net/grackergao/article/details/18322749&quot; title=&quot;Log2File类的使用&quot;&gt;http://blog.csdn.net/grackergao/article/details/18322749&lt;/a&gt; .现在讲他转移到了这里,代码的Github地址 :&lt;a href=&quot;https://github.com/Gracker/Android-Utils/blob/master/Log2File.java&quot; title=&quot;Log2File的github地址&quot;&gt;https://github.com/Gracker/Android-Utils/blob/master/Log2File.java&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android Service：开发自己的通知中心(2):辅助性服务实战</title>
    <link href="https://androidperformance.com/2014/04/01/android-service-build-your-own-notification-servers-app/"/>
    <id>https://androidperformance.com/2014/04/01/android-service-build-your-own-notification-servers-app/</id>
    <published>2014-03-31T16:05:47.000Z</published>
    <updated>2026-02-07T05:17:47.863Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-辅助性服务实战介绍"><a href="#1-辅助性服务实战介绍" class="headerlink" title="1.辅助性服务实战介绍"></a>1.辅助性服务实战介绍</h2><p>上一篇文章介绍了什么是Accessibility以及简单的使用，这一篇文章就来讲讲如何使用Accessibility服务来创建一个简单的Android通知中心。Android中通知中心是一个系统层面的服务，负责显示应用和系统发来的通知（Notification，比如USB插入、选择输入法、未接来电、截图、天气信息、新闻推送等等）。在android4.3之前，一般的第三方应用是无法获取Notification list的（在Android4.3之后，有了一个新的接口,NotificationListenerService.getActiveNotifications(),可以获取当前的Notification）。但是利用Accessibility服务可以监听到各种事件的特性，可以开发一个第三方的通知中心，实现与系统通知栏类似的功能。</p><span id="more"></span><p>下面就来介绍如何开发自己的通知中心。</p><h2 id="2-开发第三方通知中心"><a href="#2-开发第三方通知中心" class="headerlink" title="2.开发第三方通知中心"></a>2.开发第三方通知中心</h2><h3 id="2-1继承AccessibilitySerivce"><a href="#2-1继承AccessibilitySerivce" class="headerlink" title="2.1继承AccessibilitySerivce"></a>2.1继承AccessibilitySerivce</h3><p>按照上一篇辅助性服务的介绍，一个辅助性服务可以被捆绑到一个标准的应用程序上，或者以一个单独的安卓工程被创建，我们这里建立一个服务，继承AccessibilitySerivce</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.accessibilityservice.AccessibilityService;</span><br><span class="line"><span class="keyword">import</span> android.accessibilityservice.AccessibilityServiceInfo;</span><br><span class="line"><span class="keyword">import</span> android.app.Notification;</span><br><span class="line"><span class="keyword">import</span> android.app.PendingIntent;</span><br><span class="line"><span class="keyword">import</span> android.content.Intent;</span><br><span class="line"><span class="keyword">import</span> android.os.Parcelable;</span><br><span class="line"><span class="keyword">import</span> android.util.Log;</span><br><span class="line"><span class="keyword">import</span> android.view.accessibility.AccessibilityEvent;</span><br><span class="line"><span class="keyword">import</span> android.view.accessibility.AccessibilityNodeInfo;</span><br><span class="line"><span class="keyword">import</span> android.view.accessibility.AccessibilityRecord;</span><br><span class="line"><span class="keyword">import</span> android.widget.Toast;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NotificationFetcherService</span> <span class="keyword">extends</span> <span class="title class_">AccessibilityService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TAG</span> <span class="operator">=</span> \<span class="string">&quot;NotificationFetcherService: \&quot;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    public void onAccessibilityEvent(AccessibilityEvent event) &#123;</span></span><br><span class="line"><span class="string">        if (!(event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) )&#123;</span></span><br><span class="line"><span class="string">            return;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Notification localNotification = (Notification)event.getParcelableData();</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        if (localNotification != null) &#123;</span></span><br><span class="line"><span class="string">            Intent intent=new Intent();</span></span><br><span class="line"><span class="string">            intent.putExtra(\&quot;NotifyData\&quot;, localNotification);</span></span><br><span class="line"><span class="string">            intent.setAction(\&quot;.NotificationFetcherService\&quot;);</span></span><br><span class="line"><span class="string">            sendBroadcast(intent);</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    @Override</span></span><br><span class="line"><span class="string">    protected void onServiceConnected() &#123;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        // Define it in both xml file and here,  for compatibility with pre-ICS devices</span></span><br><span class="line"><span class="string">        AccessibilityServiceInfo info = new AccessibilityServiceInfo();</span></span><br><span class="line"><span class="string">        info.eventTypes = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED |</span></span><br><span class="line"><span class="string">                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | </span></span><br><span class="line"><span class="string">                AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;</span></span><br><span class="line"><span class="string">        setServiceInfo(info);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    @Override</span></span><br><span class="line"><span class="string">    public void onInterrupt() &#123;</span></span><br><span class="line"><span class="string">        System.out.println(\&quot;onInterrupt\&quot;);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p>继承AccessibilitySerivce必须要重写几个重要的方法：</p><ol><li>onServiceConnected方法负责在服务和Activity绑定的时候，进行初始化数据，这里新建了一个AccessibilityServiceInfo对象，并将TYPE_NOTIFICATION_STATE_CHANGED、TYPE_WINDOW_STATE_CHANGED、TYPE_WINDOW_CONTENT_CHANGED纳入监听范围，TYPE_NOTIFICATION_STATE_CHANGED表示这个服务可以监听Notification的变化，我们正是使用这个特性来实现第三方的通知中心功能。</li><li>onInterrupt是服务断开时调用的函数</li><li>onAccessibilityEvent是最重要的，它负责监听所注册的eventTypes（在onServiceConnected中注册的）的事件。从上面的代码中我们可以得到一个Notification对象：</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Notification</span> <span class="variable">localNotification</span> <span class="operator">=</span> (Notification)event.getParcelableData();</span><br></pre></td></tr></table></figure><p>得到Notification对象之后，就可以进行自己的操作，我这里是通过广播的形式，将收到的Notification发送给Activity进行处理。</p><p>这里也会碰到一个小问题：当一个Notification对象太大时（比如截图、未接来电等，Notification.contentView就很大，通过广播传播会出现data过大无法传输的问题），这时可以把Notification.contentView对象暂时保存在Application中，然后再置为null，Activity中接收到数据后，再进行赋值。</p><h3 id="2-2在Manifest中注册service"><a href="#2-2在Manifest中注册service" class="headerlink" title="2.2在Manifest中注册service"></a>2.2在Manifest中注册service</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">service</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:name</span>=<span class="string">\</span>&quot;<span class="attr">.NotificationFetcherService</span>\&quot;</span></span><br><span class="line"><span class="tag">            <span class="attr">android:permission</span>=<span class="string">\</span>&quot;<span class="attr">android.permission.BIND_ACCESSIBILITY_SERVICE</span>\&quot; &gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">\</span>&quot;<span class="attr">android.accessibilityservice.AccessibilityService</span>\&quot; /&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></span><br><span class="line"></span><br><span class="line">            <span class="tag">&lt;<span class="name">meta-data</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:name</span>=<span class="string">\</span>&quot;<span class="attr">android.accessibilityservice</span>\&quot;</span></span><br><span class="line"><span class="tag">                <span class="attr">android:resource</span>=<span class="string">\</span>&quot;@<span class="attr">xml</span>/<span class="attr">accessibilityserviceconfig</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">service</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里就是普通的service注册，注意 &lt;mate-data&gt;标签中的xml文件：从Android 4.0版本开始，有另外一种方法：使用XML文件来配置这类服务。如果你以XML的形式来定义你的服务，某些像canRetrieveWindowContent可配置的选项就可用了。和service一样的配置，使用XML来定义。如果你要使用XML路径，要在你的mainfest文件中指定它，在你的服务声明中添加&lt;meta-data&gt;标签,并指向这个XML资源文件。比如上面的代码，我们在res&#x2F;xml&#x2F;中建立accessibilityaseviceconfig.xml，内容如下：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=\<span class="string">&quot;1.0\&quot; encoding=\&quot;utf-8\&quot;?&gt;</span></span></span><br><span class="line"><span class="string"><span class="meta">&lt;accessibility-service xmlns:android=\&quot;http://schemas.android.com/apk/res/android\&quot;</span></span></span><br><span class="line"><span class="string"><span class="meta">    android:accessibilityEventTypes=\&quot;typeWindowStateChanged|typeNotificationStateChanged|typeWindowContentChanged\&quot;</span></span></span><br><span class="line"><span class="string"><span class="meta">    android:accessibilityFeedbackType=\&quot;feedbackGeneric\&quot;</span></span></span><br><span class="line"><span class="string"><span class="meta">/&gt;</span></span></span><br></pre></td></tr></table></figure><p>服务这里就配置好了。</p><h3 id="2-3-接受并处理Notification"><a href="#2-3-接受并处理Notification" class="headerlink" title="2.3 接受并处理Notification"></a>2.3 接受并处理Notification</h3><p>下面的Activity中就可以接受这个数据，然后怎么处理就看自己了，这里只是简单地显示出来。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.app.Activity;</span><br><span class="line"><span class="keyword">import</span> android.app.Application;</span><br><span class="line"><span class="keyword">import</span> android.app.Notification;</span><br><span class="line"><span class="keyword">import</span> android.app.PendingIntent;</span><br><span class="line"><span class="keyword">import</span> android.app.PendingIntent.CanceledException;</span><br><span class="line"><span class="keyword">import</span> android.content.BroadcastReceiver;</span><br><span class="line"><span class="keyword">import</span> android.content.Context;</span><br><span class="line"><span class="keyword">import</span> android.content.Intent;</span><br><span class="line"><span class="keyword">import</span> android.content.IntentFilter;</span><br><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> android.os.Handler;</span><br><span class="line"><span class="keyword">import</span> android.os.Message;</span><br><span class="line"><span class="keyword">import</span> android.os.Parcelable;</span><br><span class="line"><span class="keyword">import</span> android.os.Process;</span><br><span class="line"><span class="keyword">import</span> android.text.method.ScrollingMovementMethod;</span><br><span class="line"><span class="keyword">import</span> android.util.Log;</span><br><span class="line"><span class="keyword">import</span> android.view.LayoutInflater;</span><br><span class="line"><span class="keyword">import</span> android.view.View;</span><br><span class="line"><span class="keyword">import</span> android.view.ViewGroup;</span><br><span class="line"><span class="keyword">import</span> android.widget.Button;</span><br><span class="line"><span class="keyword">import</span> android.widget.LinearLayout;</span><br><span class="line"><span class="keyword">import</span> android.widget.RemoteViews;</span><br><span class="line"><span class="keyword">import</span> android.widget.TextView;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NotificaitonActivity</span> <span class="keyword">extends</span> <span class="title class_">Activity</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">NOTIFY_DATA_FLAG</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String NOTIFY_DATA_ID_STR= \<span class="string">&quot;NotifyData\&quot;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    private NotifyDataReceiver  receiver;</span></span><br><span class="line"><span class="string">    private TextView textView;</span></span><br><span class="line"><span class="string">    private LinearLayout rootLayout;</span></span><br><span class="line"><span class="string">    private Button button;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    protected void onCreate(Bundle savedInstanceState) &#123;</span></span><br><span class="line"><span class="string">        super.onCreate(savedInstanceState);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        setContentView(R.layout.activity_main);</span></span><br><span class="line"><span class="string">        textView = (TextView) findViewById(R.id.notify_test_textview);</span></span><br><span class="line"><span class="string">        textView.setMovementMethod(ScrollingMovementMethod.getInstance());</span></span><br><span class="line"><span class="string">        rootLayout = (LinearLayout) findViewById(R.id.root_layout);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        registerBroadcast();</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        button = (Button) findViewById(R.id.test_button);</span></span><br><span class="line"><span class="string">        button.setOnClickListener(new View.OnClickListener() &#123;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            @Override</span></span><br><span class="line"><span class="string">            public void onClick(View v) &#123;</span></span><br><span class="line"><span class="string">                Button b = new Button(NotificaitonActivity.this);</span></span><br><span class="line"><span class="string">                b.setText(&quot;</span>Tthis<span class="string">&quot;);</span></span><br><span class="line"><span class="string">                rootLayout.addView(b);</span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">        &#125;);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    private void registerBroadcast() &#123;</span></span><br><span class="line"><span class="string">        receiver = new NotifyDataReceiver();</span></span><br><span class="line"><span class="string">        IntentFilter filter = new IntentFilter();</span></span><br><span class="line"><span class="string">        filter.addAction(&quot;</span>.NotificationFetcherService<span class="string">&quot;);</span></span><br><span class="line"><span class="string">        this.registerReceiver(receiver, filter);</span></span><br><span class="line"><span class="string">        Log.e(&quot;</span>Dx:<span class="string">&quot;, &quot;</span>Broadcast registered.........<span class="string">&quot;);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    private void addToUi(RemoteViews remoteView) &#123;</span></span><br><span class="line"><span class="string">        rootLayout.addView(remoteView);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    private void showNotify(String notiString) &#123;</span></span><br><span class="line"><span class="string">        textView.setText(textView.getText() + \&quot;n\&quot; + notiString);</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    private class NotifyDataReceiver extends BroadcastReceiver&#123;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        @Override</span></span><br><span class="line"><span class="string">        public void onReceive(Context context, Intent intent) &#123;</span></span><br><span class="line"><span class="string">            Log.e(\&quot;Dx:\&quot;, \&quot;Receiver got msg in onReceive()...\&quot;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            Parcelable notifyParcelable = intent.getParcelableExtra(\&quot;NotifyData\&quot;);</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            if (notifyParcelable != null) &#123;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">                Notification notification = (Notification) notifyParcelable;</span></span><br><span class="line"><span class="string">                showNotify(\&quot;tickerText: \&quot; + notification.tickerText);</span></span><br><span class="line"><span class="string">                showNotify(\&quot;toString: \&quot; + (String)(notification.toString()));</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">                RemoteViews remoteV = notification.contentView;</span></span><br><span class="line"><span class="string">                if (remoteV==null) &#123;</span></span><br><span class="line"><span class="string">                    showNotify(\&quot;remoteView is: null\&quot; );</span></span><br><span class="line"><span class="string">                &#125; else &#123;</span></span><br><span class="line"><span class="string">                    showNotify(\&quot;remoteView is: not null\&quot; );</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">                    addToUi(remoteV);</span></span><br><span class="line"><span class="string">                &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">                PendingIntent pendIntent = notification.contentIntent;</span></span><br><span class="line"><span class="string">                if (pendIntent==null) &#123;</span></span><br><span class="line"><span class="string">                    showNotify(\&quot;pendIntent is: null\&quot; );</span></span><br><span class="line"><span class="string">                &#125; else &#123;</span></span><br><span class="line"><span class="string">                showNotify(\&quot;pendIntent is: not null\&quot; );</span></span><br><span class="line"><span class="string">                &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">                showNotify(\&quot;**************************\&quot; );</span></span><br><span class="line"><span class="string">                showNotify(\&quot;                    \&quot; );</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p>注：这里有很重要的一点，由于AccessibilityService的特殊性，用户必须手动到设置-辅助功能中，打开对应的服务，我们才可以通过AccessibilityService获得对应的数据，这一点非常重要。</p><p>上面的Activity只是简单地显示Notification，关于更多Notification的操作，可以参考Notification这个类，其中重要的属性有：contentView，flags。要模拟真正的通知中心，还是要费一番功夫的。这里由于公司项目的保密，暂不提供对应的实现代码（其实得到Notification就已经成功了一半了），有兴趣的同学可以私下和我交流。</p><h2 id="3-总结和问题"><a href="#3-总结和问题" class="headerlink" title="3.总结和问题"></a>3.总结和问题</h2><p>AccessibilityService的实战就讲到这里，这一篇博文也是拖了一段时间才写完的，也算是为前一段时间的项目做个了结。</p><p>项目中目前还存在的问题：</p><ol><li>无法获取安装这个应用之前的系统的Notification</li><li>得到的Notification对象没法保存在本地，所以这个服务被杀掉之后，所有的数据都会丢失。（试过用db4o这种对象数据库来进行存储，发现行不通）</li><li>对Android系统的Notification对象的行为模仿不够（有些系统的事件监听不到，比如usb的插拔、usb调试的开关等）</li></ol><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;1-辅助性服务实战介绍&quot;&gt;&lt;a href=&quot;#1-辅助性服务实战介绍&quot; class=&quot;headerlink&quot; title=&quot;1.辅助性服务实战介绍&quot;&gt;&lt;/a&gt;1.辅助性服务实战介绍&lt;/h2&gt;&lt;p&gt;上一篇文章介绍了什么是Accessibility以及简单的使用，这一篇文章就来讲讲如何使用Accessibility服务来创建一个简单的Android通知中心。Android中通知中心是一个系统层面的服务，负责显示应用和系统发来的通知（Notification，比如USB插入、选择输入法、未接来电、截图、天气信息、新闻推送等等）。在android4.3之前，一般的第三方应用是无法获取Notification list的（在Android4.3之后，有了一个新的接口,NotificationListenerService.getActiveNotifications(),可以获取当前的Notification）。但是利用Accessibility服务可以监听到各种事件的特性，可以开发一个第三方的通知中心，实现与系统通知栏类似的功能。&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android:Ubuntu 下执行 Adb 命令找不到设备</title>
    <link href="https://androidperformance.com/2014/03/25/ubuntu-adb-can-not-find-devices/"/>
    <id>https://androidperformance.com/2014/03/25/ubuntu-adb-can-not-find-devices/</id>
    <published>2014-03-25T03:15:17.000Z</published>
    <updated>2026-02-07T05:17:47.881Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-问题概述"><a href="#1-问题概述" class="headerlink" title="1.问题概述"></a>1.问题概述</h2><p><span style="color: #000000;">最近开发Nokia项目，遇到的问题如下：</span></p><p><span style="color: #000000;">插入Nokia x后，电脑没有反应，即不识别，同事的windows也不识别，最后在谷歌上搜索了良久，才找到了解决方案，但是没有记录，后来又要给别人配置的时候，发现忘记怎么配置了。想想这也是一个具有通性的问题，还是记录下来，分享给大家。</span></p><h2 id="2-问题解决方案"><a href="#2-问题解决方案" class="headerlink" title="2.问题解决方案"></a>2.问题解决方案</h2><p><span style="color: #000000;">首先问题是：执行adb命令提示找不到设备，在做其他操作之前，请先确认已经做了如下操作：</span></p><ol><li><span style="color: #000000;">确定已经打开了USB调试选项（设置-开发者选项-USB调试），有的机器没有开发者选项，需要到关于里面点击版本号若干下，或者去百度谷歌。</span></li><li><span style="color: #000000;">确定使用sudo命令adb kill-server 和adb start-server后仍然没有用。</span><br><span style="color: #000000;">如果上述操作都确认了，还是找不到设备，那么继续往下看：</span></li></ol><span id="more"></span><h3 id="2-1-运行lsusb"><a href="#2-1-运行lsusb" class="headerlink" title="2.1.运行lsusb"></a><span style="color: #000000;">2.1.运行lsusb</span></h3><pre class="lang:java decode:true">~ » lsusb                                                                  Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching HubBus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching HubBus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hubBus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hubBus 003 Device 004: ID 1532:0016 Razer USA, Ltd DeathAdder MouseBus 003 Device 003: ID 05d5:624c Super Gate Technology Co., Ltd Bus 003 Device 033: ID 0421:06e8 Nokia Mobile Phones Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hu</pre><h3 id="2-2-注册到udve的rules文件"><a href="#2-2-注册到udve的rules文件" class="headerlink" title="2.2.注册到udve的rules文件"></a><span style="color: #000000;">2.2.注册到udve的rules文件</span></h3><pre class="lang:java decode:true">vim  /etc/udev/rules.d/51-android.rules    添加这个ID：1ebf，如下：    SUBSYSTEM=="usb", SYSFS{"Nokia Mobile Phones"}=="0421", MODE="06e8"   保存文件并运行：    sudo chmod a+rx /etc/udev/rules.d/51-android.rules    sudo /etc/init.d/udev restart    运行结果如下：    Rather than invoking init scripts through /etc/init.d, use the service(8)    utility, e.g. service udev restart    Since the script you are attempting to invoke has been converted to an    Upstart job, you may also use the stop(8) and then start(8) utilities,    e.g. stop udev ; start udev. The restart(8) utility is also available.    udev stop/waiting    udev start/running, process 14636</pre><h3 id="2-3-使用sudo-关闭和启动adb"><a href="#2-3-使用sudo-关闭和启动adb" class="headerlink" title=" 2.3.使用sudo 关闭和启动adb"></a><span style="color: #000000;"> 2.3.使用sudo 关闭和启动adb</span></h3><pre class="lang:java decode:true">cd ~/tools/android-sdk-linux_x86/platform-tools  sudo ./adb kill-server  sudo ./adb start-server</pre><p><span style="color: #000000;"><span style="color: #ff0000;"> 注：一般情况下，上面的操作就可以。特殊情况下，usb设备还是不能被识别，比如我手上这台Nokia X</span>。那么继续：</span></p><h3 id="2-4-打开-～-x2F-android-x2F-adb-usb-ini，加入之前的lsusb得到的信息"><a href="#2-4-打开-～-x2F-android-x2F-adb-usb-ini，加入之前的lsusb得到的信息" class="headerlink" title="2.4.打开 ～&#x2F;.android&#x2F;adb_usb.ini，加入之前的lsusb得到的信息"></a><span style="color: #000000;">2.4.打开 ～&#x2F;.android&#x2F;adb_usb.ini，加入之前的lsusb得到的信息</span></h3><pre class="lang:java decode:true"># ANDROID 4RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.# USE 'android update adb' TO GENERATE.# 1 USB VENDOR ID PER LINE#for nokia x0x0421</pre><p><span style="color: #000000;"> 保存关闭后，就可以识别了。windows下也是如此，不多叙述了。</span></p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;1-问题概述&quot;&gt;&lt;a href=&quot;#1-问题概述&quot; class=&quot;headerlink&quot; title=&quot;1.问题概述&quot;&gt;&lt;/a&gt;1.问题概述&lt;/h2&gt;&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;最近开发Nokia项目，遇到的问题如下：&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;插入Nokia x后，电脑没有反应，即不识别，同事的windows也不识别，最后在谷歌上搜索了良久，才找到了解决方案，但是没有记录，后来又要给别人配置的时候，发现忘记怎么配置了。想想这也是一个具有通性的问题，还是记录下来，分享给大家。&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-问题解决方案&quot;&gt;&lt;a href=&quot;#2-问题解决方案&quot; class=&quot;headerlink&quot; title=&quot;2.问题解决方案&quot;&gt;&lt;/a&gt;2.问题解决方案&lt;/h2&gt;&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;首先问题是：执行adb命令提示找不到设备，在做其他操作之前，请先确认已经做了如下操作：&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;确定已经打开了USB调试选项（设置-开发者选项-USB调试），有的机器没有开发者选项，需要到关于里面点击版本号若干下，或者去百度谷歌。&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;确定使用sudo命令adb kill-server 和adb start-server后仍然没有用。&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;如果上述操作都确认了，还是找不到设备，那么继续往下看：&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Linux" scheme="https://androidperformance.com/tags/Linux/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Android Service：开发自己的通知中心(1):辅助性服务介绍</title>
    <link href="https://androidperformance.com/2014/03/17/android-build-your-own-android-notification-service-app/"/>
    <id>https://androidperformance.com/2014/03/17/android-build-your-own-android-notification-service-app/</id>
    <published>2014-03-17T06:07:53.000Z</published>
    <updated>2026-02-07T05:17:47.859Z</updated>
    
    <content type="html"><![CDATA[<h1 id="1-辅助性服务介绍"><a href="#1-辅助性服务介绍" class="headerlink" title="1 辅助性服务介绍"></a>1 辅助性服务介绍</h1><p>辅助性服务是安卓框架的一个特性，它的设计是为了让已经安装在安卓设备上的应用程序能够为用户提供一种导航式(引导式）回应。一个辅助性服务能够传达给<br>用户关于这个应用程序的利益，例如把文本转换成语音、当用户手指停留屏幕的一个重要区域时的haptic反馈。这一节涵盖了怎样去创建一个辅助性服务，如何处理应用程序的信息接收，还有如何把信息反馈给用户。<br>创建自己的辅助性服务</p><span id="more"></span><h1 id="2-创建自己的辅助性服务"><a href="#2-创建自己的辅助性服务" class="headerlink" title="2 创建自己的辅助性服务"></a>2 创建自己的辅助性服务</h1><h2 id="2-1-继承AccessibilitySerivce"><a href="#2-1-继承AccessibilitySerivce" class="headerlink" title="2.1 继承AccessibilitySerivce"></a>2.1 继承AccessibilitySerivce</h2><p>一个辅助性服务可以被捆绑到一个标准的应用程序上，或者以一个单独的安卓工程被创建。在任何情况下，创建这类服务的步骤都是一样的。在你的工程中，创建一个类继续AccessibilitySerivce。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.accessibilityservice.AccessibilityService;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyAccessibilityService</span> <span class="keyword">extends</span> <span class="title class_">AccessibilityService</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAccessibilityEvent</span><span class="params">(AccessibilityEvent event)</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onInterrupt</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="2-2-在mainfest中声明service"><a href="#2-2-在mainfest中声明service" class="headerlink" title="2.2 在mainfest中声明service"></a>2.2 在mainfest中声明service</h2><p>像其他服务一样，你也可以在mainfest文件中声明它。记得要指定它处理android.accessibilityservice这个意图。以便当应用程序触发一个AccessibilityEvent时，这个服务能被调用。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">application</span> &gt;</span></span><br><span class="line">...</span><br><span class="line"><span class="tag">&lt;<span class="name">service</span> <span class="attr">android:name</span>=<span class="string">&quot;.MyAccessibilityService&quot;</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">         <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.accessibilityservice.AccessibilityService&quot;</span> /&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">     . . .</span><br><span class="line"><span class="tag">&lt;/<span class="name">service</span>&gt;</span></span><br><span class="line">...</span><br><span class="line"><span class="tag">&lt;/<span class="name">application</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="2-3-配置serivce"><a href="#2-3-配置serivce" class="headerlink" title="2.3 配置serivce"></a>2.3 配置serivce</h2><p>如果你为这个服务创建一个新的工程的话，且不打算要一个应用程序，你可以把它启动活动的类（通常叫做MainActivity.java)从你的源文件中删除。同时也把相应的活动元素从你的mainfest文件中删除。<br>配置自己的辅助性服务<br>为你的辅助性服务设置配置变量，用它来告诉系统，如何和何时你想要它运行。哪一类事件你想要去响应？这个服务对所有的应用程序都是活动的吗？或者只有指定的包名的？它使用什么样的反馈？<br>你有两种方法去设置这些变量。反向兼容的方法是以代码的形式来设置它们。<br>可以使setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo).如果要这样做的话，要重写onServiceConnected()方法，然后配置在那里配置你的服务。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onServiceConnected</span><span class="params">()</span> &#123;</span><br><span class="line">    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |</span><br><span class="line">            AccessibilityEvent.TYPE_VIEW_FOCUSED;</span><br><span class="line">    info.packageNames = <span class="keyword">new</span> <span class="title class_">String</span>[]</span><br><span class="line">            &#123;<span class="string">&quot;com.example.android.myFirstApp&quot;</span>, <span class="string">&quot;com.example.android.mySecondApp&quot;</span>&#125;;</span><br><span class="line">    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;</span><br><span class="line">    info.flags = AccessibilityServiceInfo.DEFAULT;</span><br><span class="line">    info.notificationTimeout = <span class="number">100</span>;</span><br><span class="line">    <span class="built_in">this</span>.setServiceInfo(info);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从Android 4.0版本开始，有另外一种方法：使用XML文件来配置这类服务。如果你以XML的形式来定义你的服务，某些像canRetrieveWindowContent可配置的选项就可用了。和上面一样的配置，使用XML来定义，格式如下所示：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;accessibility-service</span><br><span class="line">     android:accessibilityEventTypes=<span class="string">&quot;typeViewClicked|typeViewFocused&quot;</span></span><br><span class="line">     android:packageNames=<span class="string">&quot;com.example.android.myFirstApp, com.example.android.mySecondApp&quot;</span></span><br><span class="line">     android:accessibilityFeedbackType=<span class="string">&quot;feedbackSpoken&quot;</span></span><br><span class="line">     android:notificationTimeout=<span class="string">&quot;100&quot;</span></span><br><span class="line">     android:settingsActivity=<span class="string">&quot;com.example.android.apis.accessibility.TestBackActivity&quot;</span></span><br><span class="line">     android:canRetrieveWindowContent=<span class="string">&quot;true&quot;</span></span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><p>如果你要使用XML路径，要在你的mainfest文件中指定它，在你的服务声明中添加 meta-data;标签,并指向这个XML资源文件。假如你把你的XML文件存储在res&#x2F;xml&#x2F;serviceconfig.xml这个路径下，新的标签格式如下所示：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">service</span> <span class="attr">android:name</span>=<span class="string">&quot;.MyAccessibilityService&quot;</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">         <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;android.accessibilityservice.AccessibilityService&quot;</span> /&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">meta-data</span> <span class="attr">android:name</span>=<span class="string">&quot;android.accessibilityservice&quot;</span></span></span><br><span class="line"><span class="tag">     <span class="attr">android:resource</span>=<span class="string">&quot;@xml/serviceconfig&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">service</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="2-4-响应AccessibilityEvents事件"><a href="#2-4-响应AccessibilityEvents事件" class="headerlink" title="2.4 响应AccessibilityEvents事件"></a>2.4 响应AccessibilityEvents事件</h2><p>现在,您的服务被设置为运行和监听事件，写一些代码，这样当一个AccessibilityEvent真的到来，它就知道要做什么了！<br>从重写onAccessibilityEvent(AccessibilityEvent)方法开始。然后使用getEventType()来确定事件类型，然后用getContentDescription来取出任何与触发事件相关的标签文本。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAccessibilityEvent</span><span class="params">(AccessibilityEvent event)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">eventType</span> <span class="operator">=</span> event.getEventType();</span><br><span class="line">    <span class="type">String</span> <span class="variable">eventText</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">switch</span>(eventType) &#123;</span><br><span class="line">        <span class="keyword">case</span> AccessibilityEvent.TYPE_VIEW_CLICKED:</span><br><span class="line">            eventText = <span class="string">&quot;Focused: &quot;</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> AccessibilityEvent.TYPE_VIEW_FOCUSED:</span><br><span class="line">            eventText = <span class="string">&quot;Focused: &quot;</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    eventText = eventText + event.getContentDescription();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Do something nifty with this text, like speak the composed string</span></span><br><span class="line">    <span class="comment">// back to the user.</span></span><br><span class="line">    speakToUser(eventText);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为更多的上下文查询视图层次结构<br>这一步是可选的，然而它非常有用。Android 4.0(API level 14)的一个新特性：可以用AccessibilityService来查询视图层次结构，收集事件所生成的一些UI组件信息，还有这些UI的父控件和子控件。要做到这一点，确保在你的XML配置文件中做了如下设置：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">android:canRetrieveWindowContent=<span class="string">&quot;true&quot;</span></span><br></pre></td></tr></table></figure><p>如果设置了，通过getSource()可获得一个AccessibilityNodeInfo对象。如果发起的窗口事件仍然是活动的窗口，该调用将会返回一个对象,否则，会返回null。下面这段代码演示何时接收一个事件，步骤如下：<br>立即捕获触发事件的父视图。<br>在那个视图中，寻找一个标签和一个复选框作的子视图。<br>如果找到，创建一个字符串来向用户报告，以表明这个标签是否被选择了。<br>如果遍历视图层次结构后返回null,则会退出该方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAccessibilityEvent</span><span class="params">(AccessibilityEvent event)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">AccessibilityNodeInfo</span> <span class="variable">source</span> <span class="operator">=</span> event.getSource();</span><br><span class="line">    <span class="keyword">if</span> (source == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Grab the parent of the view that fired the event.</span></span><br><span class="line">    <span class="type">AccessibilityNodeInfo</span> <span class="variable">rowNode</span> <span class="operator">=</span> getListItemNodeInfo(source);</span><br><span class="line">    <span class="keyword">if</span> (rowNode == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Using this parent, get references to both child nodes, the label and the checkbox.</span></span><br><span class="line">    <span class="type">AccessibilityNodeInfo</span> <span class="variable">labelNode</span> <span class="operator">=</span> rowNode.getChild(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (labelNode == <span class="literal">null</span>) &#123;</span><br><span class="line">        rowNode.recycle();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">AccessibilityNodeInfo</span> <span class="variable">completeNode</span> <span class="operator">=</span> rowNode.getChild(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (completeNode == <span class="literal">null</span>) &#123;</span><br><span class="line">        rowNode.recycle();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Determine what the task is and whether or not it&#x27;s complete, based on</span></span><br><span class="line">    <span class="comment">// the text inside the label, and the state of the check-box.</span></span><br><span class="line">    <span class="keyword">if</span> (rowNode.getChildCount() &lt; <span class="number">2</span> || !rowNode.getChild(<span class="number">1</span>).isCheckable()) &#123;</span><br><span class="line">        rowNode.recycle();</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">CharSequence</span> <span class="variable">taskLabel</span> <span class="operator">=</span> labelNode.getText();</span><br><span class="line">    <span class="keyword">final</span> <span class="type">boolean</span> <span class="variable">isComplete</span> <span class="operator">=</span> completeNode.isChecked();</span><br><span class="line">    <span class="type">String</span> <span class="variable">completeStr</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (isComplete) &#123;</span><br><span class="line">        completeStr = getString(R.string.checked);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        completeStr = getString(R.string.not_checked);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">String</span> <span class="variable">reportStr</span> <span class="operator">=</span> taskLabel + completeStr;</span><br><span class="line">    speakToUser(reportStr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在你有一个完整的，可以工作的辅助性服务。现在，你也可以试着配置一下，看看Android的text-to-speech engine,或使用Vibrator提供触觉反馈是如何与用户交互。</p><p>最后，要使用配置好的service，必须要到“设置–辅助性服务”中打开对应的service，然后才能相应对应的事件。</p><h1 id="关于我-amp-amp-博客"><a href="#关于我-amp-amp-博客" class="headerlink" title="关于我 &amp;&amp; 博客"></a>关于我 &amp;&amp; 博客</h1><p>下面是个人的介绍和相关的链接，期望与同行的各位多多交流，三人行，则必有我师!</p><ol><li><a href="https://www.androidperformance.com/about/">博主个人介绍</a> ：里面有个人的微信和微信群链接。</li><li><a href="https://androidperformance.com/2019/12/01/BlogMap/">本博客内容导航</a> ：个人博客内容的一个导航。</li><li><a href="https://androidperformance.com/2018/05/07/Android-performance-optimization-skills-and-tools/">个人整理和搜集的优秀博客文章 - Android 性能优化必知必会</a> ：欢迎大家自荐和推荐 （微信私聊即可）</li><li><a href="https://www.androidperformance.com/2023/12/30/the-performance/">Android性能优化知识星球</a> ： 欢迎加入，多谢支持～</li></ol><blockquote><p><strong>一个人可以走的更快 , 一群人可以走的更远</strong></p></blockquote><p><img src="/images/WechatIMG581.webp" alt="微信扫一扫"></p>]]></content>
    
    
    <summary type="html">&lt;h1 id=&quot;1-辅助性服务介绍&quot;&gt;&lt;a href=&quot;#1-辅助性服务介绍&quot; class=&quot;headerlink&quot; title=&quot;1 辅助性服务介绍&quot;&gt;&lt;/a&gt;1 辅助性服务介绍&lt;/h1&gt;&lt;p&gt;辅助性服务是安卓框架的一个特性，它的设计是为了让已经安装在安卓设备上的应用程序能够为用户提供一种导航式(引导式）回应。一个辅助性服务能够传达给&lt;br&gt;用户关于这个应用程序的利益，例如把文本转换成语音、当用户手指停留屏幕的一个重要区域时的haptic反馈。这一节涵盖了怎样去创建一个辅助性服务，如何处理应用程序的信息接收，还有如何把信息反馈给用户。&lt;br&gt;创建自己的辅助性服务&lt;/p&gt;</summary>
    
    
    
    <category term="Android" scheme="https://androidperformance.com/categories/Android/"/>
    
    
    <category term="Startup" scheme="https://androidperformance.com/tags/Startup/"/>
    
    <category term="Android" scheme="https://androidperformance.com/tags/Android/"/>
    
    <category term="Java" scheme="https://androidperformance.com/tags/Java/"/>
    
    <category term="Compose" scheme="https://androidperformance.com/tags/Compose/"/>
    
  </entry>
  
</feed>
