D

Dorck's Blog

修技术之道,日三省吾身 | App Dev & Freelance Thinker & Hacker Growth | 这里是 @道轲(Dorck) 的个人博客,与你一起探寻世界。

把文章发布到掘金,做成一个可复用的 juejin-skill

<p>很多工具的起点都不宏大,往往只是为了去掉一段反复出现、但没有创造性的劳动。</p> <p><a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 也是这样长出来的。我平时习惯在本地写 Markdown,配图和草稿都按项目目录管理。文章完成后,再回到掘金编辑器里补分类、挑标签、上传图片、创建草稿,偶尔做一次问题不大,但只要写作频率稍微稳定一点,这条链路就会迅速变成额外负担。更麻烦的是,文章一旦修改,还得回到网页端重新同步一遍,本地明明已经有完整原稿,却还要重复操作。</p> <!--more--> <p>把这件事拆开以后,真正需要被自动化的部分其实非常集中:</p> <ul> <li>登录态最好能复用,而不是每次重新处理登录流程。</li> <li>分类、标签和热榜信息应该可以直接查询,最好还能给出合理推荐。</li> <li>本地 Markdown 应该能够直接转换成平台草稿。</li> <li>本地图片需要在发布前完成上传和替换,不能把相对路径带到线上。</li> <li>已发布文章需要支持基于本地稿件继续更新。</li> <li>真正公开发布时,应该保留一次明确确认。</li> </ul> <p>于是就有了 <a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a>。它现在是一套同时面向 Codex 和 Claude Code 的掘金发文 Skill,底层共用一套 Node.js CLI。两个 Agent 的接入方式不同,但最终执行的是同一条发布链路。</p> <p>这篇文章会按照实现顺序,讲清楚它为什么这样拆、哪些设计后来证明是必要的。出于安全和稳定性的考虑,文中对少量平台内部接口路径、字段名和上传链路细节做了抽象化处理,但整体实现思路与工程结构保持真实。</p> <h2 id="一先把能力收敛到-cli">一、先把能力收敛到 CLI</h2> <p>做 Agent Skill 时,很容易先写提示词,让模型去判断“什么时候该发布、什么时候该更新、参数该怎么组装”。这种方式短期能跑,但随着功能变多,维护成本会上升得很快。更稳妥的做法,是先把确定性的动作收敛到 CLI,再让不同平台的 Skill 文档去调用这套命令。</p> <p>现在 <a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 的命令大致是这几类:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 </pre></td><td class="rouge-code"><pre>juejin-skill login juejin-skill verify juejin-skill categories juejin-skill tags <span class="nt">--category</span> &lt;category-id&gt; juejin-skill hot <span class="nt">--limit</span> 10 juejin-skill publish <span class="nt">--file</span> ./article.md <span class="nt">--category</span> &lt;category-id&gt; juejin-skill update <span class="nt">--article</span> https://juejin.cn/post/&lt;article-id&gt; <span class="nt">--file</span> ./article.md juejin-skill download <span class="nt">--article</span> https://juejin.cn/post/&lt;article-id&gt; </pre></td></tr></tbody></table></code></pre></div></div> <p>CLI 放在 <code class="language-plaintext highlighter-rouge">src/commands/cli.js</code>,职责尽量保持简单:解析参数、读取文件、组装调用、输出结果。真正的业务逻辑都下沉到 <code class="language-plaintext highlighter-rouge">src/core/</code>,例如登录、热榜、发布、图片上传、下载等能力都在这一层实现。</p> <p>这个分层没有复杂设计,价值却非常直接。Codex Skill 和 Claude Code 命令不需要各写一套发布逻辑,只要约定如何调用 CLI 即可。后续修复 bug、补充参数或调整发布流程时,也能保证两个平台的行为保持一致。</p> <h2 id="二http-层尽量薄错误尽量早暴露">二、HTTP 层尽量薄,错误尽量早暴露</h2> <p>掘金的核心操作最终都落在 HTTP 请求上,因此我先写了一个比较薄的 <code class="language-plaintext highlighter-rouge">JuejinHttpClient</code>。它主要负责统一请求头、带上本地会话信息,以及处理非成功响应。</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 </pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">class</span> <span class="nx">JuejinHttpClient</span> <span class="p">{</span> <span class="kd">constructor</span><span class="p">({</span> <span class="nx">cookie</span> <span class="o">=</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">fetchImpl</span> <span class="o">=</span> <span class="nx">fetch</span> <span class="p">}</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="nx">cookie</span><span class="p">;</span> <span class="k">this</span><span class="p">.</span><span class="nx">fetchImpl</span> <span class="o">=</span> <span class="nx">fetchImpl</span><span class="p">;</span> <span class="p">}</span> <span class="k">async</span> <span class="nx">post</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">body</span> <span class="o">=</span> <span class="p">{},</span> <span class="p">{</span> <span class="nx">headers</span> <span class="o">=</span> <span class="p">{}</span> <span class="p">}</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span> <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="err">#</span><span class="nx">request</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span> <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span> <span class="nx">headers</span><span class="p">,</span> <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span> <span class="p">});</span> <span class="p">}</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这里我刻意没有做太厚的封装,比如自动重试、业务错误猜测、静默降级等。和第三方站点打交道时,接口变化、登录失效、参数缺失往往对应完全不同的问题,越早看到状态码和响应片段,排查就越快。如果客户端层过度“聪明”,出问题时反而不容易定位。</p> <p>另外保留 <code class="language-plaintext highlighter-rouge">fetchImpl</code> 这个注入点,是为了让测试更干净。单元测试可以直接替换成假的 fetch,只验证请求结构和分支逻辑,不需要真的访问平台。</p> <p>端点和配置统一收敛在 <code class="language-plaintext highlighter-rouge">src/core/config.js</code>。这点看起来普通,但对这类依赖网页端实现细节的工具非常重要。平台调整接口时,只改配置层,维护成本会低很多。</p> <h2 id="三登录交给浏览器会话交给本地文件">三、登录交给浏览器,会话交给本地文件</h2> <p>发布、更新和上传图片都依赖登录态。对用户来说,最自然的体验不是把账号密码交给工具,而是用浏览器完成正常登录,然后让程序复用登录结果。</p> <p><a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 的 <code class="language-plaintext highlighter-rouge">login</code> 命令会用 Playwright 启动浏览器,打开登录页,并在登录成功后提取当前站点所需的会话 Cookie,最终保存到:</p> <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>~/.juejin-skill/session.json </pre></td></tr></tbody></table></code></pre></div></div> <p>后续命令都从这个文件读取会话信息。这样做有几个很实际的好处:</p> <ul> <li>工具本身不接触账号密码,也不需要处理扫码、验证码等登录细节。</li> <li>用户登录一次后,发布、更新和上传图片都可以复用同一份本地会话。</li> <li>会话失效时,只需要重新执行 <code class="language-plaintext highlighter-rouge">login</code>,不必改动其他逻辑。</li> <li>如果想撤销本地登录状态,删除 <code class="language-plaintext highlighter-rouge">session.json</code> 就可以。</li> </ul> <p>我还补了一个 <code class="language-plaintext highlighter-rouge">verify</code> 命令,用当前会话去校验登录状态。这个命令在实际使用里非常有价值,因为不少“发布失败”表面看像参数问题,最后往往只是会话已经过期。</p> <h2 id="四把分类标签和热榜纳入同一条工作流">四、把分类、标签和热榜纳入同一条工作流</h2> <p>发文章不是只把正文上传出去那么简单。真正落地时,往往还要先确认当前有哪些分类、某个分类下有哪些推荐标签、最近热门内容是什么。把这些辅助能力放进同一个工具里,才能让发文流程更顺手。</p> <p><a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 目前支持:</p> <ul> <li><code class="language-plaintext highlighter-rouge">categories</code>:查询平台分类列表。</li> <li><code class="language-plaintext highlighter-rouge">tags</code>:查询指定分类下的推荐标签,并支持关键词过滤。</li> <li><code class="language-plaintext highlighter-rouge">hot</code>:查询全站或指定分类下的热门文章。</li> </ul> <p>这里有一个很典型的工程经验:平台表面上“能用”的接口,不一定是最适合自动化调用的接口。早期标签能力使用过另一条数据来源,但在发布场景里稳定性不够,后来我对照网页端当前网络请求,切换到了更接近实际产品逻辑的一条接口。做这类工具时,经常需要顺着真实页面行为反推更可靠的数据入口。</p> <p>如果用户在发布时没有显式指定分类,CLI 会根据标题和正文里的关键词给出推荐。比如命中 <code class="language-plaintext highlighter-rouge">Android</code>、<code class="language-plaintext highlighter-rouge">Kotlin</code>、<code class="language-plaintext highlighter-rouge">Compose</code>,就更偏向 Android;命中 <code class="language-plaintext highlighter-rouge">React</code>、<code class="language-plaintext highlighter-rouge">Vue</code>、<code class="language-plaintext highlighter-rouge">Vite</code>,就更偏向前端。</p> <p>这套推荐规则并不复杂,但我很喜欢它的两个特点:可解释、可测试。Agent 可以直接告诉用户“为什么推荐这个分类”,而不是给出一个黑盒结论。分类会影响文章的受众,因此我没有让工具默默替用户做最终决定,而是把推荐结果作为辅助信息交给作者确认。</p> <h2 id="五发布默认只创建草稿">五、发布默认只创建草稿</h2> <p>真正的发布逻辑集中在 <code class="language-plaintext highlighter-rouge">ArticlePublisher.publishMarkdown()</code>。整体流程大致如下:</p> <ol> <li>读取 Markdown 文件。</li> <li>提取第一个一级标题作为文章标题。</li> <li>从正文中移除标题行,避免平台正文重复出现标题。</li> <li>扫描本地图片,完成上传并替换成可公开访问的地址。</li> <li>生成摘要,除非用户显式传入 <code class="language-plaintext highlighter-rouge">--brief</code>。</li> <li>将 Markdown 渲染为 HTML,并同步整理平台所需的图片元数据。</li> <li>创建文章草稿。</li> <li>只有在明确确认后,才执行公开发布。</li> </ol> <p>默认行为是“创建草稿而不是直接公开”。要真正发布,必须显式传入:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="nt">--publish</span> <span class="nt">--confirm-publish</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>也可以通过环境变量开启明确确认:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="nv">JUEJIN_CONFIRM_PUBLISH</span><span class="o">=</span>1 </pre></td></tr></tbody></table></code></pre></div></div> <p>这个边界我设得比较坚决。自动化非常适合承担重复劳动,例如读取原稿、上传图片、创建草稿;但公开发布意味着内容会对外可见,工具最好只在作者明确授权后才继续推进。</p> <p>标题提取也只认第一个一级标题。这样本地 Markdown 仍然可以保持常见写法:</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre><span class="gh"># 我的文章标题</span> 正文从这里开始。 </pre></td></tr></tbody></table></code></pre></div></div> <p>对作者来说,本地文稿依旧完整;对平台来说,标题和正文则被正确拆分到不同字段中。</p> <h2 id="六本地图片上传是整条链路最容易出问题的部分">六、本地图片上传,是整条链路最容易出问题的部分</h2> <p>本地 Markdown 里经常会有这样的图片引用:</p> <div class="language-md highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="p">![</span><span class="nv">架构图</span><span class="p">](</span><span class="sx">./images/architecture.png</span><span class="p">)</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>在本地预览时它完全正常,但只要把这段 Markdown 直接提交到平台,线上读者就无法访问作者电脑里的相对路径。也正因为如此,图片上传几乎是整条发文链路里最容易出问题、也最必须做好的部分。</p> <p><code class="language-plaintext highlighter-rouge">0.1.2</code> 版本之后,我补上了完整的本地图片处理。逻辑分成两步。</p> <p>第一步,识别 Markdown 中的图片链接。工具只处理本地文件路径,远程图片、<code class="language-plaintext highlighter-rouge">data:</code> 图片和其他非本地资源会直接跳过。相对路径统一按 Markdown 文件所在目录解析;同一张图片多次出现时,只上传一次,后续复用上传结果。</p> <p>第二步,走平台当前网页编辑器所使用的媒体上传链路。这里我不展开具体接口细节,只保留实现层面的关键抽象:先拿一次临时授权,再申请上传会话,上传二进制文件,最后提交会话并换取可用于正文的正式资源地址。</p> <p>这部分实现时有两个容易踩坑的点。</p> <p>一个是媒体链路里通常存在平台侧资源标识和上传会话标识,它们对最终提交格式有约束,不能只看见“上传成功”就结束。另一个是正文内容和图片元数据往往要同时更新,编辑器能显示图片,并不代表文章提交后的内容结构就是完整的。</p> <p>所以在实现里,<code class="language-plaintext highlighter-rouge">prepareJuejinContent()</code> 不只是简单替换 Markdown 图片地址,它还会同步整理 HTML 内容和平台要求的图片元数据,确保草稿、编辑器预览和最终文章使用的是同一组资源描述。做完这一层之后,发布流程才算真正稳定。</p> <h2 id="七更新文章时尽量不要碰用户没让你碰的东西">七、更新文章时,尽量不要碰用户没让你碰的东西</h2> <p>文章发布之后,本地稿件继续修改是常态。于是 <code class="language-plaintext highlighter-rouge">update</code> 命令对应解决的是另一个高频问题:如何让本地 Markdown 安全地同步回线上文章。</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre>juejin-skill update <span class="se">\</span> <span class="nt">--article</span> https://juejin.cn/post/&lt;article-id&gt; <span class="se">\</span> <span class="nt">--file</span> ./article.md </pre></td></tr></tbody></table></code></pre></div></div> <p>默认行为依旧是更新草稿。如果要进一步同步到公开文章,还需要显式确认:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 </pre></td><td class="rouge-code"><pre>juejin-skill update <span class="se">\</span> <span class="nt">--article</span> https://juejin.cn/post/&lt;article-id&gt; <span class="se">\</span> <span class="nt">--file</span> ./article.md <span class="se">\</span> <span class="nt">--publish</span> <span class="se">\</span> <span class="nt">--confirm-publish</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>更新流程比新建草稿多了一层:程序会先根据文章链接获取文章详情,再找到与之关联的编辑上下文,随后更新对应草稿。这里最重要的不是“把正文换掉”,而是“尽量保留原文章的其他信息”。</p> <p>分类、标签、封面、摘要、主题、原创状态这类元信息,理论上都应该继承原值。否则用户只是想修一张图、改一个段落,最后却把封面清空了、标签弄丢了,体验会非常差。</p> <p>另外,平台里某些标识字段的数值范围已经很大,更新逻辑里如果处理不谨慎,容易遇到精度问题。所以实现上我优先沿用原始字符串形式,而不是把所有字段都当作普通数字处理。</p> <p>我对这部分能力的要求一直很明确:少做多余的事。正文可以更新,图片可以重传,但用户没有显式要求修改的元信息,不应该被工具顺手“重置”。</p> <h2 id="八下载能力补齐了内容闭环">八、下载能力补齐了内容闭环</h2> <p>除了发布和更新,项目里还补了一组下载命令:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>juejin-skill download <span class="nt">--article</span> https://juejin.cn/post/&lt;article-id&gt; juejin-skill download-user <span class="nt">--user</span> https://juejin.cn/user/&lt;user-id&gt; <span class="nt">--max-count</span> 20 </pre></td></tr></tbody></table></code></pre></div></div> <p>下载单篇文章时,程序会优先尝试获取平台返回的 Markdown 内容;如果拿不到,再退回到浏览器抓取页面正文 HTML,并通过 Turndown 转回 Markdown。</p> <p>如果带上 <code class="language-plaintext highlighter-rouge">--download-images</code>,远程图片会一并下载到本地目录,Markdown 里的地址也会被改写成本地相对路径。</p> <p>这个能力一开始只是“顺手做了”,后来反而让我觉得它很重要。它让文章内容形成了闭环:线上文章可以回收到本地 Markdown,本地 Markdown 也可以重新发布和继续更新。对长期写技术文章的人来说,本地原稿、图片和历史版本始终掌握在自己手里,是非常有价值的事情。</p> <h2 id="九codex-和-claude-code-共用一套运行时">九、Codex 和 Claude Code 共用一套运行时</h2> <p><a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 同时服务 Codex 和 Claude Code,但我没有为两个平台各维护一套逻辑,而是让它们共用同一套 Node.js CLI。</p> <p>Codex 侧主要依赖 <code class="language-plaintext highlighter-rouge">SKILL.md</code>,用于描述何时触发、有哪些命令、默认的安全边界是什么;Claude Code 侧则在 <code class="language-plaintext highlighter-rouge">.claude/commands/</code> 下定义命令文档,例如 <code class="language-plaintext highlighter-rouge">juejin-publish.md</code>、<code class="language-plaintext highlighter-rouge">juejin-hot.md</code>、<code class="language-plaintext highlighter-rouge">juejin-login.md</code>。</p> <p>两边文档格式不同,但最终调用的都是同一个入口:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>node ./bin/juejin-skill.js ... </pre></td></tr></tbody></table></code></pre></div></div> <p>构建脚本会生成两套分发目录:</p> <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>dist/codex/juejin-skill dist/claude/juejin-skill </pre></td></tr></tbody></table></code></pre></div></div> <p>这种结构的好处很直接:功能只实现一次,测试也围绕同一套核心代码展开,平台适配层只负责“怎么接”,而不负责“怎么做”。</p> <h2 id="十既然打算复用就把工程化一起补齐">十、既然打算复用,就把工程化一起补齐</h2> <p>如果一个 Skill 只能在作者自己的机器上工作,它本质上还只是个人脚本。既然想让它被复用,就必须把版本发布和交付路径也做完整。</p> <p>现在推送 <code class="language-plaintext highlighter-rouge">v*</code> 标签后,GitHub Actions 会自动完成这些事:</p> <ol> <li>安装依赖和浏览器运行环境。</li> <li>运行 lint 和测试。</li> <li>构建 Codex、Claude 两套产物。</li> <li>生成版本说明。</li> <li>打包并上传 Release 附件。</li> </ol> <p>这一层我也踩过一个很典型的 CI 坑:自动创建 Release 时返回权限错误。最后定位下来,并不是脚本逻辑有问题,而是 workflow token 缺少写入发布内容所需的权限。</p> <p>这类问题并不复杂,却很容易被忽略。尤其当一个工具要交付给别人安装和使用时,构建产物、版本说明、Release 附件、安装入口这些环节都必须跑通,否则代码虽然写完了,使用体验仍然是不完整的。</p> <h2 id="十一测试守住的是我们自己的边界">十一、测试守住的是我们自己的边界</h2> <p>这个项目的测试覆盖了认证、标签、图片上传、发布更新、下载和分类推荐等核心能力。</p> <p>我没有试图在测试里证明“平台永远不会变”。第三方接口会调整,页面结构会变,登录策略也可能改变。测试真正适合守住的,是我们自己定义的那些边界和规则。</p> <p>例如:</p> <ul> <li>同一张本地图片重复出现时,只上传一次。</li> <li>远程图片和 <code class="language-plaintext highlighter-rouge">data:</code> 图片不会被误判为本地文件。</li> <li>更新文章时尽量保留原有分类、标签、封面和主题信息。</li> <li>没有明确确认时,发布流程只停留在草稿阶段。</li> <li>标识字段尽量沿用字符串表示,降低精度风险。</li> </ul> <p>这些规则稳定之后,排查问题会快很多。出故障时,我们能够更快区分:到底是登录态失效、平台接口变化、媒体链路异常,还是我们自己的参数组织出了偏差。</p> <h2 id="十二最后想强调的其实是边界感">十二、最后想强调的,其实是边界感</h2> <p>到这里,<a href="https://github.com/Moosphan/juejin-skill">juejin-skill</a> 已经不再只是一个“帮我把文章发出去”的脚本,而是一条相对完整的内容工作流:查分类、查标签、读 Markdown、上传图片、创建草稿、更新文章、下载归档,都被串进了同一套工具里。</p> <p>我比较喜欢它现在的边界。重复操作交给工具,关键决策仍然留给作者。分类可以推荐,图片可以自动上传,草稿可以自动创建;但要不要公开发布、要不要覆盖线上内容,依然需要明确确认。</p> <p>如果你也习惯在本地写 Markdown,又经常把文章同步到掘金,不妨试试这个 Skill:<a href="https://github.com/Moosphan/juejin-skill">Moosphan/juejin-skill</a>。它未必是最复杂的工具,但我希望它足够克制、足够稳定,也足够接近真实写作者每天会用到的那条路径。</p>

2026/6/3
阅读更多

Jekyll 博客自动化发文实践:一个可维护的 blog-post skill 是如何落地的

<p>为个人博客补一套自动化发文能力,看似只是“少敲几次 front matter”,真正落地时却会迅速演变成一个工程问题:如何适配现有博客结构、如何控制发布风险、如何让脚本保持确定性、又如何让不同 AI 编程环境复用同一套能力。本文就围绕这几个问题,复盘 <code class="language-plaintext highlighter-rouge">blog-post skill</code> 的完整实现过程。</p> <!--more--> <p>这次实现的目标其实很明确:在当前 Jekyll 博客仓库中,新增一个可以被 <code class="language-plaintext highlighter-rouge">Codex</code> 和 <code class="language-plaintext highlighter-rouge">Claude Code</code> 共用的发文 skill,让它支持创建文章、切换公开状态、补齐 front matter、按需执行 <code class="language-plaintext highlighter-rouge">git add / commit / push</code>,并尽可能贴合现有博客的目录结构与写作习惯。</p> <h2 id="1-为什么不是继续手写文章模板">1. 为什么不是继续手写文章模板</h2> <p>如果只是偶尔写一篇文章,直接复制旧文章的头部并不算昂贵。但一旦要把“创建文章”和“发布文章”变成一条可重复执行的工作流,手工方式的几个问题会立刻暴露出来:</p> <ul> <li>front matter 容易漏字段,例如 <code class="language-plaintext highlighter-rouge">published</code>、<code class="language-plaintext highlighter-rouge">tags</code>、<code class="language-plaintext highlighter-rouge">categories</code></li> <li>文件路径不稳定,尤其是多主题目录下容易放错位置</li> <li><code class="language-plaintext highlighter-rouge">date</code>、标题、slug 和最终访问链接之间需要保持一致</li> <li>不同 Agent 环境需要反复解释“这篇文章应该怎么建”</li> </ul> <p>更关键的一点是,这个博客仓库本身并不是一个全新的 Jekyll 默认模板,而是已经演化出自己的内容结构。文章实际并不都放在 <code class="language-plaintext highlighter-rouge">_posts/</code> 根目录,而是分散在:</p> <ul> <li><code class="language-plaintext highlighter-rouge">_posts/post_android</code></li> <li><code class="language-plaintext highlighter-rouge">_posts/post_skills</code></li> <li><code class="language-plaintext highlighter-rouge">_posts/post_gradle</code></li> <li><code class="language-plaintext highlighter-rouge">_posts/post_other</code></li> </ul> <p>这意味着自动化方案不能只会“生成一篇 markdown”,还必须理解这个仓库自己的组织方式。</p> <h2 id="2-先分析仓库再决定-skill-的边界">2. 先分析仓库,再决定 skill 的边界</h2> <p>在开始写脚本之前,第一步不是设计命令,而是先确认博客本身的实现约定。</p> <p>当前仓库有几个关键事实:</p> <ul> <li>导航栏通过 <code class="language-plaintext highlighter-rouge">_includes/nav.html</code> 自动枚举 <code class="language-plaintext highlighter-rouge">site.pages</code></li> <li>文章页面主要依赖 <code class="language-plaintext highlighter-rouge">title</code>、<code class="language-plaintext highlighter-rouge">date</code>、<code class="language-plaintext highlighter-rouge">tags</code>、<code class="language-plaintext highlighter-rouge">description</code>、<code class="language-plaintext highlighter-rouge">published</code></li> <li>SEO 描述层默认会读取 <code class="language-plaintext highlighter-rouge">excerpt</code> 或 <code class="language-plaintext highlighter-rouge">description</code></li> <li><code class="language-plaintext highlighter-rouge">Rakefile</code> 中虽然有 <code class="language-plaintext highlighter-rouge">rake post</code>,但它只会把文章生成到 <code class="language-plaintext highlighter-rouge">_posts/</code> 根目录</li> </ul> <p>这意味着现成脚本并不能直接满足需要。尤其是 <code class="language-plaintext highlighter-rouge">rake post</code> 这一类脚手架,只解决了“建文件”问题,却没有解决“建在正确的位置”“带上正确的元数据”“兼容现有工作流”这些更关键的问题。</p> <p>于是这个 skill 的目标被进一步收敛为两层:</p> <ul> <li>上层 skill:负责理解用户意图,整理出参数</li> <li>下层 CLI:负责用确定性的方式修改仓库</li> </ul> <p>这个职责划分后来被证明是整个实现里最重要的一次收敛。</p> <h2 id="3-一个关键转折脚本不该直接吃自然语言">3. 一个关键转折:脚本不该直接吃自然语言</h2> <p>最开始的直觉方案,其实很容易走向另一条路:直接让脚本接收整段自然语言,例如“创建一篇 Android 文章,标题是什么,发布时间是什么,是否公开”,然后在 CLI 内部用正则把这些信息抠出来。</p> <p>这条路短期看起来很方便,但问题也非常明显。</p> <h3 id="31-不确定性太高">3.1 不确定性太高</h3> <p>自然语言解析一旦放进脚本层,CLI 就会同时承担两类职责:</p> <ul> <li>语义理解</li> <li>文件落盘</li> </ul> <p>前者天然是不稳定的,后者却必须是稳定的。把两件事情放在一起,最终会让一个原本应该确定可测试的工具,变成带着隐式猜测的执行器。</p> <h3 id="32-不利于测试">3.2 不利于测试</h3> <p>如果 CLI 接收的是结构化参数,那么测试只需要验证:</p> <ul> <li>输入某组参数,会落在哪个目录</li> <li>front matter 是否正确</li> <li><code class="language-plaintext highlighter-rouge">published</code> 切换是否符合预期</li> </ul> <p>但如果 CLI 先要解析自然语言,测试就会被迫跟着验证一堆语言变体。这对工具层来说,并不是一个值得承担的复杂度。</p> <h3 id="33-不利于多环境复用">3.3 不利于多环境复用</h3> <p><code class="language-plaintext highlighter-rouge">Codex</code> 和 <code class="language-plaintext highlighter-rouge">Claude Code</code> 都具备很强的自然语言理解能力,真正缺的并不是“会不会解析一句话”,而是“有没有一条可以稳定调用的仓库命令”。既然如此,更合理的方式就是让 Agent 负责理解,脚本负责执行。</p> <p>所以最终的实现明确改成:</p> <ul> <li><code class="language-plaintext highlighter-rouge">Codex</code> / <code class="language-plaintext highlighter-rouge">Claude Code</code> 把自然语言转换为结构化参数</li> <li><code class="language-plaintext highlighter-rouge">blog-post-skill.js</code> 只接受明确命令与 flags</li> </ul> <p>这也是为什么现在的 CLI 会采用如下形式:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 </pre></td><td class="rouge-code"><pre>node ./tools/blog-post-skill/bin/blog-post-skill.js create <span class="se">\</span> <span class="nt">--title</span> <span class="s2">"Activity 启动过程"</span> <span class="se">\</span> <span class="nt">--section</span> android <span class="se">\</span> <span class="nt">--date</span> <span class="s2">"2026-05-24 09:30"</span> <span class="se">\</span> <span class="nt">--published</span> <span class="nb">false</span> <span class="se">\</span> <span class="nt">--tags</span> <span class="s2">"Android,源码分析"</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这类接口虽然看起来没有“整段自然语言”那么花哨,但它的可测试性和可维护性要高得多。</p> <h2 id="4-结构化-cli-是如何设计的">4. 结构化 CLI 是如何设计的</h2> <p>这套工具最后被收敛为三个明确命令:</p> <ul> <li><code class="language-plaintext highlighter-rouge">sections</code></li> <li><code class="language-plaintext highlighter-rouge">create</code></li> <li><code class="language-plaintext highlighter-rouge">publish</code></li> </ul> <h3 id="41-sections">4.1 <code class="language-plaintext highlighter-rouge">sections</code></h3> <p><code class="language-plaintext highlighter-rouge">sections</code> 的作用是列出当前博客仓库支持的文章分区,例如:</p> <ul> <li><code class="language-plaintext highlighter-rouge">android</code></li> <li><code class="language-plaintext highlighter-rouge">skills</code></li> <li><code class="language-plaintext highlighter-rouge">gradle</code></li> <li><code class="language-plaintext highlighter-rouge">other</code></li> </ul> <p>它对应的意义并不只是“打印一个列表”,而是把仓库内部的目录映射显式暴露出来,让上层 Agent 不必硬编码这些知识。</p> <h3 id="42-create">4.2 <code class="language-plaintext highlighter-rouge">create</code></h3> <p><code class="language-plaintext highlighter-rouge">create</code> 负责:</p> <ul> <li>解析显式参数</li> <li>根据 <code class="language-plaintext highlighter-rouge">section</code> 找到目标目录</li> <li>生成 front matter</li> <li>根据标题生成 slug</li> <li>组合正文模板或导入外部 markdown</li> </ul> <p>例如 <code class="language-plaintext highlighter-rouge">android</code> 会映射到 <code class="language-plaintext highlighter-rouge">_posts/post_android</code>,<code class="language-plaintext highlighter-rouge">skills</code> 会映射到 <code class="language-plaintext highlighter-rouge">_posts/post_skills</code>。这一层逻辑被固化在脚本内部的 <code class="language-plaintext highlighter-rouge">SECTION_CONFIG</code> 里,避免每次创建文章时重新判断路径规则。</p> <h3 id="43-publish">4.3 <code class="language-plaintext highlighter-rouge">publish</code></h3> <p><code class="language-plaintext highlighter-rouge">publish</code> 的职责相对更偏向“已有文件修改”:</p> <ul> <li>切换 <code class="language-plaintext highlighter-rouge">published: true/false</code></li> <li>更新 <code class="language-plaintext highlighter-rouge">date</code></li> <li>更新 <code class="language-plaintext highlighter-rouge">tags</code>、<code class="language-plaintext highlighter-rouge">description</code></li> <li>可选执行 <code class="language-plaintext highlighter-rouge">git add / commit / push</code></li> </ul> <p>这让“创建文章”和“发布文章”不再混在一起。换句话说,创建是一次性落盘动作,而发布则是一次状态切换动作。</p> <p>这种拆分对于博客工作流非常重要,因为许多文章并不是写完立刻发布,而是会在草稿态停留一段时间。</p> <h2 id="5-front-matter-生成为什么要贴合现有博客">5. front matter 生成为什么要贴合现有博客</h2> <p>自动化发文脚本如果只会生成 Jekyll 最小头部,实际价值并不大。真正有用的是:它生成的文章必须天然符合当前博客自己的渲染约定。</p> <p>因此这套 skill 在 front matter 设计上做了几件事。</p> <h3 id="51-固定页面布局约定">5.1 固定页面布局约定</h3> <p>默认写入:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre><span class="na">layout</span><span class="pi">:</span> <span class="s">post</span> <span class="na">header-style</span><span class="pi">:</span> <span class="s">text</span> <span class="na">catalog</span><span class="pi">:</span> <span class="no">false</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这与现有文章的大多数配置保持一致,避免新文章渲染风格突然漂移。</p> <h3 id="52-自动补-description">5.2 自动补 <code class="language-plaintext highlighter-rouge">description</code></h3> <p>博客文章页本身会利用 <code class="language-plaintext highlighter-rouge">description</code> 和 <code class="language-plaintext highlighter-rouge">excerpt</code> 做头部描述与 SEO 文本,因此脚本在用户未显式传入摘要时,会基于:</p> <ul> <li>标题</li> <li>subtitle</li> <li>tags</li> <li>section</li> </ul> <p>自动生成一条短描述。它未必是最终最优文本,但足以保证生成出来的文章不是一份“只有标题没有描述”的半成品。</p> <h3 id="53-默认加入-excerpt_separator">5.3 默认加入 <code class="language-plaintext highlighter-rouge">excerpt_separator</code></h3> <p>脚本会默认写入:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="na">excerpt_separator</span><span class="pi">:</span> <span class="s2">"</span><span class="s">&lt;!--more--&gt;"</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这样做的目的,是把首页摘要和正文主体的边界提前规范化,而不是把摘要逻辑留给每一篇文章临时发挥。</p> <h2 id="6-为什么还要补正文骨架模板">6. 为什么还要补正文骨架模板</h2> <p>很多发文工具会停在 front matter 层,但这个 skill 又向前多走了一步:当用户没有提供完整正文时,脚本会根据 <code class="language-plaintext highlighter-rouge">template</code> 生成一个最小可写的骨架。</p> <p>目前支持的模板包括:</p> <ul> <li><code class="language-plaintext highlighter-rouge">default</code></li> <li><code class="language-plaintext highlighter-rouge">tutorial</code></li> <li><code class="language-plaintext highlighter-rouge">note</code></li> <li><code class="language-plaintext highlighter-rouge">review</code></li> </ul> <p>例如 <code class="language-plaintext highlighter-rouge">tutorial</code> 会生成如下结构:</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 </pre></td><td class="rouge-code"><pre><span class="gu">## 问题背景</span> <span class="gu">## 环境说明</span> <span class="gu">## 核心步骤</span> <span class="gu">## 关键细节</span> <span class="gu">## 总结</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这里的关键并不是“自动生成了几级标题”,而是让不同类型的文章在开稿阶段就拥有稳定结构。对于持续写作来说,这种结构化约束往往比单纯少敲几行 YAML 更有价值。</p> <h2 id="7-如何同时适配-codex-与-claude-code">7. 如何同时适配 Codex 与 Claude Code</h2> <p>实现共享能力的核心思路,是让仓库只维护一套真正可执行的能力,把环境差异压缩到外围包装层。</p> <p>所以整个目录被拆成了三部分:</p> <ul> <li><code class="language-plaintext highlighter-rouge">tools/blog-post-skill/</code></li> <li><code class="language-plaintext highlighter-rouge">.codex/skills/blog-post-publisher/</code></li> <li><code class="language-plaintext highlighter-rouge">.claude/commands/blog-post.md</code></li> </ul> <p>其中:</p> <ul> <li><code class="language-plaintext highlighter-rouge">tools/blog-post-skill/</code> 是共享运行时</li> <li><code class="language-plaintext highlighter-rouge">SKILL.md</code> 负责告诉 Codex 什么时候该使用这套能力</li> <li><code class="language-plaintext highlighter-rouge">.claude/commands/blog-post.md</code> 负责告诉 Claude Code 该如何组装命令</li> </ul> <p>这个设计的好处是,真正决定仓库状态的逻辑永远只有一份。上层环境无论是 Codex 还是 Claude Code,都不需要各自再维护一套文件写入实现。</p> <h2 id="8-一个额外踩坑为什么工具-readme-会出现在导航栏里">8. 一个额外踩坑:为什么工具 README 会出现在导航栏里</h2> <p>skill 本身落地之后,又暴露出一个很典型的 Jekyll 仓库问题:只要某个目录没有被正确排除,它就可能被当作站点内容的一部分。</p> <p>这个博客的导航栏会自动枚举 <code class="language-plaintext highlighter-rouge">site.pages</code>。而 <code class="language-plaintext highlighter-rouge">tools/blog-post-skill/README.md</code> 作为仓库里的说明文档,如果不被排除出构建范围,就有机会进入站点页面集合,进而在导航栏中冒出一个不该出现的入口。</p> <p>解决方式很直接:把 <code class="language-plaintext highlighter-rouge">tools</code> 以及相关工具目录加入 <code class="language-plaintext highlighter-rouge">_config.yml</code> 的 <code class="language-plaintext highlighter-rouge">exclude</code>。</p> <p>这类问题很容易被忽视,因为它不属于“发文脚本是否能运行”的范畴,却直接影响最终站点表现。也正因为如此,发文自动化不应该被理解为一段孤立脚本,而应该被视为站点工程的一部分。</p> <h2 id="9-这套实现真正解决了什么">9. 这套实现真正解决了什么</h2> <p>到这里再回头看,这个 skill 真正解决的并不是“创建文章文件很麻烦”这么简单的问题,而是把博客发文流程中的几个关键环节结构化了:</p> <ul> <li>文章目录归类</li> <li>front matter 规范化</li> <li>草稿与公开状态切换</li> <li>AI 编程环境复用</li> <li>Git 提交与推送串联</li> </ul> <p>更重要的是,它明确了一个适用于很多仓库自动化场景的原则:</p> <blockquote> <p>语义理解交给 Agent,确定性执行交给脚本。</p> </blockquote> <p>一旦这条边界稳定下来,后续无论是继续接图片处理、封面生成、文章校验还是发布检查,都可以沿着同一条结构继续扩展,而不需要反复推翻底层设计。</p> <h2 id="10-后续可以继续演进的方向">10. 后续可以继续演进的方向</h2> <p>目前这套 <code class="language-plaintext highlighter-rouge">blog-post skill</code> 已经足够覆盖个人博客的日常发文流程,但它仍然有不少可以继续扩展的空间:</p> <ul> <li>在创建文章前自动校验标签与分类是否符合既有约定</li> <li>增加图片资源目录的自动创建与引用修正</li> <li>在发布前执行本地预览或构建检查</li> <li>将文章元数据进一步抽象为可复用 schema</li> </ul> <p>如果把它放到更大的团队环境里,这套能力甚至可以继续演化成一个“内容仓库操作层”:前端页面、博客文章、知识库文档、发布说明,统一都通过结构化命令落库。</p> <p>对个人博客来说,这一步或许还谈不上“平台化”,但它至少证明了一件事:只要先把职责边界拉清,自动化往往比想象中更容易做对,也更容易持续迭代。</p>

2026/5/24
阅读更多

Android 手写渲染技术演进:前缓冲、Ink API 与 Compose 高级触控笔能力

<p>在 Android 手写、批注与白板类应用中,决定体验上限的通常不是单一的绘制 API,而是整条输入与渲染链路的协同效率。过去,开发者往往需要自行处理 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 采样、预测点插值、压感映射、误触回滚以及低延迟渲染。近两年,Google 逐步将这套能力沉淀为可复用的官方组件:底层由 <code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core</code> 提供低延迟渲染基础设施,上层由 <code class="language-plaintext highlighter-rouge">androidx.ink:ink-*</code> 提供笔迹建模、笔刷系统与几何操作能力;同时,Jetpack Compose 文档也补充了高级触控笔输入的工程化处理方式。</p> <p>本文基于截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code> 的官方资料,对 Android 手写技术栈的最新状态进行梳理,并讨论前缓冲、Ink API 与 Compose 高级触控笔能力在实际项目中的组合方式。</p> <p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr-fQK660FgiUsJF64wNgAAxkWgA-Ul68stuzOKasoAIpQddUhACLL1nEmwSu7oRJriJh_B1vlKftd2VfQ2kDAnDNQNhQ8SGd0y6-U5Zatg9xAKe0EFW7Q13YtB1owvG2vMbC72XsBzsJOhBrGegpCnn4PEEfyFksSkSdAqDviGhDbtm_SyH89Uy4QmQo/s1600/v2-header-Android-type-safe-navigation-for-compose.png" alt="Ink API 官方封面图" /></p> <p>图源:Google Android Developers Blog,Ink API 官方文章</p> <h2 id="1-官方技术栈的最新状态">1. 官方技术栈的最新状态</h2> <p>截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code>,与 Android 手写能力直接相关的官方组件处于以下状态:</p> <ul> <li><code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core:1.0.0</code> 稳定版发布于 <code class="language-plaintext highlighter-rouge">2024-05-29</code></li> <li><code class="language-plaintext highlighter-rouge">androidx.graphics:graphics-core:1.0.4</code> 发布于 <code class="language-plaintext highlighter-rouge">2025-12-03</code></li> <li><code class="language-plaintext highlighter-rouge">androidx.ink:ink-*:1.0.0</code> 稳定版发布于 <code class="language-plaintext highlighter-rouge">2025-12-17</code></li> <li><code class="language-plaintext highlighter-rouge">androidx.ink:ink-*:1.1.0-alpha03</code> 发布于 <code class="language-plaintext highlighter-rouge">2026-05-19</code></li> <li>Compose 文档《高级触控笔功能》最后更新时间为 <code class="language-plaintext highlighter-rouge">2026-05-23</code></li> </ul> <p>这组时间线说明两点。</p> <p>其一,低延迟渲染不再只是依赖内部实现细节的试验性方案,而已经演化为稳定的 AndroidX 能力。其二,笔迹生成、笔刷配置、输入序列化、几何运算与在途笔迹渲染,已经形成了较完整的上层抽象。这意味着当前 Android 手写应用的技术重点,正在从“如何从零实现一套书写系统”转移到“如何基于官方组件搭建更稳定的工程架构”。</p> <h2 id="2-前缓冲的技术意义">2. 前缓冲的技术意义</h2> <p>手写场景与普通界面渲染的主要差异在于延迟容忍度极低。列表、卡片或页面切换通常可以接受几十毫秒级的视觉延迟,但在书写过程中,用户会直接感知笔尖与墨迹之间的空间偏移。所谓“跟手性”,本质上是输入到显示的端到端延迟是否足够小。</p> <p>前缓冲(front-buffered rendering)的价值正在于此。与只依赖双缓冲或多缓冲的完整帧提交流程相比,前缓冲允许当前正在变化的局部内容更早进入显示链路,从而缩短笔迹首次可见的时间。这种优化并不直接提高最终画质,但可以显著改善书写阶段的主观响应速度。</p> <p>Google 在 <code class="language-plaintext highlighter-rouge">graphics-core</code> 中围绕低延迟渲染提供了几类关键能力:</p> <ul> <li><code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code></li> <li><code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code></li> <li><code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code></li> <li><code class="language-plaintext highlighter-rouge">CanvasBufferedRenderer</code></li> </ul> <p>其中,<code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code> 与 <code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code> 使基于 <code class="language-plaintext highlighter-rouge">Canvas</code> 的低延迟渲染路径更加直接,适合触控笔输入类场景;<code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code> 则更适合已有 OpenGL 渲染栈的应用。</p> <p>从 release notes 可以看到,这套能力的成熟过程高度依赖设备适配与系统兼容修复:</p> <ul> <li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.2</code> 修复了部分 Android 14+ 设备在不支持 front buffer usage flag 时的闪烁问题</li> <li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.3</code> 修复了部分 <code class="language-plaintext highlighter-rouge">API &lt; 33</code> 设备上的全屏闪烁</li> <li><code class="language-plaintext highlighter-rouge">graphics-core:1.0.4</code> 继续改进了特定设备上的兼容性与性能</li> </ul> <p>这类修复说明,前缓冲的价值已经得到验证,但它依然是接近平台层的能力。对于大多数业务团队而言,直接从 <code class="language-plaintext highlighter-rouge">graphics-core</code> 最底层开始搭建书写系统,意味着需要自己承担更多的设备差异、资源释放与回退逻辑。因此,更常见也更稳妥的做法,是优先站在 Ink API 这一层向下借力。</p> <h2 id="3-ink-api-的定位从笔迹绘制到书写系统">3. Ink API 的定位:从笔迹绘制到书写系统</h2> <p>如果只将 Ink API 理解为“官方画笔库”,会低估它的工程价值。更准确的说法是:Ink API 试图为 Android 提供一套结构化的手写系统抽象。</p> <p>官方将其拆分为多个模块:</p> <ul> <li><code class="language-plaintext highlighter-rouge">Authoring</code>:处理输入接收、在途笔迹管理与交互状态</li> <li><code class="language-plaintext highlighter-rouge">Rendering</code>:负责实时笔迹与完成态笔迹的绘制</li> <li><code class="language-plaintext highlighter-rouge">Strokes</code>:定义笔迹数据模型</li> <li><code class="language-plaintext highlighter-rouge">Brush</code>:定义笔刷的外观、材质与行为</li> <li><code class="language-plaintext highlighter-rouge">Geometry</code>:提供选区、相交、覆盖等几何能力</li> </ul> <p>这一分层的关键价值,在于将“实时反馈”与“最终结果”从概念上分离。</p> <p>在手写系统中,用户落笔后的第一诉求是尽快看到墨迹;而笔迹结束之后,系统又必须支持持久化、撤销、回放、重绘、选中与同步。这两类需求关注点不同:前者以延迟最小化为目标,后者以稳定性和可操作性为目标。Google 因而在 Ink API 中明确区分:</p> <ul> <li>使用 <code class="language-plaintext highlighter-rouge">InProgressStrokesView</code> 处理在途笔迹</li> <li>使用 <code class="language-plaintext highlighter-rouge">CanvasStrokeRenderer</code> 或 <code class="language-plaintext highlighter-rouge">ViewStrokeRenderer</code> 处理完成态笔迹</li> </ul> <p>这种分层并不是实现细节,而是一种值得直接采纳的架构原则:在书写进行时优先追求显示时延,在笔迹结束后再转入质量与结构化表示优先的渲染路径。</p> <p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiE_VC7aiMgrPRq-rBG-c1LdauGYqi2U8alx80PoxZI4G9kNoURFNHh6RM21KO5SQL8RGRNDxIn_KuYjqA2sFXy_KWjorI1w16wN8HWaXEus2JlEjRtTWfInJGyJb9LpexvxzneqsVL2_Uw214MHlUhi4R7nw1O6QsiJyMEZ4N3hQ-nGQw5PzsNBk6kQe0/w640-h468/image5.gif" alt="Ink API 低延迟书写演示 1" /></p> <p>图源:Google Android Developers Blog,Samsung Tab S8 上基于 Ink API 的低延迟书写演示</p> <p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNLDZmNs7BwcQsuCn4orht8-OGhsaW4xLs-SEiGOsmu5UdPhBfs787uUFsX9zc25Fd2OEjootN6_U_FhifgLW_3855RdPWlFBZ2kKMLX0pV-ZaR5HrzZt2EevpaQghcnJbDhScQYKke3o3WmBhMvvoyQ9Dt1bqbj6N9vsevjdKa47YzhJqBv_vQQt06u8/s1600/image6.gif" alt="Ink API 低延迟书写演示 2" /></p> <p>图源:Google Android Developers Blog,Ink API 官方演示动图</p> <h2 id="4-笔刷能力的近期演进">4. 笔刷能力的近期演进</h2> <p>截至 <code class="language-plaintext highlighter-rouge">2026-05-24</code>,Ink API 在笔刷能力上的演进,主要集中在三个方向。</p> <h3 id="41-从预置笔刷走向可编程笔刷">4.1 从预置笔刷走向可编程笔刷</h3> <p><code class="language-plaintext highlighter-rouge">1.0.0-alpha04</code> 于 <code class="language-plaintext highlighter-rouge">2025-04-09</code> 引入实验性的自定义 <code class="language-plaintext highlighter-rouge">BrushFamily</code> 能力,官方明确提到可支持类似 <code class="language-plaintext highlighter-rouge">Pencil</code>、<code class="language-plaintext highlighter-rouge">Laser Pointer</code> 的新笔刷。到 <code class="language-plaintext highlighter-rouge">1.1.0-alpha03</code>,程序化自定义笔刷 API 进一步公开为 public。</p> <p>这一变化的意义在于,笔刷系统开始从“参数调节型配置”转向“可编排的笔刷模型”。对于笔记、教育、批注、设计和创作类产品,这意味着笔刷不再只是颜色与线宽的组合,而可以逐渐成为产品差异化的一部分。</p> <h3 id="42-笔刷配置的可序列化与版本兼容">4.2 笔刷配置的可序列化与版本兼容</h3> <p><code class="language-plaintext highlighter-rouge">1.1.0-alpha02</code> 对 <code class="language-plaintext highlighter-rouge">BrushFamily.decode</code> 的兼容性控制进行了增强,同时将 <code class="language-plaintext highlighter-rouge">BrushFamily</code> 的序列化 API 从 experimental 状态提升出来。</p> <p>这类更新对于线上产品尤其重要。笔刷系统一旦参与文档存储、跨端同步或长期版本演进,就必须回答三个问题:</p> <ul> <li>历史数据是否可以被新版本正确解析</li> <li>多设备间是否能得到一致的笔刷表现</li> <li>笔刷协议是否支持平滑迁移</li> </ul> <p>序列化与兼容性 API 的完善,意味着 Ink API 正在从“本地渲染工具”向“可持久化的文档能力”演进。</p> <h3 id="43-输入建模与数值稳定性">4.3 输入建模与数值稳定性</h3> <p>从 <code class="language-plaintext highlighter-rouge">1.0.0-alpha07</code> 及后续 beta、stable 版本的变更记录来看,Google 持续在优化以下内容:</p> <ul> <li>stroke input smoothing</li> <li><code class="language-plaintext highlighter-rouge">StrokeInputBatch</code> 序列化</li> <li>浮点精度</li> <li>某些场景下的渲染伪影</li> </ul> <p>这些更新虽然不如新增笔刷直观,却直接决定笔迹系统的稳定性。手写体验中的“抖动感”“回放变形”“跨设备表现不一致”,往往都与输入建模和数值处理有关,而不仅仅是渲染器本身。</p> <p><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsX5BcJ2qvBaVRKJXf2T-1hxCl9E_VuSZqUDgJnAMhmUGEiOhaC8XreGD9y_I9nRi-Ibz4IQfrWA0tVHmuCzVRmg3oMiJBpsqr-S5m3UHnMKogPfFf8M6Dk9MJv5qxhOE8H5JGypK0ZGpOyRvOhrnNR-z6H-8t8epx76z1jBI2oDlNP211Z1WSP9glBC0/s1600/v2-card-Android-type-safe-navigation-for-compose.png" alt="Ink API 官方卡片图" /></p> <p>图源:Google Android Developers Blog,Ink API 官方文章配图</p> <h2 id="5-compose-高级触控笔能力的工程意义">5. Compose 高级触控笔能力的工程意义</h2> <p>Google 在 Compose 文档《高级触控笔功能》中给出的一个重要信号是:即便应用的 UI 主体采用 Compose,实现高质量手写能力时仍然需要回到 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 级别获取完整输入信息。官方示例使用 <code class="language-plaintext highlighter-rouge">pointerInteropFilter</code> 作为桥接入口:</p> <div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 </pre></td><td class="rouge-code"><pre><span class="nd">@Composable</span> <span class="nd">@OptIn</span><span class="p">(</span><span class="nc">ExperimentalComposeUiApi</span><span class="o">::</span><span class="k">class</span><span class="p">)</span> <span class="k">fun</span> <span class="nf">DrawArea</span><span class="p">(</span><span class="n">modifier</span><span class="p">:</span> <span class="nc">Modifier</span> <span class="p">=</span> <span class="nc">Modifier</span><span class="p">)</span> <span class="p">{</span> <span class="nc">Canvas</span><span class="p">(</span> <span class="n">modifier</span> <span class="p">=</span> <span class="n">modifier</span> <span class="p">.</span><span class="nf">clipToBounds</span><span class="p">()</span> <span class="p">.</span><span class="nf">pointerInteropFilter</span> <span class="p">{</span> <span class="n">viewModel</span><span class="p">.</span><span class="nf">processMotionEvent</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// Drawing code here.</span> <span class="p">}</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这一写法的意义不在于“Compose 也能处理触控笔”,而在于它明确了职责划分:Compose 适合承担界面组织与交互壳层,而笔输入细节仍然应由 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 驱动的专门逻辑处理。对多数项目而言,这意味着更合理的结构通常是“Compose UI + MotionEvent 互操作 + 独立的笔迹层”。</p> <h2 id="6-高级触控笔特性与笔刷设计">6. 高级触控笔特性与笔刷设计</h2> <p>Compose 文档中最值得纳入手写系统设计的,不是单一 API,而是一组输入能力及其工程含义。</p> <h3 id="61-工具类型识别">6.1 工具类型识别</h3> <p>官方建议通过 <code class="language-plaintext highlighter-rouge">getToolType(pointerIndex)</code> 区分输入工具,例如:</p> <ul> <li><code class="language-plaintext highlighter-rouge">TOOL_TYPE_STYLUS</code></li> <li><code class="language-plaintext highlighter-rouge">TOOL_TYPE_ERASER</code></li> </ul> <p>这使系统能够在同一交互链路中区分书写与擦除,而无需依赖额外的模式切换。对于笔记和批注类应用,这种“翻转触控笔即可擦除”的行为接近原生书写习惯,应优先纳入设计。</p> <h3 id="62-压力方向倾斜与距离">6.2 压力、方向、倾斜与距离</h3> <p>高级触控笔输入的核心价值,在于提供比二维坐标更丰富的轴数据。官方文档重点涉及:</p> <ul> <li><code class="language-plaintext highlighter-rouge">AXIS_PRESSURE</code></li> <li><code class="language-plaintext highlighter-rouge">AXIS_ORIENTATION</code></li> <li><code class="language-plaintext highlighter-rouge">AXIS_TILT</code></li> <li><code class="language-plaintext highlighter-rouge">AXIS_DISTANCE</code></li> </ul> <p>这些轴数据应直接参与笔刷建模,而不应仅用于简单线宽映射。较合理的使用方式包括:</p> <ul> <li>使用 <code class="language-plaintext highlighter-rouge">AXIS_PRESSURE</code> 调整粗细、透明度或墨量感</li> <li>使用 <code class="language-plaintext highlighter-rouge">AXIS_ORIENTATION</code> 构建扁头笔、马克笔或书法笔的方向性</li> <li>使用 <code class="language-plaintext highlighter-rouge">AXIS_TILT</code> 实现铅笔侧锋、阴影与扫刷效果</li> <li>使用 <code class="language-plaintext highlighter-rouge">AXIS_DISTANCE</code> 支持悬停预览或落笔前的目标提示</li> </ul> <p>官方同时提醒,压力值虽然通常位于 <code class="language-plaintext highlighter-rouge">0..1</code> 区间,但部分设备可能返回更高数值。因此,在进入笔刷计算前,应先做归一化与边界处理,避免不同硬件上的表现漂移。</p> <h3 id="63-悬停反馈">6.3 悬停反馈</h3> <p>悬停(hover)在手机场景中未必显著,但在平板、折叠屏与 ChromeOS 设备上具有明确价值。其作用不在于“多一个特效”,而在于提升输入前的可预期性。例如:</p> <ul> <li>显示当前笔刷的实际落笔范围</li> <li>高亮即将操作的对象或区域</li> <li>在复杂工具栏或文档页面中给出更细粒度的定位提示</li> </ul> <p>在 Compose 中,文档建议配合 <code class="language-plaintext highlighter-rouge">hoverable</code> 与 <code class="language-plaintext highlighter-rouge">indication</code> 使用。这类反馈虽然不改变笔迹本身,却能够显著提高交互确定性。</p> <h2 id="7-误触取消与输入回滚">7. 误触、取消与输入回滚</h2> <p>高质量手写系统必须将误触处理视为数据一致性问题,而不仅仅是事件过滤问题。官方文档明确指出,手掌误触相关处理涉及:</p> <ul> <li><code class="language-plaintext highlighter-rouge">ACTION_CANCEL</code></li> <li><code class="language-plaintext highlighter-rouge">FLAG_CANCELED</code></li> </ul> <p>其中,<code class="language-plaintext highlighter-rouge">ACTION_CANCEL</code> 可能在导航手势或防手掌误触触发时出现;<code class="language-plaintext highlighter-rouge">FLAG_CANCELED</code> 自 Android 13(API 33)起可用于标识一次抬起事件很可能源于非预期接触。</p> <p>正确的处理方式不是简单忽略后续输入,而是支持对已有笔迹进行回滚。具体而言,系统应保留结构化输入历史,在检测到取消语义后定位对应 pointer 所生成的笔迹,并将其从当前状态中移除,再触发重新渲染。只有采用结构化 stroke 数据而非一次性 Path 累积,误触撤销才具备可实现性。</p> <p>这一点也解释了为什么 Ink API 对 <code class="language-plaintext highlighter-rouge">StrokeInputBatch</code>、几何模型和在途/完成态分层如此重视:它们并非仅服务于渲染,而是服务于后续的状态修正与编辑操作。</p> <h2 id="8-动作预测与低延迟输入分发">8. 动作预测与低延迟输入分发</h2> <p>Compose 文档推荐使用 <code class="language-plaintext highlighter-rouge">androidx.input:input-motionprediction:1.0.0-beta01</code> 中的 <code class="language-plaintext highlighter-rouge">MotionEventPredictor</code> 进行动作预测。其基本思路是:</p> <ol> <li>对真实 <code class="language-plaintext highlighter-rouge">MotionEvent</code> 调用 <code class="language-plaintext highlighter-rouge">record()</code></li> <li>在 <code class="language-plaintext highlighter-rouge">ACTION_MOVE</code> 阶段调用 <code class="language-plaintext highlighter-rouge">predict()</code></li> <li>将预测点仅作为临时可视化结果参与绘制</li> </ol> <p>官方同时明确指出,预测点不能作为最终渲染与持久化数据使用。原因在于预测本质上是估算值,随着真实事件到来,旧预测必须被替换。也就是说,预测机制适合改善主观跟手性,但不应污染最终的笔迹数据模型。</p> <p>与动作预测配套的另一项能力,是 <code class="language-plaintext highlighter-rouge">requestUnbufferedDispatch()</code>。它的作用不是直接提高渲染性能,而是缩短输入在系统队列中被批量缓存的时间。对于手写系统而言,端到端延迟并不只由绘制决定;如果输入事件本身到达应用过晚,再快的前缓冲路径也只能更快地显示“已经过时的输入”。因此,更完整的低延迟链路应同时包含:</p> <ul> <li>低延迟输入分发</li> <li>在途笔迹的快速显示</li> <li>完成态笔迹的稳定提交</li> </ul> <h2 id="9-推荐的工程组合方式">9. 推荐的工程组合方式</h2> <p>综合 <code class="language-plaintext highlighter-rouge">graphics-core</code>、Ink API 与 Compose 文档中的能力,可以得到一套较稳健的工程分层。</p> <h3 id="91-界面层">9.1 界面层</h3> <p>使用 Compose 构建页面结构、工具栏、颜色与笔刷选择器、文档容器以及悬停反馈。这一层的目标是组织交互,而不是承担全部输入解析逻辑。</p> <h3 id="92-输入层">9.2 输入层</h3> <p>通过 <code class="language-plaintext highlighter-rouge">pointerInteropFilter</code> 接入 <code class="language-plaintext highlighter-rouge">MotionEvent</code>,完成以下工作:</p> <ul> <li>区分 <code class="language-plaintext highlighter-rouge">TOOL_TYPE_STYLUS</code> 与 <code class="language-plaintext highlighter-rouge">TOOL_TYPE_ERASER</code></li> <li>读取压力、方向、倾斜、距离等轴数据</li> <li>处理 <code class="language-plaintext highlighter-rouge">ACTION_DOWN / MOVE / UP / CANCEL</code></li> <li>根据需要接入 <code class="language-plaintext highlighter-rouge">MotionEventPredictor</code></li> <li>在落笔阶段调用 <code class="language-plaintext highlighter-rouge">requestUnbufferedDispatch()</code></li> </ul> <h3 id="93-笔迹层">9.3 笔迹层</h3> <p>使用 Ink API 承接实时与完成态笔迹:</p> <ul> <li><code class="language-plaintext highlighter-rouge">InProgressStrokesView</code> 用于在途笔迹显示</li> <li><code class="language-plaintext highlighter-rouge">CanvasStrokeRenderer</code> 或 <code class="language-plaintext highlighter-rouge">ViewStrokeRenderer</code> 用于完成态绘制</li> <li><code class="language-plaintext highlighter-rouge">BrushFamily</code> 用于笔刷建模</li> <li><code class="language-plaintext highlighter-rouge">StrokeInputBatch</code> 用于输入数据存储、回放与同步</li> </ul> <h3 id="94-渲染优化层">9.4 渲染优化层</h3> <p>仅在确有必要时继续下沉到 <code class="language-plaintext highlighter-rouge">graphics-core</code> 层,针对设备性能、显示时延或现有渲染栈进行深度优化,例如:</p> <ul> <li><code class="language-plaintext highlighter-rouge">CanvasFrontBufferedRenderer</code></li> <li><code class="language-plaintext highlighter-rouge">LowLatencyCanvasView</code></li> <li><code class="language-plaintext highlighter-rouge">GLFrontBufferedRenderer</code></li> </ul> <p>这种分层的优点在于,既能充分利用官方已验证的上层抽象,又保留了继续向底层优化的空间。</p> <h2 id="10-结论">10. 结论</h2> <p>Android 手写技术栈当前的变化,不在于单一新 API 的出现,而在于官方能力开始形成清晰的层次结构:<code class="language-plaintext highlighter-rouge">graphics-core</code> 负责低延迟渲染基础设施,Ink API 负责笔迹与笔刷语义,Compose 高级触控笔文档则补足了原始输入处理与工程实践。</p> <p>从实现角度看,前缓冲解决的是“墨迹何时被尽快看到”,Ink API 解决的是“笔迹如何被建模、绘制、存储与编辑”,而高级触控笔能力解决的是“输入信号如何被完整、准确且可回滚地纳入系统”。只有将这三层能力组合起来,Android 手写应用才能同时获得可接受的时延、稳定的数据模型以及接近原生纸笔的交互质量。</p> <p>对于当前准备建设书写、批注或圈选能力的团队而言,这套官方技术栈已经具备较高的采用价值。真正需要关注的重点,已经不是是否“应该上官方方案”,而是如何根据业务场景,在实时显示、笔刷表现、状态管理与设备兼容之间完成合理取舍。</p> <h2 id="参考资料">参考资料</h2> <ul> <li><a href="https://developer.android.com/jetpack/androidx/releases/graphics">AndroidX Graphics Release Notes</a></li> <li><a href="https://developer.android.com/jetpack/androidx/releases/ink">AndroidX Ink Release Notes</a></li> <li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/about-ink-api">About Ink API</a></li> <li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/low-latency-graphics">Low latency graphics</a></li> <li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/ink-api-brush-apis-views">Ink API brush APIs for Views</a></li> <li><a href="https://developer.android.com/develop/ui/views/touch-and-input/stylus-input/ink-api-draw-stroke">Draw a stroke with Ink API</a></li> <li><a href="https://developer.android.com/develop/ui/compose/touch-input/stylus-input/advanced-stylus-features?hl=zh-cn">高级触控笔功能(Jetpack Compose)</a></li> <li><a href="https://android-developers.googleblog.com/2024/10/introducing-ink-api-jetpack-library.html">Introducing Ink API, a new Jetpack library for stylus apps</a></li> </ul>

2026/4/12
阅读更多

Android上的Deep-Link技术调研

<blockquote> <p><em>本文是两年前输出的文章,可能与现在的成熟方案存在部分细节上“代沟”,请诸位选择性阅读,适当参考即可。</em></p> </blockquote> <h3 id="一需求背景">一、需求背景</h3> <p>现在有很多 APP 支持从浏览器或其他地方唤起自家应用,并跳转至特定页面去提高用户黏性。在提高运营手段的背景下,应用也需要迫切提高自身的曝光入口以及活动场景。如果用户可以通过点击短信中的一个活动链接能够直接跳转到目标 APP 的特定页面,那么带来的好处也是不言而喻的。</p> <h3 id="二方案调研">二、方案调研</h3> <p>iOS 系统提供的 universial link 来帮助应用实现这个功能,而至于 Android 可能就比较繁琐了,Android 6.0 之后开始才提供了 <a href="https://developer.android.com/training/app-links/index.html">App Links</a> 来帮助开发者从外部唤起自家应用,但是 6.0 之前一般只能通过 URI Scheme 方式来唤起应用。不同的方案各自都存在对应的兼容性问题,以下是各方案的对比情况:</p> <table> <thead> <tr> <th>技术</th> <th>Universal Link</th> <th>Android App Link</th> <th>URI Scheme</th> <th>Chrome Intent</th> </tr> </thead> <tbody> <tr> <td>平台要求</td> <td>&gt;= iOS 9</td> <td>&gt;= Android 6</td> <td>Chrome 1 &lt; 25, iOS</td> <td>Chrome 1 &gt;= 25</td> </tr> <tr> <td>未安装表现</td> <td>打开 Web 页面</td> <td>打开 Web 页面</td> <td>发生错误</td> <td>可以打开 Web 页面</td> </tr> <tr> <td>能否不发生跳转</td> <td>不能</td> <td>不能</td> <td>能</td> <td>能</td> </tr> <tr> <td>能否去下载页面</td> <td>能</td> <td>能</td> <td>不能</td> <td>能</td> </tr> <tr> <td>iframe 触发</td> <td>不支持</td> <td>不支持</td> <td>Chrome 1 &lt;= 18, iOS &lt; 9</td> <td>不支持</td> </tr> <tr> <td>链接格式 2</td> <td>正常的 URL</td> <td>正常的 URL</td> <td>自定义协议的 URL</td> <td>intent 协议的 URL</td> </tr> </tbody> </table> <ol> <li>本文只针对移动端浏览器,其中 Chrome 表示 Chrome for Android,以及 Android Browser 的对应版本。</li> <li>链接的作用方式有 3 种:用户点击这样的 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 标签;脚本中进行页面重定向;设置 <code class="language-plaintext highlighter-rouge">iframe</code> 的 <code class="language-plaintext highlighter-rouge">src</code>。</li> <li>每种实现方式都有其适用的平台和浏览器,要兼容多数浏览器需要根据 User Agent 应用不同的策略。 这些实现方式的行为也不一致,可能需要配合产品需求才能确定结合哪几种实现方式。</li> </ol> <p>下面来看下各方案的具体实现过程。</p> <h3 id="三uri-scheme-方案">三、URI Scheme 方案</h3> <p>URI Scheme 这种方式比较古老,目前也广泛用于从外部应用打开APP,这种协议由开发者自己定义,有比较丰富的定制性,但唤起时会有一个系统级别弹窗来让用户去选择通过匹配到的 APP 或者浏览器打开。URI 协议格式如下:</p> <p><strong>[scheme]://[host]/[path]?[params]</strong></p> <p>例如:<code class="language-plaintext highlighter-rouge">http://www.baidu.com/data?page=home&amp;uid=ut9092</code></p> <p>上面的 URI 可以在 Android 应用中通过 intent-filter 去识别和处理:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 </pre></td><td class="rouge-code"><pre><span class="nt">&lt;intent-filter&gt;</span> <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.VIEW"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.DEFAULT"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.BROWSABLE"</span><span class="nt">/&gt;</span> <span class="nt">&lt;data</span> <span class="na">android:host=</span><span class="s">"www.baidu.com"</span> <span class="na">android:pathPattern=</span><span class="s">"data"</span> <span class="na">android:scheme=</span><span class="s">"http"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/intent-filter&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>下面,我们来通过 URI Scheme 来实现一个简单的小 demo,要求能够通过系统的记事本应用中的链接进入到 APP 的目标页面。首先,我们先来约束一下协议格式内容,如下所示:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>http://com.example.deeplink?path=demo_page&amp;text=im_from_system_notebook </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,我们定义的 scheme 为 http,而 host 对应的 <code class="language-plaintext highlighter-rouge">com.example.deeplink</code>,这里我设置的是应用的包名,一般不会更改。至于 <strong>path</strong> 和 <strong>text</strong> 就很显然是作为参数内容了,这里分别代表着页面路径参数和对应的页面内容参数,方便我们定制不同的页面逻辑,大家可以根据情况制定自己的规则。好的,接下来开始我们的第二步 — 在 demo 项目中额外创建一个页面(<em>EvokeDemoActivity</em>)并添加相应的 intent 匹配逻辑:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;activity</span> <span class="na">android:name=</span><span class="s">".deeplink.EvokeDemoActivity"</span><span class="nt">&gt;</span> <span class="nt">&lt;intent-filter&gt;</span> <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.VIEW"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.DEFAULT"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.BROWSABLE"</span><span class="nt">/&gt;</span> <span class="nt">&lt;data</span> <span class="na">android:scheme=</span><span class="s">"http"</span> <span class="na">android:host=</span><span class="s">"com.example.deeplink"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/intent-filter&gt;</span> <span class="nt">&lt;/activity&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>接下来,只需要在目标唤醒页面获取对应的参数内容即可:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Deep link 唤起的目标页面 */</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">EvokeDemoActivity</span> <span class="kd">extends</span> <span class="nc">AppCompatActivity</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">EVOKE_DEMO_PAGE</span> <span class="o">=</span> <span class="s">"demo_page"</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">TextView</span> <span class="n">textView</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span> <span class="n">setContentView</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">activity_evoke_demo</span><span class="o">);</span> <span class="n">textView</span> <span class="o">=</span> <span class="n">findViewById</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">textView</span><span class="o">);</span> <span class="n">obtainDeepLink</span><span class="o">();</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">obtainDeepLink</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Intent</span> <span class="n">intent</span> <span class="o">=</span> <span class="n">getIntent</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">intent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">intent</span><span class="o">.</span><span class="na">getData</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">intent</span><span class="o">.</span><span class="na">getData</span><span class="o">().</span><span class="na">getQueryParameter</span><span class="o">(</span><span class="s">"path"</span><span class="o">).</span><span class="na">equals</span><span class="o">(</span><span class="no">EVOKE_DEMO_PAGE</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="s">"DemoPage"</span><span class="o">,</span> <span class="s">"received deep link intent: "</span> <span class="o">+</span> <span class="n">intent</span><span class="o">.</span><span class="na">getData</span><span class="o">().</span><span class="na">toString</span><span class="o">());</span> <span class="nc">String</span> <span class="n">paramContent</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getData</span><span class="o">().</span><span class="na">getQueryParameter</span><span class="o">(</span><span class="s">"text"</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="nc">TextUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">paramContent</span><span class="o">))</span> <span class="o">{</span> <span class="n">textView</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="n">paramContent</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>接着运行一下 demo,然后在系统记事本中输入测试链接,如: <code class="language-plaintext highlighter-rouge">[http://com.example.deeplink?path=demo_page&amp;text=im_from_system_notebook](http://com.example.deeplink?path=demo_page&amp;text=im_from_system_notebook)</code> 保存后点击查看,可以看到如下效果:</p> <p><img src="/img/in-post/post-android/1613976376040-f5705a01-6536-4604-8c49-5839700f823d.png" alt="Kapture 2021-02-22 at 14.45.04.gif" /></p> <p>可以发现,我们在记事本里点击链接后弹出了系统弹窗让我们选择打开链接的应用,需要选择我们自己的 APP 来打开(示例这边是 Android samples 应用),然后成功进入了目标 app 的制定页面并拿到了携带的参数内容。</p> <h3 id="四app-links-方案">四、App links 方案</h3> <p>与 iOS 中的 universial links 方案类似,Android 在 6.0 推出了 App links,用标准的 Web 页面 URL,同时绑定对应的 App。可以支持从白名单配置中的链接直接唤起 App,而不需要额外弹窗提示,对于用户来说会降低困扰,提升用户体验度。 首先,我们需要生成一份配置文件放到服务端,点击 Android studio 的 Tools &gt; App Links Assistant,进入配置页面后第一步点击 Open URL Mapping Editor,添加一个 URL: <img src="/img/in-post/post-android/1613993915191-1ded7dcf-1228-443c-b530-eb6f59eb01fb.png" alt="add_url" /> 然后在 Android manifest 文件中修改一下配置:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 </pre></td><td class="rouge-code"><pre><span class="nt">&lt;activity</span> <span class="na">android:name=</span><span class="s">".deeplink.EvokeDemoActivity"</span><span class="nt">&gt;</span> <span class="nt">&lt;intent-filter</span> <span class="na">android:autoVerify=</span><span class="s">"true"</span><span class="nt">&gt;</span> <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.VIEW"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.DEFAULT"</span><span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.BROWSABLE"</span><span class="nt">/&gt;</span> <span class="nt">&lt;data</span> <span class="na">android:scheme=</span><span class="s">"http"</span> <span class="na">android:host=</span><span class="s">"com.example.deeplink"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/intent-filter&gt;</span> <span class="nt">&lt;/activity&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,这里我们只是添加了一个 autoVerify 属性。接着通过点击 <strong>Open Digital Asset Links File Generator **来生成配置的 json 文件: <img src="/img/in-post/post-android/1613994492217-c518d1d8-aca1-44f2-ab3a-9e763871e049.png" alt="config" /> **具体可参考官方文档的配置过程:</strong><a href="https://developer.android.com/studio/write/app-link-indexing?utm_source=android-studio#associatesite">https://developer.android.com/studio/write/app-link-indexing?utm_source=android-studio#associatesite</a> 最终配置文件上传到服务器并测试通过后,就可以同样打开上面的测试链接,发现直接可以唤起我们的 APP,而不需要经过弹窗拦截。 此外,我们除了可以通过Android studio提供的 tools 来验证 app link 配置的正确性以外,也可以直接通过以下 adb 命令来验证:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>adb shell dumpsys [package_name] domain-preferred-apps </pre></td></tr></tbody></table></code></pre></div></div> <p>如果显示如下信息,则校验成功:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre>Package: com.xxx.example Domains: example.applink.xxx.com Status: always : 200000036 </pre></td></tr></tbody></table></code></pre></div></div> <p>另外,需要注意的是:想要该配置关联生效,APP 在安装时必须联网。</p> <h3 id="五测试-app-links-唤起方式">五、测试 App Links 唤起方式</h3> <p>当您 App Links 所有的环节已经配置完成后,可以利用测试设备测试唤起效果,测试步骤如下: 1、创建一条用于测试的 DeepLink ; 2、在测试设备中原生场景下进行测试,如:短信、备忘录等等; (不要直接在第三方软件中进行测试,例如微信。第三方软件通常对 DeepLink 跳转存在限制) 3、点击测试 DeepLink,如果可以直接跳转到 App 中,说明可以直接唤起 App,为最理想流程。 4、如果未直接跳转到 App 中,而是跳入下载引导中间页,并且系统弹窗询问是否要在应用中打开,此时可以通过上面的 <code class="language-plaintext highlighter-rouge">adb shell dumpsys [package_name] domain-preferred-apps</code> 命令 中的 Status 查看状态,如果状态显示为“ask”,并且确认 App Links 集成流程正确无误,则可能是当前测试设备机型在 AppLinks 遇到校验问题,对于该情况请参考下方常见问题说明。 对于 Status 状态的说明:</p> <table> <thead> <tr> <th>Status 状态</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>ask</td> <td>Applink校验失败, 每次打开连接跳转时会弹出一个对话框, 提示选择打开短链的App</td> </tr> <tr> <td>always</td> <td>校验成功,理想状态</td> </tr> <tr> <td>never</td> <td>用户选择不再打开</td> </tr> <tr> <td>always-ask</td> <td>可忽略,尚未发现这一个出现, 跟 never 一样需要手动干预才会出现</td> </tr> <tr> <td>undefined</td> <td>尚未校验完成, 请稍后再试</td> </tr> </tbody> </table> <p><strong>常见问题:</strong> <strong>1、多数客户可能会在验证环节得到的 Status 为 ask,这是为什么呢?</strong> AppLinks 的合法性是由系统校验,不同的手机系统使用不同的校验组件,即使是一个厂商的不同型号手机都可能使用不同的校验组件。 如果系统使用 com.android.statementservice 进行 AppLinks 的校验,在网络正常的情况下基本都能顺利通过, 如果系统使用 com.google.android.gms 组件校验,在手机能够科学上网的情况,也就是能够正常访问 Google 时,校验才能通过。常见华为 mate 系列,P 系列使用的都是 gms,也就是 Status 会为 ask。 查看自己手机是使用哪种组件,在命令行中输入以下命令: adb shell dumpsys package i 综上,Applink 不能顺利通过系统检验,原因有以下可能:</p> <ul> <li>可能是国内网络问题,使用 gms 组件校验的手机需要联通 Google 服务</li> <li>可能是您产品配置问题,GIO 填写的签名和手机上运行的 APP 签名不同</li> </ul> <h3 id="六在浏览器唤起-app-的场景">六、在浏览器唤起 APP 的场景</h3> <p>可是,重点来了,我们在实际测试过程中发现了一堆兼容性问题,在说明这些兼容性问题前,我们先解释一个概念:<code class="language-plaintext highlighter-rouge">intent://</code> 协议。 Android Chrome 25+ 后已经不支持自定义 <code class="language-plaintext highlighter-rouge">scheme</code> 的方式,只支持 <code class="language-plaintext highlighter-rouge">intent://</code> 协议(<a href="https://developer.chrome.com/multidevice/android/intents">Android Intents with Chrome</a>),最终要的是需要用户手动进行 <strong>点击</strong> 才能跳转,举个例子:</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end"</span><span class="nt">&gt;</span> Take a QR code <span class="nt">&lt;/a&gt;</span> <span class="nt">&lt;iframe</span> <span class="na">href=</span><span class="s">"intent://xxx"</span> <span class="na">style=</span><span class="s">"display: none;"</span><span class="nt">&gt;</span>&gt;<span class="nt">&lt;/iframe&gt;</span>// 失效 <span class="nt">&lt;iframe</span> <span class="na">href=</span><span class="s">"strange://xxx"</span> <span class="na">style=</span><span class="s">"display: none;"</span><span class="nt">&gt;</span>&gt;<span class="nt">&lt;/iframe&gt;</span>// 失效 </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">intent://</code> 协议格式说明:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 </pre></td><td class="rouge-code"><pre>intent: //scan/ #Intent; package=com.google.zxing.client.android; scheme=zxing; end; </pre></td></tr></tbody></table></code></pre></div></div> <p>目前市面上大多第三方浏览器都是基于 Chrome 开发,这就带来了兼容性问题(没有条件覆盖所有的系统浏览器,这里只是有限测试的结果): <img src="/img/in-post/post-android/1613995861766-a0a7aae1-7d24-46ea-9150-91e05218433a.png" alt="browsers" /></p> <p>1、部分浏览器,只支持 <code class="language-plaintext highlighter-rouge">intent://</code> 协议 <strong>手动</strong> 唤起,如chrome、锤子。 2、部分浏览器只支持 <code class="language-plaintext highlighter-rouge">scheme</code> 唤起,如 UC 浏览器。 3、大部分浏览器,同时支持 <code class="language-plaintext highlighter-rouge">scheme</code> 私有协议和 <code class="language-plaintext highlighter-rouge">intent://</code> 协议 <strong>自动</strong> 唤起。但,都没有按标准的 <code class="language-plaintext highlighter-rouge">intent://</code> 协议来实现(除了 360 浏览器,给 360 点个赞):</p> <ul> <li>有的浏览器在 App 没安装时并没有执行 <code class="language-plaintext highlighter-rouge">S.browser_fallback_url</code>,而是跳转到应用市场如猎豹浏览器 4.46.3、乐视浏览器 1.2.1.29。</li> <li>有的浏览器不支持 <code class="language-plaintext highlighter-rouge">S.browser_fallback_url</code> 如搜狗浏览器、欧朋浏览器、猎豹浏览器。</li> <li>有的浏览器无论应用有无安装 <code class="language-plaintext highlighter-rouge">S.browser_fallback_url</code> 一直都会执行如 QQ 浏览器。</li> </ul> <p>所以对这部分浏览器,不能使用 <code class="language-plaintext highlighter-rouge">intent://</code> 协议。 4、更奇葩者,二者都不支持,如百度浏览器。</p> <h5 id="兼容性问题解决方案">兼容性问题解决方案</h5> <p>针对上述三个兼容性问题,第 4 种情况无解我们直接忽略,第 2 第 3 种情况只能用自定义 scheme 的方式。 问题出在第 1 种情况,因为只能手动唤起,我们需要对浏览器类型进行判断(浏览器没有提供是否支持 <code class="language-plaintext highlighter-rouge">自定义 scheme</code>、<code class="language-plaintext highlighter-rouge">intent://</code> 的 API 只能通过 UA 判断),结合我们有限的测试结果,如果是锤子、Chrome 原生浏览器,需在页面中内置一个“下载应用”的按钮引导用户点击。</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 </pre></td><td class="rouge-code"><pre> // 如果安装了 App 就跳转,否则就访问 S.browser_fallback_url 的值 http://strange.com <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"intent://strange/#Intent;scheme=strange://login;package=com.strange;S.browser_fallback_url=http%3A%2F%2Fstrange.com;end"</span><span class="nt">&gt;</span>跳转到活动页/下载<span class="nt">&lt;/a&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>我们来分析一下浏览器的 UA ,举几个例子:</p> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 </pre></td><td class="rouge-code"><pre>//小米系统浏览器 User-Agent:Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.9.5 // 小米 Chrome 原生浏览器 User-Agent:Mozilla/5.0 (Linux; Android 6.0.1; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.108 Mobile Safari/537.36 // 锤子系统浏览器 User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.11 Mobile Safari/537.36 // 锤子 Chrome 原生浏览器 User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.89 Mobile Safari/537.36 </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看出小米浏览器是在 Chrome 原生浏览器的 UA 上增加了 <code class="language-plaintext highlighter-rouge">XiaoMi/MiuiBrowser/8.9.5</code> 这部分特征码。类似的,很多第三方浏览器都是在 Chrome 基础上增加自己的特征码,换句话说 Chrome 原生浏览器 UA 没有自己的特征。 而锤子系统浏览器和 Chrome 原生浏览器 UA 几乎一样,这就使得<code class="language-plaintext highlighter-rouge">判断是否锤子系统浏览器、 Chrome 原生浏览器</code> 变得异常困难,要想尽可能完美解决问题只能使用排除法。 排除法由于不可能排除所有非 Chrome 原生浏览器,可能会存在误伤的可能。</p> <h5 id="经浏览器中转唤起-app-总结">经浏览器中转唤起 App 总结</h5> <p>要实现经浏览器中转 <strong>自动</strong> 唤起 App,Android 和 iOS 都可以通过 <code class="language-plaintext highlighter-rouge">自定义 scheme</code> 的方式,但 Android 的情况稍显复杂,因为部分浏览器并不支持,必须换成 <code class="language-plaintext highlighter-rouge">intent://</code> 协议的方式 <strong>手动</strong> 唤起。 考虑到浏览器判断的难度,结合浏览器市场占有率的情况,我们最终的方案是暂时忽略 <code class="language-plaintext highlighter-rouge">锤子系统浏览器、 Chrome 原生浏览器</code> 这部分不支持 <code class="language-plaintext highlighter-rouge">自定义 scheme 自动唤起 App</code> 的用户。</p> <h5 id="app-links兼容性问题">App Links兼容性问题</h5> <ul> <li>App links在国内的支持还不够,部分安卓浏览器并不支持跳转至App,而是直接在浏览器上打开对应页面(直接将Applink作为链接跳转)。</li> <li>系统询问是否打开对应App时,假如用户选择“取消”并且选中了“记住此操作”,那么用户以后就无法再跳转 App。</li> </ul> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"https://tuyasmart.applink.smart321.com/triple/fromalexa?client_id=xxx&amp;response_type=alexa&amp;state=1&amp;scope=uejdj&amp;redirect_uri=https.baidu.com"</span><span class="nt">&gt;</span>通过applink唤起<span class="nt">&lt;/a&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"intent://applink/deviceshare/#Intent;scheme=tuyasmart;package=com.tuya.smart;S.browser_fallback_url=https://www.zhihu.com/question/270839820;end"</span><span class="nt">&gt;</span>通过 chrome Intent 唤起<span class="nt">&lt;/a&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"tuyasmart://com.tuya.smart/applink/deviceshare?short_code=124141"</span><span class="nt">&gt;</span>通过scheme唤起<span class="nt">&lt;/a&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>经过自己简单部署的前端页面发现,在 App link、chrome intent 以及 scheme 三种唤起方式在不同机型和浏览器表现上存在较大差异,以下是几种机型和浏览器简单的对比(并不全):</p> <table> <thead> <tr> <th> </th> <th>App link(Android 6.0)</th> <th>chrome intent</th> <th>scheme</th> </tr> </thead> <tbody> <tr> <td>Pixel 2 XL(chrome)</td> <td>成功且无弹窗</td> <td>成功且无弹窗</td> <td>成功且无弹窗</td> </tr> <tr> <td>一加7t(夸克)</td> <td>失败</td> <td>成功,唤起前会有确认弹窗</td> <td>成功,唤起前会有确认弹窗</td> </tr> <tr> <td>三星GT-19500(三星浏览器 6.0 以下)</td> <td>失败</td> <td>成功且无弹窗</td> <td>成功且无弹窗</td> </tr> <tr> <td>三星GT-19500(chrome浏览器 6.0 以下)</td> <td>成功,但有选择弹窗</td> <td>成功且无弹窗</td> <td>成功且无弹窗</td> </tr> </tbody> </table> <p><strong>虽然安卓还支持 APP Links 技术,但是这个技术依赖于谷歌服务器的验证,而谷歌服务器是被墙的,所以是在国内基本不能用。所以在安卓端能用的技术只有scheme技术了)可是大部分 App 的 URL Scheme 方式都被微信QQ封锁了,不过微信QQ有维护着一个白名单,如果你的域名在白名单内,那这个域名下所有的页面发起的 URL Scheme 就都会被允许。</strong></p> <h3 id="七项目中应用现状">七、项目中应用现状</h3> <p>目前内置项目中已经部分模块实现了从外部网页直接唤起进入 APP 内部的功能,主要还是借助于传统 Deep link 的方式实现的:</p> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 </pre></td><td class="rouge-code"><pre> <span class="nt">&lt;activity</span> <span class="na">android:name=</span><span class="s">"com.example.activity.ThirdAuthActivity"</span> <span class="na">android:exported=</span><span class="s">"@ref/0x7f05000d"</span> <span class="na">android:launchMode=</span><span class="s">"2"</span> <span class="na">android:screenOrientation=</span><span class="s">"1"</span> <span class="na">android:configChanges=</span><span class="s">"0x4a0"</span><span class="nt">&gt;</span> <span class="nt">&lt;intent-filter</span> <span class="na">android:autoVerify=</span><span class="s">"true"</span><span class="nt">&gt;</span> <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.VIEW"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.DEFAULT"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.BROWSABLE"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;data</span> <span class="na">android:scheme=</span><span class="s">"https"</span> <span class="na">android:host=</span><span class="s">"xyz.com"</span> <span class="na">android:path=</span><span class="s">"/action/share"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/intent-filter&gt;</span> <span class="nt">&lt;/activity&gt;</span> <span class="nt">&lt;activity</span> <span class="na">android:name=</span><span class="s">"com.example.activity.ThirdLinkActivity"</span> <span class="na">android:screenOrientation=</span><span class="s">"1"</span> <span class="na">android:configChanges=</span><span class="s">"0x4a0"</span><span class="nt">&gt;</span> <span class="nt">&lt;intent-filter</span> <span class="na">android:autoVerify=</span><span class="s">"true"</span><span class="nt">&gt;</span> <span class="nt">&lt;action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.VIEW"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.DEFAULT"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.BROWSABLE"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;data</span> <span class="na">android:scheme=</span><span class="s">"https"</span> <span class="na">android:host=</span><span class="s">"xyz123.com"</span> <span class="na">android:path=</span><span class="s">"/action/share"</span> <span class="nt">/&gt;</span> <span class="nt">&lt;/intent-filter&gt;</span> <span class="nt">&lt;/activity&gt;</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>经过与产品确认,目前只考虑 Android 6.0及以上的情况统一使用 AppLinks 来从外部网页唤起应用。然而,针对于国内浏览器环境等综合因素及 Android6.0 以下的情况,还是使用传统 DeepLinks 方式兜底。</p> <h3 id="八理想方案">八、理想方案</h3> <p>综合起来就是:</p> <ul> <li>通过 App Links(iOS 则是Universal Links),可以实现点击短信链接直接唤起 App;</li> <li>如果系统因为各种原因不支持 App Links,备选方案是 <code class="language-plaintext highlighter-rouge">URI Scheme</code>,不过会出弹框让用户选择用哪个 App 打开链接;</li> <li>如果用户没有选择我们的 App 而是选择了浏览器打开,则通过 <code class="language-plaintext highlighter-rouge">URI scheme</code> 尝试唤起 App;</li> <li>由于技术和成本问题,我们忽略不支持 URI Scheme 的浏览器。</li> </ul> <p>如下图所示:</p> <p><img src="/img/in-post/post-android/1613995927345-daa104ec-88c1-43db-9bc9-c06526da922c.png" alt="perfect_flow" /> 网页能否判断当前运行的浏览器环境,如果是 chrome 浏览器(或内置chrome内核),则通过 Applink 唤起应用页面,如果其他类型的浏览器能否判断是否支持 <code class="language-plaintext highlighter-rouge">intent://</code> 唤起,否则都通过原生 scheme 来兜底,至于 Android 6.0 以下机型默认执行 scheme 唤起,这也是 App link 在低版本机型上的默认兜底方式。</p> <h3 id="相关参考">相关参考</h3> <ul> <li><a href="https://sq.163yun.com/blog/article/172802377606160384">一切为了运营!如何从推广短信链接唤起 App?</a></li> <li><a href="https://www.zhihu.com/question/34846193">知乎讨论:如何用手机短信唤起app</a></li> <li><a href="https://developer.android.com/studio/write/app-link-indexing.html">Android官方文档:App Links 使用</a></li> <li><a href="https://developer.android.com/training/app-links/index.html">Android官方:处理Android应用链接</a></li> <li><a href="https://blog.csdn.net/u014626094/article/details/79501298">通过 Url 链接打开 APP</a></li> <li><a href="https://harttle.land/2017/12/24/launch-app-from-browser.html">Deep Linking:从浏览器调起 APP</a></li> <li><a href="https://developer.android.com/training/app-links/verify-site-associations">Android 文档:APP links 验证</a></li> <li><a href="https://growingio.gitbook.io/docs/product-manual/growing/ads/advance/deeplink">Growingio 专注用户增长 sdk 文档</a></li> <li><a href="https://cloud.tencent.com/developer/article/1558422">说说 Applink 的使用以及原理</a></li> <li><a href="https://juejin.cn/post/6844903494760349703">https://juejin.cn/post/6844903494760349703</a></li> <li><a href="https://segmentfault.com/a/1190000038308634">几种在浏览器网页唤起APP的方案</a></li> <li><a href="https://github.com/suanmei/callapp-lib/issues/26">网页唤起APP开源库推荐</a></li> <li><a href="https://developer.umeng.com/docs/191212/detail/194585">https://developer.umeng.com/docs/191212/detail/194585</a></li> <li><a href="https://segmentfault.com/a/1190000039744899">Web 前端几种常见的本地部署方式</a></li> </ul>

2023/9/16
阅读更多

Launcher进程启动过程剖析

<p>上一篇<a href="https://dorck.cn/2023/04/22/android-initialization/">文章</a>我们对 Android 系统启动过程做了一定了解,本文将继续分析 Launcher 进程的启动流程。</p> <blockquote> <p>本文基于 Android 13 最新代码来进行源码分析。</p> </blockquote> <h3 id="launcher-进程启动">Launcher 进程启动</h3> <p>上文中,我们最终谈到了 <code class="language-plaintext highlighter-rouge">system_server</code> 进程的创建过程,并且该进程主要负责:</p> <ul> <li>创建系统服务管理器——SystemServiceManager</li> <li>启动各种服务(AMS、PMS等)</li> <li>开启 Looper 循环,等待其他线程发送消息并处理</li> </ul> <p>我们看一下 AMS 服务创建及配置这块实现:</p> <blockquote> <p><em>frameworks/base/services/java/com/android/server/SystemServer.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 </pre></td><td class="rouge-code"><pre><span class="c1">// Activity manager runs the show.</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"StartActivityManager"</span><span class="o">);</span> <span class="c1">// TODO: Might need to move after migration to WM.</span> <span class="nc">ActivityTaskManagerService</span> <span class="n">atm</span> <span class="o">=</span> <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startService</span><span class="o">(</span> <span class="nc">ActivityTaskManagerService</span><span class="o">.</span><span class="na">Lifecycle</span><span class="o">.</span><span class="na">class</span><span class="o">).</span><span class="na">getService</span><span class="o">();</span> <span class="n">mActivityManagerService</span> <span class="o">=</span> <span class="nc">ActivityManagerService</span><span class="o">.</span><span class="na">Lifecycle</span><span class="o">.</span><span class="na">startService</span><span class="o">(</span> <span class="n">mSystemServiceManager</span><span class="o">,</span> <span class="n">atm</span><span class="o">);</span> <span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">setSystemServiceManager</span><span class="o">(</span><span class="n">mSystemServiceManager</span><span class="o">);</span> <span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">setInstaller</span><span class="o">(</span><span class="n">installer</span><span class="o">);</span> <span class="o">......</span> <span class="c1">// We now tell the activity manager it is okay to run third party</span> <span class="c1">// code. It will call back into us once it has gotten to the state</span> <span class="c1">// where third party code can really run (but before it has actually</span> <span class="c1">// started launching the initial applications), for us to complete our</span> <span class="c1">// initialization.</span> <span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">systemReady</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Making services ready"</span><span class="o">);</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"StartActivityManagerReadyPhase"</span><span class="o">);</span> <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startBootPhase</span><span class="o">(</span><span class="n">t</span><span class="o">,</span> <span class="nc">SystemService</span><span class="o">.</span><span class="na">PHASE_ACTIVITY_MANAGER_READY</span><span class="o">);</span> <span class="n">t</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">();</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"StartObservingNativeCrashes"</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">startObservingNativeCrashes</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">reportWtf</span><span class="o">(</span><span class="s">"observing native crashes"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="o">......</span> <span class="o">},</span> <span class="n">t</span><span class="o">);</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>下面重点关注一下 <code class="language-plaintext highlighter-rouge">ActivityManagerService.systemReady</code> 方法,通过注释可以简单了解到 Launcher 应用貌似就是在这里启动的,来找一下代码实现:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="nf">ActivityManagerService</span><span class="o">(</span><span class="nc">Context</span> <span class="n">systemContext</span><span class="o">,</span> <span class="nc">ActivityTaskManagerService</span> <span class="n">atm</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="n">mAtmInternal</span> <span class="o">=</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="nc">ActivityTaskManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> <span class="cm">/** * Ready. Set. Go! */</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">systemReady</span><span class="o">(</span><span class="kd">final</span> <span class="nc">Runnable</span> <span class="n">goingCallback</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">TimingsTraceAndSlog</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"PhaseActivityManagerReady"</span><span class="o">);</span> <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">preSystemReady</span><span class="o">();</span> <span class="o">...</span> <span class="c1">// Some systems - like automotive - will explicitly unlock system user then switch</span> <span class="c1">// to a secondary user.</span> <span class="c1">// TODO(b/242195409): this workaround shouldn't be necessary once we move</span> <span class="c1">// the headless-user start logic to UserManager-land.</span> <span class="k">if</span> <span class="o">(</span><span class="n">isBootingSystemUser</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nc">UserManager</span><span class="o">.</span><span class="na">isHeadlessSystemUserMode</span><span class="o">())</span> <span class="o">{</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"startHomeOnAllDisplays"</span><span class="o">);</span> <span class="n">mAtmInternal</span><span class="o">.</span><span class="na">startHomeOnAllDisplays</span><span class="o">(</span><span class="n">currentUserId</span><span class="o">,</span> <span class="s">"systemReady"</span><span class="o">);</span> <span class="n">t</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>启动 Launcher 应用是在 <code class="language-plaintext highlighter-rouge">ActivityTaskManagerInternal#startHomeOnAllDisplays</code> 方法中,我们需要找一下 ActivityTaskManagerInternal 的实现类,但发现它是通过 <code class="language-plaintext highlighter-rouge">LocalServices.getService</code> 方法获取的:</p> <blockquote> <p><em>frameworks/base/core/java/com/android/server/LocalServices.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">LocalServices</span> <span class="o">{</span> <span class="kd">private</span> <span class="nf">LocalServices</span><span class="o">()</span> <span class="o">{}</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ArrayMap</span><span class="o">&lt;</span><span class="nc">Class</span><span class="o">&lt;?&gt;,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">sLocalServiceObjects</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayMap</span><span class="o">&lt;</span><span class="nc">Class</span><span class="o">&lt;?&gt;,</span> <span class="nc">Object</span><span class="o">&gt;();</span> <span class="cm">/** * Returns a local service instance that implements the specified interface. * * @param type The type of service. * @return The service object. */</span> <span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span> <span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">getService</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">sLocalServiceObjects</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="no">T</span><span class="o">)</span> <span class="n">sLocalServiceObjects</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">type</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="cm">/** * Adds a service instance of the specified interface to the global registry of local services. */</span> <span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">addService</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">,</span> <span class="no">T</span> <span class="n">service</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">sLocalServiceObjects</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">sLocalServiceObjects</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">type</span><span class="o">))</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Overriding service registration"</span><span class="o">);</span> <span class="o">}</span> <span class="n">sLocalServiceObjects</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">type</span><span class="o">,</span> <span class="n">service</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="cm">/** * Remove a service instance, must be only used in tests. */</span> <span class="nd">@VisibleForTesting</span> <span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">removeServiceForTest</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">sLocalServiceObjects</span><span class="o">)</span> <span class="o">{</span> <span class="n">sLocalServiceObjects</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">type</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>不难发现,LocalServices 其实就是个容器类,用于存储各项服务,既然可以通过 getService 获取服务实现,那么添加服务应该就是通过 addService 实现的,我们查找一下调用该方法的地方,并指定 class 为 <code class="language-plaintext highlighter-rouge">ActivityTaskManagerInternal</code>,最终发现实现层在 <code class="language-plaintext highlighter-rouge">ActivityTaskManagerService#LocalService</code> 中:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ActivityTaskManagerService</span> <span class="kd">extends</span> <span class="nc">IActivityTaskManager</span><span class="o">.</span><span class="na">Stub</span> <span class="o">{</span> <span class="kd">public</span> <span class="nf">ActivityTaskManagerService</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span> <span class="n">mContext</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span> <span class="o">...</span> <span class="n">mInternal</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LocalService</span><span class="o">();</span> <span class="o">...</span> <span class="o">}</span> <span class="o">...</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">start</span><span class="o">()</span> <span class="o">{</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="nc">ActivityTaskManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">mInternal</span><span class="o">);</span> <span class="o">}</span> <span class="o">...</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">LocalService</span> <span class="kd">extends</span> <span class="nc">ActivityTaskManagerInternal</span> <span class="o">{</span> <span class="o">...</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">startHomeOnAllDisplays</span><span class="o">(</span><span class="kt">int</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mGlobalLock</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">startHomeOnAllDisplays</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续追溯到 <code class="language-plaintext highlighter-rouge">WindowManagerService#mRoot</code> 并查找实现:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 </pre></td><td class="rouge-code"><pre> <span class="kt">boolean</span> <span class="nf">startHomeOnDisplay</span><span class="o">(</span><span class="kt">int</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">,</span> <span class="kt">int</span> <span class="n">displayId</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowInstrumenting</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">fromHomeKey</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Fallback to top focused display or default display if the displayId is invalid.</span> <span class="k">if</span> <span class="o">(</span><span class="n">displayId</span> <span class="o">==</span> <span class="no">INVALID_DISPLAY</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">rootTask</span> <span class="o">=</span> <span class="n">getTopDisplayFocusedRootTask</span><span class="o">();</span> <span class="n">displayId</span> <span class="o">=</span> <span class="n">rootTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">getDisplayId</span><span class="o">()</span> <span class="o">:</span> <span class="no">DEFAULT_DISPLAY</span><span class="o">;</span> <span class="o">}</span> <span class="kd">final</span> <span class="nc">DisplayContent</span> <span class="n">display</span> <span class="o">=</span> <span class="n">getDisplayContent</span><span class="o">(</span><span class="n">displayId</span><span class="o">);</span> <span class="k">return</span> <span class="n">display</span><span class="o">.</span><span class="na">reduceOnAllTaskDisplayAreas</span><span class="o">((</span><span class="n">taskDisplayArea</span><span class="o">,</span> <span class="n">result</span><span class="o">)</span> <span class="o">-&gt;</span> <span class="n">result</span> <span class="o">|</span> <span class="n">startHomeOnTaskDisplayArea</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">reason</span><span class="o">,</span> <span class="n">taskDisplayArea</span><span class="o">,</span> <span class="n">allowInstrumenting</span><span class="o">,</span> <span class="n">fromHomeKey</span><span class="o">),</span> <span class="kc">false</span> <span class="cm">/* initValue */</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续再跟进到该类的 <code class="language-plaintext highlighter-rouge">startHomeOnTaskDisplayArea</code> 中:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 </pre></td><td class="rouge-code"><pre><span class="cm">/** * This starts home activity on display areas that can have system decorations based on * displayId - default display area always uses primary home component. * For secondary display areas, the home activity must have category SECONDARY_HOME and then * resolves according to the priorities listed below. * - If default home is not set, always use the secondary home defined in the config. * - Use currently selected primary home activity. * - Use the activity in the same package as currently selected primary home activity. * If there are multiple activities matched, use first one. * - Use the secondary home defined in the config. */</span> <span class="kt">boolean</span> <span class="nf">startHomeOnTaskDisplayArea</span><span class="o">(</span><span class="kt">int</span> <span class="n">userId</span><span class="o">,</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">,</span> <span class="nc">TaskDisplayArea</span> <span class="n">taskDisplayArea</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowInstrumenting</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">fromHomeKey</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Fallback to top focused display area if the provided one is invalid.</span> <span class="k">if</span> <span class="o">(</span><span class="n">taskDisplayArea</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">rootTask</span> <span class="o">=</span> <span class="n">getTopDisplayFocusedRootTask</span><span class="o">();</span> <span class="n">taskDisplayArea</span> <span class="o">=</span> <span class="n">rootTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">getDisplayArea</span><span class="o">()</span> <span class="o">:</span> <span class="n">getDefaultTaskDisplayArea</span><span class="o">();</span> <span class="o">}</span> <span class="nc">Intent</span> <span class="n">homeIntent</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="nc">ActivityInfo</span> <span class="n">aInfo</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">taskDisplayArea</span> <span class="o">==</span> <span class="n">getDefaultTaskDisplayArea</span><span class="o">())</span> <span class="o">{</span> <span class="n">homeIntent</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">getHomeIntent</span><span class="o">();</span> <span class="n">aInfo</span> <span class="o">=</span> <span class="n">resolveHomeActivity</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">homeIntent</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">shouldPlaceSecondaryHomeOnDisplayArea</span><span class="o">(</span><span class="n">taskDisplayArea</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">ActivityInfo</span><span class="o">,</span> <span class="nc">Intent</span><span class="o">&gt;</span> <span class="n">info</span> <span class="o">=</span> <span class="n">resolveSecondaryHomeActivity</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">taskDisplayArea</span><span class="o">);</span> <span class="n">aInfo</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="na">first</span><span class="o">;</span> <span class="n">homeIntent</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="na">second</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">aInfo</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">homeIntent</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(!</span><span class="n">canStartHomeOnDisplayArea</span><span class="o">(</span><span class="n">aInfo</span><span class="o">,</span> <span class="n">taskDisplayArea</span><span class="o">,</span> <span class="n">allowInstrumenting</span><span class="o">))</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// Updates the home component of the intent.</span> <span class="n">homeIntent</span><span class="o">.</span><span class="na">setComponent</span><span class="o">(</span><span class="k">new</span> <span class="nc">ComponentName</span><span class="o">(</span><span class="n">aInfo</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">.</span><span class="na">name</span><span class="o">));</span> <span class="n">homeIntent</span><span class="o">.</span><span class="na">setFlags</span><span class="o">(</span><span class="n">homeIntent</span><span class="o">.</span><span class="na">getFlags</span><span class="o">()</span> <span class="o">|</span> <span class="no">FLAG_ACTIVITY_NEW_TASK</span><span class="o">);</span> <span class="c1">// Updates the extra information of the intent.</span> <span class="k">if</span> <span class="o">(</span><span class="n">fromHomeKey</span><span class="o">)</span> <span class="o">{</span> <span class="n">homeIntent</span><span class="o">.</span><span class="na">putExtra</span><span class="o">(</span><span class="nc">WindowManagerPolicy</span><span class="o">.</span><span class="na">EXTRA_FROM_HOME_KEY</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">mWindowManager</span><span class="o">.</span><span class="na">getRecentsAnimationController</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">mWindowManager</span><span class="o">.</span><span class="na">getRecentsAnimationController</span><span class="o">().</span><span class="na">cancelAnimationForHomeStart</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="n">homeIntent</span><span class="o">.</span><span class="na">putExtra</span><span class="o">(</span><span class="nc">WindowManagerPolicy</span><span class="o">.</span><span class="na">EXTRA_START_REASON</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span> <span class="c1">// Update the reason for ANR debugging to verify if the user activity is the one that</span> <span class="c1">// actually launched.</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">myReason</span> <span class="o">=</span> <span class="n">reason</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">userId</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="nc">UserHandle</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(</span> <span class="n">aInfo</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">uid</span><span class="o">)</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">taskDisplayArea</span><span class="o">.</span><span class="na">getDisplayId</span><span class="o">();</span> <span class="n">mService</span><span class="o">.</span><span class="na">getActivityStartController</span><span class="o">().</span><span class="na">startHomeActivity</span><span class="o">(</span><span class="n">homeIntent</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">myReason</span><span class="o">,</span> <span class="n">taskDisplayArea</span><span class="o">);</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>定位到这里就知道我们离最终启动 Launcher 进程的地方不远了,因为我们发现了 Intent 的踪迹,这个方法内构建了一个 <code class="language-plaintext highlighter-rouge">homeIntent</code> 用于启动 Launcher 的 Activity。于是继续跟进:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">startHomeActivity</span><span class="o">(</span><span class="nc">Intent</span> <span class="n">intent</span><span class="o">,</span> <span class="nc">ActivityInfo</span> <span class="n">aInfo</span><span class="o">,</span> <span class="nc">String</span> <span class="n">reason</span><span class="o">,</span> <span class="nc">TaskDisplayArea</span> <span class="n">taskDisplayArea</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">ActivityOptions</span> <span class="n">options</span> <span class="o">=</span> <span class="nc">ActivityOptions</span><span class="o">.</span><span class="na">makeBasic</span><span class="o">();</span> <span class="n">options</span><span class="o">.</span><span class="na">setLaunchWindowingMode</span><span class="o">(</span><span class="no">WINDOWING_MODE_FULLSCREEN</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="nc">ActivityRecord</span><span class="o">.</span><span class="na">isResolverActivity</span><span class="o">(</span><span class="n">aInfo</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// The resolver activity shouldn't be put in root home task because when the</span> <span class="c1">// foreground is standard type activity, the resolver activity should be put on the</span> <span class="c1">// top of current foreground instead of bring root home task to front.</span> <span class="n">options</span><span class="o">.</span><span class="na">setLaunchActivityType</span><span class="o">(</span><span class="no">ACTIVITY_TYPE_HOME</span><span class="o">);</span> <span class="o">}</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">displayId</span> <span class="o">=</span> <span class="n">taskDisplayArea</span><span class="o">.</span><span class="na">getDisplayId</span><span class="o">();</span> <span class="n">options</span><span class="o">.</span><span class="na">setLaunchDisplayId</span><span class="o">(</span><span class="n">displayId</span><span class="o">);</span> <span class="n">options</span><span class="o">.</span><span class="na">setLaunchTaskDisplayArea</span><span class="o">(</span><span class="n">taskDisplayArea</span><span class="o">.</span><span class="na">mRemoteToken</span> <span class="o">.</span><span class="na">toWindowContainerToken</span><span class="o">());</span> <span class="c1">// The home activity will be started later, defer resuming to avoid unnecessary operations</span> <span class="c1">// (e.g. start home recursively) when creating root home task.</span> <span class="n">mSupervisor</span><span class="o">.</span><span class="na">beginDeferResume</span><span class="o">();</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">rootHomeTask</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// Make sure root home task exists on display area.</span> <span class="n">rootHomeTask</span> <span class="o">=</span> <span class="n">taskDisplayArea</span><span class="o">.</span><span class="na">getOrCreateRootHomeTask</span><span class="o">(</span><span class="no">ON_TOP</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mSupervisor</span><span class="o">.</span><span class="na">endDeferResume</span><span class="o">();</span> <span class="o">}</span> <span class="n">mLastHomeActivityStartResult</span> <span class="o">=</span> <span class="n">obtainStarter</span><span class="o">(</span><span class="n">intent</span><span class="o">,</span> <span class="s">"startHomeActivity: "</span> <span class="o">+</span> <span class="n">reason</span><span class="o">)</span> <span class="o">.</span><span class="na">setOutActivity</span><span class="o">(</span><span class="n">tmpOutRecord</span><span class="o">)</span> <span class="o">.</span><span class="na">setCallingUid</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span> <span class="o">.</span><span class="na">setActivityInfo</span><span class="o">(</span><span class="n">aInfo</span><span class="o">)</span> <span class="o">.</span><span class="na">setActivityOptions</span><span class="o">(</span><span class="n">options</span><span class="o">.</span><span class="na">toBundle</span><span class="o">())</span> <span class="o">.</span><span class="na">execute</span><span class="o">();</span> <span class="n">mLastHomeActivityStartRecord</span> <span class="o">=</span> <span class="n">tmpOutRecord</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span> <span class="k">if</span> <span class="o">(</span><span class="n">rootHomeTask</span><span class="o">.</span><span class="na">mInResumeTopActivity</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// If we are in resume section already, home activity will be initialized, but not</span> <span class="c1">// resumed (to avoid recursive resume) and will stay that way until something pokes it</span> <span class="c1">// again. We need to schedule another resume.</span> <span class="n">mSupervisor</span><span class="o">.</span><span class="na">scheduleResumeTopActivities</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>接着发现启动 Launcher 是通过 obtainStarter 获取 ActivityStarter 并内部调用 execute 方法来实现的,而这里的 mLastHomeActivityStartResult 则是启动结果。我们具体来看下 ActivityStarter 的 execute 是如何实现的:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Resolve necessary information according the request parameters provided earlier, and execute * the request which begin the journey of starting an activity. * @return The starter result. */</span> <span class="kt">int</span> <span class="nf">execute</span><span class="o">()</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">onExecutionStarted</span><span class="o">();</span> <span class="c1">// Refuse possible leaked file descriptors</span> <span class="k">if</span> <span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">intent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">hasFileDescriptors</span><span class="o">())</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"File descriptors passed in Intent"</span><span class="o">);</span> <span class="o">}</span> <span class="kd">final</span> <span class="nc">LaunchingState</span> <span class="n">launchingState</span><span class="o">;</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">mGlobalLock</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">caller</span> <span class="o">=</span> <span class="nc">ActivityRecord</span><span class="o">.</span><span class="na">forTokenLocked</span><span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">resultTo</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">callingUid</span> <span class="o">=</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">realCallingUid</span> <span class="o">==</span> <span class="nc">Request</span><span class="o">.</span><span class="na">DEFAULT_REAL_CALLING_UID</span> <span class="o">?</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">getCallingUid</span><span class="o">()</span> <span class="o">:</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">realCallingUid</span><span class="o">;</span> <span class="n">launchingState</span> <span class="o">=</span> <span class="n">mSupervisor</span><span class="o">.</span><span class="na">getActivityMetricsLogger</span><span class="o">().</span><span class="na">notifyActivityLaunching</span><span class="o">(</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">intent</span><span class="o">,</span> <span class="n">caller</span><span class="o">,</span> <span class="n">callingUid</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// If the caller hasn't already resolved the activity, we're willing</span> <span class="c1">// to do so here. If the caller is already holding the WM lock here,</span> <span class="c1">// and we need to check dynamic Uri permissions, then we're forced</span> <span class="c1">// to assume those permissions are denied to avoid deadlocking.</span> <span class="k">if</span> <span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">activityInfo</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">resolveActivity</span><span class="o">(</span><span class="n">mSupervisor</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Add checkpoint for this shutdown or reboot attempt, so we can record the original</span> <span class="c1">// intent action and package name.</span> <span class="k">if</span> <span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">intent</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">intentAction</span> <span class="o">=</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">getAction</span><span class="o">();</span> <span class="nc">String</span> <span class="n">callingPackage</span> <span class="o">=</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">callingPackage</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">intentAction</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">callingPackage</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="nc">Intent</span><span class="o">.</span><span class="na">ACTION_REQUEST_SHUTDOWN</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">intentAction</span><span class="o">)</span> <span class="o">||</span> <span class="nc">Intent</span><span class="o">.</span><span class="na">ACTION_SHUTDOWN</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">intentAction</span><span class="o">)</span> <span class="o">||</span> <span class="nc">Intent</span><span class="o">.</span><span class="na">ACTION_REBOOT</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">intentAction</span><span class="o">)))</span> <span class="o">{</span> <span class="nc">ShutdownCheckPoints</span><span class="o">.</span><span class="na">recordCheckPoint</span><span class="o">(</span><span class="n">intentAction</span><span class="o">,</span> <span class="n">callingPackage</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kt">int</span> <span class="n">res</span><span class="o">;</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">mGlobalLock</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">globalConfigWillChange</span> <span class="o">=</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">globalConfig</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mService</span><span class="o">.</span><span class="na">getGlobalConfiguration</span><span class="o">().</span><span class="na">diff</span><span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">globalConfig</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">rootTask</span> <span class="o">=</span> <span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">getTopDisplayFocusedRootTask</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">rootTask</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">mConfigWillChange</span> <span class="o">=</span> <span class="n">globalConfigWillChange</span><span class="o">;</span> <span class="o">}</span> <span class="nc">ProtoLog</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="no">WM_DEBUG_CONFIGURATION</span><span class="o">,</span> <span class="s">"Starting activity when config "</span> <span class="o">+</span> <span class="s">"will change = %b"</span><span class="o">,</span> <span class="n">globalConfigWillChange</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">origId</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">clearCallingIdentity</span><span class="o">();</span> <span class="n">res</span> <span class="o">=</span> <span class="n">resolveToHeavyWeightSwitcherIfNeeded</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">res</span> <span class="o">!=</span> <span class="no">START_SUCCESS</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">res</span><span class="o">;</span> <span class="o">}</span> <span class="n">res</span> <span class="o">=</span> <span class="n">executeRequest</span><span class="o">(</span><span class="n">mRequest</span><span class="o">);</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">restoreCallingIdentity</span><span class="o">(</span><span class="n">origId</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">globalConfigWillChange</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// If the caller also wants to switch to a new configuration, do so now.</span> <span class="c1">// This allows a clean switch, as we are waiting for the current activity</span> <span class="c1">// to pause (so we will not destroy it), and have not yet started the</span> <span class="c1">// next activity.</span> <span class="n">mService</span><span class="o">.</span><span class="na">mAmInternal</span><span class="o">.</span><span class="na">enforceCallingPermission</span><span class="o">(</span> <span class="n">android</span><span class="o">.</span><span class="na">Manifest</span><span class="o">.</span><span class="na">permission</span><span class="o">.</span><span class="na">CHANGE_CONFIGURATION</span><span class="o">,</span> <span class="s">"updateConfiguration()"</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">rootTask</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">mConfigWillChange</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="nc">ProtoLog</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="no">WM_DEBUG_CONFIGURATION</span><span class="o">,</span> <span class="s">"Updating to new configuration after starting activity."</span><span class="o">);</span> <span class="n">mService</span><span class="o">.</span><span class="na">updateConfigurationLocked</span><span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">globalConfig</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// The original options may have additional info about metrics. The mOptions is not</span> <span class="c1">// used here because it may be cleared in setTargetRootTaskIfNeeded.</span> <span class="kd">final</span> <span class="nc">ActivityOptions</span> <span class="n">originalOptions</span> <span class="o">=</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">activityOptions</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">activityOptions</span><span class="o">.</span><span class="na">getOriginalOptions</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span> <span class="c1">// If the new record is the one that started, a new activity has created.</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">newActivityCreated</span> <span class="o">=</span> <span class="n">mStartActivity</span> <span class="o">==</span> <span class="n">mLastStartActivityRecord</span><span class="o">;</span> <span class="c1">// Notify ActivityMetricsLogger that the activity has launched.</span> <span class="c1">// ActivityMetricsLogger will then wait for the windows to be drawn and populate</span> <span class="c1">// WaitResult.</span> <span class="n">mSupervisor</span><span class="o">.</span><span class="na">getActivityMetricsLogger</span><span class="o">().</span><span class="na">notifyActivityLaunched</span><span class="o">(</span><span class="n">launchingState</span><span class="o">,</span> <span class="n">res</span><span class="o">,</span> <span class="n">newActivityCreated</span><span class="o">,</span> <span class="n">mLastStartActivityRecord</span><span class="o">,</span> <span class="n">originalOptions</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">waitResult</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">mRequest</span><span class="o">.</span><span class="na">waitResult</span><span class="o">.</span><span class="na">result</span> <span class="o">=</span> <span class="n">res</span><span class="o">;</span> <span class="n">res</span> <span class="o">=</span> <span class="n">waitResultIfNeeded</span><span class="o">(</span><span class="n">mRequest</span><span class="o">.</span><span class="na">waitResult</span><span class="o">,</span> <span class="n">mLastStartActivityRecord</span><span class="o">,</span> <span class="n">launchingState</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="nf">getExternalResult</span><span class="o">(</span><span class="n">res</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">onExecutionComplete</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这里主要做一些 Activity 启动相关细节参数的校验,验证通过后继续执行 <code class="language-plaintext highlighter-rouge">executeRequest</code> 方法:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 </pre></td><td class="rouge-code"><pre> <span class="cm">/** * Executing activity start request and starts the journey of starting an activity. Here * begins with performing several preliminary checks. The normally activity launch flow will * go through {@link #startActivityUnchecked} to {@link #startActivityInner}. */</span> <span class="kd">private</span> <span class="kt">int</span> <span class="nf">executeRequest</span><span class="o">(</span><span class="nc">Request</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ActivityRecord</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="n">mService</span><span class="o">)</span> <span class="o">.</span><span class="na">setCaller</span><span class="o">(</span><span class="n">callerApp</span><span class="o">)</span> <span class="o">.</span><span class="na">setLaunchedFromPid</span><span class="o">(</span><span class="n">callingPid</span><span class="o">)</span> <span class="o">.</span><span class="na">setLaunchedFromUid</span><span class="o">(</span><span class="n">callingUid</span><span class="o">)</span> <span class="o">.</span><span class="na">setLaunchedFromPackage</span><span class="o">(</span><span class="n">callingPackage</span><span class="o">)</span> <span class="o">.</span><span class="na">setLaunchedFromFeature</span><span class="o">(</span><span class="n">callingFeatureId</span><span class="o">)</span> <span class="o">.</span><span class="na">setIntent</span><span class="o">(</span><span class="n">intent</span><span class="o">)</span> <span class="o">.</span><span class="na">setResolvedType</span><span class="o">(</span><span class="n">resolvedType</span><span class="o">)</span> <span class="o">.</span><span class="na">setActivityInfo</span><span class="o">(</span><span class="n">aInfo</span><span class="o">)</span> <span class="o">.</span><span class="na">setConfiguration</span><span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">getGlobalConfiguration</span><span class="o">())</span> <span class="o">.</span><span class="na">setResultTo</span><span class="o">(</span><span class="n">resultRecord</span><span class="o">)</span> <span class="o">.</span><span class="na">setResultWho</span><span class="o">(</span><span class="n">resultWho</span><span class="o">)</span> <span class="o">.</span><span class="na">setRequestCode</span><span class="o">(</span><span class="n">requestCode</span><span class="o">)</span> <span class="o">.</span><span class="na">setComponentSpecified</span><span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">componentSpecified</span><span class="o">)</span> <span class="o">.</span><span class="na">setRootVoiceInteraction</span><span class="o">(</span><span class="n">voiceSession</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">.</span><span class="na">setActivityOptions</span><span class="o">(</span><span class="n">checkedOptions</span><span class="o">)</span> <span class="o">.</span><span class="na">setSourceRecord</span><span class="o">(</span><span class="n">sourceRecord</span><span class="o">)</span> <span class="o">.</span><span class="na">build</span><span class="o">();</span> <span class="n">mLastStartActivityRecord</span> <span class="o">=</span> <span class="n">r</span><span class="o">;</span> <span class="o">...</span> <span class="n">mLastStartActivityResult</span> <span class="o">=</span> <span class="n">startActivityUnchecked</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">sourceRecord</span><span class="o">,</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="n">request</span><span class="o">.</span><span class="na">voiceInteractor</span><span class="o">,</span> <span class="n">startFlags</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/* doResume */</span><span class="o">,</span> <span class="n">checkedOptions</span><span class="o">,</span> <span class="n">inTask</span><span class="o">,</span> <span class="n">inTaskFragment</span><span class="o">,</span> <span class="n">restrictedBgActivity</span><span class="o">,</span> <span class="n">intentGrants</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">request</span><span class="o">.</span><span class="na">outActivity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">request</span><span class="o">.</span><span class="na">outActivity</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">mLastStartActivityRecord</span><span class="o">;</span> <span class="o">}</span> <span class="k">return</span> <span class="n">mLastStartActivityResult</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>首先构建了一个 ActivityRecord 用于记录 Activity 相关信息(如包名、进程名、启动模式、task 信息等),接着调用 <code class="language-plaintext highlighter-rouge">startActivityUnchecked</code>:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Start an activity while most of preliminary checks has been done and caller has been * confirmed that holds necessary permissions to do so. * Here also ensures that the starting activity is removed if the start wasn't successful. */</span> <span class="kd">private</span> <span class="kt">int</span> <span class="nf">startActivityUnchecked</span><span class="o">(</span><span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="nc">ActivityRecord</span> <span class="n">sourceRecord</span><span class="o">,</span> <span class="nc">IVoiceInteractionSession</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="nc">IVoiceInteractor</span> <span class="n">voiceInteractor</span><span class="o">,</span> <span class="kt">int</span> <span class="n">startFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">doResume</span><span class="o">,</span> <span class="nc">ActivityOptions</span> <span class="n">options</span><span class="o">,</span> <span class="nc">Task</span> <span class="n">inTask</span><span class="o">,</span> <span class="nc">TaskFragment</span> <span class="n">inTaskFragment</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">restrictedBgActivity</span><span class="o">,</span> <span class="nc">NeededUriGrants</span> <span class="n">intentGrants</span><span class="o">)</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="no">START_CANCELED</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">startedActivityRootTask</span><span class="o">;</span> <span class="c1">// Create a transition now to record the original intent of actions taken within</span> <span class="c1">// startActivityInner. Otherwise, logic in startActivityInner could start a different</span> <span class="c1">// transition based on a sub-action.</span> <span class="c1">// Only do the create here (and defer requestStart) since startActivityInner might abort.</span> <span class="kd">final</span> <span class="nc">TransitionController</span> <span class="n">transitionController</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">mTransitionController</span><span class="o">;</span> <span class="nc">Transition</span> <span class="n">newTransition</span> <span class="o">=</span> <span class="o">(!</span><span class="n">transitionController</span><span class="o">.</span><span class="na">isCollecting</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">transitionController</span><span class="o">.</span><span class="na">getTransitionPlayer</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="n">transitionController</span><span class="o">.</span><span class="na">createTransition</span><span class="o">(</span><span class="no">TRANSIT_OPEN</span><span class="o">)</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span> <span class="nc">RemoteTransition</span> <span class="n">remoteTransition</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">takeRemoteTransition</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mService</span><span class="o">.</span><span class="na">deferWindowLayout</span><span class="o">();</span> <span class="n">transitionController</span><span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="n">r</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_WINDOW_MANAGER</span><span class="o">,</span> <span class="s">"startActivityInner"</span><span class="o">);</span> <span class="n">result</span> <span class="o">=</span> <span class="n">startActivityInner</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">sourceRecord</span><span class="o">,</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="n">voiceInteractor</span><span class="o">,</span> <span class="n">startFlags</span><span class="o">,</span> <span class="n">doResume</span><span class="o">,</span> <span class="n">options</span><span class="o">,</span> <span class="n">inTask</span><span class="o">,</span> <span class="n">inTaskFragment</span><span class="o">,</span> <span class="n">restrictedBgActivity</span><span class="o">,</span> <span class="n">intentGrants</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_WINDOW_MANAGER</span><span class="o">);</span> <span class="n">startedActivityRootTask</span> <span class="o">=</span> <span class="n">handleStartResult</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">options</span><span class="o">,</span> <span class="n">result</span><span class="o">,</span> <span class="n">newTransition</span><span class="o">,</span> <span class="n">remoteTransition</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mService</span><span class="o">.</span><span class="na">continueWindowLayout</span><span class="o">();</span> <span class="o">}</span> <span class="n">postStartActivityProcessing</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">result</span><span class="o">,</span> <span class="n">startedActivityRootTask</span><span class="o">);</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续追踪到 <code class="language-plaintext highlighter-rouge">startActivityInner</code> 方法中:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">startActivityInner</span><span class="o">(</span><span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="nc">ActivityRecord</span> <span class="n">sourceRecord</span><span class="o">,</span> <span class="nc">IVoiceInteractionSession</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="nc">IVoiceInteractor</span> <span class="n">voiceInteractor</span><span class="o">,</span> <span class="kt">int</span> <span class="n">startFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">doResume</span><span class="o">,</span> <span class="nc">ActivityOptions</span> <span class="n">options</span><span class="o">,</span> <span class="nc">Task</span> <span class="n">inTask</span><span class="o">,</span> <span class="nc">TaskFragment</span> <span class="n">inTaskFragment</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">restrictedBgActivity</span><span class="o">,</span> <span class="nc">NeededUriGrants</span> <span class="n">intentGrants</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">mDoResume</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">topTaskActivity</span> <span class="o">=</span> <span class="n">startedTask</span><span class="o">.</span><span class="na">topRunningActivityLocked</span><span class="o">();</span> <span class="k">if</span> <span class="o">(!</span><span class="n">mTargetRootTask</span><span class="o">.</span><span class="na">isTopActivityFocusable</span><span class="o">()</span> <span class="o">||</span> <span class="o">(</span><span class="n">topTaskActivity</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">topTaskActivity</span><span class="o">.</span><span class="na">isTaskOverlay</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">mStartActivity</span> <span class="o">!=</span> <span class="n">topTaskActivity</span><span class="o">))</span> <span class="o">{</span> <span class="n">mTargetRootTask</span><span class="o">.</span><span class="na">ensureActivitiesVisible</span><span class="o">(</span><span class="kc">null</span> <span class="cm">/* starting */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* configChanges */</span><span class="o">,</span> <span class="o">!</span><span class="no">PRESERVE_WINDOWS</span><span class="o">);</span> <span class="n">mTargetRootTask</span><span class="o">.</span><span class="na">mDisplayContent</span><span class="o">.</span><span class="na">executeAppTransition</span><span class="o">();</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// If the target root-task was not previously focusable (previous top running</span> <span class="c1">// activity on that root-task was not visible) then any prior calls to move the</span> <span class="c1">// root-task to the will not update the focused root-task. If starting the new</span> <span class="c1">// activity now allows the task root-task to be focusable, then ensure that we</span> <span class="c1">// now update the focused root-task accordingly.</span> <span class="k">if</span> <span class="o">(</span><span class="n">mTargetRootTask</span><span class="o">.</span><span class="na">isTopActivityFocusable</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">isTopDisplayFocusedRootTask</span><span class="o">(</span><span class="n">mTargetRootTask</span><span class="o">))</span> <span class="o">{</span> <span class="n">mTargetRootTask</span><span class="o">.</span><span class="na">moveToFront</span><span class="o">(</span><span class="s">"startActivityInner"</span><span class="o">);</span> <span class="o">}</span> <span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">resumeFocusedTasksTopActivities</span><span class="o">(</span> <span class="n">mTargetRootTask</span><span class="o">,</span> <span class="n">mStartActivity</span><span class="o">,</span> <span class="n">mOptions</span><span class="o">,</span> <span class="n">mTransientLaunch</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">updateUserRootTask</span><span class="o">(</span><span class="n">mStartActivity</span><span class="o">.</span><span class="na">mUserId</span><span class="o">,</span> <span class="n">mTargetRootTask</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>发现最终又调用回了 RootWindowContainer:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 </pre></td><td class="rouge-code"><pre><span class="kt">boolean</span> <span class="nf">resumeFocusedTasksTopActivities</span><span class="o">(</span> <span class="nc">Task</span> <span class="n">targetRootTask</span><span class="o">,</span> <span class="nc">ActivityRecord</span> <span class="n">target</span><span class="o">,</span> <span class="nc">ActivityOptions</span> <span class="n">targetOptions</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">deferPause</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(!</span><span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">readyToResume</span><span class="o">())</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="n">result</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">// 如果是栈顶 Activity 则调用 resumeTopActivityUncheckedLocked</span> <span class="k">if</span> <span class="o">(</span><span class="n">targetRootTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">targetRootTask</span><span class="o">.</span><span class="na">isTopRootTaskInDisplayArea</span><span class="o">()</span> <span class="o">||</span> <span class="n">getTopDisplayFocusedRootTask</span><span class="o">()</span> <span class="o">==</span> <span class="n">targetRootTask</span><span class="o">))</span> <span class="o">{</span> <span class="n">result</span> <span class="o">=</span> <span class="n">targetRootTask</span><span class="o">.</span><span class="na">resumeTopActivityUncheckedLocked</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">targetOptions</span><span class="o">,</span> <span class="n">deferPause</span><span class="o">);</span> <span class="o">}</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">displayNdx</span> <span class="o">=</span> <span class="n">getChildCount</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">displayNdx</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">displayNdx</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">DisplayContent</span> <span class="n">display</span> <span class="o">=</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">displayNdx</span><span class="o">);</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">curResult</span> <span class="o">=</span> <span class="n">result</span><span class="o">;</span> <span class="kt">boolean</span><span class="o">[]</span> <span class="n">resumedOnDisplay</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">boolean</span><span class="o">[</span><span class="mi">1</span><span class="o">];</span> <span class="n">display</span><span class="o">.</span><span class="na">forAllRootTasks</span><span class="o">(</span><span class="n">rootTask</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">topRunningActivity</span> <span class="o">=</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">topRunningActivity</span><span class="o">();</span> <span class="k">if</span> <span class="o">(!</span><span class="n">rootTask</span><span class="o">.</span><span class="na">isFocusableAndVisible</span><span class="o">()</span> <span class="o">||</span> <span class="n">topRunningActivity</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">rootTask</span> <span class="o">==</span> <span class="n">targetRootTask</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Simply update the result for targetRootTask because the targetRootTask</span> <span class="c1">// had already resumed in above. We don't want to resume it again,</span> <span class="c1">// especially in some cases, it would cause a second launch failure</span> <span class="c1">// if app process was dead.</span> <span class="n">resumedOnDisplay</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">|=</span> <span class="n">curResult</span><span class="o">;</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">rootTask</span><span class="o">.</span><span class="na">getDisplayArea</span><span class="o">().</span><span class="na">isTopRootTask</span><span class="o">(</span><span class="n">rootTask</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">topRunningActivity</span><span class="o">.</span><span class="na">isState</span><span class="o">(</span><span class="no">RESUMED</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// Kick off any lingering app transitions from the MoveTaskToFront</span> <span class="c1">// operation, but only consider the top task and root-task on that</span> <span class="c1">// display.</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">executeAppTransition</span><span class="o">(</span><span class="n">targetOptions</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">resumedOnDisplay</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">|=</span> <span class="n">topRunningActivity</span><span class="o">.</span><span class="na">makeActiveIfNeeded</span><span class="o">(</span><span class="n">target</span><span class="o">);</span> <span class="o">}</span> <span class="o">});</span> <span class="n">result</span> <span class="o">|=</span> <span class="n">resumedOnDisplay</span><span class="o">[</span><span class="mi">0</span><span class="o">];</span> <span class="k">if</span> <span class="o">(!</span><span class="n">resumedOnDisplay</span><span class="o">[</span><span class="mi">0</span><span class="o">])</span> <span class="o">{</span> <span class="c1">// In cases when there are no valid activities (e.g. device just booted or launcher</span> <span class="c1">// crashed) it's possible that nothing was resumed on a display. Requesting resume</span> <span class="c1">// of top activity in focused root task explicitly will make sure that at least home</span> <span class="c1">// activity is started and resumed, and no recursion occurs.</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">focusedRoot</span> <span class="o">=</span> <span class="n">display</span><span class="o">.</span><span class="na">getFocusedRootTask</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">focusedRoot</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">result</span> <span class="o">|=</span> <span class="n">focusedRoot</span><span class="o">.</span><span class="na">resumeTopActivityUncheckedLocked</span><span class="o">(</span><span class="n">target</span><span class="o">,</span> <span class="n">targetOptions</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">targetRootTask</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">result</span> <span class="o">|=</span> <span class="n">resumeHomeActivity</span><span class="o">(</span><span class="kc">null</span> <span class="cm">/* prev */</span><span class="o">,</span> <span class="s">"no-focusable-task"</span><span class="o">,</span> <span class="n">display</span><span class="o">.</span><span class="na">getDefaultTaskDisplayArea</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>第一次正常启动 Launcher 会执行到 <code class="language-plaintext highlighter-rouge">resumeTopActivityUncheckedLocked</code>:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/Task.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Ensure that the top activity in the root task is resumed. * * @param prev The previously resumed activity, for when in the process * of pausing; can be null to call from elsewhere. * @param options Activity options. * @param deferPause When {@code true}, this will not pause back tasks. * * @return Returns true if something is being resumed, or false if * nothing happened. * * NOTE: It is not safe to call this method directly as it can cause an activity in a * non-focused root task to be resumed. * Use {@link RootWindowContainer#resumeFocusedTasksTopActivities} to resume the * right activity for the current system state. */</span> <span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mService"</span><span class="o">)</span> <span class="kt">boolean</span> <span class="nf">resumeTopActivityUncheckedLocked</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">prev</span><span class="o">,</span> <span class="nc">ActivityOptions</span> <span class="n">options</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">deferPause</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mInResumeTopActivity</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Don't even start recursing.</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="n">someActivityResumed</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// Protect against recursion.</span> <span class="n">mInResumeTopActivity</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">isLeafTask</span><span class="o">())</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">isFocusableAndVisible</span><span class="o">())</span> <span class="o">{</span> <span class="n">someActivityResumed</span> <span class="o">=</span> <span class="n">resumeTopActivityInnerLocked</span><span class="o">(</span><span class="n">prev</span><span class="o">,</span> <span class="n">options</span><span class="o">,</span> <span class="n">deferPause</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="k">while</span> <span class="o">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Task</span> <span class="n">child</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Task</span><span class="o">)</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">idx</span><span class="o">--);</span> <span class="k">if</span> <span class="o">(!</span><span class="n">child</span><span class="o">.</span><span class="na">isTopActivityFocusable</span><span class="o">())</span> <span class="o">{</span> <span class="k">continue</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">child</span><span class="o">.</span><span class="na">getVisibility</span><span class="o">(</span><span class="kc">null</span> <span class="cm">/* starting */</span><span class="o">)</span> <span class="o">!=</span> <span class="no">TASK_FRAGMENT_VISIBILITY_VISIBLE</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">child</span><span class="o">.</span><span class="na">topRunningActivity</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Skip the task if no running activity and continue resuming next task.</span> <span class="k">continue</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// Otherwise, assuming everything behind this task should also be invisible.</span> <span class="k">break</span><span class="o">;</span> <span class="o">}</span> <span class="n">someActivityResumed</span> <span class="o">|=</span> <span class="n">child</span><span class="o">.</span><span class="na">resumeTopActivityUncheckedLocked</span><span class="o">(</span><span class="n">prev</span><span class="o">,</span> <span class="n">options</span><span class="o">,</span> <span class="n">deferPause</span><span class="o">);</span> <span class="c1">// Doing so in order to prevent IndexOOB since hierarchy might changes while</span> <span class="c1">// resuming activities, for example dismissing split-screen while starting</span> <span class="c1">// non-resizeable activity.</span> <span class="k">if</span> <span class="o">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="n">mChildren</span><span class="o">.</span><span class="na">size</span><span class="o">())</span> <span class="o">{</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">mChildren</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// When resuming the top activity, it may be necessary to pause the top activity (for</span> <span class="c1">// example, returning to the lock screen. We suppress the normal pause logic in</span> <span class="c1">// {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the</span> <span class="c1">// end. We call the {@link ActivityTaskSupervisor#checkReadyForSleepLocked} again here</span> <span class="c1">// to ensure any necessary pause logic occurs. In the case where the Activity will be</span> <span class="c1">// shown regardless of the lock screen, the call to</span> <span class="c1">// {@link ActivityTaskSupervisor#checkReadyForSleepLocked} is skipped.</span> <span class="kd">final</span> <span class="nc">ActivityRecord</span> <span class="n">next</span> <span class="o">=</span> <span class="n">topRunningActivity</span><span class="o">(</span><span class="kc">true</span> <span class="cm">/* focusableOnly */</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">next</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">next</span><span class="o">.</span><span class="na">canTurnScreenOn</span><span class="o">())</span> <span class="o">{</span> <span class="n">checkReadyForSleep</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mInResumeTopActivity</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="k">return</span> <span class="n">someActivityResumed</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>最终会跟进到 <code class="language-plaintext highlighter-rouge">TaskFragment</code> 当中:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 </pre></td><td class="rouge-code"><pre><span class="kd">final</span> <span class="kt">boolean</span> <span class="nf">resumeTopActivity</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">prev</span><span class="o">,</span> <span class="nc">ActivityOptions</span> <span class="n">options</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">deferPause</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">attachedToProcess</span><span class="o">())</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// Whoops, need to restart this activity!</span> <span class="k">if</span> <span class="o">(!</span><span class="n">next</span><span class="o">.</span><span class="na">hasBeenLaunched</span><span class="o">)</span> <span class="o">{</span> <span class="n">next</span><span class="o">.</span><span class="na">hasBeenLaunched</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="no">SHOW_APP_STARTING_PREVIEW</span><span class="o">)</span> <span class="o">{</span> <span class="n">next</span><span class="o">.</span><span class="na">showStartingWindow</span><span class="o">(</span><span class="kc">false</span> <span class="cm">/* taskSwich */</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="no">DEBUG_SWITCH</span><span class="o">)</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="no">TAG_SWITCH</span><span class="o">,</span> <span class="s">"Restarting: "</span> <span class="o">+</span> <span class="n">next</span><span class="o">);</span> <span class="o">}</span> <span class="nc">ProtoLog</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="no">WM_DEBUG_STATES</span><span class="o">,</span> <span class="s">"resumeTopActivity: Restarting %s"</span><span class="o">,</span> <span class="n">next</span><span class="o">);</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">startSpecificActivity</span><span class="o">(</span><span class="n">next</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>重点看下 <code class="language-plaintext highlighter-rouge">ActivityTaskSupervisor#startSpecificActivity</code>:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">startSpecificActivity</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">andResume</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">checkConfig</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Is this activity's application already running?</span> <span class="kd">final</span> <span class="nc">WindowProcessController</span> <span class="n">wpc</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">getProcessController</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">wpc</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">wpc</span><span class="o">.</span><span class="na">hasThread</span><span class="o">())</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">realStartActivityLocked</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">wpc</span><span class="o">,</span> <span class="n">andResume</span><span class="o">,</span> <span class="n">checkConfig</span><span class="o">);</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Exception when starting activity "</span> <span class="o">+</span> <span class="n">r</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">().</span><span class="na">flattenToShortString</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// If a dead object exception was thrown -- fall through to</span> <span class="c1">// restart the application.</span> <span class="n">knownToBeDead</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="c1">// Remove the process record so it won't be considered as alive.</span> <span class="n">mService</span><span class="o">.</span><span class="na">mProcessNames</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">wpc</span><span class="o">.</span><span class="na">mName</span><span class="o">,</span> <span class="n">wpc</span><span class="o">.</span><span class="na">mUid</span><span class="o">);</span> <span class="n">mService</span><span class="o">.</span><span class="na">mProcessMap</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">wpc</span><span class="o">.</span><span class="na">getPid</span><span class="o">());</span> <span class="o">}</span> <span class="n">r</span><span class="o">.</span><span class="na">notifyUnknownVisibilityLaunchedForKeyguardTransition</span><span class="o">();</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isTop</span> <span class="o">=</span> <span class="n">andResume</span> <span class="o">&amp;&amp;</span> <span class="n">r</span><span class="o">.</span><span class="na">isTopRunningActivity</span><span class="o">();</span> <span class="n">mService</span><span class="o">.</span><span class="na">startProcessAsync</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="n">isTop</span><span class="o">,</span> <span class="n">isTop</span> <span class="o">?</span> <span class="nc">HostingRecord</span><span class="o">.</span><span class="na">HOSTING_TYPE_TOP_ACTIVITY</span> <span class="o">:</span> <span class="nc">HostingRecord</span><span class="o">.</span><span class="na">HOSTING_TYPE_ACTIVITY</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>跟踪了方法调用栈这么久终于找到“罪魁祸首”了,startSpecificActivity 方法做了两件事:</p> <ul> <li>如果 Activity 的进程已经启动,则直接执行 <code class="language-plaintext highlighter-rouge">realStartActivityLocked</code> 来启动 Activity(通常是启动一般已运行进程的 Activity);</li> <li>如果 Activity 的进程尚未启动,那么需要先执行 <code class="language-plaintext highlighter-rouge">ActivityTaskManagerService#startProcessAsync</code> 创建进程(通常是进程尚未启动的情况,例如初次打开 APP)。</li> </ul> <p>上面的 Activity 在本场景下指代 Launcher 进程的首个 Activity,由于是第一次启动该 Activity,所以进程肯定还没有创建,我们先去看下 startProcessAsync 中是如何创建 Launcher 进程的:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">startProcessAsync</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">activity</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTop</span><span class="o">,</span> <span class="nc">String</span> <span class="n">hostingType</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">isTagEnabled</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">,</span> <span class="s">"dispatchingStartProcess:"</span> <span class="o">+</span> <span class="n">activity</span><span class="o">.</span><span class="na">processName</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Post message to start process to avoid possible deadlock of calling into AMS with the</span> <span class="c1">// ATMS lock held.</span> <span class="kd">final</span> <span class="nc">Message</span> <span class="n">m</span> <span class="o">=</span> <span class="nc">PooledLambda</span><span class="o">.</span><span class="na">obtainMessage</span><span class="o">(</span><span class="nl">ActivityManagerInternal:</span><span class="o">:</span><span class="n">startProcess</span><span class="o">,</span> <span class="n">mAmInternal</span><span class="o">,</span> <span class="n">activity</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">activity</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">,</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="n">isTop</span><span class="o">,</span> <span class="n">hostingType</span><span class="o">,</span> <span class="n">activity</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">());</span> <span class="n">mH</span><span class="o">.</span><span class="na">sendMessage</span><span class="o">(</span><span class="n">m</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>由此可见,这里通过 Message-Handler 来发送创建进程的消息,消息接收处理是通过设置的 Callback,最终回调给 <code class="language-plaintext highlighter-rouge">ActivityManagerInternal::startProcess</code>:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 </pre></td><td class="rouge-code"><pre><span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startProcess</span><span class="o">(</span><span class="nc">String</span> <span class="n">processName</span><span class="o">,</span> <span class="nc">ApplicationInfo</span> <span class="n">info</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTop</span><span class="o">,</span> <span class="nc">String</span> <span class="n">hostingType</span><span class="o">,</span> <span class="nc">ComponentName</span> <span class="n">hostingName</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">isTagEnabled</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"startProcess:"</span> <span class="o">+</span> <span class="n">processName</span><span class="o">);</span> <span class="o">}</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="nc">ActivityManagerService</span><span class="o">.</span><span class="na">this</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// If the process is known as top app, set a hint so when the process is</span> <span class="c1">// started, the top priority can be applied immediately to avoid cpu being</span> <span class="c1">// preempted by other processes before attaching the process of top app.</span> <span class="n">startProcessLocked</span><span class="o">(</span><span class="n">processName</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* intentFlags */</span><span class="o">,</span> <span class="k">new</span> <span class="nf">HostingRecord</span><span class="o">(</span><span class="n">hostingType</span><span class="o">,</span> <span class="n">hostingName</span><span class="o">,</span> <span class="n">isTop</span><span class="o">),</span> <span class="no">ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* allowWhileBooting */</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* isolated */</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"this"</span><span class="o">)</span> <span class="kd">final</span> <span class="nc">ProcessRecord</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="nc">String</span> <span class="n">processName</span><span class="o">,</span> <span class="nc">ApplicationInfo</span> <span class="n">info</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="kt">int</span> <span class="n">intentFlags</span><span class="o">,</span> <span class="nc">HostingRecord</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowWhileBooting</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isolated</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mProcessList</span><span class="o">.</span><span class="na">startProcessLocked</span><span class="o">(</span><span class="n">processName</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="n">intentFlags</span><span class="o">,</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">allowWhileBooting</span><span class="o">,</span> <span class="n">isolated</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* isolatedUid */</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* isSdkSandbox */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* sdkSandboxClientAppUid */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* sdkSandboxClientAppPackage */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* ABI override */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* entryPoint */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* entryPointArgs */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* crashHandler */</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>接着执行到 <code class="language-plaintext highlighter-rouge">ProcessList#startProcessLocked</code> :</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/am/ProcessList.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 </pre></td><td class="rouge-code"><pre><span class="nc">ProcessRecord</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="nc">String</span> <span class="n">processName</span><span class="o">,</span> <span class="nc">ApplicationInfo</span> <span class="n">info</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="kt">int</span> <span class="n">intentFlags</span><span class="o">,</span> <span class="nc">HostingRecord</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowWhileBooting</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isolated</span><span class="o">,</span> <span class="kt">int</span> <span class="n">isolatedUid</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isSdkSandbox</span><span class="o">,</span> <span class="kt">int</span> <span class="n">sdkSandboxUid</span><span class="o">,</span> <span class="nc">String</span> <span class="n">sdkSandboxClientAppPackage</span><span class="o">,</span> <span class="nc">String</span> <span class="n">abiOverride</span><span class="o">,</span> <span class="nc">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">entryPointArgs</span><span class="o">,</span> <span class="nc">Runnable</span> <span class="n">crashHandler</span><span class="o">)</span> <span class="o">{</span> <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">();</span> <span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">;</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isolated</span><span class="o">)</span> <span class="o">{</span> <span class="n">app</span> <span class="o">=</span> <span class="n">getProcessRecordLocked</span><span class="o">(</span><span class="n">processName</span><span class="o">,</span> <span class="n">info</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span> <span class="n">checkSlow</span><span class="o">(</span><span class="n">startTime</span><span class="o">,</span> <span class="s">"startProcess: after getProcessRecord"</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// If this is an isolated process, it can't re-use an existing process.</span> <span class="n">app</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="o">...</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">success</span> <span class="o">=</span> <span class="n">startProcessLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">abiOverride</span><span class="o">);</span> <span class="n">checkSlow</span><span class="o">(</span><span class="n">startTime</span><span class="o">,</span> <span class="s">"startProcess: done starting proc!"</span><span class="o">);</span> <span class="k">return</span> <span class="n">success</span> <span class="o">?</span> <span class="n">app</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * @return {@code true} if process start is successful, false otherwise. */</span> <span class="nd">@GuardedBy</span><span class="o">(</span><span class="s">"mService"</span><span class="o">)</span> <span class="kt">boolean</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">,</span> <span class="nc">HostingRecord</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">disableHiddenApiChecks</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">disableTestApiChecks</span><span class="o">,</span> <span class="nc">String</span> <span class="n">abiOverride</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">isPendingStart</span><span class="o">())</span> <span class="o">{</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="o">...</span> <span class="nc">ApplicationInfo</span> <span class="n">definingAppInfo</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">hostingRecord</span><span class="o">.</span><span class="na">getDefiningPackageName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">definingAppInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApplicationInfo</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">);</span> <span class="n">definingAppInfo</span><span class="o">.</span><span class="na">packageName</span> <span class="o">=</span> <span class="n">hostingRecord</span><span class="o">.</span><span class="na">getDefiningPackageName</span><span class="o">();</span> <span class="n">definingAppInfo</span><span class="o">.</span><span class="na">uid</span> <span class="o">=</span> <span class="n">uid</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">definingAppInfo</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">;</span> <span class="o">}</span> <span class="n">runtimeFlags</span> <span class="o">|=</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">getMemorySafetyRuntimeFlags</span><span class="o">(</span> <span class="n">definingAppInfo</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">processInfo</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">mPlatformCompat</span><span class="o">);</span> <span class="o">...</span> <span class="c1">// 进程入口函数所在类 </span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">entryPoint</span> <span class="o">=</span> <span class="s">"android.app.ActivityThread"</span><span class="o">;</span> <span class="k">return</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="n">hostingRecord</span><span class="o">,</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">app</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="n">startUptime</span><span class="o">,</span> <span class="n">startElapsedTime</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="nc">ActivityManagerService</span><span class="o">.</span><span class="na">TAG</span><span class="o">,</span> <span class="s">"Failure starting process "</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span> <span class="n">mService</span><span class="o">.</span><span class="na">forceStopPackageLocked</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="nc">UserHandle</span><span class="o">.</span><span class="na">getAppId</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">uid</span><span class="o">),</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">userId</span><span class="o">,</span> <span class="s">"start failure"</span><span class="o">);</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="nc">HostingRecord</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="nc">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">,</span> <span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="nc">String</span> <span class="n">seInfo</span><span class="o">,</span> <span class="nc">String</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startUptime</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startElapsedTime</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">mConstants</span><span class="o">.</span><span class="na">FLAG_PROCESS_START_ASYNC</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="n">startResult</span> <span class="o">=</span> <span class="n">startProcess</span><span class="o">(</span><span class="n">hostingRecord</span><span class="o">,</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">app</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="n">startUptime</span><span class="o">);</span> <span class="n">handleProcessStartedLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">startResult</span><span class="o">.</span><span class="na">pid</span><span class="o">,</span> <span class="n">startResult</span><span class="o">.</span><span class="na">usingWrapper</span><span class="o">,</span> <span class="n">startSeq</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="na">getPid</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="cm">/** * Main handler routine to start the given process from the ProcStartHandler. * * &lt;p&gt;Note: this function doesn't hold the global AM lock intentionally.&lt;/p&gt; */</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleProcessStart</span><span class="o">(</span><span class="kd">final</span> <span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">startSeq</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Runnable</span> <span class="n">startRunnable</span> <span class="o">=</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="n">startResult</span> <span class="o">=</span> <span class="n">startProcess</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getHostingRecord</span><span class="o">(),</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">app</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartUid</span><span class="o">(),</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getSeInfo</span><span class="o">(),</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartTime</span><span class="o">());</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mService</span><span class="o">)</span> <span class="o">{</span> <span class="n">handleProcessStartedLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">startResult</span><span class="o">,</span> <span class="n">startSeq</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="o">};</span> <span class="c1">// Use local reference since we are not using locks here</span> <span class="kd">final</span> <span class="nc">ProcessRecord</span> <span class="n">predecessor</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">mPredecessor</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">predecessor</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">predecessor</span><span class="o">.</span><span class="na">getDyingPid</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="n">handleProcessStartWithPredecessor</span><span class="o">(</span><span class="n">predecessor</span><span class="o">,</span> <span class="n">startRunnable</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// Kick off the process start for real.</span> <span class="n">startRunnable</span><span class="o">.</span><span class="na">run</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="nf">startProcess</span><span class="o">(</span><span class="nc">HostingRecord</span> <span class="n">hostingRecord</span><span class="o">,</span> <span class="nc">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">,</span> <span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="nc">String</span> <span class="n">seInfo</span><span class="o">,</span> <span class="nc">String</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startTime</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"Start proc: "</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">processName</span><span class="o">);</span> <span class="o">...</span> <span class="kd">final</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="n">startResult</span><span class="o">;</span> <span class="kt">boolean</span> <span class="n">regularZygote</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">hostingRecord</span><span class="o">.</span><span class="na">usesWebviewZygote</span><span class="o">())</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">hostingRecord</span><span class="o">.</span><span class="na">usesAppZygote</span><span class="o">())</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">AppZygote</span> <span class="n">appZygote</span> <span class="o">=</span> <span class="n">createAppZygoteForProcessIfNeeded</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="c1">// We can't isolate app data and storage data as parent zygote already did that.</span> <span class="n">startResult</span> <span class="o">=</span> <span class="n">appZygote</span><span class="o">.</span><span class="na">getProcess</span><span class="o">().</span><span class="na">start</span><span class="o">(</span><span class="n">entryPoint</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">targetSdkVersion</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">dataDir</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="cm">/*zygotePolicyFlags=*/</span> <span class="no">ZYGOTE_POLICY_FLAG_EMPTY</span><span class="o">,</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getDisabledCompatChanges</span><span class="o">(),</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="n">allowlistedAppDataInfoMap</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="no">PROC_START_SEQ_IDENT</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartSeq</span><span class="o">()});</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">regularZygote</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="n">startResult</span> <span class="o">=</span> <span class="nc">Process</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="n">entryPoint</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">targetSdkVersion</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">requiredAbi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">dataDir</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getDisabledCompatChanges</span><span class="o">(),</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="n">allowlistedAppDataInfoMap</span><span class="o">,</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[]{</span><span class="no">PROC_START_SEQ_IDENT</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartSeq</span><span class="o">()});</span> <span class="o">}</span> <span class="o">...</span> <span class="k">return</span> <span class="n">startResult</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>经过重重方法调用,最终抵达了进程创建的目的地,它的前置方法 <code class="language-plaintext highlighter-rouge">startProcessLocked</code> 主要用于包装进程启动相关参数信息及合并状态信息。值得注意的是,参数中的 entryPoint 这里特指 <code class="language-plaintext highlighter-rouge">android.app.ActivityThread</code>,表示后续在创建进程时的入口类。我们主要关注一下上面的 <code class="language-plaintext highlighter-rouge">startProcess</code> 方法,它内部涉及到两个不同的创建新进程的分支,分别为:</p> <ul> <li>appZygote:AppZygote 进程来孵化新进程,与常规 zygote 创建的应用相比受到更多限制;</li> <li>regularZygote:常规的 zygote32/zygote64 进程,是所有 Android 应用的父进程。</li> </ul> <p>我们这里着重关注 regularZygote 的分支,具体看下内部通过 <code class="language-plaintext highlighter-rouge">Process.start</code> 创建进程的实现:</p> <blockquote> <p><em>frameworks/base/core/java/android/os/Process.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 </pre></td><td class="rouge-code"><pre><span class="cm">/** * State associated with the zygote process. * @hide */</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">ZygoteProcess</span> <span class="no">ZYGOTE_PROCESS</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ZygoteProcess</span><span class="o">();</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">ProcessStartResult</span> <span class="nf">start</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">processClass</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">niceName</span><span class="o">,</span> <span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">gid</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="kt">int</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">seInfo</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">String</span> <span class="n">abi</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">appDataDir</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">packageName</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">whitelistedDataInfoMap</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">zygoteArgs</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="no">ZYGOTE_PROCESS</span><span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="n">processClass</span><span class="o">,</span> <span class="n">niceName</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">abi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">appDataDir</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="n">packageName</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="n">whitelistedDataInfoMap</span><span class="o">,</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="n">zygoteArgs</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,<code class="language-plaintext highlighter-rouge">Process.start</code> 最终调用的是 <code class="language-plaintext highlighter-rouge">ZygoteProcess#start</code> 方法:</p> <blockquote> <p><em>frameworks/base/core/java/android/os/ZygoteProcess.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">final</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="nf">start</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">processClass</span><span class="o">,</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">niceName</span><span class="o">,</span> <span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">gid</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="kt">int</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">seInfo</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">String</span> <span class="n">abi</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">appDataDir</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">packageName</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">allowlistedDataInfoList</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">zygoteArgs</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// TODO (chriswailes): Is there a better place to check this value?</span> <span class="k">if</span> <span class="o">(</span><span class="n">fetchUsapPoolEnabledPropWithMinInterval</span><span class="o">())</span> <span class="o">{</span> <span class="n">informZygotesOfUsapPoolStatus</span><span class="o">();</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">startViaZygote</span><span class="o">(</span><span class="n">processClass</span><span class="o">,</span> <span class="n">niceName</span><span class="o">,</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="n">seInfo</span><span class="o">,</span> <span class="n">abi</span><span class="o">,</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="n">appDataDir</span><span class="o">,</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="cm">/*startChildZygote=*/</span> <span class="kc">false</span><span class="o">,</span> <span class="n">packageName</span><span class="o">,</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="n">allowlistedDataInfoList</span><span class="o">,</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="n">zygoteArgs</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ZygoteStartFailedEx</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="no">LOG_TAG</span><span class="o">,</span> <span class="s">"Starting VM process through Zygote failed"</span><span class="o">);</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Starting VM process through Zygote failed"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="nf">startViaZygote</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">processClass</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">niceName</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">gid</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kd">final</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mountExternal</span><span class="o">,</span> <span class="kt">int</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">seInfo</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">String</span> <span class="n">abi</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">instructionSet</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">appDataDir</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">invokeWith</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">startChildZygote</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">packageName</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTopApp</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">pkgDataInfoMap</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Pair</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Long</span><span class="o">&gt;&gt;</span> <span class="n">allowlistedDataInfoList</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppsData</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">bindMountAppStorageDirs</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">extraArgs</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ZygoteStartFailedEx</span> <span class="o">{</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">argsForZygote</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span> <span class="o">...</span> <span class="n">argsForZygote</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">processClass</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">extraArgs</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">argsForZygote</span><span class="o">,</span> <span class="n">extraArgs</span><span class="o">);</span> <span class="o">}</span> <span class="kd">synchronized</span><span class="o">(</span><span class="n">mLock</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// The USAP pool can not be used if the application will not use the systems graphics</span> <span class="c1">// driver. If that driver is requested use the Zygote application start path.</span> <span class="k">return</span> <span class="nf">zygoteSendArgsAndGetResult</span><span class="o">(</span><span class="n">openZygoteSocketIfNeeded</span><span class="o">(</span><span class="n">abi</span><span class="o">),</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="n">argsForZygote</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="nf">zygoteSendArgsAndGetResult</span><span class="o">(</span> <span class="nc">ZygoteState</span> <span class="n">zygoteState</span><span class="o">,</span> <span class="kt">int</span> <span class="n">zygotePolicyFlags</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ZygoteStartFailedEx</span> <span class="o">{</span> <span class="o">...</span> <span class="k">return</span> <span class="nf">attemptZygoteSendArgsAndGetResult</span><span class="o">(</span><span class="n">zygoteState</span><span class="o">,</span> <span class="n">msgStr</span><span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="nf">attemptZygoteSendArgsAndGetResult</span><span class="o">(</span> <span class="nc">ZygoteState</span> <span class="n">zygoteState</span><span class="o">,</span> <span class="nc">String</span> <span class="n">msgStr</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ZygoteStartFailedEx</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">BufferedWriter</span> <span class="n">zygoteWriter</span> <span class="o">=</span> <span class="n">zygoteState</span><span class="o">.</span><span class="na">mZygoteOutputWriter</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">DataInputStream</span> <span class="n">zygoteInputStream</span> <span class="o">=</span> <span class="n">zygoteState</span><span class="o">.</span><span class="na">mZygoteInputStream</span><span class="o">;</span> <span class="n">zygoteWriter</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">msgStr</span><span class="o">);</span> <span class="n">zygoteWriter</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span> <span class="c1">// Always read the entire result from the input stream to avoid leaving</span> <span class="c1">// bytes in the stream for future process starts to accidentally stumble</span> <span class="c1">// upon.</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span><span class="o">();</span> <span class="n">result</span><span class="o">.</span><span class="na">pid</span> <span class="o">=</span> <span class="n">zygoteInputStream</span><span class="o">.</span><span class="na">readInt</span><span class="o">();</span> <span class="n">result</span><span class="o">.</span><span class="na">usingWrapper</span> <span class="o">=</span> <span class="n">zygoteInputStream</span><span class="o">.</span><span class="na">readBoolean</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ZygoteStartFailedEx</span><span class="o">(</span><span class="s">"fork() failed"</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="n">zygoteState</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="nc">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="no">LOG_TAG</span><span class="o">,</span> <span class="s">"IO Exception while communicating with Zygote - "</span> <span class="o">+</span> <span class="n">ex</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ZygoteStartFailedEx</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>总的来说,上面就是 zygote 孵化进程的过程。主要分为以下几个步骤:</p> <ul> <li>启动进程的参数封装</li> <li>打开 Socket 套接字,并与 zygote 进程建立连接(详见 <code class="language-plaintext highlighter-rouge">openZygoteSocketIfNeeded</code>)</li> <li>将进程的参数信息写入 <code class="language-plaintext highlighter-rouge">BufferedWriter</code>,并传输给 zygote 进程;通过 <code class="language-plaintext highlighter-rouge">DataInputStream</code> 来接收 zygote 进程返回的结果</li> </ul> <p>打开套接字并与 ZygoteServer 建立连接是在 openZygoteSocketIfNeeded 中完成的:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Tries to open a session socket to a Zygote process with a compatible ABI if one is not * already open. If a compatible session socket is already open that session socket is returned. * This function may block and may have to try connecting to multiple Zygotes to find the * appropriate one. Requires that mLock be held. */</span> <span class="kd">private</span> <span class="nc">ZygoteState</span> <span class="nf">openZygoteSocketIfNeeded</span><span class="o">(</span><span class="nc">String</span> <span class="n">abi</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">ZygoteStartFailedEx</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">attemptConnectionToPrimaryZygote</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">primaryZygoteState</span><span class="o">.</span><span class="na">matches</span><span class="o">(</span><span class="n">abi</span><span class="o">))</span> <span class="o">{</span> <span class="k">return</span> <span class="n">primaryZygoteState</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">mZygoteSecondarySocketAddress</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// The primary zygote didn't match. Try the secondary.</span> <span class="n">attemptConnectionToSecondaryZygote</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">secondaryZygoteState</span><span class="o">.</span><span class="na">matches</span><span class="o">(</span><span class="n">abi</span><span class="o">))</span> <span class="o">{</span> <span class="k">return</span> <span class="n">secondaryZygoteState</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ioe</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ZygoteStartFailedEx</span><span class="o">(</span><span class="s">"Error connecting to zygote"</span><span class="o">,</span> <span class="n">ioe</span><span class="o">);</span> <span class="o">}</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">ZygoteStartFailedEx</span><span class="o">(</span><span class="s">"Unsupported zygote ABI: "</span> <span class="o">+</span> <span class="n">abi</span><span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">attemptConnectionToPrimaryZygote</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">primaryZygoteState</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">primaryZygoteState</span><span class="o">.</span><span class="na">isClosed</span><span class="o">())</span> <span class="o">{</span> <span class="n">primaryZygoteState</span> <span class="o">=</span> <span class="nc">ZygoteState</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">mZygoteSocketAddress</span><span class="o">,</span> <span class="n">mUsapPoolSocketAddress</span><span class="o">);</span> <span class="n">maybeSetApiDenylistExemptions</span><span class="o">(</span><span class="n">primaryZygoteState</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="n">maybeSetHiddenApiAccessLogSampleRate</span><span class="o">(</span><span class="n">primaryZygoteState</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>如果没有打开 zygote 进程的套接字,则尝试开启并建立连接。下面看下 <code class="language-plaintext highlighter-rouge">ZygoteState</code> 类的静态方法 <code class="language-plaintext highlighter-rouge">connect</code>。</p> <blockquote> <p><em>frameworks/base/core/java/android/os/ZygoteProcess.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Create a new ZygoteState object by connecting to the given Zygote socket and saving the * given USAP socket address. * * @param zygoteSocketAddress Zygote socket to connect to * @param usapSocketAddress USAP socket address to save for later * @return A new ZygoteState object containing a session socket for the given Zygote socket * address * @throws IOException */</span> <span class="kd">static</span> <span class="nc">ZygoteState</span> <span class="nf">connect</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">LocalSocketAddress</span> <span class="n">zygoteSocketAddress</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">LocalSocketAddress</span> <span class="n">usapSocketAddress</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="nc">DataInputStream</span> <span class="n">zygoteInputStream</span><span class="o">;</span> <span class="nc">BufferedWriter</span> <span class="n">zygoteOutputWriter</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">LocalSocket</span> <span class="n">zygoteSessionSocket</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LocalSocket</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">zygoteSocketAddress</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"zygoteSocketAddress can't be null"</span><span class="o">);</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="n">zygoteSessionSocket</span><span class="o">.</span><span class="na">connect</span><span class="o">(</span><span class="n">zygoteSocketAddress</span><span class="o">);</span> <span class="n">zygoteInputStream</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DataInputStream</span><span class="o">(</span><span class="n">zygoteSessionSocket</span><span class="o">.</span><span class="na">getInputStream</span><span class="o">());</span> <span class="n">zygoteOutputWriter</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">BufferedWriter</span><span class="o">(</span> <span class="k">new</span> <span class="nf">OutputStreamWriter</span><span class="o">(</span><span class="n">zygoteSessionSocket</span><span class="o">.</span><span class="na">getOutputStream</span><span class="o">()),</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">SOCKET_BUFFER_SIZE</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">zygoteSessionSocket</span><span class="o">.</span><span class="na">close</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IOException</span> <span class="n">ignore</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">ZygoteState</span><span class="o">(</span><span class="n">zygoteSocketAddress</span><span class="o">,</span> <span class="n">usapSocketAddress</span><span class="o">,</span> <span class="n">zygoteSessionSocket</span><span class="o">,</span> <span class="n">zygoteInputStream</span><span class="o">,</span> <span class="n">zygoteOutputWriter</span><span class="o">,</span> <span class="n">getAbiList</span><span class="o">(</span><span class="n">zygoteOutputWriter</span><span class="o">,</span> <span class="n">zygoteInputStream</span><span class="o">));</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">ZygoteState</code> 类哟用于表示与 Zygote 进程通信的状态。假如传递给 connect 方法的实参是 <code class="language-plaintext highlighter-rouge">ZYGOTE_SOCKET</code>(即”zygote”),这代表要连接的远程地址。该方法中首先创建一个 <code class="language-plaintext highlighter-rouge">LocalSocket</code> 对象,接着调用其 <code class="language-plaintext highlighter-rouge">connect</code> 方法,连接成功之后,就可以得到套接字的输入和输出流,并将输入流其封装成 <code class="language-plaintext highlighter-rouge">DataInputStream</code> 对象,然后将输出流封装成 <code class="language-plaintext highlighter-rouge">BufferedWriter</code> 对象。接着就可以通过 <code class="language-plaintext highlighter-rouge">DataInputStream</code> 对象获得 Zygote 进程发送过来的消息,而通过 <code class="language-plaintext highlighter-rouge">BufferedWriter</code> 对象发送消息给 Zygote 进程。</p> <p>那么问题来了,我们只知道了 socket 通信的一端,另外一端如何找到呢?其实上一篇关于<a href="https://dorck.cn/2023/04/22/android-initialization/">Android系统启动过程</a>的文章中在分析到 zygote 进程启动过程时已经提过一笔了:</p> <blockquote> <p><em>com.android.internal.os.ZygoteInit</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">)</span> <span class="o">{</span> <span class="nc">ZygoteServer</span> <span class="n">zygoteServer</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="nc">Runnable</span> <span class="n">caller</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">zygoteSocketName</span> <span class="o">=</span> <span class="s">"zygote"</span><span class="o">;</span> <span class="o">...</span> <span class="nc">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Accepting command socket connections"</span><span class="o">);</span> <span class="c1">// The select loop returns early in the child process after a fork and</span> <span class="c1">// loops forever in the zygote.</span> <span class="n">caller</span> <span class="o">=</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">runSelectLoop</span><span class="o">(</span><span class="n">abiList</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"System zygote died with fatal exception"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">zygoteServer</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">closeServerSocket</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// We're in the child process and have exited the select loop. Proceed to execute the</span> <span class="c1">// command.</span> <span class="k">if</span> <span class="o">(</span><span class="n">caller</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">caller</span><span class="o">.</span><span class="na">run</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>zygote 进程通过 <code class="language-plaintext highlighter-rouge">ZygoteServer#runSelectLoop</code> 来循环监听其他进程的 Socket 连接:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 </pre></td><td class="rouge-code"><pre> <span class="nc">Runnable</span> <span class="nf">runSelectLoop</span><span class="o">(</span><span class="nc">String</span> <span class="n">abiList</span><span class="o">)</span> <span class="o">{</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">FileDescriptor</span><span class="o">&gt;</span> <span class="n">socketFDs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">ZygoteConnection</span><span class="o">&gt;</span> <span class="n">peers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span> <span class="c1">// sServerSocket是socket通信中的服务端,即zygote进程,保存到fds[0]</span> <span class="n">socketFDs</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">mZygoteSocket</span><span class="o">.</span><span class="na">getFileDescriptor</span><span class="o">());</span> <span class="n">peers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span> <span class="n">mUsapPoolRefillTriggerTimestamp</span> <span class="o">=</span> <span class="no">INVALID_TIMESTAMP</span><span class="o">;</span> <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="nc">StructPollfd</span><span class="o">[]</span> <span class="n">pollFDs</span><span class="o">;</span> <span class="c1">// Allocate enough space for the poll structs, taking into account</span> <span class="c1">// the state of the USAP pool for this Zygote (could be a</span> <span class="c1">// regular Zygote, a WebView Zygote, or an AppZygote).</span> <span class="k">if</span> <span class="o">(</span><span class="n">mUsapPoolEnabled</span><span class="o">)</span> <span class="o">{</span> <span class="n">usapPipeFDs</span> <span class="o">=</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">getUsapPipeFDs</span><span class="o">();</span> <span class="n">pollFDs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructPollfd</span><span class="o">[</span><span class="n">socketFDs</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">usapPipeFDs</span><span class="o">.</span><span class="na">length</span><span class="o">];</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">pollFDs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructPollfd</span><span class="o">[</span><span class="n">socketFDs</span><span class="o">.</span><span class="na">size</span><span class="o">()];</span> <span class="o">}</span> <span class="cm">/* * For reasons of correctness the USAP pool pipe and event FDs * must be processed before the session and server sockets. This * is to ensure that the USAP pool accounting information is * accurate when handling other requests like API deny list * exemptions. */</span> <span class="kt">int</span> <span class="n">pollIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="nc">FileDescriptor</span> <span class="n">socketFD</span> <span class="o">:</span> <span class="n">socketFDs</span><span class="o">)</span> <span class="o">{</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructPollfd</span><span class="o">();</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">fd</span> <span class="o">=</span> <span class="n">socketFD</span><span class="o">;</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">events</span> <span class="o">=</span> <span class="o">(</span><span class="kt">short</span><span class="o">)</span> <span class="no">POLLIN</span><span class="o">;</span> <span class="o">++</span><span class="n">pollIndex</span><span class="o">;</span> <span class="o">}</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">usapPoolEventFDIndex</span> <span class="o">=</span> <span class="n">pollIndex</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">mUsapPoolEnabled</span><span class="o">)</span> <span class="o">{</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructPollfd</span><span class="o">();</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">fd</span> <span class="o">=</span> <span class="n">mUsapPoolEventFD</span><span class="o">;</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">events</span> <span class="o">=</span> <span class="o">(</span><span class="kt">short</span><span class="o">)</span> <span class="no">POLLIN</span><span class="o">;</span> <span class="o">++</span><span class="n">pollIndex</span><span class="o">;</span> <span class="c1">// The usapPipeFDs array will always be filled in if the USAP Pool is enabled.</span> <span class="k">assert</span> <span class="n">usapPipeFDs</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">usapPipeFD</span> <span class="o">:</span> <span class="n">usapPipeFDs</span><span class="o">)</span> <span class="o">{</span> <span class="nc">FileDescriptor</span> <span class="n">managedFd</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileDescriptor</span><span class="o">();</span> <span class="n">managedFd</span><span class="o">.</span><span class="na">setInt</span><span class="err">$</span><span class="o">(</span><span class="n">usapPipeFD</span><span class="o">);</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">StructPollfd</span><span class="o">();</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">fd</span> <span class="o">=</span> <span class="n">managedFd</span><span class="o">;</span> <span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">events</span> <span class="o">=</span> <span class="o">(</span><span class="kt">short</span><span class="o">)</span> <span class="no">POLLIN</span><span class="o">;</span> <span class="o">++</span><span class="n">pollIndex</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="kt">int</span> <span class="n">pollTimeoutMs</span><span class="o">;</span> <span class="o">...</span> <span class="kt">int</span> <span class="n">pollReturnValue</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// 处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里</span> <span class="n">pollReturnValue</span> <span class="o">=</span> <span class="nc">Os</span><span class="o">.</span><span class="na">poll</span><span class="o">(</span><span class="n">pollFDs</span><span class="o">,</span> <span class="n">pollTimeoutMs</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ErrnoException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"poll failed"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">pollReturnValue</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// The poll returned zero results either when the timeout value has been exceeded</span> <span class="c1">// or when a non-blocking poll is issued and no FDs are ready. In either case it</span> <span class="c1">// is time to refill the pool. This will result in a duplicate assignment when</span> <span class="c1">// the non-blocking poll returns zero results, but it avoids an additional</span> <span class="c1">// conditional in the else branch.</span> <span class="n">mUsapPoolRefillTriggerTimestamp</span> <span class="o">=</span> <span class="no">INVALID_TIMESTAMP</span><span class="o">;</span> <span class="n">mUsapPoolRefillAction</span> <span class="o">=</span> <span class="nc">UsapPoolRefillAction</span><span class="o">.</span><span class="na">DELAYED</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="kt">boolean</span> <span class="n">usapPoolFDRead</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">while</span> <span class="o">(--</span><span class="n">pollIndex</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 采用I/O多路复用机制,当接收到客户端发出连接请求或者数据处理请求到来,则往下执行;</span> <span class="c1">// 否则进入continue,跳出本次循环。</span> <span class="k">if</span> <span class="o">((</span><span class="n">pollFDs</span><span class="o">[</span><span class="n">pollIndex</span><span class="o">].</span><span class="na">revents</span> <span class="o">&amp;</span> <span class="no">POLLIN</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="k">continue</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">pollIndex</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 即fds[0],代表的是sServerSocket,则意味着有客户端连接请求;</span> <span class="c1">// 则创建ZygoteConnection对象,并添加到fds。</span> <span class="nc">ZygoteConnection</span> <span class="n">newPeer</span> <span class="o">=</span> <span class="n">acceptCommandPeer</span><span class="o">(</span><span class="n">abiList</span><span class="o">);</span> <span class="n">peers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">newPeer</span><span class="o">);</span> <span class="n">socketFDs</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">newPeer</span><span class="o">.</span><span class="na">getFileDescriptor</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">pollIndex</span> <span class="o">&lt;</span> <span class="n">usapPoolEventFDIndex</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 从Zygote服务器套接字接受会话套接字</span> <span class="k">try</span> <span class="o">{</span> <span class="nc">ZygoteConnection</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">peers</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">pollIndex</span><span class="o">);</span> <span class="kt">boolean</span> <span class="n">multipleForksOK</span> <span class="o">=</span> <span class="o">!</span><span class="n">isUsapPoolEnabled</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="nc">ZygoteHooks</span><span class="o">.</span><span class="na">isIndefiniteThreadSuspensionSafe</span><span class="o">();</span> <span class="kd">final</span> <span class="nc">Runnable</span> <span class="n">command</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="na">processCommand</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">multipleForksOK</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="c1">// Reset the child flag, in the event that the child process is a child-</span> <span class="c1">// zygote. The flag will not be consulted this loop pass after the</span> <span class="c1">// Runnable is returned.</span> <span class="n">mIsForkChild</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">mUsapPoolEnabled</code> 标识是否开启 USAP 机制。从Android Q(10)开始,Google 引入了一种新的机制:USAP(Unspecialized App Process)。通过 prefork 的方式提前创建好一批进程,当有应用启动时,直接将已经创建好的进程分配给它,从而省去了 fork 的动作,因此可以提升性能。后面将会有专门篇幅来研究 USAP 机制的运行原理,本文暂不做详细介绍。</p> <p>其实,最终 ZygoteServer 会接收到来自 system_server 进程 AMS 创建新应用进程的消息,并通过 <code class="language-plaintext highlighter-rouge">ZygoteConnection#processCommand</code> 来处理并创建新进程:</p> <blockquote> <p><em>frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Reads a command from the command socket. If a child is successfully forked, a * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child * process. {@code null} is always returned in the parent process (the zygote). * If multipleOK is set, we may keep processing additional fork commands before returning. * * If the client closes the socket, an {@code EOF} condition is set, which callers can test * for by calling {@code ZygoteConnection.isClosedByPeer}. */</span> <span class="nc">Runnable</span> <span class="nf">processCommand</span><span class="o">(</span><span class="nc">ZygoteServer</span> <span class="n">zygoteServer</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">multipleOK</span><span class="o">)</span> <span class="o">{</span> <span class="nc">ZygoteArguments</span> <span class="n">parsedArgs</span><span class="o">;</span> <span class="k">try</span> <span class="o">(</span><span class="nc">ZygoteCommandBuffer</span> <span class="n">argBuffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ZygoteCommandBuffer</span><span class="o">(</span><span class="n">mSocket</span><span class="o">))</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="kt">int</span> <span class="n">pid</span><span class="o">;</span> <span class="nc">FileDescriptor</span> <span class="n">childPipeFd</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="nc">FileDescriptor</span> <span class="n">serverPipeFd</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">canPreloadApp</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mPreloadApp</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">rawParcelData</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">getDecoder</span><span class="o">().</span><span class="na">decode</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mPreloadApp</span><span class="o">);</span> <span class="nc">Parcel</span> <span class="n">appInfoParcel</span> <span class="o">=</span> <span class="nc">Parcel</span><span class="o">.</span><span class="na">obtain</span><span class="o">();</span> <span class="n">appInfoParcel</span><span class="o">.</span><span class="na">unmarshall</span><span class="o">(</span><span class="n">rawParcelData</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">rawParcelData</span><span class="o">.</span><span class="na">length</span><span class="o">);</span> <span class="n">appInfoParcel</span><span class="o">.</span><span class="na">setDataPosition</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> <span class="nc">ApplicationInfo</span> <span class="n">appInfo</span> <span class="o">=</span> <span class="nc">ApplicationInfo</span><span class="o">.</span><span class="na">CREATOR</span><span class="o">.</span><span class="na">createFromParcel</span><span class="o">(</span><span class="n">appInfoParcel</span><span class="o">);</span> <span class="n">appInfoParcel</span><span class="o">.</span><span class="na">recycle</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">appInfo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">handlePreloadApp</span><span class="o">(</span><span class="n">appInfo</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Failed to deserialize --preload-app"</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mInvokeWith</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mStartChildZygote</span> <span class="o">||</span> <span class="o">!</span><span class="n">multipleOK</span> <span class="o">||</span> <span class="n">peer</span><span class="o">.</span><span class="na">getUid</span><span class="o">()</span> <span class="o">!=</span> <span class="nc">Process</span><span class="o">.</span><span class="na">SYSTEM_UID</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Continue using old code for now. TODO: Handle these cases in the other path.</span> <span class="n">pid</span> <span class="o">=</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">forkAndSpecialize</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mUid</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mGid</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mGids</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRuntimeFlags</span><span class="o">,</span> <span class="n">rlimits</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mMountExternal</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mSeInfo</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mNiceName</span><span class="o">,</span> <span class="n">fdsToClose</span><span class="o">,</span> <span class="n">fdsToIgnore</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mStartChildZygote</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mInstructionSet</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mAppDataDir</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mIsTopApp</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mPkgDataInfoList</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mAllowlistedDataInfoList</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mBindMountAppDataDirs</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mBindMountAppStorageDirs</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// in child</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">setForkChild</span><span class="o">();</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">closeServerSocket</span><span class="o">();</span> <span class="nc">IoUtils</span><span class="o">.</span><span class="na">closeQuietly</span><span class="o">(</span><span class="n">serverPipeFd</span><span class="o">);</span> <span class="n">serverPipeFd</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="k">return</span> <span class="nf">handleChildProc</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">,</span> <span class="n">childPipeFd</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mStartChildZygote</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// In the parent. A pid &lt; 0 indicates a failure and will be handled in</span> <span class="c1">// handleParentProc.</span> <span class="nc">IoUtils</span><span class="o">.</span><span class="na">closeQuietly</span><span class="o">(</span><span class="n">childPipeFd</span><span class="o">);</span> <span class="n">childPipeFd</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="n">handleParentProc</span><span class="o">(</span><span class="n">pid</span><span class="o">,</span> <span class="n">serverPipeFd</span><span class="o">);</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">IoUtils</span><span class="o">.</span><span class="na">closeQuietly</span><span class="o">(</span><span class="n">childPipeFd</span><span class="o">);</span> <span class="nc">IoUtils</span><span class="o">.</span><span class="na">closeQuietly</span><span class="o">(</span><span class="n">serverPipeFd</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="nc">ZygoteHooks</span><span class="o">.</span><span class="na">preFork</span><span class="o">();</span> <span class="nc">Runnable</span> <span class="n">result</span> <span class="o">=</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">forkSimpleApps</span><span class="o">(</span><span class="n">argBuffer</span><span class="o">,</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">getZygoteSocketFileDescriptor</span><span class="o">(),</span> <span class="n">peer</span><span class="o">.</span><span class="na">getUid</span><span class="o">(),</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">minChildUid</span><span class="o">(</span><span class="n">peer</span><span class="o">),</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mNiceName</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">result</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// parent; we finished some number of forks. Result is Boolean.</span> <span class="c1">// We already did the equivalent of handleParentProc().</span> <span class="nc">ZygoteHooks</span><span class="o">.</span><span class="na">postForkCommon</span><span class="o">();</span> <span class="c1">// argBuffer contains a command not understood by forksimpleApps.</span> <span class="k">continue</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// child; result is a Runnable.</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">setForkChild</span><span class="o">();</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">setAppProcessName</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">,</span> <span class="no">TAG</span><span class="o">);</span> <span class="c1">// ??? Necessary?</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>首先调用 ZygoteArguments 得到客户端发来的启动进程参数,接着进行权限检查,然后调用 <code class="language-plaintext highlighter-rouge">forkAndSpecialize</code> 创建新进程(native 层通过 fork() 来克隆出新进程,此时 native 层的进程其实相当于已经生成了),返回 pid 若为 0,则开始调用 <code class="language-plaintext highlighter-rouge">handleChildProc</code> 来处理子进程;如果返回 pid &gt; 0,则开始调用 <code class="language-plaintext highlighter-rouge">handleParentProc</code> 来处理父进程。注意,这里并不是简单的 if-else 关系,调用 forkAndSpecialize 之后其实会返回两次,一次返回到父进程,另一次返回到新启动的子进程。至于为什么会返回两次,这里先埋下一个<strong>彩蛋</strong>,后续文章会填坑。接下来我们来继续看下如果返回的 pid 是 0,即在子进程返回的情况下,handleChildProc 做了些什么:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="nc">Runnable</span> <span class="nf">handleChildProc</span><span class="o">(</span><span class="nc">ZygoteArguments</span> <span class="n">parsedArgs</span><span class="o">,</span> <span class="nc">FileDescriptor</span> <span class="n">pipeFd</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isZygote</span><span class="o">)</span> <span class="o">{</span> <span class="cm">/* * By the time we get here, the native code has closed the two actual Zygote * socket connections, and substituted /dev/null in their place. The LocalSocket * objects still need to be closed properly. */</span> <span class="n">closeSocket</span><span class="o">();</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">setAppProcessName</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">,</span> <span class="no">TAG</span><span class="o">);</span> <span class="c1">// End of the postFork event.</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mInvokeWith</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">WrapperInit</span><span class="o">.</span><span class="na">execApplication</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mInvokeWith</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mNiceName</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mTargetSdkVersion</span><span class="o">,</span> <span class="nc">VMRuntime</span><span class="o">.</span><span class="na">getCurrentInstructionSet</span><span class="o">(),</span> <span class="n">pipeFd</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRemainingArgs</span><span class="o">);</span> <span class="c1">// Should not get here.</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"WrapperInit.execApplication unexpectedly returned"</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isZygote</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">ZygoteInit</span><span class="o">.</span><span class="na">zygoteInit</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mTargetSdkVersion</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mDisabledCompatChanges</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRemainingArgs</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* classLoader */</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">ZygoteInit</span><span class="o">.</span><span class="na">childZygoteInit</span><span class="o">(</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRemainingArgs</span> <span class="cm">/* classLoader */</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>由于我们创建的是应用进程,所以上方代码显然会执行到 <code class="language-plaintext highlighter-rouge">ZygoteInit.childZygoteInit</code>:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 </pre></td><td class="rouge-code"><pre><span class="cm">/** * The main function called when starting a child zygote process. This is used as an alternative * to zygoteInit(), which skips calling into initialization routines that start the Binder * threadpool. */</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">childZygoteInit</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">)</span> <span class="o">{</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">Arguments</span> <span class="n">args</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">Arguments</span><span class="o">(</span><span class="n">argv</span><span class="o">);</span> <span class="k">return</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">findStaticMain</span><span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">startClass</span><span class="o">,</span> <span class="n">args</span><span class="o">.</span><span class="na">startArgs</span><span class="o">,</span> <span class="cm">/* classLoader= */</span><span class="kc">null</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/** * Invokes a static "main(argv[]) method on class "className". * Converts various failing exceptions into RuntimeExceptions, with * the assumption that they will then cause the VM instance to exit. * * @param className Fully-qualified class name * @param argv Argument vector for main() * @param classLoader the classLoader to load {@className} with */</span> <span class="kd">protected</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">findStaticMain</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">cl</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">cl</span> <span class="o">=</span> <span class="nc">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">className</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">classLoader</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ClassNotFoundException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Missing class when invoking static main "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="nc">Method</span> <span class="n">m</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">m</span> <span class="o">=</span> <span class="n">cl</span><span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="s">"main"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[].</span><span class="na">class</span> <span class="o">});</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchMethodException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Missing static main on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SecurityException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Problem getting static main on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="kt">int</span> <span class="n">modifiers</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="na">getModifiers</span><span class="o">();</span> <span class="k">if</span> <span class="o">(!</span> <span class="o">(</span><span class="nc">Modifier</span><span class="o">.</span><span class="na">isStatic</span><span class="o">(</span><span class="n">modifiers</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="nc">Modifier</span><span class="o">.</span><span class="na">isPublic</span><span class="o">(</span><span class="n">modifiers</span><span class="o">)))</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Main method is not public and static on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">MethodAndArgsCaller</span><span class="o">(</span><span class="n">m</span><span class="o">,</span> <span class="n">argv</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>由上述代码可知,最终会通过反射的方式来为之前传递进来的 <code class="language-plaintext highlighter-rouge">processClass</code> 创建实例对象,根据上面流程的分析,这里的 processClass 显然是 <code class="language-plaintext highlighter-rouge">android.app.ActivityThread</code> 这个类,接着调用该类的 <code class="language-plaintext highlighter-rouge">main</code> 方法:</p> <blockquote> <p><em>frameworks/base/core/java/android/app/ActivityThread.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"ActivityThreadMain"</span><span class="o">);</span> <span class="c1">// Install selective syscall interception</span> <span class="nc">AndroidOs</span><span class="o">.</span><span class="na">install</span><span class="o">();</span> <span class="o">...</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">prepareMainLooper</span><span class="o">();</span> <span class="o">...</span> <span class="nc">ActivityThread</span> <span class="n">thread</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ActivityThread</span><span class="o">();</span> <span class="n">thread</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="n">startSeq</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">sMainThreadHandler</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">sMainThreadHandler</span> <span class="o">=</span> <span class="n">thread</span><span class="o">.</span><span class="na">getHandler</span><span class="o">();</span> <span class="o">}</span> <span class="c1">// End of event ActivityThreadMain.</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">loop</span><span class="o">();</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Main thread loop unexpectedly exited"</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>ActivityThread 的 <code class="language-plaintext highlighter-rouge">main</code> 中准备一个主线程的 Looper 并初始化消息队列,并创建一个 ActivityThread 实例,执行 <code class="language-plaintext highlighter-rouge">attach</code> 方法创建 Application 和绑定生命周期,最后开启主线程的消息循环。我们重点看下 attach 方法:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="kt">void</span> <span class="nf">attach</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">system</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startSeq</span><span class="o">)</span> <span class="o">{</span> <span class="n">sCurrentActivityThread</span> <span class="o">=</span> <span class="k">this</span><span class="o">;</span> <span class="n">mConfigurationController</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConfigurationController</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="n">mSystemThread</span> <span class="o">=</span> <span class="n">system</span><span class="o">;</span> <span class="c1">// 此处 system 为 false</span> <span class="k">if</span> <span class="o">(!</span><span class="n">system</span><span class="o">)</span> <span class="o">{</span> <span class="n">android</span><span class="o">.</span><span class="na">ddm</span><span class="o">.</span><span class="na">DdmHandleAppName</span><span class="o">.</span><span class="na">setAppName</span><span class="o">(</span><span class="s">"&lt;pre-initialized&gt;"</span><span class="o">,</span> <span class="nc">UserHandle</span><span class="o">.</span><span class="na">myUserId</span><span class="o">());</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">setApplicationObject</span><span class="o">(</span><span class="n">mAppThread</span><span class="o">.</span><span class="na">asBinder</span><span class="o">());</span> <span class="kd">final</span> <span class="nc">IActivityManager</span> <span class="n">mgr</span> <span class="o">=</span> <span class="nc">ActivityManager</span><span class="o">.</span><span class="na">getService</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mgr</span><span class="o">.</span><span class="na">attachApplication</span><span class="o">(</span><span class="n">mAppThread</span><span class="o">,</span> <span class="n">startSeq</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">.</span><span class="na">rethrowFromSystemServer</span><span class="o">();</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>attach 中执行了 IActivityManager 的 <code class="language-plaintext highlighter-rouge">attachApplication</code> 方法,而 IActivityManager 其实是一个 Binder 接口,这里通过 ActivityManager.getService 获取它的代理服务 AMS,进而调用 <code class="language-plaintext highlighter-rouge">attachApplication</code> 来将 ActivityThread 内部类 ApplicationThread 绑定到 AMS,这样就可以通过 Binder IPC 间接控制应用进程了:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 </pre></td><td class="rouge-code"><pre><span class="nd">@Override</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">attachApplication</span><span class="o">(</span><span class="nc">IApplicationThread</span> <span class="n">thread</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startSeq</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">thread</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">SecurityException</span><span class="o">(</span><span class="s">"Invalid application interface"</span><span class="o">);</span> <span class="o">}</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="k">this</span><span class="o">)</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">callingPid</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">getCallingPid</span><span class="o">();</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">callingUid</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">getCallingUid</span><span class="o">();</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">origId</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">clearCallingIdentity</span><span class="o">();</span> <span class="n">attachApplicationLocked</span><span class="o">(</span><span class="n">thread</span><span class="o">,</span> <span class="n">callingPid</span><span class="o">,</span> <span class="n">callingUid</span><span class="o">,</span> <span class="n">startSeq</span><span class="o">);</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">restoreCallingIdentity</span><span class="o">(</span><span class="n">origId</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">attachApplicationLocked</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">IApplicationThread</span> <span class="n">thread</span><span class="o">,</span> <span class="kt">int</span> <span class="n">pid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">callingUid</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startSeq</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Find the application record that is being attached... either via</span> <span class="c1">// the pid if we are running in multiple processes, or just pull the</span> <span class="c1">// next app record if we are emulating process with anonymous threads.</span> <span class="nc">ProcessRecord</span> <span class="n">app</span><span class="o">;</span> <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">();</span> <span class="kt">long</span> <span class="n">bindApplicationTimeMillis</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">pid</span> <span class="o">!=</span> <span class="no">MY_PID</span> <span class="o">&amp;&amp;</span> <span class="n">pid</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPidsSelfLocked</span><span class="o">)</span> <span class="o">{</span> <span class="n">app</span> <span class="o">=</span> <span class="n">mPidsSelfLocked</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">pid</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">app</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getStartUid</span><span class="o">()</span> <span class="o">!=</span> <span class="n">callingUid</span> <span class="o">||</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartSeq</span><span class="o">()</span> <span class="o">!=</span> <span class="n">startSeq</span><span class="o">))</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// If there is already an app occupying that pid that hasn't been cleaned up</span> <span class="n">cleanUpApplicationRecordLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">pid</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="o">-</span><span class="mi">1</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/*replacingPid*/</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* fromBinderDied */</span><span class="o">);</span> <span class="n">removePidLocked</span><span class="o">(</span><span class="n">pid</span><span class="o">,</span> <span class="n">app</span><span class="o">);</span> <span class="n">app</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">app</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="o">...</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="nc">ApplicationInfo</span> <span class="n">appInfo</span> <span class="o">=</span> <span class="n">instr</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">instr</span><span class="o">.</span><span class="na">mTargetInfo</span> <span class="o">:</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">;</span> <span class="n">app</span><span class="o">.</span><span class="na">setCompat</span><span class="o">(</span><span class="n">compatibilityInfoForPackage</span><span class="o">(</span><span class="n">appInfo</span><span class="o">));</span> <span class="k">if</span> <span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPoint</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// This is an isolated process which should just call an entry point instead of</span> <span class="c1">// being bound to an application.</span> <span class="n">thread</span><span class="o">.</span><span class="na">runIsolatedEntryPoint</span><span class="o">(</span> <span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPoint</span><span class="o">(),</span> <span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPointArgs</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">instr2</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">thread</span><span class="o">.</span><span class="na">bindApplication</span><span class="o">(</span><span class="n">processName</span><span class="o">,</span> <span class="n">appInfo</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">sdkSandboxClientAppVolumeUuid</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">sdkSandboxClientAppPackage</span><span class="o">,</span> <span class="n">providerList</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">profilerInfo</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">testMode</span><span class="o">,</span> <span class="n">mBinderTransactionTrackingEnabled</span><span class="o">,</span> <span class="n">enableTrackAllocation</span><span class="o">,</span> <span class="n">isRestrictedBackupMode</span> <span class="o">||</span> <span class="o">!</span><span class="n">normalMode</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">isPersistent</span><span class="o">(),</span> <span class="k">new</span> <span class="nf">Configuration</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getWindowProcessController</span><span class="o">().</span><span class="na">getConfiguration</span><span class="o">()),</span> <span class="n">app</span><span class="o">.</span><span class="na">getCompat</span><span class="o">(),</span> <span class="n">getCommonServicesLocked</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">isolated</span><span class="o">),</span> <span class="n">mCoreSettingsObserver</span><span class="o">.</span><span class="na">getCoreSettingsLocked</span><span class="o">(),</span> <span class="n">buildSerial</span><span class="o">,</span> <span class="n">autofillOptions</span><span class="o">,</span> <span class="n">contentCaptureOptions</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getDisabledCompatChanges</span><span class="o">(),</span> <span class="n">serializedSystemFontMap</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartElapsedTime</span><span class="o">(),</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartUptime</span><span class="o">());</span> <span class="o">}</span> <span class="o">...</span> <span class="c1">// Make app active after binding application or client may be running requests (e.g</span> <span class="c1">// starting activities) before it is ready.</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mProcLock</span><span class="o">)</span> <span class="o">{</span> <span class="n">app</span><span class="o">.</span><span class="na">makeActive</span><span class="o">(</span><span class="n">thread</span><span class="o">,</span> <span class="n">mProcessStats</span><span class="o">);</span> <span class="n">checkTime</span><span class="o">(</span><span class="n">startTime</span><span class="o">,</span> <span class="s">"attachApplicationLocked: immediately after bindApplication"</span><span class="o">);</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="n">didSomething</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">// See if the top visible activity is waiting to run in this process...</span> <span class="k">if</span> <span class="o">(</span><span class="n">normalMode</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">didSomething</span> <span class="o">=</span> <span class="n">mAtmInternal</span><span class="o">.</span><span class="na">attachApplication</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getWindowProcessController</span><span class="o">());</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">wtf</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Exception thrown launching activities in "</span> <span class="o">+</span> <span class="n">app</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span> <span class="n">badApp</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>ApplicationThread 作为 ActivityThread 的内部类,它是 <code class="language-plaintext highlighter-rouge">IApplicationThread</code> 这个 Binder 接口的最终实现类,即 ApplicationThread 内部实现了一套 AIDL 接口,这个接口与 Application 的创建、Activity 及 Service 等四大组件启动等息息相关。最终调用 <code class="language-plaintext highlighter-rouge">IApplicationThread</code> 的 <code class="language-plaintext highlighter-rouge">bindApplication</code> 方法:</p> <blockquote> <p><em>frameworks/base/core/java/android/app/ActivityThread.java#ApplicationThread</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 </pre></td><td class="rouge-code"><pre><span class="nd">@Override</span> <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">bindApplication</span><span class="o">(</span><span class="nc">String</span> <span class="n">processName</span><span class="o">,</span> <span class="nc">ApplicationInfo</span> <span class="n">appInfo</span><span class="o">,</span> <span class="nc">String</span> <span class="n">sdkSandboxClientAppVolumeUuid</span><span class="o">,</span> <span class="nc">String</span> <span class="n">sdkSandboxClientAppPackage</span><span class="o">,</span> <span class="nc">ProviderInfoList</span> <span class="n">providerList</span><span class="o">,</span> <span class="nc">ComponentName</span> <span class="n">instrumentationName</span><span class="o">,</span> <span class="nc">ProfilerInfo</span> <span class="n">profilerInfo</span><span class="o">,</span> <span class="nc">Bundle</span> <span class="n">instrumentationArgs</span><span class="o">,</span> <span class="nc">IInstrumentationWatcher</span> <span class="n">instrumentationWatcher</span><span class="o">,</span> <span class="nc">IUiAutomationConnection</span> <span class="n">instrumentationUiConnection</span><span class="o">,</span> <span class="kt">int</span> <span class="n">debugMode</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">enableBinderTracking</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">trackAllocation</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isRestrictedBackupMode</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">persistent</span><span class="o">,</span> <span class="nc">Configuration</span> <span class="n">config</span><span class="o">,</span> <span class="nc">CompatibilityInfo</span> <span class="n">compatInfo</span><span class="o">,</span> <span class="nc">Map</span> <span class="n">services</span><span class="o">,</span> <span class="nc">Bundle</span> <span class="n">coreSettings</span><span class="o">,</span> <span class="nc">String</span> <span class="n">buildSerial</span><span class="o">,</span> <span class="nc">AutofillOptions</span> <span class="n">autofillOptions</span><span class="o">,</span> <span class="nc">ContentCaptureOptions</span> <span class="n">contentCaptureOptions</span><span class="o">,</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nc">SharedMemory</span> <span class="n">serializedSystemFontMap</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startRequestedElapsedTime</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startRequestedUptime</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="nc">AppBindData</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AppBindData</span><span class="o">();</span> <span class="n">data</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">processName</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">appInfo</span> <span class="o">=</span> <span class="n">appInfo</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">sdkSandboxClientAppVolumeUuid</span> <span class="o">=</span> <span class="n">sdkSandboxClientAppVolumeUuid</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">sdkSandboxClientAppPackage</span> <span class="o">=</span> <span class="n">sdkSandboxClientAppPackage</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">providers</span> <span class="o">=</span> <span class="n">providerList</span><span class="o">.</span><span class="na">getList</span><span class="o">();</span> <span class="n">data</span><span class="o">.</span><span class="na">instrumentationName</span> <span class="o">=</span> <span class="n">instrumentationName</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">instrumentationArgs</span> <span class="o">=</span> <span class="n">instrumentationArgs</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">instrumentationWatcher</span> <span class="o">=</span> <span class="n">instrumentationWatcher</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">instrumentationUiAutomationConnection</span> <span class="o">=</span> <span class="n">instrumentationUiConnection</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">debugMode</span> <span class="o">=</span> <span class="n">debugMode</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">enableBinderTracking</span> <span class="o">=</span> <span class="n">enableBinderTracking</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">trackAllocation</span> <span class="o">=</span> <span class="n">trackAllocation</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">restrictedBackupMode</span> <span class="o">=</span> <span class="n">isRestrictedBackupMode</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">persistent</span> <span class="o">=</span> <span class="n">persistent</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">compatInfo</span> <span class="o">=</span> <span class="n">compatInfo</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">initProfilerInfo</span> <span class="o">=</span> <span class="n">profilerInfo</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">buildSerial</span> <span class="o">=</span> <span class="n">buildSerial</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">autofillOptions</span> <span class="o">=</span> <span class="n">autofillOptions</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">contentCaptureOptions</span> <span class="o">=</span> <span class="n">contentCaptureOptions</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">disabledCompatChanges</span> <span class="o">=</span> <span class="n">disabledCompatChanges</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">mSerializedSystemFontMap</span> <span class="o">=</span> <span class="n">serializedSystemFontMap</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">startRequestedElapsedTime</span> <span class="o">=</span> <span class="n">startRequestedElapsedTime</span><span class="o">;</span> <span class="n">data</span><span class="o">.</span><span class="na">startRequestedUptime</span> <span class="o">=</span> <span class="n">startRequestedUptime</span><span class="o">;</span> <span class="n">sendMessage</span><span class="o">(</span><span class="no">H</span><span class="o">.</span><span class="na">BIND_APPLICATION</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>上面代码通过一个 Handler 实例发送 <code class="language-plaintext highlighter-rouge">H.BIND_APPLICATION</code> 消息,意味着需要构建和绑定 Application 对象。这个 Handler 同样是 ActivityThread 的内部类:</p> <blockquote> <p><em>frameworks/base/core/java/android/app/ActivityThread.java#H</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">H</span> <span class="kd">extends</span> <span class="nc">Handler</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">BIND_APPLICATION</span> <span class="o">=</span> <span class="mi">110</span><span class="o">;</span> <span class="o">...</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleMessage</span><span class="o">(</span><span class="nc">Message</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="no">DEBUG_MESSAGES</span><span class="o">)</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"&gt;&gt;&gt; handling: "</span> <span class="o">+</span> <span class="n">codeToString</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">what</span><span class="o">));</span> <span class="k">switch</span> <span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">what</span><span class="o">)</span> <span class="o">{</span> <span class="k">case</span> <span class="nl">BIND_APPLICATION:</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"bindApplication"</span><span class="o">);</span> <span class="nc">AppBindData</span> <span class="n">data</span> <span class="o">=</span> <span class="o">(</span><span class="nc">AppBindData</span><span class="o">)</span><span class="n">msg</span><span class="o">.</span><span class="na">obj</span><span class="o">;</span> <span class="n">handleBindApplication</span><span class="o">(</span><span class="n">data</span><span class="o">);</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="k">break</span><span class="o">;</span> <span class="k">case</span> <span class="nl">EXIT_APPLICATION:</span> <span class="k">if</span> <span class="o">(</span><span class="n">mInitialApplication</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">mInitialApplication</span><span class="o">.</span><span class="na">onTerminate</span><span class="o">();</span> <span class="o">}</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">myLooper</span><span class="o">().</span><span class="na">quit</span><span class="o">();</span> <span class="k">break</span><span class="o">;</span> <span class="o">}</span> <span class="nc">Object</span> <span class="n">obj</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="na">obj</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">SomeArgs</span><span class="o">)</span> <span class="o">{</span> <span class="o">((</span><span class="nc">SomeArgs</span><span class="o">)</span> <span class="n">obj</span><span class="o">).</span><span class="na">recycle</span><span class="o">();</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="no">DEBUG_MESSAGES</span><span class="o">)</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">v</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"&lt;&lt;&lt; done: "</span> <span class="o">+</span> <span class="n">codeToString</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">what</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过跨进程通信,Application 的创建和绑定最终回到了主线程:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleBindApplication</span><span class="o">(</span><span class="nc">AppBindData</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// Instrumentation info affects the class loader, so load it before</span> <span class="c1">// setting up the app context.</span> <span class="kd">final</span> <span class="nc">InstrumentationInfo</span> <span class="n">ii</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">instrumentationName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">ii</span> <span class="o">=</span> <span class="n">prepareInstrumentation</span><span class="o">(</span><span class="n">data</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">ii</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="kd">final</span> <span class="nc">ContextImpl</span> <span class="n">appContext</span> <span class="o">=</span> <span class="nc">ContextImpl</span><span class="o">.</span><span class="na">createAppContext</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">data</span><span class="o">.</span><span class="na">info</span><span class="o">);</span> <span class="n">mConfigurationController</span><span class="o">.</span><span class="na">updateLocaleListFromAppContext</span><span class="o">(</span><span class="n">appContext</span><span class="o">);</span> <span class="o">...</span> <span class="c1">// Continue loading instrumentation.</span> <span class="k">if</span> <span class="o">(</span><span class="n">ii</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">initInstrumentation</span><span class="o">(</span><span class="n">ii</span><span class="o">,</span> <span class="n">data</span><span class="o">,</span> <span class="n">appContext</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">mInstrumentation</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Instrumentation</span><span class="o">();</span> <span class="n">mInstrumentation</span><span class="o">.</span><span class="na">basicInit</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Allow disk access during application and provider setup. This could</span> <span class="c1">// block processing ordered broadcasts, but later processing would</span> <span class="c1">// probably end up doing the same disk access.</span> <span class="nc">Application</span> <span class="n">app</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">StrictMode</span><span class="o">.</span><span class="na">ThreadPolicy</span> <span class="n">savedPolicy</span> <span class="o">=</span> <span class="nc">StrictMode</span><span class="o">.</span><span class="na">allowThreadDiskWrites</span><span class="o">();</span> <span class="kd">final</span> <span class="nc">StrictMode</span><span class="o">.</span><span class="na">ThreadPolicy</span> <span class="n">writesAllowedPolicy</span> <span class="o">=</span> <span class="nc">StrictMode</span><span class="o">.</span><span class="na">getThreadPolicy</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// If the app is being launched for full backup or restore, bring it up in</span> <span class="c1">// a restricted environment with the base application class.</span> <span class="n">app</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">makeApplicationInner</span><span class="o">(</span><span class="n">data</span><span class="o">.</span><span class="na">restrictedBackupMode</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="o">...</span> <span class="n">mInitialApplication</span> <span class="o">=</span> <span class="n">app</span><span class="o">;</span> <span class="o">...</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mInstrumentation</span><span class="o">.</span><span class="na">callApplicationOnCreate</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(!</span><span class="n">mInstrumentation</span><span class="o">.</span><span class="na">onException</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">e</span><span class="o">))</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Unable to create application "</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">": "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">toString</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>最终通过 LoadedApk 中的 <code class="language-plaintext highlighter-rouge">makeApplication</code> 方法来尝试创建 Application 对象并执行它的 <code class="language-plaintext highlighter-rouge">onCreate</code> 生命周期方法:</p> <blockquote> <p><em>frameworks/base/core/java/android/app/LoadedApk.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="nc">Application</span> <span class="nf">makeApplicationInner</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">forceDefaultAppClass</span><span class="o">,</span> <span class="nc">Instrumentation</span> <span class="n">instrumentation</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">makeApplicationInner</span><span class="o">(</span><span class="n">forceDefaultAppClass</span><span class="o">,</span> <span class="n">instrumentation</span><span class="o">,</span> <span class="cm">/* allowDuplicateInstances= */</span> <span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="nc">Application</span> <span class="nf">makeApplicationInner</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">forceDefaultAppClass</span><span class="o">,</span> <span class="nc">Instrumentation</span> <span class="n">instrumentation</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">allowDuplicateInstances</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mApplication</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mApplication</span><span class="o">;</span> <span class="o">}</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"makeApplication"</span><span class="o">);</span> <span class="o">...</span> <span class="nc">Application</span> <span class="n">app</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">myProcessName</span> <span class="o">=</span> <span class="nc">Process</span><span class="o">.</span><span class="na">myProcessName</span><span class="o">();</span> <span class="nc">String</span> <span class="n">appClass</span> <span class="o">=</span> <span class="n">mApplicationInfo</span><span class="o">.</span><span class="na">getCustomApplicationClassNameForProcess</span><span class="o">(</span> <span class="n">myProcessName</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">forceDefaultAppClass</span> <span class="o">||</span> <span class="o">(</span><span class="n">appClass</span> <span class="o">==</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> <span class="n">appClass</span> <span class="o">=</span> <span class="s">"android.app.Application"</span><span class="o">;</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="nc">ContextImpl</span> <span class="n">appContext</span> <span class="o">=</span> <span class="nc">ContextImpl</span><span class="o">.</span><span class="na">createAppContext</span><span class="o">(</span><span class="n">mActivityThread</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span> <span class="c1">// The network security config needs to be aware of multiple</span> <span class="c1">// applications in the same process to handle discrepancies</span> <span class="nc">NetworkSecurityConfigProvider</span><span class="o">.</span><span class="na">handleNewApplication</span><span class="o">(</span><span class="n">appContext</span><span class="o">);</span> <span class="n">app</span> <span class="o">=</span> <span class="n">mActivityThread</span><span class="o">.</span><span class="na">mInstrumentation</span><span class="o">.</span><span class="na">newApplication</span><span class="o">(</span> <span class="n">cl</span><span class="o">,</span> <span class="n">appClass</span><span class="o">,</span> <span class="n">appContext</span><span class="o">);</span> <span class="n">appContext</span><span class="o">.</span><span class="na">setOuterContext</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="n">mActivityThread</span><span class="o">.</span><span class="na">mAllApplications</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="n">mApplication</span> <span class="o">=</span> <span class="n">app</span><span class="o">;</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">instrumentation</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">instrumentation</span><span class="o">.</span><span class="na">callApplicationOnCreate</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="o">}</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">);</span> <span class="k">return</span> <span class="n">app</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>如果 Application 对象依然存在则直接返回当前对象,否则会通过 Instrumentation(ActivityThread中的成员变量 mInstrumentation,而非参数传递进来的) 来新建一个 Application:</p> <blockquote> <p><em>frameworks/base/core/java/android/app/Instrumentation.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="nc">Application</span> <span class="nf">newApplication</span><span class="o">(</span><span class="nc">ClassLoader</span> <span class="n">cl</span><span class="o">,</span> <span class="nc">String</span> <span class="n">className</span><span class="o">,</span> <span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InstantiationException</span><span class="o">,</span> <span class="nc">IllegalAccessException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> <span class="nc">Application</span> <span class="n">app</span> <span class="o">=</span> <span class="n">getFactory</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">getPackageName</span><span class="o">())</span> <span class="o">.</span><span class="na">instantiateApplication</span><span class="o">(</span><span class="n">cl</span><span class="o">,</span> <span class="n">className</span><span class="o">);</span> <span class="n">app</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">context</span><span class="o">);</span> <span class="k">return</span> <span class="n">app</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="nd">@NonNull</span> <span class="nc">Application</span> <span class="nf">instantiateApplication</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">ClassLoader</span> <span class="n">cl</span><span class="o">,</span> <span class="nd">@NonNull</span> <span class="nc">String</span> <span class="n">className</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InstantiationException</span><span class="o">,</span> <span class="nc">IllegalAccessException</span><span class="o">,</span> <span class="nc">ClassNotFoundException</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="nc">Application</span><span class="o">)</span> <span class="n">cl</span><span class="o">.</span><span class="na">loadClass</span><span class="o">(</span><span class="n">className</span><span class="o">).</span><span class="na">newInstance</span><span class="o">();</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,最终是通过反射生成一个全新的 Application 对象并返回给上层,并执行了它的 <code class="language-plaintext highlighter-rouge">onCreate</code> 方法。至此,Application 创建并启动完毕。</p> <h3 id="继续启动-activity">继续启动 Activity</h3> <p>仔细想一下,之前我们分析 Launcher 进程创建过程中的时候,它会提前创建一个 ActivityRecord 对象并置于栈顶,如果当前进程已创建,则直接执行 realStartActivityLocked 来真正启动一个 Activity;如果进程还没有创建,则先创建进程。我们上面分析了一大通不也正是研究了进程还没有创建的情况吗。既然我们后来创建了一个新的 Application,那是不是应该继续之前未完成的使命呢?也就是将先前打断的 Activity 启动的工作继续执行起来。Android 系统不可能连这个问题都考虑不到对吧,所以 Application 创建完毕后应该会继续执行启动 Activity 的逻辑!</p> <p>我们回到之前的 ActivityManagerService 中查看 <code class="language-plaintext highlighter-rouge">attachApplicationLocked</code>:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">attachApplicationLocked</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">IApplicationThread</span> <span class="n">thread</span><span class="o">,</span> <span class="kt">int</span> <span class="n">pid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">callingUid</span><span class="o">,</span> <span class="kt">long</span> <span class="n">startSeq</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="nc">ApplicationInfo</span> <span class="n">appInfo</span> <span class="o">=</span> <span class="n">instr</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">instr</span><span class="o">.</span><span class="na">mTargetInfo</span> <span class="o">:</span> <span class="n">app</span><span class="o">.</span><span class="na">info</span><span class="o">;</span> <span class="n">app</span><span class="o">.</span><span class="na">setCompat</span><span class="o">(</span><span class="n">compatibilityInfoForPackage</span><span class="o">(</span><span class="n">appInfo</span><span class="o">));</span> <span class="k">if</span> <span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPoint</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// This is an isolated process which should just call an entry point instead of</span> <span class="c1">// being bound to an application.</span> <span class="n">thread</span><span class="o">.</span><span class="na">runIsolatedEntryPoint</span><span class="o">(</span> <span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPoint</span><span class="o">(),</span> <span class="n">app</span><span class="o">.</span><span class="na">getIsolatedEntryPointArgs</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">instr2</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">thread</span><span class="o">.</span><span class="na">bindApplication</span><span class="o">(</span><span class="n">processName</span><span class="o">,</span> <span class="n">appInfo</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">sdkSandboxClientAppVolumeUuid</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">sdkSandboxClientAppPackage</span><span class="o">,</span> <span class="n">providerList</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">profilerInfo</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">testMode</span><span class="o">,</span> <span class="n">mBinderTransactionTrackingEnabled</span><span class="o">,</span> <span class="n">enableTrackAllocation</span><span class="o">,</span> <span class="n">isRestrictedBackupMode</span> <span class="o">||</span> <span class="o">!</span><span class="n">normalMode</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">isPersistent</span><span class="o">(),</span> <span class="k">new</span> <span class="nf">Configuration</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getWindowProcessController</span><span class="o">().</span><span class="na">getConfiguration</span><span class="o">()),</span> <span class="n">app</span><span class="o">.</span><span class="na">getCompat</span><span class="o">(),</span> <span class="n">getCommonServicesLocked</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">isolated</span><span class="o">),</span> <span class="n">mCoreSettingsObserver</span><span class="o">.</span><span class="na">getCoreSettingsLocked</span><span class="o">(),</span> <span class="n">buildSerial</span><span class="o">,</span> <span class="n">autofillOptions</span><span class="o">,</span> <span class="n">contentCaptureOptions</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getDisabledCompatChanges</span><span class="o">(),</span> <span class="n">serializedSystemFontMap</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartElapsedTime</span><span class="o">(),</span> <span class="n">app</span><span class="o">.</span><span class="na">getStartUptime</span><span class="o">());</span> <span class="o">}</span> <span class="o">...</span> <span class="c1">// Make app active after binding application or client may be running requests (e.g</span> <span class="c1">// starting activities) before it is ready.</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mProcLock</span><span class="o">)</span> <span class="o">{</span> <span class="n">app</span><span class="o">.</span><span class="na">makeActive</span><span class="o">(</span><span class="n">thread</span><span class="o">,</span> <span class="n">mProcessStats</span><span class="o">);</span> <span class="n">checkTime</span><span class="o">(</span><span class="n">startTime</span><span class="o">,</span> <span class="s">"attachApplicationLocked: immediately after bindApplication"</span><span class="o">);</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="n">didSomething</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">// See if the top visible activity is waiting to run in this process...</span> <span class="k">if</span> <span class="o">(</span><span class="n">normalMode</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">didSomething</span> <span class="o">=</span> <span class="n">mAtmInternal</span><span class="o">.</span><span class="na">attachApplication</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">getWindowProcessController</span><span class="o">());</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">wtf</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Exception thrown launching activities in "</span> <span class="o">+</span> <span class="n">app</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span> <span class="n">badApp</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// Find any services that should be running in this process...</span> <span class="k">if</span> <span class="o">(!</span><span class="n">badApp</span><span class="o">)</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">didSomething</span> <span class="o">|=</span> <span class="n">mServices</span><span class="o">.</span><span class="na">attachApplicationLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">processName</span><span class="o">);</span> <span class="n">checkTime</span><span class="o">(</span><span class="n">startTime</span><span class="o">,</span> <span class="s">"attachApplicationLocked: after mServices.attachApplicationLocked"</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">wtf</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Exception thrown starting services in "</span> <span class="o">+</span> <span class="n">app</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span> <span class="n">badApp</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="o">...</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,在成功创建和绑定 Application 之后,又继续执行了<code class="language-plaintext highlighter-rouge">ActivityTaskManagerInternal#attachApplication</code>,它又做了什么事情呢?:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">attachApplication</span><span class="o">(</span><span class="nc">WindowProcessController</span> <span class="n">wpc</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">RemoteException</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mGlobalLockWithoutBoost</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">isTagEnabled</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">,</span> <span class="s">"attachApplication:"</span> <span class="o">+</span> <span class="n">wpc</span><span class="o">.</span><span class="na">mName</span><span class="o">);</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mRootWindowContainer</span><span class="o">.</span><span class="na">attachApplication</span><span class="o">(</span><span class="n">wpc</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="no">TRACE_TAG_WINDOW_MANAGER</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续跟进到 <code class="language-plaintext highlighter-rouge">RootWindowContainer#attachApplication</code>:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 </pre></td><td class="rouge-code"><pre><span class="kt">boolean</span> <span class="nf">attachApplication</span><span class="o">(</span><span class="nc">WindowProcessController</span> <span class="n">app</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">RemoteException</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mAttachApplicationHelper</span><span class="o">.</span><span class="na">process</span><span class="o">(</span><span class="n">app</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mAttachApplicationHelper</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kt">boolean</span> <span class="nf">process</span><span class="o">(</span><span class="nc">WindowProcessController</span> <span class="n">app</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">RemoteException</span> <span class="o">{</span> <span class="n">mApp</span> <span class="o">=</span> <span class="n">app</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">displayNdx</span> <span class="o">=</span> <span class="n">getChildCount</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">displayNdx</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">displayNdx</span><span class="o">)</span> <span class="o">{</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">displayNdx</span><span class="o">).</span><span class="na">forAllRootTasks</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">mRemoteException</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="n">mRemoteException</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(!</span><span class="n">mHasActivityStarted</span><span class="o">)</span> <span class="o">{</span> <span class="n">ensureActivitiesVisible</span><span class="o">(</span><span class="kc">null</span> <span class="cm">/* starting */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* configChanges */</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* preserveWindows */</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">mHasActivityStarted</span><span class="o">;</span> <span class="o">}</span> <span class="kt">void</span> <span class="nf">ensureActivitiesVisible</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">notifyClients</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">inActivityVisibilityUpdate</span><span class="o">()</span> <span class="o">||</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">isRootVisibilityUpdateDeferred</span><span class="o">())</span> <span class="o">{</span> <span class="c1">// Don't do recursive work.</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">beginActivityVisibilityUpdate</span><span class="o">();</span> <span class="c1">// First the front root tasks. In case any are not fullscreen and are in front of home.</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">displayNdx</span> <span class="o">=</span> <span class="n">getChildCount</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">displayNdx</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">displayNdx</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">DisplayContent</span> <span class="n">display</span> <span class="o">=</span> <span class="n">getChildAt</span><span class="o">(</span><span class="n">displayNdx</span><span class="o">);</span> <span class="n">display</span><span class="o">.</span><span class="na">ensureActivitiesVisible</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">endActivityVisibilityUpdate</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这里是查找 Activity 栈是否存在需要被启动的 Activity,继续跟进 <code class="language-plaintext highlighter-rouge">DisplayContent#ensureActivitiesVisible</code>:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">ensureActivitiesVisible</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">notifyClients</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mInEnsureActivitiesVisible</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Don't do recursive work.</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="n">mInEnsureActivitiesVisible</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="n">mAtmService</span><span class="o">.</span><span class="na">mTaskSupervisor</span><span class="o">.</span><span class="na">beginActivityVisibilityUpdate</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="n">forAllRootTasks</span><span class="o">(</span><span class="n">rootTask</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="n">rootTask</span><span class="o">.</span><span class="na">ensureActivitiesVisible</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">});</span> <span class="k">if</span> <span class="o">(</span><span class="n">mTransitionController</span><span class="o">.</span><span class="na">useShellTransitionsRotation</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">mTransitionController</span><span class="o">.</span><span class="na">isCollecting</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">mWallpaperController</span><span class="o">.</span><span class="na">getWallpaperTarget</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Also update wallpapers so that their requestedVisibility immediately reflects</span> <span class="c1">// the changes to activity visibility.</span> <span class="c1">// TODO(b/206005136): Move visibleRequested logic up to WindowToken.</span> <span class="n">mWallpaperController</span><span class="o">.</span><span class="na">adjustWallpaperWindows</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mAtmService</span><span class="o">.</span><span class="na">mTaskSupervisor</span><span class="o">.</span><span class="na">endActivityVisibilityUpdate</span><span class="o">();</span> <span class="n">mInEnsureActivitiesVisible</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>接着跟进 <code class="language-plaintext highlighter-rouge">Task#ensureActivitiesVisible</code>:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/Task.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Make sure that all activities that need to be visible in the root task (that is, they * currently can be seen by the user) actually are and update their configuration. * @param starting The top most activity in the task. * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param preserveWindows Flag indicating whether windows should be preserved when updating * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of * {@link EnsureActivitiesVisibleHelper}. * */</span> <span class="kt">void</span> <span class="nf">ensureActivitiesVisible</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">)</span> <span class="o">{</span> <span class="n">ensureActivitiesVisible</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/* notifyClients */</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/** * Ensure visibility with an option to also update the configuration of visible activities. * @see #ensureActivitiesVisible(ActivityRecord, int, boolean) * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean) * @param starting The top most activity in the task. * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param notifyClients Flag indicating whether the visibility updates should be sent to the * clients in {@link EnsureActivitiesVisibleHelper}. * @param preserveWindows Flag indicating whether windows should be preserved when updating * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of * {@link EnsureActivitiesVisibleHelper}. */</span> <span class="c1">// TODO: Should be re-worked based on the fact that each task as a root task in most cases.</span> <span class="kt">void</span> <span class="nf">ensureActivitiesVisible</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">notifyClients</span><span class="o">)</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">beginActivityVisibilityUpdate</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="n">forAllLeafTasks</span><span class="o">(</span><span class="n">task</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="n">task</span><span class="o">.</span><span class="na">updateActivityVisibilities</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">},</span> <span class="kc">true</span> <span class="cm">/* traverseTopToBottom */</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">mTranslucentActivityWaiting</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">mUndrawnActivitiesBelowTopTranslucent</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span> <span class="c1">// Nothing is getting drawn or everything was already visible, don't wait for</span> <span class="c1">// timeout.</span> <span class="n">notifyActivityDrawnLocked</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">endActivityVisibilityUpdate</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">updateActivityVisibilities</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">notifyClients</span><span class="o">)</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">beginActivityVisibilityUpdate</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mEnsureActivitiesVisibleHelper</span><span class="o">.</span><span class="na">process</span><span class="o">(</span> <span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">mTaskSupervisor</span><span class="o">.</span><span class="na">endActivityVisibilityUpdate</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>进入 <code class="language-plaintext highlighter-rouge">EnsureActivityVisibilities</code> :</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">notifyClients</span><span class="o">)</span> <span class="o">{</span> <span class="n">reset</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">...</span> <span class="c1">// We should not resume activities that being launched behind because these</span> <span class="c1">// activities are actually behind other fullscreen activities, but still required</span> <span class="c1">// to be visible (such as performing Recents animation).</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">resumeTopActivity</span> <span class="o">=</span> <span class="n">mTopRunningActivity</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">mTopRunningActivity</span><span class="o">.</span><span class="na">mLaunchTaskBehind</span> <span class="o">&amp;&amp;</span> <span class="n">mTaskFragment</span><span class="o">.</span><span class="na">canBeResumed</span><span class="o">(</span><span class="n">starting</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="o">(</span><span class="n">starting</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">starting</span><span class="o">.</span><span class="na">isDescendantOf</span><span class="o">(</span><span class="n">mTaskFragment</span><span class="o">));</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">TaskFragment</span><span class="o">&gt;</span> <span class="n">adjacentTaskFragments</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">mTaskFragment</span><span class="o">.</span><span class="na">mChildren</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span> <span class="kd">final</span> <span class="nc">WindowContainer</span> <span class="n">child</span> <span class="o">=</span> <span class="n">mTaskFragment</span><span class="o">.</span><span class="na">mChildren</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span> <span class="kd">final</span> <span class="nc">TaskFragment</span> <span class="n">childTaskFragment</span> <span class="o">=</span> <span class="n">child</span><span class="o">.</span><span class="na">asTaskFragment</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">childTaskFragment</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">childTaskFragment</span><span class="o">.</span><span class="na">getTopNonFinishingActivity</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">childTaskFragment</span><span class="o">.</span><span class="na">updateActivityVisibilities</span><span class="o">(</span><span class="n">starting</span><span class="o">,</span> <span class="n">configChanges</span><span class="o">,</span> <span class="n">preserveWindows</span><span class="o">,</span> <span class="n">notifyClients</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">child</span><span class="o">.</span><span class="na">asActivityRecord</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">setActivityVisibilityState</span><span class="o">(</span><span class="n">child</span><span class="o">.</span><span class="na">asActivityRecord</span><span class="o">(),</span> <span class="n">starting</span><span class="o">,</span> <span class="n">resumeTopActivity</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">setActivityVisibilityState</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">resumeTopActivity</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">reallyVisible</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(!</span><span class="n">r</span><span class="o">.</span><span class="na">attachedToProcess</span><span class="o">())</span> <span class="o">{</span> <span class="c1">// 需要关联到具体的进程并启动activity</span> <span class="n">makeVisibleAndRestartIfNeeded</span><span class="o">(</span><span class="n">mStarting</span><span class="o">,</span> <span class="n">mConfigChanges</span><span class="o">,</span> <span class="n">isTop</span><span class="o">,</span> <span class="n">resumeTopActivity</span> <span class="o">&amp;&amp;</span> <span class="n">isTop</span><span class="o">,</span> <span class="n">r</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">isVisibleRequested</span><span class="o">())</span> <span class="o">{</span> <span class="c1">// If this activity is already visible, then there is nothing to do here.</span> <span class="o">...</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">r</span><span class="o">.</span><span class="na">makeVisibleIfNeeded</span><span class="o">(</span><span class="n">mStarting</span><span class="o">,</span> <span class="n">mNotifyClients</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Aggregate current change flags.</span> <span class="n">mConfigChanges</span> <span class="o">|=</span> <span class="n">r</span><span class="o">.</span><span class="na">configChangeFlags</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="o">...</span> <span class="n">r</span><span class="o">.</span><span class="na">makeInvisible</span><span class="o">();</span> <span class="o">}</span> <span class="o">...</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">makeVisibleAndRestartIfNeeded</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">starting</span><span class="o">,</span> <span class="kt">int</span> <span class="n">configChanges</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isTop</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">andResume</span><span class="o">,</span> <span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// We need to make sure the app is running if it's the top, or it is just made visible from</span> <span class="c1">// invisible. If the app is already visible, it must have died while it was visible. In this</span> <span class="c1">// case, we'll show the dead window but will not restart the app. Otherwise we could end up</span> <span class="c1">// thrashing.</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isTop</span> <span class="o">&amp;&amp;</span> <span class="n">r</span><span class="o">.</span><span class="na">isVisibleRequested</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">r</span><span class="o">.</span><span class="na">isState</span><span class="o">(</span><span class="no">INITIALIZING</span><span class="o">))</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// This activity needs to be visible, but isn't even running...</span> <span class="c1">// get it started and resume if no other root task in this root task is resumed.</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">!=</span> <span class="n">starting</span><span class="o">)</span> <span class="o">{</span> <span class="n">mTaskFragment</span><span class="o">.</span><span class="na">mTaskSupervisor</span><span class="o">.</span><span class="na">startSpecificActivity</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">andResume</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/* checkConfig */</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>看到这里,我们可以大胆猜测 Activity 即使已经存在,但尚未显示,也没有关联到具体的进程。如果顶部没有其他的 Activity,我们可以尝试启动这个 Activity:</p> <blockquote> <p><em>frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nf">startSpecificActivity</span><span class="o">(</span><span class="nc">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">andResume</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">checkConfig</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Is this activity's application already running?</span> <span class="kd">final</span> <span class="nc">WindowProcessController</span> <span class="n">wpc</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">getProcessController</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span> <span class="kt">boolean</span> <span class="n">knownToBeDead</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="n">wpc</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">wpc</span><span class="o">.</span><span class="na">hasThread</span><span class="o">())</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">realStartActivityLocked</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">wpc</span><span class="o">,</span> <span class="n">andResume</span><span class="o">,</span> <span class="n">checkConfig</span><span class="o">);</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Exception when starting activity "</span> <span class="o">+</span> <span class="n">r</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">().</span><span class="na">flattenToShortString</span><span class="o">(),</span> <span class="n">e</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// If a dead object exception was thrown -- fall through to</span> <span class="c1">// restart the application.</span> <span class="n">knownToBeDead</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="c1">// Remove the process record so it won't be considered as alive.</span> <span class="n">mService</span><span class="o">.</span><span class="na">mProcessNames</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">wpc</span><span class="o">.</span><span class="na">mName</span><span class="o">,</span> <span class="n">wpc</span><span class="o">.</span><span class="na">mUid</span><span class="o">);</span> <span class="n">mService</span><span class="o">.</span><span class="na">mProcessMap</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">wpc</span><span class="o">.</span><span class="na">getPid</span><span class="o">());</span> <span class="o">}</span> <span class="n">r</span><span class="o">.</span><span class="na">notifyUnknownVisibilityLaunchedForKeyguardTransition</span><span class="o">();</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isTop</span> <span class="o">=</span> <span class="n">andResume</span> <span class="o">&amp;&amp;</span> <span class="n">r</span><span class="o">.</span><span class="na">isTopRunningActivity</span><span class="o">();</span> <span class="n">mService</span><span class="o">.</span><span class="na">startProcessAsync</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">knownToBeDead</span><span class="o">,</span> <span class="n">isTop</span><span class="o">,</span> <span class="n">isTop</span> <span class="o">?</span> <span class="nc">HostingRecord</span><span class="o">.</span><span class="na">HOSTING_TYPE_TOP_ACTIVITY</span> <span class="o">:</span> <span class="nc">HostingRecord</span><span class="o">.</span><span class="na">HOSTING_TYPE_ACTIVITY</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>Wonderful!这代码是不是有点熟悉,没错,我们再次回到了这个分叉路口:</p> <ul> <li>如果 Activity 的进程已经启动,则直接执行 <code class="language-plaintext highlighter-rouge">realStartActivityLocked</code> 来启动 Activity;</li> <li>如果 Activity 的进程尚未启动,那么需要先执行 <code class="language-plaintext highlighter-rouge">ActivityTaskManagerService#startProcessAsync</code> 创建进程。</li> </ul> <p><strong>只不过这次我们已经拥有了一个正在运行的进程,所以这一次将会执行到 realStartActivityLocked 来启动一个 Activity。</strong></p> <h3 id="总结">总结</h3> <p>Android 中在 system_server 进程启动 Launcher 进程主要有以下几个步骤:</p> <ol> <li>准备参数信息:AMS 执行 systemReady,接着组装 homeIntent 和 ActivityRecord。</li> <li>判断是否需要新建进程:如果当前存在进程,则直接启动 Activity,否则先创建和启动进程。</li> <li>等待创建请求:Zygote 进程会在启动后等待新的应用程序进程的创建请求。</li> <li>接收创建请求:当有新的应用程序进程的创建请求到达时,Zygote 进程会接收该请求,并根据该请求创建一个新的进程。</li> <li>复制进程:为了快速创建新的进程,Zygote 进程会通过复制自身进程并清除一些状态来创建一个新的应用程序进程。这个过程是通过使用类似于 Unix 的 fork 系统调用实现的。</li> <li>初始化应用程序进程:新的应用程序进程会继承 Zygote 进程的一些特性,例如 Java 虚拟机实例、类和资源等。Zygote 进程会对新进程进行一些初始化工作,例如为新进程设置一些系统属性、启动主线程等。</li> <li>加载应用程序代码:新的应用程序进程会在启动时加载应用程序的代码,并开始执行应用程序的入口函数。</li> <li>启动 Launcher Activity:当进程和 Application 创建完毕后,如果存在未启动的 Activity,则前往创建执行启动。</li> </ol> <p>关于 Activity 的启动过程将在下一章节来剖析,到时也会附上整个 APP 启动过程关键节点流程图。</p> <h3 id="相关面试题">相关面试题</h3> <ul> <li>应用进程的孵化过程?</li> <li>Android 创建进程时的 USAP 机制是什么?</li> <li>为什么 Android 进程的 <code class="language-plaintext highlighter-rouge">fork()</code> 会返回两次?</li> <li>Android 系统的 Activity Task 管理机制?</li> </ul> <h3 id="相关参考">相关参考</h3> <ul> <li>https://juejin.cn/post/6978743408756162590</li> <li>https://blog.csdn.net/zhuzp_blog/article/details/120894309</li> <li>https://blog.csdn.net/qq_34512207/article/details/113725772</li> </ul>

2023/5/4
阅读更多

回顾Java中经典的生产/消费者模型

<h3 id="概述">概述</h3> <p>生产者-消费者模型是 Java 并发编程中比较常见的加锁应用场景之一,以下是维基百科的对于该名词的定义:</p> <blockquote> <p><strong>生产者消费者问题</strong>(Producer-consumer problem),也称<strong>有限缓冲问题</strong>(Bounded-buffer problem),是一个<a href="https://zh.wikipedia.org/wiki/多进程">多进程</a><a href="https://zh.wikipedia.org/wiki/同步">同步</a>问题的经典案例。该问题描述了共享固定大小<a href="https://zh.wikipedia.org/wiki/缓冲区">缓冲区</a>的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。</p> </blockquote> <h3 id="问题分析">问题分析</h3> <p><img src="/img/in-post/post-android/java_producer_consumer.png" alt="java_producer_consumer" /></p> <p>结合上图及定义我们可以提取出该模型具有以下特点:</p> <ul> <li>生产者和消费者可以同时并发运作</li> <li>缓冲区满则阻塞等待消费者消费数据</li> <li>缓冲区空则阻塞等待生产者生产数据</li> </ul> <p>我们知道,生产者和消费者可以同时运作,并且二者执行效率和规模也很有可能出现严重的不对等性。那么为了保证生产和消费操作的<strong>原子性</strong>和共享数据的<strong>可见性</strong>,我们需要借助一种<strong>同步机制</strong>来保障该模型的正常运行。</p> <h3 id="实现方式">实现方式</h3> <p>以下来介绍几种常见的生产-消费模型的实现方式。</p> <h5 id="1-synchorized--waitnotify">1. synchorized + wait/notify</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GoodsStorageDispatcher</span> <span class="o">{</span> <span class="c1">// 仓库容纳上限</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">MAX_SIZE</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span> <span class="c1">// 仓库容纳的货物集合</span> <span class="kd">private</span> <span class="nc">LinkedList</span><span class="o">&lt;</span><span class="nc">Good</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedList</span><span class="o">();</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">produce</span><span class="o">(</span><span class="nc">Good</span> <span class="n">good</span><span class="o">)</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">list</span><span class="o">)</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="no">MAX_SIZE</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"仓库已满 &gt;&gt; 生产暂停,等待消费"</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="n">list</span><span class="o">.</span><span class="na">wait</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">good</span><span class="o">);</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"生产了一个新产品,现库存为:"</span> <span class="o">+</span> <span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">());</span> <span class="n">list</span><span class="o">.</span><span class="na">notifyAll</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">consume</span><span class="o">()</span> <span class="o">{</span> <span class="kd">synchronized</span> <span class="o">(</span><span class="n">list</span><span class="o">)</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"库存已清仓 &gt;&gt; 消费暂停,等待生产"</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="n">list</span><span class="o">.</span><span class="na">wait</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="n">list</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"消费了一个产品,现库存为:"</span> <span class="o">+</span> <span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">());</span> <span class="n">list</span><span class="o">.</span><span class="na">notifyAll</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,我们采用对象锁以及等待/唤醒的方式来实现生产和消费的同步机制。这里的 wait 和 notifyAll 在示例中含义如下:</p> <ul> <li>wait:在缓冲区<strong>满/空</strong>的情况下先挂起<strong>生产/消费</strong>线程并释放锁,进入等待状态,让其他线程执行</li> <li>notify:当生产/消费一个商品时,通知其他等待的线程继续执行并释放锁,进入等待状态</li> </ul> <p>值得注意的是,produce 和 consume 方法中都使用了 while 循环来判断缓冲区的存储状态(空/满),这样处理的原因是什么呢,为何不直接用 if 呢?</p> <p>其实这里用 while 是为了<strong>防止虚假唤醒</strong>,我们结合一个例子更好理解一些:</p> <p><img src="/img/in-post/post-android/fake_notify.png" alt="fake_notify" style="zoom: 67%;" /></p> <p>如上图所示,假设我们现在有 1 个生产者,3 个消费者在执行生产和消费任务,而目前缓冲区(Warehouse)仅有一个数据:A。</p> <p>如果当 Consumer-1 消费了 A 后,缓冲区就为空了,Consumer-2 和 Consumer-3 从缓冲区取数据消费时就会陷入等待状态(因为 Consumer-2 消费时缓冲区为空会执行 wait 方法并释放锁,Consumer-3也会重蹈覆辙)。而此时又有一个生产者 Producer-2 生产了数据 B 并加入缓冲区,通过 notifyAll 去唤醒所有等待的消费者,假设消费者 Consumer-2 优先抢到了使用权将 B 消费后缓冲区又恢复到空空如也的状态。接下来的情况就值得注意了:消费者 Consumer-3 想终于可以消费了,然而由于使用的是 if,所以唤醒后继续执行到 <code class="language-plaintext highlighter-rouge">list.remove()</code>,毫无疑问的抛出了 IndexOutOfBoundsException,因为此时 list 为空了,这就是所谓的“<strong>虚假唤醒</strong>”。其实这里我们不能直接消费数据,而是要继续等待。因此这里使用 while 循环判断,当唤醒继续执行代码时重新进入 while 内判断缓冲区数据是否为空。</p> <h5 id="2-lock--condition">2. Lock + Condition</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GoodsStorageDispatcher</span> <span class="o">{</span> <span class="c1">// 仓库容纳上限</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">MAX_SIZE</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span> <span class="c1">// 仓库容纳的货物集合</span> <span class="kd">private</span> <span class="nc">LinkedList</span><span class="o">&lt;</span><span class="nc">Good</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedList</span><span class="o">();</span> <span class="c1">// 锁</span> <span class="kd">final</span> <span class="nc">Lock</span> <span class="n">lock</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ReentrantLock</span><span class="o">();</span> <span class="c1">// 用于等待或唤醒线程(仓库满后需要等待被消费,成功消费后需要唤醒线程继续生产)</span> <span class="kd">final</span> <span class="nc">Condition</span> <span class="n">notFull</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">newCondition</span><span class="o">();</span> <span class="c1">// 用于等待或唤醒线程(仓库清仓后需要等待生产,生产成功后需要唤醒线程继续消费)</span> <span class="kd">final</span> <span class="nc">Condition</span> <span class="n">notEmpty</span> <span class="o">=</span> <span class="n">lock</span><span class="o">.</span><span class="na">newCondition</span><span class="o">();</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">produce</span><span class="o">(</span><span class="nc">Good</span> <span class="n">good</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="no">MAX_SIZE</span><span class="o">)</span> <span class="o">{</span> <span class="c1">//防止虚假唤醒,Condition的await调用一般会放在一个循环判断中</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"仓库已满 &gt;&gt; 生产暂停,等待消费"</span><span class="o">);</span> <span class="n">notFull</span><span class="o">.</span><span class="na">await</span><span class="o">();</span> <span class="o">}</span> <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">good</span><span class="o">);</span> <span class="n">notEmpty</span><span class="o">.</span><span class="na">signal</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">consume</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">lock</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="k">while</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"库存已清仓 &gt;&gt; 消费暂停,等待生产"</span><span class="o">);</span> <span class="n">notEmpty</span><span class="o">.</span><span class="na">await</span><span class="o">();</span> <span class="o">}</span> <span class="n">list</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span> <span class="n">notFull</span><span class="o">.</span><span class="na">signal</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">lock</span><span class="o">.</span><span class="na">unlock</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过 ReentrantLock + Condition 来替代实现上面的等待/唤醒,但无疑它的功能性更加齐全和灵活(有限等待、公平锁、读写锁分离等)。</p> <h5 id="3-blockingqueue">3. BlockingQueue</h5> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">GoodsStorageDispatcher</span> <span class="o">{</span> <span class="c1">// 仓库容纳上限</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="no">MAX_SIZE</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span> <span class="c1">// 仓库容纳的货物集合</span> <span class="kd">private</span> <span class="nc">LinkedBlockingQueue</span><span class="o">&lt;</span><span class="nc">Good</span><span class="o">&gt;</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedBlockingQueue</span><span class="o">&lt;&gt;(</span><span class="no">MAX_SIZE</span><span class="o">);</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">produce</span><span class="o">(</span><span class="nc">Good</span> <span class="n">good</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="no">MAX_SIZE</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"仓库已满 &gt;&gt; 生产暂停,等待消费"</span><span class="o">);</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="n">list</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">good</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">consume</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">list</span><span class="o">.</span><span class="na">size</span><span class="o">()</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"库存已清仓 &gt;&gt; 消费暂停,等待生产"</span><span class="o">);</span> <span class="o">}</span> <span class="k">try</span> <span class="o">{</span> <span class="n">list</span><span class="o">.</span><span class="na">take</span><span class="o">();</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">BlockingQueue</code> 其实就是阻塞队列,是基于阻塞机制实现的线程安全的队列。而阻塞机制的实现是通过在入队和出队时加锁的方式避免并发操作。我们可以理解它内部其实已经实现了上述的加锁和等待/唤醒这一套流程,只不过帮我们封装好了这一切。</p> <h3 id="应用场景">应用场景</h3> <p>我们所熟知的线程池,它的内部其实就是构建了一个生产者-消费者模型用于将<strong>任务管理</strong>和<strong>线程管理</strong>两部分工作解耦,很大程度上提高了可扩展性。</p> <p><img src="/img/in-post/post-android/threadpool_running_process.png" alt="threadpool_running_process" /></p> <p>平时业务中也会有生产消费模型的用武之地,比如多固件更新过程就符合生产-消费模型,用户通过手动操作选中特定设备的固件包加入升级队列,随后由 UpdateDispatcher 根据具体设备类型进行不同策略的分发消费。</p> <h3 id="相关参考">相关参考</h3> <ul> <li><a href="https://blog.csdn.net/thetimelyrain/article/details/107209101"><em>wait()方法为什么要放在while循环里面-虚假唤醒</em></a></li> <li><a href="https://juejin.cn/post/6999798721269465102"><em>深入理解 BlockingQueue</em></a></li> </ul>

2023/4/29
阅读更多

Android系统启动流程剖析

<p>Android 手机从长按开机到应用的启动阶段都经历了些什么呢?Android 启动过程涉及一系列操作,首先是启动 ROM,接着是引导加载程序、内核启动、init、Zygote 和 SystemServer 创建等过程。整个过程涉及到虚拟机的启动、Binder 线程池的创建以及各项系统服务启动等过程。熟悉 APP 启动过程还可以帮助我们打破性能优化瓶颈,助力于启动性能的提升。下面是启动过程涉及到的关键进程和服务:</p> <p><img src="/img/in-post/post-android/android_init_brief.png" alt="android_init_brief" style="zoom:70%;" /></p> <blockquote> <p><em>本文基于 Android 13 最新代码来分析 Android 系统及应用启动过程。</em></p> </blockquote> <h3 id="init-进程启动">Init 进程启动</h3> <p>init 进程在 Android 系统中扮演着非常重要的角色,它不仅仅是系统的第一个用户进程(pid 为 1),还负责整个系统的启动和进程的管理。当 Android 设备启动时,Bootloader 会初始化硬件设备,并在硬件自检之后将控制权交给 Kernel,Kernel 会加载 Android 系统的内核。而 init 进程的启动过程就发生在内核启动后,接着调用 <em>system/core/init/main.cpp</em> 来创建 init 进程,在此期间读取 init.rc 文件,启动 zygote 进程、servicemanager 等,最后完成初始化工作。下面来结合源码具体分析 init 启动后都做了些什么:</p> <blockquote> <p><em>system/core/init/main.cpp</em></p> </blockquote> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 </pre></td><td class="rouge-code"><pre> <span class="k">using</span> <span class="k">namespace</span> <span class="n">android</span><span class="o">::</span><span class="n">init</span><span class="p">;</span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span> <span class="cp">#if __has_feature(address_sanitizer) </span> <span class="n">__asan_set_error_report_callback</span><span class="p">(</span><span class="n">AsanReportCallback</span><span class="p">);</span> <span class="cp">#elif __has_feature(hwaddress_sanitizer) </span> <span class="n">__hwasan_set_error_report_callback</span><span class="p">(</span><span class="n">AsanReportCallback</span><span class="p">);</span> <span class="cp">#endif </span> <span class="c1">// Boost prio which will be restored later</span> <span class="n">setpriority</span><span class="p">(</span><span class="n">PRIO_PROCESS</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">basename</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="s">"ueventd"</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="n">ueventd_main</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"subcontext"</span><span class="p">))</span> <span class="p">{</span> <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">InitLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">KernelLogger</span><span class="p">);</span> <span class="k">const</span> <span class="n">BuiltinFunctionMap</span><span class="o">&amp;</span> <span class="n">function_map</span> <span class="o">=</span> <span class="n">GetBuiltinFunctionMap</span><span class="p">();</span> <span class="k">return</span> <span class="n">SubcontextMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">function_map</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"selinux_setup"</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="n">SetupSelinux</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"second_stage"</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="n">SecondStageMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">return</span> <span class="n">FirstStageMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>内核启动后会调用到这里的 main 函数,主要执行了以下几个操作:</p> <ul> <li>ueventd_main:该函数是 ueventd 守护进程的主要函数,通过 netlink scoket 响应内核生成的 uevent 事件。它设置信号处理程序,创建用于侦听传入 uevent 的套接字,然后进入无限循环以处理 uevent 事件。 它其实也算是 init 进程,不过具体细节本文不再深究,后面会单独拎出来讲一讲。</li> <li>SubcontextMain:subcontext 进程的主要处理函数,负责为 init 进程创建和管理子进程,并通过 socket 与 init 进行通信。</li> <li>SetupSelinux:该函数会尝试加载在init.rc文件中定义的SELinux策略文件。如果文件存在,则在系统启动时加载所需的SELinux策略,并执行其他相关的SELinux设置。这些设置包括设置SELinux上下文,为设备中的所有进程和授权的服务分配安全策略和权限等。</li> <li>FirstStageMain:init 执行的第一阶段,主要做一些初始化文件系统、kernel 日志以及为第二阶段设定一些环境变量。</li> <li>SecondStageMain:init 执行的第二阶段,主要负责加载属性文件、启动属性服务、加载 init.rc 文件并创建和启动子进程。</li> </ul> <p>下面我们着重分析一下 init.rc 加载及子进程启动过程。</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">SecondStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="p">(</span><span class="n">REBOOT_BOOTLOADER_ON_PANIC</span><span class="p">)</span> <span class="p">{</span> <span class="n">InstallRebootSignalHandlers</span><span class="p">();</span> <span class="p">}</span> <span class="p">......</span> <span class="c1">// 加载属性文件 </span> <span class="n">PropertyInit</span><span class="p">();</span> <span class="p">......</span> <span class="c1">// 启动属性服务 </span> <span class="n">StartPropertyService</span><span class="p">(</span><span class="o">&amp;</span><span class="n">property_fd</span><span class="p">);</span> <span class="c1">// Make the time that init stages started available for bootstat to log.</span> <span class="n">RecordStageBoottimes</span><span class="p">(</span><span class="n">start_time</span><span class="p">);</span> <span class="p">......</span> <span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">am</span> <span class="o">=</span> <span class="n">ActionManager</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span> <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">sm</span> <span class="o">=</span> <span class="n">ServiceList</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span> <span class="c1">// 加载解析 init.rc</span> <span class="n">LoadBootScripts</span><span class="p">(</span><span class="n">am</span><span class="p">,</span> <span class="n">sm</span><span class="p">);</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>init.rc 文件的解析是在 LoadBootScripts() 中完成的,详见下面的实现:</p> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 </pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">LoadBootScripts</span><span class="p">(</span><span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">action_manager</span><span class="p">,</span> <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">service_list</span><span class="p">)</span> <span class="p">{</span> <span class="n">Parser</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">CreateParser</span><span class="p">(</span><span class="n">action_manager</span><span class="p">,</span> <span class="n">service_list</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bootscript</span> <span class="o">=</span> <span class="n">GetProperty</span><span class="p">(</span><span class="s">"ro.boot.init_rc"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">bootscript</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span> <span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/system/etc/init/hw/init.rc"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/system/etc/init"</span><span class="p">))</span> <span class="p">{</span> <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/system/etc/init"</span><span class="p">);</span> <span class="p">}</span> <span class="c1">// late_import is available only in Q and earlier release. As we don't</span> <span class="c1">// have system_ext in those versions, skip late_import for system_ext.</span> <span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/system_ext/etc/init"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/vendor/etc/init"</span><span class="p">))</span> <span class="p">{</span> <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/vendor/etc/init"</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/odm/etc/init"</span><span class="p">))</span> <span class="p">{</span> <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/odm/etc/init"</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/product/etc/init"</span><span class="p">))</span> <span class="p">{</span> <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/product/etc/init"</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="n">bootscript</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>Init 进程依次解析不同路径下谷歌内置 system、SoC 厂商 vendor 以及 odm 厂商的 init.rc 文件并执行启动相关进程。init.rc 本质上只是一个语法文件,至于具体的 Service 和 Action 解析逻辑是在 ServiceParser 和 ActionParser 中处理,至于最终执行逻辑则是由解析处理器将所有 Service 和 Action 封装到 ServiceList 和 ActionList 单例对象中集中处理的。这里以 <code class="language-plaintext highlighter-rouge">init.zygote64_32.rc</code> 中一段启动 zygote 的 rc 代码为例:</p> <blockquote> <p><em>Note:system/core/rootdir/init.rc 中通过 import 动态引入了基于不同平台的 zygote 进程启动配置,以下仅以常见的 init.zygote64_32.rc 平台为例。</em></p> </blockquote> <pre><code class="language-rst"> service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket- name=zygote class main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system socket usap_pool_primary stream 660 root system onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart media.tuner onrestart restart netd onrestart restart wificond task_profiles ProcessCapacityHigh MaxPerformance critical window=${zygote.critical_window.minute:-off} target=zygote-fatal </code></pre> <p>Service 是一个服务类型的程序,以 service 开头,由 init 进程启动,其中每一个 service ,在启动时会通过 fork 方式生成子进程。其中,<code class="language-plaintext highlighter-rouge">zygote</code> 是进程的名字,<code class="language-plaintext highlighter-rouge">/system/bin/app_process </code> 是执行程序的路径,它是通过编译生成的,其包含了 <code class="language-plaintext highlighter-rouge">main()</code> 函数在内的一系列代码。在编译过程中,编译器会将源代码编译成目标文件,并将目标文件链接成可执行文件。在链接过程中,编译器会将不同的目标文件链接到一起,形成一个统一的可执行文件。在这个过程中, <code class="language-plaintext highlighter-rouge">app_process</code> 可执行文件会链接到 <code class="language-plaintext highlighter-rouge">app_main.cpp</code> 文件中定义的 <code class="language-plaintext highlighter-rouge">main()</code> 函数,因此运行 <code class="language-plaintext highlighter-rouge">app_process</code> 可执行文件时,系统会自动调用 <strong><code class="language-plaintext highlighter-rouge">app_main.cpp</code></strong> 文件中的 <code class="language-plaintext highlighter-rouge">main()</code> 函数。</p> <p>后面几项则是传递给执行程序的参数,其中 <code class="language-plaintext highlighter-rouge">--start-system-server</code> 表示在 Zygote 进程启动后需要启动 System Server 进程。Zygote 进程是使用 Socket 来进行跨进程通信的,所以会创建一个名为 zygote 的 socket,660 表示访问权限 <code class="language-plaintext highlighter-rouge">rw-rw----</code>,表示文件拥有者和同一群组用户具有读写权限。</p> <blockquote> <p><em>了解更多关于 init.rc 语法约定参考:<a href="https://zhuanlan.zhihu.com/p/448765932">Android启动初始化 init.rc 详解</a></em></p> </blockquote> <p>接着 zygote 进程通过以下代码被启动:</p> <blockquote> <p><em>system/core/init/service.cpp</em></p> </blockquote> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 </pre></td><td class="rouge-code"><pre><span class="n">Result</span><span class="o">&lt;</span><span class="kt">void</span><span class="o">&gt;</span> <span class="n">Service</span><span class="o">::</span><span class="n">Start</span><span class="p">()</span> <span class="p">{</span> <span class="p">...</span> <span class="n">pid_t</span> <span class="n">pid</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">namespaces_</span><span class="p">.</span><span class="n">flags</span><span class="p">)</span> <span class="p">{</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">clone</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">namespaces_</span><span class="p">.</span><span class="n">flags</span> <span class="o">|</span> <span class="n">SIGCHLD</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">fork</span><span class="p">();</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">umask</span><span class="p">(</span><span class="mo">077</span><span class="p">);</span> <span class="n">cgroups_activated</span><span class="p">.</span><span class="n">CloseWriteFd</span><span class="p">();</span> <span class="n">setsid_finished</span><span class="p">.</span><span class="n">CloseReadFd</span><span class="p">();</span> <span class="n">RunService</span><span class="p">(</span><span class="n">descriptors</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">cgroups_activated</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">setsid_finished</span><span class="p">));</span> <span class="n">_exit</span><span class="p">(</span><span class="mi">127</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">cgroups_activated</span><span class="p">.</span><span class="n">CloseReadFd</span><span class="p">();</span> <span class="n">setsid_finished</span><span class="p">.</span><span class="n">CloseWriteFd</span><span class="p">();</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">pid_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">return</span> <span class="n">ErrnoError</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to fork"</span><span class="p">;</span> <span class="p">}</span> <span class="p">...</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到最终是通过 clone 或者 fork 的方式来创建一个进程,并返回 pid,其实最终它们都是通过系统调用基于父进程孵化出一个子进程,并与当前进程共享地址空间和文件描述符等资源。</p> <h3 id="zygote-进程">zygote 进程</h3> <p>通过上面分析我们知道,zygote 进程在启动后会执行到 <em>app_main.cpp</em> 的 main 函数中:</p> <blockquote> <p><em>frameworks/base/cmds/app_process/app_main.cpp</em></p> </blockquote> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 </pre></td><td class="rouge-code"><pre><span class="cp">#if defined(__LP64__) </span><span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">ABI_LIST_PROPERTY</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"ro.product.cpu.abilist64"</span><span class="p">;</span> <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">ZYGOTE_NICE_NAME</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"zygote64"</span><span class="p">;</span> <span class="cp">#else </span><span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">ABI_LIST_PROPERTY</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"ro.product.cpu.abilist32"</span><span class="p">;</span> <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">ZYGOTE_NICE_NAME</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"zygote"</span><span class="p">;</span> <span class="cp">#endif </span> <span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="k">const</span> <span class="n">argv</span><span class="p">[])</span> <span class="p">{</span> <span class="n">AppRuntime</span> <span class="n">runtime</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">computeArgBlockSize</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">));</span> <span class="p">...</span> <span class="c1">// Parse runtime arguments. Stop at first unrecognized option.</span> <span class="kt">bool</span> <span class="n">zygote</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="kt">bool</span> <span class="n">startSystemServer</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="kt">bool</span> <span class="n">application</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="n">String8</span> <span class="n">niceName</span><span class="p">;</span> <span class="n">String8</span> <span class="n">className</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">;</span> <span class="c1">// Skip unused "parent dir" argument.</span> <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">argc</span><span class="p">)</span> <span class="p">{</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">arg</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="n">i</span><span class="o">++</span><span class="p">];</span> <span class="c1">// 通过 init.rc 中约定的 `--zygote` 来判断是zygote进程</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="s">"--zygote"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">zygote</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="n">niceName</span> <span class="o">=</span> <span class="n">ZYGOTE_NICE_NAME</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="s">"--start-system-server"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">startSystemServer</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="s">"--application"</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">application</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strncmp</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="s">"--nice-name="</span><span class="p">,</span> <span class="mi">12</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">niceName</span><span class="p">.</span><span class="n">setTo</span><span class="p">(</span><span class="n">arg</span> <span class="o">+</span> <span class="mi">12</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">strncmp</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="s">"--"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">className</span><span class="p">.</span><span class="n">setTo</span><span class="p">(</span><span class="n">arg</span><span class="p">);</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="o">--</span><span class="n">i</span><span class="p">;</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">...</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">niceName</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">())</span> <span class="p">{</span> <span class="n">runtime</span><span class="p">.</span><span class="n">setArgv0</span><span class="p">(</span><span class="n">niceName</span><span class="p">.</span><span class="n">string</span><span class="p">(),</span> <span class="nb">true</span> <span class="cm">/* setProcName */</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">zygote</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 进入 AppRunTime 中继续执行,准备穿越到 Java 世界</span> <span class="n">runtime</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="s">"com.android.internal.os.ZygoteInit"</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">zygote</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">className</span><span class="p">)</span> <span class="p">{</span> <span class="n">runtime</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="s">"com.android.internal.os.RuntimeInit"</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">zygote</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Error: no class name or --zygote supplied.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="n">app_usage</span><span class="p">();</span> <span class="n">LOG_ALWAYS_FATAL</span><span class="p">(</span><span class="s">"app_process: no class name or --zygote supplied."</span><span class="p">);</span> <span class="p">}</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过上述代码我们可以发现,zygote 进程通过 <code class="language-plaintext highlighter-rouge">--zygote</code> 参数标记,如果是 zygote 进程,最终会执行到 <em>AppRuntime.start</em> 中:</p> <blockquote> <p><em>frameworks/base/core/jni/AndroidRuntime.cpp</em></p> </blockquote> <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 </pre></td><td class="rouge-code"><pre><span class="cm">/* * Start the Android runtime. This involves starting the virtual machine * and calling the "static void main(String[] args)" method in the class * named by "className". * * Passes the main function two arguments, the class name and the specified * options string. */</span> <span class="kt">void</span> <span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">start</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">className</span><span class="p">,</span> <span class="k">const</span> <span class="n">Vector</span><span class="o">&lt;</span><span class="n">String8</span><span class="o">&gt;&amp;</span> <span class="n">options</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">zygote</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGD</span><span class="p">(</span><span class="s">"&gt;&gt;&gt;&gt;&gt;&gt; START %s uid %d &lt;&lt;&lt;&lt;&lt;&lt;</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">className</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">?</span> <span class="n">className</span> <span class="o">:</span> <span class="s">"(unknown)"</span><span class="p">,</span> <span class="n">getuid</span><span class="p">());</span> <span class="k">static</span> <span class="k">const</span> <span class="n">String8</span> <span class="n">startSystemServer</span><span class="p">(</span><span class="s">"start-system-server"</span><span class="p">);</span> <span class="c1">// Whether this is the primary zygote, meaning the zygote which will fork system server.</span> <span class="kt">bool</span> <span class="n">primary_zygote</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="p">...</span> <span class="cm">/* start the virtual machine */</span> <span class="n">JniInvocation</span> <span class="n">jni_invocation</span><span class="p">;</span> <span class="n">jni_invocation</span><span class="p">.</span><span class="n">Init</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span> <span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">startVm</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mJavaVM</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">env</span><span class="p">,</span> <span class="n">zygote</span><span class="p">,</span> <span class="n">primary_zygote</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="n">onVmCreated</span><span class="p">(</span><span class="n">env</span><span class="p">);</span> <span class="cm">/* * Register android functions. */</span> <span class="k">if</span> <span class="p">(</span><span class="n">startReg</span><span class="p">(</span><span class="n">env</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGE</span><span class="p">(</span><span class="s">"Unable to register all android natives</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="p">...</span> <span class="cm">/* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */</span> <span class="kt">char</span><span class="o">*</span> <span class="n">slashClassName</span> <span class="o">=</span> <span class="n">toSlashClassName</span><span class="p">(</span><span class="n">className</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">?</span> <span class="n">className</span> <span class="o">:</span> <span class="s">""</span><span class="p">);</span> <span class="n">jclass</span> <span class="n">startClass</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">FindClass</span><span class="p">(</span><span class="n">slashClassName</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">startClass</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGE</span><span class="p">(</span><span class="s">"JavaVM unable to locate class '%s'</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">slashClassName</span><span class="p">);</span> <span class="cm">/* keep going */</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">jmethodID</span> <span class="n">startMeth</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">GetStaticMethodID</span><span class="p">(</span><span class="n">startClass</span><span class="p">,</span> <span class="s">"main"</span><span class="p">,</span> <span class="s">"([Ljava/lang/String;)V"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">startMeth</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGE</span><span class="p">(</span><span class="s">"JavaVM unable to find main() in '%s'</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">className</span><span class="p">);</span> <span class="cm">/* keep going */</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">CallStaticVoidMethod</span><span class="p">(</span><span class="n">startClass</span><span class="p">,</span> <span class="n">startMeth</span><span class="p">,</span> <span class="n">strArray</span><span class="p">);</span> <span class="c">#if 0 if (env-&gt;ExceptionCheck()) threadExitUncaughtException(env); #endif </span> <span class="p">}</span> <span class="p">}</span> <span class="n">free</span><span class="p">(</span><span class="n">slashClassName</span><span class="p">);</span> <span class="n">ALOGD</span><span class="p">(</span><span class="s">"Shutting down VM</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">mJavaVM</span><span class="o">-&gt;</span><span class="n">DetachCurrentThread</span><span class="p">()</span> <span class="o">!=</span> <span class="n">JNI_OK</span><span class="p">)</span> <span class="n">ALOGW</span><span class="p">(</span><span class="s">"Warning: unable to detach main thread</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">mJavaVM</span><span class="o">-&gt;</span><span class="n">DestroyJavaVM</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="n">ALOGW</span><span class="p">(</span><span class="s">"Warning: VM did not shut down cleanly</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过这部分代码我们了解到,jni 层的 <em>AndroidRuntime</em> 的 <em>start</em> 函数主要完成了三件事:</p> <ul> <li>启动 zygote 进程的虚拟机</li> <li>完成 JNI 方法注册(在 VM 上注册 Android 本地方法,只为了能够让 native 调用到 Java 层方法)</li> <li>调用 Java 层的 <em>com.android.internal.os.ZygoteInit#main</em> 方法</li> </ul> <p>至此,终于来到了我们熟悉的 Java 世界了,继续 zygote 的初始化逻辑:</p> <blockquote> <p><em>com.android.internal.os.ZygoteInit</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 </pre></td><td class="rouge-code"><pre><span class="cm">/** * This is the entry point for a Zygote process. It creates the Zygote server, loads resources, * and handles other tasks related to preparing the process for forking into applications. * * This process is started with a nice value of -20 (highest priority). All paths that flow * into new processes are required to either set the priority to the default value or terminate * before executing any non-system code. The native side of this occurs in SpecializeCommon, * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess, * ZygoteConnection.handleChildProc, and Zygote.childMain. * * @param argv Command line arguments used to specify the Zygote's configuration. */</span> <span class="nd">@UnsupportedAppUsage</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">)</span> <span class="o">{</span> <span class="nc">ZygoteServer</span> <span class="n">zygoteServer</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="nc">Runnable</span> <span class="n">caller</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="kt">boolean</span> <span class="n">startSystemServer</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="nc">String</span> <span class="n">zygoteSocketName</span> <span class="o">=</span> <span class="s">"zygote"</span><span class="o">;</span> <span class="kt">boolean</span> <span class="n">enableLazyPreload</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">argv</span><span class="o">.</span><span class="na">length</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="s">"start-system-server"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">]))</span> <span class="o">{</span> <span class="n">startSystemServer</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="s">"--enable-lazy-preload"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">]))</span> <span class="o">{</span> <span class="n">enableLazyPreload</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">startsWith</span><span class="o">(</span><span class="no">ABI_LIST_ARG</span><span class="o">))</span> <span class="o">{</span> <span class="n">abiList</span> <span class="o">=</span> <span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">substring</span><span class="o">(</span><span class="no">ABI_LIST_ARG</span><span class="o">.</span><span class="na">length</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">startsWith</span><span class="o">(</span><span class="no">SOCKET_NAME_ARG</span><span class="o">))</span> <span class="o">{</span> <span class="n">zygoteSocketName</span> <span class="o">=</span> <span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">substring</span><span class="o">(</span><span class="no">SOCKET_NAME_ARG</span><span class="o">.</span><span class="na">length</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Unknown command line argument: "</span> <span class="o">+</span> <span class="n">argv</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// In some configurations, we avoid preloading resources and classes eagerly.</span> <span class="c1">// In such cases, we will preload things prior to our first fork.</span> <span class="k">if</span> <span class="o">(!</span><span class="n">enableLazyPreload</span><span class="o">)</span> <span class="o">{</span> <span class="n">preload</span><span class="o">(</span><span class="n">bootTimingsTraceLog</span><span class="o">);</span> <span class="o">}</span> <span class="n">zygoteServer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ZygoteServer</span><span class="o">(</span><span class="n">isPrimaryZygote</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">startSystemServer</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Runnable</span> <span class="n">r</span> <span class="o">=</span> <span class="n">forkSystemServer</span><span class="o">(</span><span class="n">abiList</span><span class="o">,</span> <span class="n">zygoteSocketName</span><span class="o">,</span> <span class="n">zygoteServer</span><span class="o">);</span> <span class="c1">// {@code r == null} in the parent (zygote) process, and {@code r != null} in the</span> <span class="c1">// child (system_server) process.</span> <span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">r</span><span class="o">.</span><span class="na">run</span><span class="o">();</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="nc">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Accepting command socket connections"</span><span class="o">);</span> <span class="c1">// The select loop returns early in the child process after a fork and</span> <span class="c1">// loops forever in the zygote.</span> <span class="n">caller</span> <span class="o">=</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">runSelectLoop</span><span class="o">(</span><span class="n">abiList</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Log</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"System zygote died with fatal exception"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">zygoteServer</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">closeServerSocket</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// We're in the child process and have exited the select loop. Proceed to execute the</span> <span class="c1">// command.</span> <span class="k">if</span> <span class="o">(</span><span class="n">caller</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">caller</span><span class="o">.</span><span class="na">run</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><em>ZygoteInit#main</em> 中大致做了以下几件事:</p> <ul> <li>preload:预加载系统基础类、资源文件、共享库等。</li> <li>ZygoteServer:创建 Socket 服务端,用于和其他进程通信。</li> <li>forkSystemServer:fork 出 system_server 子进程,下面会分析到。</li> <li>ZygoteServer#runSelectLoop:socket 循环监听 AMS 用于创建应用进程的请求。</li> </ul> <p>那么我简单总结一下,zygote 进程负责创建虚拟机,并注册 JNI 方法,成为 Java 进程的母体,用于持续孵化 Java 进程。在创建完system_server 进程后调用 <code class="language-plaintext highlighter-rouge">runSelectLoop</code>,随时等待接收创建新进程的请求并立即唤醒开始执行相应创建工作。</p> <h3 id="system_server-进程">system_server 进程</h3> <p>Zygote 的初始化过程涉及到 system_server 进程的创建和启动,那我们就来看下 system_server 进程的启动过程:</p> <blockquote> <p><em>com.android.internal.os.ZygoteInit</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Prepare the arguments and forks for the system server process. * * @return A {@code Runnable} that provides an entrypoint into system_server code in the child * process; {@code null} in the parent. */</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">forkSystemServer</span><span class="o">(</span><span class="nc">String</span> <span class="n">abiList</span><span class="o">,</span> <span class="nc">String</span> <span class="n">socketName</span><span class="o">,</span> <span class="nc">ZygoteServer</span> <span class="n">zygoteServer</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span> <span class="o">=</span> <span class="o">{</span> <span class="s">"--setuid=1000"</span><span class="o">,</span> <span class="s">"--setgid=1000"</span><span class="o">,</span> <span class="s">"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"</span> <span class="o">+</span> <span class="s">"1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011"</span><span class="o">,</span> <span class="s">"--capabilities="</span> <span class="o">+</span> <span class="n">capabilities</span> <span class="o">+</span> <span class="s">","</span> <span class="o">+</span> <span class="n">capabilities</span><span class="o">,</span> <span class="s">"--nice-name=system_server"</span><span class="o">,</span> <span class="s">"--runtime-args"</span><span class="o">,</span> <span class="s">"--target-sdk-version="</span> <span class="o">+</span> <span class="nc">VMRuntime</span><span class="o">.</span><span class="na">SDK_VERSION_CUR_DEVELOPMENT</span><span class="o">,</span> <span class="c1">// system_server 执行程序的类名</span> <span class="s">"com.android.server.SystemServer"</span><span class="o">,</span> <span class="o">};</span> <span class="o">...</span> <span class="nc">ZygoteArguments</span> <span class="n">parsedArgs</span><span class="o">;</span> <span class="kt">int</span> <span class="n">pid</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="cm">/* Request to fork the system server process */</span> <span class="n">pid</span> <span class="o">=</span> <span class="nc">Zygote</span><span class="o">.</span><span class="na">forkSystemServer</span><span class="o">(</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mUid</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mGid</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mGids</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRuntimeFlags</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mPermittedCapabilities</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mEffectiveCapabilities</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IllegalArgumentException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/* For child process */</span> <span class="k">if</span> <span class="o">(</span><span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">hasSecondZygote</span><span class="o">(</span><span class="n">abiList</span><span class="o">))</span> <span class="o">{</span> <span class="n">waitForSecondaryZygote</span><span class="o">(</span><span class="n">socketName</span><span class="o">);</span> <span class="o">}</span> <span class="n">zygoteServer</span><span class="o">.</span><span class="na">closeServerSocket</span><span class="o">();</span> <span class="k">return</span> <span class="nf">handleSystemServerProcess</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * Finish remaining work for the newly forked system server process. */</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">handleSystemServerProcess</span><span class="o">(</span><span class="nc">ZygoteArguments</span> <span class="n">parsedArgs</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mNiceName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Process</span><span class="o">.</span><span class="na">setArgV0</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mNiceName</span><span class="o">);</span> <span class="o">}</span> <span class="o">...</span> <span class="k">if</span> <span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mInvokeWith</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">"Unexpected return from WrapperInit.execApplication"</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="nc">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">getOrCreateSystemServerClassLoader</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">cl</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">setContextClassLoader</span><span class="o">(</span><span class="n">cl</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/* * Pass the remaining arguments to SystemServer. */</span> <span class="k">return</span> <span class="nc">ZygoteInit</span><span class="o">.</span><span class="na">zygoteInit</span><span class="o">(</span><span class="n">parsedArgs</span><span class="o">.</span><span class="na">mTargetSdkVersion</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mDisabledCompatChanges</span><span class="o">,</span> <span class="n">parsedArgs</span><span class="o">.</span><span class="na">mRemainingArgs</span><span class="o">,</span> <span class="n">cl</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/* should never reach here */</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">zygoteInit</span><span class="o">(</span><span class="kt">int</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">DEBUG</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">TAG</span><span class="o">,</span> <span class="s">"RuntimeInit: Starting application from zygote"</span><span class="o">);</span> <span class="o">}</span> <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_ACTIVITY_MANAGER</span><span class="o">,</span> <span class="s">"ZygoteInit"</span><span class="o">);</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">redirectLogStreams</span><span class="o">();</span> <span class="c1">// 设置异常处理机制</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">commonInit</span><span class="o">();</span> <span class="c1">// 启动进程的Binder线程池</span> <span class="nc">ZygoteInit</span><span class="o">.</span><span class="na">nativeZygoteInit</span><span class="o">();</span> <span class="k">return</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">applicationInit</span><span class="o">(</span><span class="n">targetSdkVersion</span><span class="o">,</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="n">argv</span><span class="o">,</span> <span class="n">classLoader</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>看到上面的参数信息 args 包含了 <code class="language-plaintext highlighter-rouge">com.android.server.SystemServer</code> 就可以大致猜到最后应该是反射创建的实例。上面方法 <code class="language-plaintext highlighter-rouge">ZygoteInit#forkSystemServer</code> 继续追踪下去,system_server 进程的创建最终通过 Zygote.forkSystemServer 实现的:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 </pre></td><td class="rouge-code"><pre><span class="cm">/** * Special method to start the system server process. In addition to the * common actions performed in forkAndSpecialize, the pid of the child * process is recorded such that the death of the child process will cause * zygote to exit. * * @param uid the UNIX uid that the new process should setuid() to after * fork()ing and and before spawning any threads. * @param gid the UNIX gid that the new process should setgid() to after * fork()ing and and before spawning any threads. * @param gids null-ok; a list of UNIX gids that the new process should * setgroups() to after fork and before spawning any threads. * @param runtimeFlags bit flags that enable ART features. * @param rlimits null-ok an array of rlimit tuples, with the second * dimension having a length of 3 and representing * (resource, rlim_cur, rlim_max). These are set via the posix * setrlimit(2) call. * @param permittedCapabilities argument for setcap() * @param effectiveCapabilities argument for setcap() * * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">forkSystemServer</span><span class="o">(</span><span class="kt">int</span> <span class="n">uid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">gid</span><span class="o">,</span> <span class="kt">int</span><span class="o">[]</span> <span class="n">gids</span><span class="o">,</span> <span class="kt">int</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="kt">int</span><span class="o">[][]</span> <span class="n">rlimits</span><span class="o">,</span> <span class="kt">long</span> <span class="n">permittedCapabilities</span><span class="o">,</span> <span class="kt">long</span> <span class="n">effectiveCapabilities</span><span class="o">)</span> <span class="o">{</span> <span class="nc">ZygoteHooks</span><span class="o">.</span><span class="na">preFork</span><span class="o">();</span> <span class="kt">int</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">nativeForkSystemServer</span><span class="o">(</span> <span class="n">uid</span><span class="o">,</span> <span class="n">gid</span><span class="o">,</span> <span class="n">gids</span><span class="o">,</span> <span class="n">runtimeFlags</span><span class="o">,</span> <span class="n">rlimits</span><span class="o">,</span> <span class="n">permittedCapabilities</span><span class="o">,</span> <span class="n">effectiveCapabilities</span><span class="o">);</span> <span class="c1">// Set the Java Language thread priority to the default value for new apps.</span> <span class="nc">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">().</span><span class="na">setPriority</span><span class="o">(</span><span class="nc">Thread</span><span class="o">.</span><span class="na">NORM_PRIORITY</span><span class="o">);</span> <span class="nc">ZygoteHooks</span><span class="o">.</span><span class="na">postForkCommon</span><span class="o">();</span> <span class="k">return</span> <span class="n">pid</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>system_server 进程的最终实现是在 native 层,但是从注释来看,它的返回值如果是 0 则说明当前是子进程,否则可能依然是父进程或者 fork 失败。如果 system_server 进程创建成功,会依次执行到 <em>handleSystemServerProcess</em> 和 <em>zygoteInit</em> 方法,继续跟进到 <em>RuntimeInit.applicationInit</em> 一探究竟:</p> <blockquote> <p><em>frameworks/base/core/java/com/android/internal/os/RuntimeInit.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 </pre></td><td class="rouge-code"><pre><span class="kd">protected</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">applicationInit</span><span class="o">(</span><span class="kt">int</span> <span class="n">targetSdkVersion</span><span class="o">,</span> <span class="kt">long</span><span class="o">[]</span> <span class="n">disabledCompatChanges</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// Remaining arguments are passed to the start class's static main</span> <span class="k">return</span> <span class="nf">findStaticMain</span><span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">startClass</span><span class="o">,</span> <span class="n">args</span><span class="o">.</span><span class="na">startArgs</span><span class="o">,</span> <span class="n">classLoader</span><span class="o">);</span> <span class="o">}</span> <span class="kd">protected</span> <span class="kd">static</span> <span class="nc">Runnable</span> <span class="nf">findStaticMain</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">argv</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">cl</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">cl</span> <span class="o">=</span> <span class="nc">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">className</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">classLoader</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ClassNotFoundException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Missing class when invoking static main "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="nc">Method</span> <span class="n">m</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="n">m</span> <span class="o">=</span> <span class="n">cl</span><span class="o">.</span><span class="na">getMethod</span><span class="o">(</span><span class="s">"main"</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Class</span><span class="o">[]</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[].</span><span class="na">class</span> <span class="o">});</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">NoSuchMethodException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Missing static main on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">SecurityException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Problem getting static main on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="kt">int</span> <span class="n">modifiers</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="na">getModifiers</span><span class="o">();</span> <span class="k">if</span> <span class="o">(!</span> <span class="o">(</span><span class="nc">Modifier</span><span class="o">.</span><span class="na">isStatic</span><span class="o">(</span><span class="n">modifiers</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="nc">Modifier</span><span class="o">.</span><span class="na">isPublic</span><span class="o">(</span><span class="n">modifiers</span><span class="o">)))</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span> <span class="s">"Main method is not public and static on "</span> <span class="o">+</span> <span class="n">className</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">MethodAndArgsCaller</span><span class="o">(</span><span class="n">m</span><span class="o">,</span> <span class="n">argv</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/** * Helper class which holds a method and arguments and can call them. This is used as part of * a trampoline to get rid of the initial process setup stack frames. */</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MethodAndArgsCaller</span> <span class="kd">implements</span> <span class="nc">Runnable</span> <span class="o">{</span> <span class="cm">/** method to call */</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Method</span> <span class="n">mMethod</span><span class="o">;</span> <span class="cm">/** argument array */</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">mArgs</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">MethodAndArgsCaller</span><span class="o">(</span><span class="nc">Method</span> <span class="n">method</span><span class="o">,</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span> <span class="n">mMethod</span> <span class="o">=</span> <span class="n">method</span><span class="o">;</span> <span class="n">mArgs</span> <span class="o">=</span> <span class="n">args</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="n">mMethod</span><span class="o">.</span><span class="na">invoke</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Object</span><span class="o">[]</span> <span class="o">{</span> <span class="n">mArgs</span> <span class="o">});</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IllegalAccessException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InvocationTargetException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Throwable</span> <span class="n">cause</span> <span class="o">=</span> <span class="n">ex</span><span class="o">.</span><span class="na">getCause</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">cause</span> <span class="k">instanceof</span> <span class="nc">RuntimeException</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="o">(</span><span class="nc">RuntimeException</span><span class="o">)</span> <span class="n">cause</span><span class="o">;</span> <span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">cause</span> <span class="k">instanceof</span> <span class="nc">Error</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="o">(</span><span class="nc">Error</span><span class="o">)</span> <span class="n">cause</span><span class="o">;</span> <span class="o">}</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>最终不出我们所料,<em>findStaticMain</em> 中传递的 className 对应的就是我们之前的 <em>com.android.server.SystemServer</em> 类,我们来看看它里面都做了些什么(<em>SystemServer#main</em> 会执行到 <code class="language-plaintext highlighter-rouge">run</code> 方法):</p> <blockquote> <p><em>frameworks/base/services/java/com/android/server/SystemServer.java</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// Prepare the main looper thread (this thread).</span> <span class="n">android</span><span class="o">.</span><span class="na">os</span><span class="o">.</span><span class="na">Process</span><span class="o">.</span><span class="na">setThreadPriority</span><span class="o">(</span> <span class="n">android</span><span class="o">.</span><span class="na">os</span><span class="o">.</span><span class="na">Process</span><span class="o">.</span><span class="na">THREAD_PRIORITY_FOREGROUND</span><span class="o">);</span> <span class="n">android</span><span class="o">.</span><span class="na">os</span><span class="o">.</span><span class="na">Process</span><span class="o">.</span><span class="na">setCanSelfBackground</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">prepareMainLooper</span><span class="o">();</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">getMainLooper</span><span class="o">().</span><span class="na">setSlowLogThresholdMs</span><span class="o">(</span> <span class="no">SLOW_DISPATCH_THRESHOLD_MS</span><span class="o">,</span> <span class="no">SLOW_DELIVERY_THRESHOLD_MS</span><span class="o">);</span> <span class="nc">SystemServiceRegistry</span><span class="o">.</span><span class="na">sEnableServiceNotFoundWtf</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">...</span> <span class="c1">// Initialize the system context.</span> <span class="n">createSystemContext</span><span class="o">();</span> <span class="o">...</span> <span class="c1">// Create the system service manager.</span> <span class="n">mSystemServiceManager</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SystemServiceManager</span><span class="o">(</span><span class="n">mSystemContext</span><span class="o">);</span> <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">setStartInfo</span><span class="o">(</span><span class="n">mRuntimeRestart</span><span class="o">,</span> <span class="n">mRuntimeStartElapsedTime</span><span class="o">,</span> <span class="n">mRuntimeStartUptime</span><span class="o">);</span> <span class="n">mDumper</span><span class="o">.</span><span class="na">addDumpable</span><span class="o">(</span><span class="n">mSystemServiceManager</span><span class="o">);</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="nc">SystemServiceManager</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">mSystemServiceManager</span><span class="o">);</span> <span class="c1">// Prepare the thread pool for init tasks that can be parallelized</span> <span class="nc">SystemServerInitThreadPool</span> <span class="n">tp</span> <span class="o">=</span> <span class="nc">SystemServerInitThreadPool</span><span class="o">.</span><span class="na">start</span><span class="o">();</span> <span class="n">mDumper</span><span class="o">.</span><span class="na">addDumpable</span><span class="o">(</span><span class="n">tp</span><span class="o">);</span> <span class="o">...</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">t</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">();</span> <span class="c1">// InitBeforeStartServices</span> <span class="o">}</span> <span class="c1">// Setup the default WTF handler</span> <span class="nc">RuntimeInit</span><span class="o">.</span><span class="na">setDefaultApplicationWtfHandler</span><span class="o">(</span><span class="nl">SystemServer:</span><span class="o">:</span><span class="n">handleEarlySystemWtf</span><span class="o">);</span> <span class="c1">// Start services.</span> <span class="k">try</span> <span class="o">{</span> <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"StartServices"</span><span class="o">);</span> <span class="n">startBootstrapServices</span><span class="o">(</span><span class="n">t</span><span class="o">);</span> <span class="n">startCoreServices</span><span class="o">(</span><span class="n">t</span><span class="o">);</span> <span class="n">startOtherServices</span><span class="o">(</span><span class="n">t</span><span class="o">);</span> <span class="n">startApexServices</span><span class="o">(</span><span class="n">t</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="s">"System"</span><span class="o">,</span> <span class="s">"******************************************"</span><span class="o">);</span> <span class="nc">Slog</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="s">"System"</span><span class="o">,</span> <span class="s">"************ Failure starting system services"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="n">t</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">();</span> <span class="c1">// StartServices</span> <span class="o">}</span> <span class="o">...</span> <span class="c1">// Loop forever.</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">loop</span><span class="o">();</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Main thread loop unexpectedly exited"</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过分析上面代码可以发现 system_server 为我们做了很多事情:</p> <ul> <li>创建系统服务管理器 SystemServiceManager</li> <li>启动引导服务(AMS、PMS等)、核心服务和其他各种服务</li> <li>创建主线程 Looper 并一直循环,等待其他线程通过 Handler 发送消息再唤醒并处理。</li> </ul> <h5 id="nativezygoteinit">nativeZygoteInit</h5> <p>上面的 <em>com.android.internal.os.ZygoteInit#forkSystemServer</em> 中 <code class="language-plaintext highlighter-rouge">ZygoteInit.nativeZygoteInit()</code> 方法主要从 native 层创建了进程的 Binder 线程池。<strong>值得注意的,这一切发生在 system_server 进程创建之后</strong>。最终可以追溯到:</p> <blockquote> <p><em>frameworks/native/libs/binder/ProcessState.cpp</em></p> </blockquote> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="nl">ProcessState:</span><span class="o">:</span><span class="n">spawnPooledThread</span><span class="o">(</span><span class="n">bool</span> <span class="n">isMain</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mThreadPoolStarted</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String8</span> <span class="n">name</span> <span class="o">=</span> <span class="n">makeBinderThreadName</span><span class="o">();</span> <span class="no">ALOGV</span><span class="o">(</span><span class="s">"Spawning new pooled thread, name=%s\n"</span><span class="o">,</span> <span class="n">name</span><span class="o">.</span><span class="na">string</span><span class="o">());</span> <span class="n">sp</span><span class="o">&lt;</span><span class="nc">Thread</span><span class="o">&gt;</span> <span class="n">t</span> <span class="o">=</span> <span class="n">sp</span><span class="o">&lt;</span><span class="nc">PoolThread</span><span class="o">&gt;::</span><span class="n">make</span><span class="o">(</span><span class="n">isMain</span><span class="o">);</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">run</span><span class="o">(</span><span class="n">name</span><span class="o">.</span><span class="na">string</span><span class="o">());</span> <span class="n">pthread_mutex_lock</span><span class="o">(&amp;</span><span class="n">mThreadCountLock</span><span class="o">);</span> <span class="n">mKernelStartedThreads</span><span class="o">++;</span> <span class="n">pthread_mutex_unlock</span><span class="o">(&amp;</span><span class="n">mThreadCountLock</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <h3 id="系统启动日志分析">系统启动日志分析</h3> <p>从 Android 开机开始到我们系统初始化完毕,这中间设有大量的关键日志,如果能够好好利用,对于我们排查一些系统性问题或者性能问题会有巨大帮助。以下列举部分关键节点的 Tag:</p> <h5 id="可查看启动耗时">可查看启动耗时</h5> <ul> <li>boot_progress_start:表示 kernel 启动完成,第一个用户空间进程 init 进程启动</li> <li>boot_progress_preload_start:zygote 进程开始预加载 class 资源(记录于 <em>ZygoteInit#preload</em> 调用时机)</li> <li>boot_progress_preload_end:zygote 进程 class 资源预加载结束</li> <li>boot_progress_system_run:表示 system_server 进程启动(记录于 <em>SystemServer#run</em> 调用时机)</li> <li>boot_progress_pms_start:表示 PMS 服务启动并开始扫描应用的动作</li> <li>boot_progress_pms_ready:表示 PMS 服务应用扫描完毕</li> <li>boot_progress_ams_ready:表示 AMS 已经进入 systemReady 状态可以启动桌面应用了</li> <li>boot_progress_enable_screen:标志着锁频界面已经点亮显示,开机完成并统计耗时</li> </ul> <p>更多 native 层相关统计的 tag 参照:<a href="https://cs.android.com/android/platform/superproject/+/master:system/logging/logcat/event.logtags">system/logging/logcat/event.logtags</a></p> <p>我们可以额外留意一些系统进程和服务的相关日志,比如 zygote 运行相关日志:</p> <div class="language-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 </pre></td><td class="rouge-code"><pre><span class="nv">//</span> <span class="nv">Zygote64</span><span class="err">进程</span><span class="p">(</span><span class="nv">Zygote</span><span class="p">)</span><span class="err">:</span> <span class="nv">AndroidRuntime::start</span> <span class="nv">11-23</span> <span class="nv">14:36:51.260</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">D</span> <span class="nv">AndroidRuntime:</span> <span class="nv">&gt;&gt;&gt;&gt;&gt;&gt;</span> <span class="nv">START</span> <span class="nv">com.android.internal.os.ZygoteInit</span> <span class="nv">uid</span> <span class="mi">0</span> <span class="nv">&lt;&lt;&lt;&lt;&lt;&lt;</span> <span class="nv">//</span> <span class="nv">Zygote64</span><span class="err">进程:</span> <span class="nv">AndroidRuntime::startVm</span> <span class="nv">11-23</span> <span class="nv">14:36:51.304</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">D</span> <span class="nv">AndroidRuntime:</span> <span class="nv">CheckJNI</span> <span class="nv">is</span> <span class="nv">OFF</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">ZygoteInit.preload</span><span class="p">()</span> <span class="nv">11-23</span> <span class="nv">14:36:52.134</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">D</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">begin</span> <span class="nv">preload</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">ZygoteInit.preloadClasses</span><span class="p">()</span><span class="o">,</span> <span class="err">预加载</span><span class="nv">3860</span><span class="err">个</span><span class="nv">classes,</span> <span class="err">花费时长</span><span class="nv">746ms</span> <span class="nv">11-23</span> <span class="nv">14:36:52.134</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">Preloading</span> <span class="nv">classes...</span> <span class="nv">11-23</span> <span class="nv">14:36:52.881</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="o">...</span><span class="nv">preloaded</span> <span class="mi">3860</span> <span class="nv">classes</span> <span class="nv">in</span> <span class="nv">746ms.</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">ZygoteInit.preloadClasses</span><span class="p">()</span><span class="o">,</span> <span class="err">预加载</span><span class="nv">86</span><span class="err">组资源</span><span class="o">,</span> <span class="err">花费时长</span><span class="nv">179ms</span> <span class="nv">11-23</span> <span class="nv">14:36:53.114</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">Preloading</span> <span class="nv">resources...</span> <span class="nv">11-23</span> <span class="nv">14:36:53.293</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="o">...</span><span class="nv">preloaded</span> <span class="mi">86</span> <span class="nv">resources</span> <span class="nv">in</span> <span class="nv">179ms.</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">ZygoteInit.preloadSharedLibraries</span><span class="p">()</span> <span class="nv">11-23</span> <span class="nv">14:36:53.494</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">Preloading</span> <span class="nv">shared</span> <span class="nv">libraries...</span> <span class="nv">11-23</span> <span class="nv">14:36:53.503</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">D</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">end</span> <span class="nv">preload</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">com_android_internal_os_Zygote_nativeForkSystemServer</span><span class="p">()</span><span class="o">,</span><span class="err">成功</span><span class="nv">fork</span><span class="err">出</span><span class="nv">system_server</span><span class="err">进程</span> <span class="nv">11-23</span> <span class="nv">14:36:53.544</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">System</span> <span class="nv">server</span> <span class="nv">process</span> <span class="mi">1274</span> <span class="nv">has</span> <span class="nv">been</span> <span class="nv">created</span> <span class="nv">//</span> <span class="nv">Zygote</span><span class="err">开始进入</span><span class="nv">runSelectLoop</span><span class="p">()</span> <span class="nv">11-23</span> <span class="nv">14:36:53.546</span> <span class="mi">557</span> <span class="mi">557</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">Accepting</span> <span class="nv">command</span> <span class="nv">socket</span> <span class="nv">connections</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>system_server 进程运行相关日志:</p> <div class="language-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 </pre></td><td class="rouge-code"><pre><span class="nv">//</span><span class="err">进入</span><span class="nv">system_server,</span> <span class="err">建立跟</span><span class="nv">Zygote</span><span class="err">进程的</span><span class="nv">socket</span><span class="err">通道</span> <span class="nv">11-23</span> <span class="nv">14:36:53.586</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">Zygote</span> <span class="err">:</span> <span class="nv">Process:</span> <span class="nv">zygote</span> <span class="nv">socket</span> <span class="nv">opened,</span> <span class="nv">supported</span> <span class="nv">ABIS:</span> <span class="nv">armeabi-v7a,armeabi</span> <span class="nv">//</span> <span class="err">执行</span><span class="nv">SystemServer.run</span><span class="p">()</span> <span class="nv">11-23</span> <span class="nv">14:36:53.618</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Entered</span> <span class="k">the</span> <span class="nv">Android</span> <span class="nv">system</span> <span class="nv">server!</span> <span class="nv">&lt;===&gt;</span> <span class="nv">boot_progress_system_run</span> <span class="nv">//</span> <span class="err">等待</span><span class="nv">installd</span><span class="err">准备就绪</span> <span class="nv">11-23</span> <span class="nv">14:36:53.707</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">Installer:</span> <span class="nv">Waiting</span> <span class="nv">for</span> <span class="nv">installd</span> <span class="nv">to</span> <span class="nv">be</span> <span class="nv">ready.</span> <span class="nv">//</span><span class="err">服务启动</span> <span class="nv">11-23</span> <span class="nv">14:36:53.732</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">ActivityManager:</span> <span class="nv">Memory</span> <span class="nv">class:</span> <span class="mi">192</span> <span class="nv">//phase100</span> <span class="nv">11-23</span> <span class="nv">14:36:53.883</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nb">phase</span> <span class="mi">100</span> <span class="nv">11-23</span> <span class="nv">14:36:53.902</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Package</span> <span class="nv">Manager</span> <span class="nv">11-23</span> <span class="nv">14:37:03.816</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">User</span> <span class="nv">Service</span> <span class="o">...</span> <span class="nv">11-23</span> <span class="nv">14:37:03.940</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Init</span> <span class="nv">Watchdog</span> <span class="nv">11-23</span> <span class="nv">14:37:03.941</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Input</span> <span class="nv">Manager</span> <span class="nv">11-23</span> <span class="nv">14:37:03.946</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Window</span> <span class="nv">Manager</span> <span class="o">...</span> <span class="nv">11-23</span> <span class="nv">14:37:04.081</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nv">com.android.server.MountService$Lifecycle</span> <span class="nv">11-23</span> <span class="nv">14:37:04.088</span> <span class="mi">1274</span> <span class="mi">2717</span> <span class="nv">D</span> <span class="nv">MountService:</span> <span class="nv">Thinking</span> <span class="nv">about</span> <span class="nv">reset,</span> <span class="nv">mSystemReady=false,</span> <span class="nv">mDaemonConnected=true</span> <span class="nv">11-23</span> <span class="nv">14:37:04.088</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nv">com.android.server.UiModeManagerService</span> <span class="nv">11-23</span> <span class="nv">14:37:04.520</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">NetworkTimeUpdateService</span> <span class="nv">//phase480</span> <span class="nv">&amp;&amp;</span> <span class="mi">500</span> <span class="nv">11-23</span> <span class="nv">14:37:05.056</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nb">phase</span> <span class="mi">480</span> <span class="nv">11-23</span> <span class="nv">14:37:05.061</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nb">phase</span> <span class="mi">500</span> <span class="nv">11-23</span> <span class="nv">14:37:05.231</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">ActivityManager:</span> <span class="nv">System</span> <span class="nv">now</span> <span class="nv">ready</span> <span class="nv">&lt;==&gt;</span> <span class="nv">boot_progress_ams_ready</span> <span class="nv">11-23</span> <span class="nv">14:37:05.234</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">Making</span> <span class="nv">services</span> <span class="nv">ready</span> <span class="nv">11-23</span> <span class="nv">14:37:05.243</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServer:</span> <span class="nv">WebViewFactory</span> <span class="nv">preparation</span> <span class="nv">//phase550</span> <span class="nv">11-23</span> <span class="nv">14:37:05.234</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nb">phase</span> <span class="mi">550</span> <span class="nv">11-23</span> <span class="nv">14:37:05.237</span> <span class="mi">1274</span> <span class="mi">1288</span> <span class="nv">I</span> <span class="nv">ActivityManager:</span> <span class="nv">Force</span> <span class="nv">stopping</span> <span class="nv">com.android.providers.media</span> <span class="nv">appid=10010</span> <span class="nv">user=-1:</span> <span class="nv">vold</span> <span class="nv">reset</span> <span class="nv">//Phase600</span> <span class="nv">11-23</span> <span class="nv">14:37:06.066</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">I</span> <span class="nv">SystemServiceManager:</span> <span class="nv">Starting</span> <span class="nb">phase</span> <span class="mi">600</span> <span class="nv">11-23</span> <span class="nv">14:37:06.236</span> <span class="mi">1274</span> <span class="mi">1274</span> <span class="nv">D</span> <span class="nv">MountService:</span> <span class="nv">onStartUser</span> <span class="mi">0</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>基于记录以上关键日志的目的,我们可以通过以下 logcat 命令在手机开机后使用:</p> <ul> <li><em><code class="language-plaintext highlighter-rouge">adb logcat -b events |grep “boot_progress”</code></em></li> <li><em><code class="language-plaintext highlighter-rouge">adb logcat -s Zygote|SystemServer|SystemServiceManager|ActivityManager</code></em></li> </ul> <h3 id="总结">总结</h3> <p>笔者认为,源码分析过程中如果能输出关键类调用的流程,能够显著加强后续理解。关于普通应用进程的创建和启动过程将在后续 Activity 启动分析中一并介绍。下图就是关于本文各进程启动过程的方法调用链路总结。</p> <p><img src="/img/in-post/post-android/init_method_call.svg" alt="init_method_call" style="zoom: 50%;" /></p> <h3 id="参考">参考</h3> <ul> <li><a href="https://source.android.com/docs/core/architecture?hl=zh-cn"><em>AOSP 架构概览</em></a></li> <li><a href="http://gityuan.com/2016/02/01/android-booting/"><em>Android 系统启动综述</em></a></li> <li><em>https://juejin.cn/post/6999491633129455653</em></li> </ul>

2023/4/22
阅读更多

AOSP在Mac上的编译实践(上)

<p>AOSP,即 Android Open Source Project,对于 Android 开发者来说再熟悉不过的项目,本文将着重介绍如何在 MacOS Monterey 环境上下载完整的 AOSP 源码。</p> <h2 id="git--python">Git &amp; Python</h2> <h4 id="1-git-安装和配置">1. Git 安装和配置</h4> <p>在安装了 <a href="https://brew.sh/">Homebrew</a> 的情况下,安装 git 我们只需要执行如下命令:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>brew <span class="nb">install </span>git </pre></td></tr></tbody></table></code></pre></div></div> <p>接着初始化一下 Git 的全局配置信息:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>git config <span class="nt">--global</span> user.name <span class="s2">"User Name"</span> git config <span class="nt">--global</span> user.email <span class="s2">"your@example.com"</span> </pre></td></tr></tbody></table></code></pre></div></div> <h4 id="2-配置-python-环境">2. 配置 python 环境</h4> <p>一般情况下我们电脑上都会安装了 python 环境,如果没有安装,可以前往 <a href="https://www.python.org">python官方站点</a>下载。安装成功并做好解释器关联后执行以下命令查看版本信息:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>➜ ~ python <span class="nt">--version</span> Python 3.10.4 </pre></td></tr></tbody></table></code></pre></div></div> <p>目前的 repo 都是基于 python 3.0,我们需要留意最新的 <a href="https://android.googlesource.com/tools/repo">repo</a> 版本中的 main.py 文件中定义的版本和我们本地的 python 环境是否一致。这里能跑通并不代表整个环境就万事大吉了,后面在安装 repo 的时候会提到因 python 配置而遇到的一个问题。</p> <h2 id="安装-repo">安装 repo</h2> <p>Repo 是建立在 Git 上的一个多仓库管理工具,可以组织多个仓库的上传和下载。它是 Google 基于 Python 和 Git 编写的脚本工具,可以协助我们管理多个 Git 存储仓库。首先,我们通过如下命令安装和配置 repo:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre>➜ <span class="nb">mkdir</span> ~/DevKit/bin ➜ curl https://storage.googleapis.com/git-repo-downloads/repo <span class="o">&gt;</span> ~/DevKit/bin/repo ➜ <span class="nb">chmod </span>a+x ~/DevKit/bin/repo </pre></td></tr></tbody></table></code></pre></div></div> <p>如果提示网络问题可能需要翻墙,可尝试使用国内镜像:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo <span class="nt">-o</span> ~/DevKit/bin/repo <span class="nb">chmod </span>a+x ~/DevKit/bin/repo </pre></td></tr></tbody></table></code></pre></div></div> <p>接着执行 <code class="language-plaintext highlighter-rouge">repo --version</code> 查看是否安装成功:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 </pre></td><td class="rouge-code"><pre>➜ ~ repo <span class="nt">--version</span> &lt;repo not installed&gt; repo launcher version 2.32 <span class="o">(</span>from /Users/dorck/DevKit/bin/repo<span class="o">)</span> git 2.32.0 <span class="o">(</span>Apple Git-132<span class="o">)</span> Python 3.10.4 <span class="o">(</span>v3.10.4:9d38120e33, Mar 23 2022, 17:29:05<span class="o">)</span> <span class="o">[</span>Clang 13.0.0 <span class="o">(</span>clang-1300.0.29.30<span class="o">)]</span> OS Darwin 21.4.0 <span class="o">(</span>Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022<span class="p">;</span> root:xnu-8020.101.4~15/RELEASE_ARM64_T6000<span class="o">)</span> CPU arm64 <span class="o">(</span>arm<span class="o">)</span> Bug reports: https://bugs.chromium.org/p/gerrit/issues/entry?template<span class="o">=</span>Repo+tool+issue ➜ ~ </pre></td></tr></tbody></table></code></pre></div></div> <p>若出现上述版本提示信息说明安装成功,可继续进行下一步操作。But,在笔者的 M1 电脑上遇到了一个坑,在执行 <code class="language-plaintext highlighter-rouge">repo --version</code> 时提示 python 配置问题:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="nb">env</span>: python: No such file or directory </pre></td></tr></tbody></table></code></pre></div></div> <p>出现这类问题的常见原因一般是本地有多个 python 版本环境,导致链接出错,于是我先定位一下本地机器中的 python 解释器版本有哪些:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>➜ where python /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到我电脑指向的是 /Library/Frameworks 下的内置 python 环境,然而我又翻看了一下 <code class="language-plaintext highlighter-rouge">~/.bash_profile</code> 中的 python 环境配置:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 </pre></td><td class="rouge-code"><pre><span class="c"># Python 配置</span> <span class="nb">alias </span><span class="nv">python</span><span class="o">=</span><span class="s2">"/Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10"</span> <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"/Library/Frameworks/Python.framework/Versions/3.10/bin:</span><span class="nv">$PATH</span><span class="s2">"</span> <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/usr/local/bin:<span class="nv">$PATH</span> <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/Users/<span class="o">{</span>YOUR_NAME<span class="o">}</span>/DevKit/bin:<span class="nv">$PATH</span> <span class="nb">export </span><span class="nv">REPO_URL</span><span class="o">=</span><span class="s1">'https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,我这边配置了不止一项 python 环境,<code class="language-plaintext highlighter-rouge">/usr/local/bin</code> 下还存在一个 3.0 版本的 python,那么为什么 repo 中的 python 代码执行不了呢?简单看了下 repo 的<a href="https://docs.python.org/3/library/sys.html">内部代码</a>,发现他里面是通过 sys 库来获取的当前运行的 python 解释器。以下是笔者的个人猜测:在 macOS 系统中,系统默认的 Python 解释器是 Python 2.x 版本,如果安装了 Python 3.x 版本,而且没有设置符号链接将其路径添加到系统 PATH 中,那么系统就无法找到 Python 3.x 版本的可执行文件。因此,当您在终端中输入 <code class="language-plaintext highlighter-rouge">python3</code> 命令时,系统会去默认的路径 <code class="language-plaintext highlighter-rouge">/usr/bin/</code> 中查找 Python 解释器,而无法找到 Python 3.x 版本的解释器,从而导致 “env: python: No such file or directory” 错误的发生。 要解决这个问题,我们可以执行以下命令:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre><span class="nb">ln</span> <span class="nt">-sf</span> /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 /usr/local/bin/python </pre></td></tr></tbody></table></code></pre></div></div> <p>这个命令的作用是将 Python 3.10 版本的可执行文件添加到了 <code class="language-plaintext highlighter-rouge">/usr/local/bin</code> 目录下,并创建了一个符号链接(软链接),使得 <code class="language-plaintext highlighter-rouge">python</code> 命令可以正确地指向 Python 3.10 版本的可执行文件,从而避免在使用 <code class="language-plaintext highlighter-rouge">python</code> 命令时出现因为找不到 Python 3.10 版本的可执行文件而报错的问题。这样,当我们在终端中输入 <code class="language-plaintext highlighter-rouge">python</code> 或 <code class="language-plaintext highlighter-rouge">python3</code> 命令时,系统会优先在 <code class="language-plaintext highlighter-rouge">/usr/local/bin</code> 目录下查找,找到该目录下的 <code class="language-plaintext highlighter-rouge">python</code> 或 <code class="language-plaintext highlighter-rouge">python3</code> 命令,然后使用其中的 Python 3.10 版本的可执行文件来执行程序,从而保证我们使用的是正确的 Python 版本。</p> <p>接下来,安装完成 repo 后,我可以将以下 <code class="language-plaintext highlighter-rouge">REPO_URL</code> 地址替换为国内的镜像地址,方便后续下载 AOSP 源码,由于该项目较大,需要保证稳定的网络环境,不具备翻墙条件的此步骤为必选项:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre><span class="nb">export </span><span class="nv">REPO_URL</span><span class="o">=</span><span class="s1">'https://mirrors.tuna.tsinghua.edu.cn/git/git-repo'</span> <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/Users/<span class="o">{</span>YOUR_NAME<span class="o">}</span>/DevKit/bin:<span class="nv">$PATH</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>以上配置请复制到 ~/.bash_profile 中,并执行 <code class="language-plaintext highlighter-rouge">source ~/.bash_profile</code> 使其生效。这里需要将 repo 的 path 也放置到环境变量中,方便我们全局调用。</p> <blockquote> <p>关于 repo 的具体用法可以参考:<a href="https://juejin.cn/post/6971009369693503519">多仓库管理工具—Repo</a></p> </blockquote> <h2 id="下载-aosp">下载 AOSP</h2> <p>接下来我们就可以愉快的下载 AOSP 代码了:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre>➜ <span class="nb">mkdir </span>AOSP ➜ <span class="nb">cd</span> ~/AOSP ➜ repo init <span class="nt">-u</span> https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest <span class="nt">-b</span> android-13.0.0_r30 </pre></td></tr></tbody></table></code></pre></div></div> <p>通过 repo init 命令来初始化、关联具体的 repo 项目,并指定了下载分支为 Android 13 版本的代码。当然,事情可能并不会一帆风顺,出现了下面的报错信息:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>fatal: Cannot get https://gerrit.googlesource.com/git-repo/clone.bundle fatal: error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590) </pre></td></tr></tbody></table></code></pre></div></div> <p>我们需要执行一下 Python 安装包下的 <code class="language-plaintext highlighter-rouge">Install Certificates.command</code> 脚本来安装证书,然后重新执行上述 init 命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 </pre></td><td class="rouge-code"><pre>Downloading Repo source from https://mirrors.tuna.tsinghua.edu.cn/git/git-repo remote: Enumerating objects: 4463, done. remote: Counting objects: 100% (4463/4463), done. remote: Compressing objects: 100% (2123/2123), done. remote: Total 8034 (delta 3979), reused 2340 (delta 2340), pack-reused 3571 Receiving objects: 100% (8034/8034), 3.74 MiB | 13.53 MiB/s, done. Resolving deltas: 100% (5171/5171), done. repo: Updating release signing keys to keyset ver 2.3 </pre></td></tr></tbody></table></code></pre></div></div> <p>输出以上信息说明 repo 的 manifest 配置下载成功,接下来只需要同步一下代码即可:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>➜ repo <span class="nb">sync</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>代码同步时间会比较长,我们能做的就是耐心等待了。最终下载完成的 AOSP 大小直逼 150 GB:</p> <p><img src="/img/in-post/post-android/aosp_download_suc.png" alt="aosp_download_suc" style="zoom: 50%;" /></p> <p>下一篇文章将会一起探索如何在 Mac 上编译 Android Open Source Project 项目,拭目以待。</p> <h2 id="参考">参考</h2> <ul> <li><a href="https://source.android.google.cn/docs/setup/about/build-numbers?hl=zh-cn#source-code-tags-and-builds"><em>AOSP 中的代号、标记和 build 号</em></a></li> </ul>

2023/4/21
阅读更多

效率编程之快捷键篇

<p>这是一个关于如何<strong>效率编程</strong>的系列专题,未来将会涉及诸如工具、脚本、插件、开源框架等各方面,只要能够帮助我们提升开发效率。</p> <p>在日常当中,熟练地运用各种快捷键能够极大地提升我们的开发和工作效率。当看到其他同学通过快捷键两三下操作就帮你定位了问题代码并修改完毕,你是否也曾露出羡慕之色。正所谓磨刀不误砍柴工,快捷键也像宝刀一样,长时间弃而不用就会生锈,长此以往我们就会失去这一强大的臂助。</p> <blockquote> <p><em>以下常用快捷键指令皆基于 MAC 系统。</em></p> </blockquote> <h3 id="基础文本操作">基础文本操作</h3> <p>其实很多文本类工具都提供了通用的快捷键,比如文本选择、光标快速移动等等,毋以善小而不为,不要小看这些小小的效率提升,聚沙成塔,长久以后就会凝聚成我们自身强大工作流的一部分。</p> <h5 id="光标移动">光标移动</h5> <ul> <li>光标移动到行头/尾:<code class="language-plaintext highlighter-rouge">Cmd + ←</code> / <code class="language-plaintext highlighter-rouge">Cmd + →</code></li> <li>光标移动到文本开头/结尾:<code class="language-plaintext highlighter-rouge">Cmd + ↑</code> / <code class="language-plaintext highlighter-rouge">Cmd + ↓</code></li> <li>光标向前/向后隔一词跳跃:<code class="language-plaintext highlighter-rouge">Option + ←</code> / <code class="language-plaintext highlighter-rouge">Option + →</code></li> </ul> <h5 id="文本浏览">文本浏览</h5> <ul> <li>移动到文本开始/末尾:<code class="language-plaintext highlighter-rouge">Fn + ←</code> / <code class="language-plaintext highlighter-rouge">Fn + →</code>(浏览器和部分文本工具支持)</li> <li>文本上/下翻页:<code class="language-plaintext highlighter-rouge">Fn + ↑</code> / <code class="language-plaintext highlighter-rouge">Fn + ↓</code> (大部分文本工具都支持)</li> </ul> <h5 id="文本选取">文本选取</h5> <p>通过 <strong><code class="language-plaintext highlighter-rouge">Shift</code></strong> 搭配上方的光标移动快捷键,我们可以便捷实现文本选取。</p> <ul> <li>从当前位置选取到行首/尾:<code class="language-plaintext highlighter-rouge">Cmd + Shift + ←</code> / <code class="language-plaintext highlighter-rouge">Cmd + Shift + →</code></li> <li>从当前位置选取到文本开头/结尾:<code class="language-plaintext highlighter-rouge">Cmd + Shift + ↑</code> / <code class="language-plaintext highlighter-rouge">Cmd + Shift + ↓</code> 或者使用 <code class="language-plaintext highlighter-rouge">Fn + Shift + ←</code> / <code class="language-plaintext highlighter-rouge">Fn + Shift + →</code></li> <li>从当前位置向前/向后选取一个单词:<code class="language-plaintext highlighter-rouge">Option + Shift + ←</code> / <code class="language-plaintext highlighter-rouge">Option + Shift + →</code></li> <li>从当前位置向上/下选取一页:<code class="language-plaintext highlighter-rouge">Fn + Shift + ↑</code> / <code class="language-plaintext highlighter-rouge">Fn + Shift + ↓</code></li> </ul> <h5 id="文本删除">文本删除</h5> <ul> <li>向前删除:<code class="language-plaintext highlighter-rouge">Delete</code></li> <li>向后删除:<code class="language-plaintext highlighter-rouge">Fn + delete</code></li> <li>向前删除一个词:<code class="language-plaintext highlighter-rouge">Option + delete</code></li> <li>向后删除一个词:<code class="language-plaintext highlighter-rouge">Option + Fn + delete</code></li> <li>删除至行首:<code class="language-plaintext highlighter-rouge">Cmd + delete</code></li> </ul> <h5 id="文本查询">文本查询</h5> <ul> <li>当前文稿内查询:<code class="language-plaintext highlighter-rouge">Cmd + F</code></li> <li> <p>项目内查询:<code class="language-plaintext highlighter-rouge">Cmd + Shift + F</code></p> </li> <li>查询并定位命中的下一个目标:<code class="language-plaintext highlighter-rouge">Enter</code> / <code class="language-plaintext highlighter-rouge">Cmd + G</code></li> <li>查询并定位命中的上一个目标:<code class="language-plaintext highlighter-rouge">Cmd + Shift + G</code></li> </ul> <h5 id="文本样式">文本样式</h5> <ul> <li>粗体:<code class="language-plaintext highlighter-rouge">Cmd + B</code></li> <li>斜体:<code class="language-plaintext highlighter-rouge">Cmd + I</code></li> <li>下划线:<code class="language-plaintext highlighter-rouge">Cmd + U</code></li> <li>文字超链接:<code class="language-plaintext highlighter-rouge">Cmd + K</code></li> </ul> <h3 id="窗口操作">窗口操作</h3> <h5 id="切换">切换</h5> <ul> <li>隐藏屏幕正在显示的最上层窗口:<code class="language-plaintext highlighter-rouge">Cmd + H</code></li> <li>隐藏屏幕正在显示的底层窗口(除最上层以外的窗口):<code class="language-plaintext highlighter-rouge">Cmd + Option + H</code></li> <li>在多个全屏窗口之间前后切换:<code class="language-plaintext highlighter-rouge">Control + ←</code> / <code class="language-plaintext highlighter-rouge">Control + →</code></li> <li>将所有窗口进入/退出预览模式:<code class="language-plaintext highlighter-rouge">Control + ↑</code> / <code class="language-plaintext highlighter-rouge">Control + ↓</code></li> </ul> <h5 id="应用程序">应用程序</h5> <ul> <li>在多个应用程序间前后切换:<code class="language-plaintext highlighter-rouge">Cmd + Tab</code> / <code class="language-plaintext highlighter-rouge">Cmd + Shift + Tab</code> / <code class="language-plaintext highlighter-rouge">Cmd + Tab + ←</code> / <code class="language-plaintext highlighter-rouge">Cmd + Tab + →</code></li> <li>退出最上层的应用程序:<code class="language-plaintext highlighter-rouge">Cmd + Q</code></li> <li>退出除最上层以外的其他应用:<code class="language-plaintext highlighter-rouge">Cmd + Shift + Q</code></li> </ul> <h5 id="关闭">关闭</h5> <ul> <li>关闭窗口:<code class="language-plaintext highlighter-rouge">Cmd + W</code></li> </ul> <h3 id="文件操作">文件操作</h3> <ul> <li>新建文件夹:<code class="language-plaintext highlighter-rouge">Command + Shift + N</code></li> <li>弹出窗口输入绝对路径课直达目标文件夹:<code class="language-plaintext highlighter-rouge">Command + Shift + G</code></li> <li>打开所选文件:<code class="language-plaintext highlighter-rouge">Command + O</code></li> <li>前往当前文件夹的上/下一层文件夹:<code class="language-plaintext highlighter-rouge">Command + ↑</code> / <code class="language-plaintext highlighter-rouge">Command + ↓</code></li> <li>将文件移至废纸篓:<code class="language-plaintext highlighter-rouge">Command + Delete</code></li> <li>清理废纸篓:<code class="language-plaintext highlighter-rouge">Command + Shift + Delete</code></li> <li>文件预览:<code class="language-plaintext highlighter-rouge">Space</code></li> <li>查看文件简介:<code class="language-plaintext highlighter-rouge">Command + I</code></li> <li>进入当前文件操作栈的上/下一层:<code class="language-plaintext highlighter-rouge">Cmd + [</code> / <code class="language-plaintext highlighter-rouge">Cmd + ]</code></li> <li>最小化窗口:<code class="language-plaintext highlighter-rouge">Cmd + M</code></li> <li>查看最近使用的文件:<code class="language-plaintext highlighter-rouge">Shift + Cmd + F</code></li> <li>进入下载目录:<code class="language-plaintext highlighter-rouge">Option + Cmd + L</code></li> <li>进入个人目录:<code class="language-plaintext highlighter-rouge">Shift + Cmd + H</code></li> </ul> <h3 id="其他系统快捷键">其他系统快捷键</h3> <h5 id="截图">截图</h5> <ul> <li>截取整个屏幕内容到桌面:<code class="language-plaintext highlighter-rouge">Command + Shift + 3</code></li> <li>截取整个屏幕内容到剪贴板:<code class="language-plaintext highlighter-rouge">Command + Shift + Control + 3</code></li> <li>截取所选屏幕区域到桌面,或按空格键仅捕捉一个窗口:<code class="language-plaintext highlighter-rouge">Command + Shift + 4</code></li> <li>截取所选屏幕区域到剪贴板,或按空格键仅捕捉一个窗口:<code class="language-plaintext highlighter-rouge">Command + Shift + Control + 4</code></li> </ul> <h5 id="撤销与恢复">撤销与恢复</h5> <p>很多时候,我们在经常编辑文本或处理文件的时候会大量用到撤销和恢复撤销两个动作。</p> <ul> <li>撤销:<code class="language-plaintext highlighter-rouge">Cmd + Z</code></li> <li>恢复撤销:<code class="language-plaintext highlighter-rouge">Cmd + Shift + Z</code></li> </ul> <h5 id="锁屏与关机">锁屏与关机</h5> <ul> <li>锁定屏幕:<code class="language-plaintext highlighter-rouge">Control + Command + Q</code></li> <li>重启:<code class="language-plaintext highlighter-rouge">Control + Command + Power</code></li> <li>关机:<code class="language-plaintext highlighter-rouge">Command + Option + COntrol + Power</code></li> </ul> <h3 id="android-studio-快捷键">Android Studio 快捷键</h3> <h5 id="搜索替换">搜索&amp;替换</h5> <table> <thead> <tr> <th>快捷键说明</th> <th>Windows | Linux</th> <th>MacOS</th> </tr> </thead> <tbody> <tr> <td>全局搜索(代码、文件、菜单)</td> <td>Shift + Shift</td> <td>Shift + Shift</td> </tr> <tr> <td>当前文件内查找代码</td> <td>Ctrl + F</td> <td>Command + F</td> </tr> <tr> <td>当前文件内代码替换</td> <td>Ctrl + R</td> <td>Command + R</td> </tr> <tr> <td>全局搜索代码(路径、项目、模块)</td> <td>Ctrl + Shift + F</td> <td>Command + Shift + F</td> </tr> <tr> <td>全局替换(路径、项目、模块)</td> <td>Ctrl + Shift + R</td> <td>Command + Shift + R</td> </tr> <tr> <td>查找下一项内容</td> <td>F3</td> <td>Command + G</td> </tr> <tr> <td>查找上一项内容</td> <td>Shift + F3</td> <td>Command + Shift+ G</td> </tr> <tr> <td>查看最近打开的文件窗口</td> <td>Ctrl + E</td> <td>Command + E</td> </tr> <tr> <td>查看最近编辑过的文件窗口</td> <td>Ctrl + Shift + E</td> <td>Command + Shift + E</td> </tr> <tr> <td>关闭当前窗口</td> <td>Ctrl + F4</td> <td>Command + W</td> </tr> </tbody> </table> <h5 id="代码查看编辑">代码查看&amp;编辑</h5> <table> <thead> <tr> <th>快捷键说明</th> <th>Windows | Linux</th> <th>MacOS</th> </tr> </thead> <tbody> <tr> <td>进入源代码</td> <td>F4 或 Ctrl + Enter</td> <td>Command + ↓ 或 Command + 鼠标单击</td> </tr> <tr> <td>跳转到代码指定行</td> <td>Ctrl+G</td> <td>Command + L</td> </tr> <tr> <td>跳转到上一个代码编辑的位置</td> <td>Ctrl + Shift + 退格键</td> <td>Command + Option + ← 或 Command+Shift+Delete</td> </tr> <tr> <td>跳转到下一个代码编辑的位置</td> <td> </td> <td>Command + Option + →</td> </tr> <tr> <td>查看类层次结构(继承关系)</td> <td>Ctrl + H</td> <td><strong>Ctrl + H</strong></td> </tr> <tr> <td>查看方法层次结构</td> <td>Ctrl + Shift + H</td> <td>Command + Shift + H</td> </tr> <tr> <td>查看调用层次结构</td> <td>Ctrl + Alt + H</td> <td><strong>Ctrl + Option + H</strong></td> </tr> <tr> <td>插入模版代码</td> <td>Alt + Insert</td> <td>Command + N</td> </tr> <tr> <td>前往子类实现该方法</td> <td>Ctrl + I</td> <td>Ctrl + I</td> </tr> <tr> <td>选择重写父类某个方法</td> <td>Ctrl + O</td> <td>Ctrl + O</td> </tr> <tr> <td>将代码放入条件语句中(if-else/try-catch..)</td> <td>Ctrl + Alt + T</td> <td><strong>Command + Option + T</strong></td> </tr> <tr> <td>删除整行</td> <td>Ctrl + Y</td> <td>Command + Delete</td> </tr> <tr> <td>收起/展开当前代码块</td> <td>Ctrl + 减号键Ctrl + 加号键</td> <td>Command+减号键或Command+加号键</td> </tr> <tr> <td>收起/展开所有代码块</td> <td>Ctrl + Shift + 减号键或 Ctrl + Shift + 加号键</td> <td>Command+Shift+减号键或 Command+Shift+加号键</td> </tr> <tr> <td>复制当前行并另起一行粘贴(不会放到粘贴板)</td> <td>Ctrl + D</td> <td>Command + D</td> </tr> <tr> <td>快速预览查看文档</td> <td>Ctrl + Q</td> <td>Ctrl + J</td> </tr> <tr> <td>预览查看选定方法的参数</td> <td>Ctrl + P</td> <td><strong>Command + P</strong></td> </tr> <tr> <td>显示或直接跳转到方法调用方</td> <td>Ctrl + B 或 Ctrl + 点击</td> <td>Command + B 或 Command + 点击</td> </tr> <tr> <td>显示或直接跳转到方法实现处</td> <td>Ctrl + Alt + B</td> <td><strong>Command + Option + B</strong></td> </tr> <tr> <td>跳转到超类方法处</td> <td>Ctrl + U</td> <td><strong>Command + U</strong></td> </tr> <tr> <td>快速预览方法定义</td> <td>Ctrl + Shift + I</td> <td><strong>Command + Y</strong></td> </tr> <tr> <td>切换项目左侧工具窗口的可见性</td> <td>Alt + 1</td> <td><strong>Command + 1</strong></td> </tr> <tr> <td>添加/取消行注释</td> <td>Ctrl + /</td> <td>Command + /</td> </tr> <tr> <td>添加/取消块注释</td> <td>Ctrl + Shift + /</td> <td><strong>Command + Shift + /</strong></td> </tr> <tr> <td>增加选中的代码块</td> <td>Ctrl + W</td> <td>Option + 向上箭头</td> </tr> <tr> <td>减少选中的代码块</td> <td>Ctrl + Shift + W</td> <td>Option + 向下箭头</td> </tr> <tr> <td>移动到代码块起始位置</td> <td>Ctrl + [</td> <td><strong>Option + Command + [</strong></td> </tr> <tr> <td>移动到代码块结束位置</td> <td>Ctrl + ]</td> <td><strong>Option + Command + ]</strong></td> </tr> <tr> <td>从当前位置选择到代码块起始位置</td> <td>Ctrl + Shift + [</td> <td>Option + Command + <strong>Shift</strong> + [</td> </tr> <tr> <td>从当前位置选择到代码块结束位置</td> <td>Ctrl + Shift + ]</td> <td>Option + Command + <strong>Shift</strong> + ]</td> </tr> <tr> <td>包名导入优化</td> <td>Ctrl + Alt + O</td> <td><strong>Control + Option + O</strong></td> </tr> <tr> <td>显示快速修复建议</td> <td>Alt + Enter</td> <td><strong>Option + Enter</strong></td> </tr> <tr> <td>重新格式化代码</td> <td>Ctrl + Alt + L</td> <td>Command + Option + L</td> </tr> <tr> <td>自动修复行缩进</td> <td>Ctrl + Alt + I</td> <td>Ctrl + Option + I</td> </tr> <tr> <td>缩进/取消缩进行</td> <td>Tab 或 Shift + Tab</td> <td>Tab 或 Shift + Tab</td> </tr> <tr> <td>另起一行</td> <td>Shift + Enter</td> <td>Shift + Enter(注意和 Command + Enter 区别:前者光标自动跟进到下一行开头,而后者光标停留在原地)</td> </tr> </tbody> </table> <h5 id="版本控制">版本控制</h5> <table> <thead> <tr> <th>快捷键说明</th> <th>Windows | Linux</th> <th>MacOS</th> </tr> </thead> <tbody> <tr> <td>将项目提交到 VCS(Push)</td> <td>Ctrl + K</td> <td>Command + K</td> </tr> <tr> <td>从 VCS 更新项目(Pull)</td> <td>Ctrl + T</td> <td>Command + T</td> </tr> <tr> <td>查看最近变更</td> <td>Alt + Shift + C</td> <td>Option + Shift + C</td> </tr> <tr> <td>打开 VCS 对话框</td> <td>Alt +`(反引号)</td> <td>Ctrl + V</td> </tr> </tbody> </table> <h5 id="重构相关">重构相关</h5> <table> <thead> <tr> <th>快捷键说明</th> <th>Windows | Linux</th> <th>MacOS</th> </tr> </thead> <tbody> <tr> <td>复制</td> <td>F5</td> <td>Fn + F5</td> </tr> <tr> <td>移动</td> <td>F6</td> <td>FN + F6</td> </tr> <tr> <td>安全删除</td> <td>Alt+Delete</td> <td>Command + Delete</td> </tr> <tr> <td>重命名(方法、属性、文件等)</td> <td>Shift+F6</td> <td><strong>Fn + Shift + F6</strong></td> </tr> <tr> <td>更改方法/属性签名</td> <td>Ctrl+F6</td> <td><strong>Fn + Command + F6</strong></td> </tr> <tr> <td>方法内联(将方法内实现部分提取到方法调用处)</td> <td>Ctrl + Alt + N</td> <td><strong>Command + Option + N</strong></td> </tr> <tr> <td>提取方法</td> <td>Ctrl+Alt+M</td> <td><strong>Command + Option + M</strong></td> </tr> <tr> <td>提取为局部变量</td> <td>Ctrl+Alt+V</td> <td><strong>Command + Option + V</strong></td> </tr> <tr> <td>提取为类成员字段</td> <td>Ctrl+Alt+F</td> <td><strong>Command + Option + F</strong></td> </tr> <tr> <td>提取为常量</td> <td>Ctrl+Alt+C</td> <td><strong>Command + Option + C</strong></td> </tr> <tr> <td>提取为方法参数(e.g,将方法内局部变量提取为通过外部传入)</td> <td>Ctrl+Alt+P</td> <td><strong>Command + Option + P</strong></td> </tr> </tbody> </table> <h5 id="代码调试">代码调试</h5> <table> <thead> <tr> <th>快捷键说明</th> <th>Windows | Linux</th> <th>MacOS</th> </tr> </thead> <tbody> <tr> <td>调试</td> <td>Shift + F9</td> <td>Ctrl+D</td> </tr> <tr> <td>单步跳过</td> <td>F8</td> <td>Fn + F8</td> </tr> <tr> <td>单步进入</td> <td>F7</td> <td>Fn + F7</td> </tr> <tr> <td>智能单步进入</td> <td>Shift + F7</td> <td>Fn + Shift + F7</td> </tr> <tr> <td>单步退出</td> <td>Shift + F8</td> <td>Fn + Shift + F8</td> </tr> <tr> <td>运行到光标位置</td> <td>Alt + F9</td> <td>Fn + Option + F9</td> </tr> <tr> <td>评估表达式</td> <td>Alt + F8</td> <td>Fn + Option + F8</td> </tr> <tr> <td>继续运行程序</td> <td>F9</td> <td>Command + Option + R</td> </tr> <tr> <td>切换断点</td> <td>Ctrl + F8</td> <td>Fn + Command + F8</td> </tr> <tr> <td>查看断点</td> <td>Ctrl + Shift + F8</td> <td>Fn + Command + Shift + F8</td> </tr> </tbody> </table> <h5 id="自定义快捷键">自定义快捷键</h5> <p>Android Studio 还有很多操作没有关联上具体的快捷键,而我们也可以通过自定义的方式来为高频操作绑定上快捷键。只需要到工具栏的 Android Studio 一栏找到 Preference(或用快捷键 <code class="language-plaintext highlighter-rouge">Cmd + ,</code> )进入设置 <code class="language-plaintext highlighter-rouge">keymap</code> 选项中搜索对应的操作即可。这里以 <em>Sync Project with Gradle Files</em> 操作为例:</p> <p><img src="/img/in-post/post-skills/image-20230416085732692.png" alt="image-20230416085732692" style="zoom:50%;" /></p> <p>接下来只需选中目标项绑定快捷键即可,我这里绑定的快捷键是 <code class="language-plaintext highlighter-rouge">CMd + Option + S</code>。Android Studio 比较智能,如果绑定了已被使用的快捷键会提示冲突问题。</p> <h3 id="小结">小结</h3> <p>开发效率即意味着生产力,长时间维持着 996 状态也并不代表生产力就高,相反,我们自身会一直处于高压状态,不利于身心健康。故而,我们应该着手于术,从实际出发,点滴汇聚,提升效率,把时间多专注在享受生活上,而不是庸庸碌碌的“机器命”。</p> <h3 id="参考">参考</h3> <ul> <li><a href="https://developer.android.google.cn/studio/intro/keyboard-shortcuts?hl=zh-cn"><em>Android Studio shortcuts</em></a></li> </ul>

2023/4/16
阅读更多

Android系统中线程的创建过程

<p>我们都知道,Android 中线程创建过程需要追溯到 Native 层面,最终是委托给一个 Linux 标准线程 pthread 来执行的,所以 Android 中线程状态本质上是 Native 线程的一种映射。Android 中运行的线程可以分为两种:一种是 attach 到虚拟机的,即虚拟机线程;另一种是没有 attach 到虚拟机的。今天我们就分别从源码层面来看看 Android 系统中 Java 和 Native 层线程的创建过程。</p> <blockquote> <p><em>以下分析基于 <a href="https://cs.android.com">Android 13 最新源码</a>。</em></p> </blockquote> <h3 id="java-线程创建过程">Java 线程创建过程</h3> <p>首先,我们需要知道的是:当我们通过 <code class="language-plaintext highlighter-rouge">new</code> 关键字创建一个 Thread 时其实并没有真正创建一个线程,只有调用 <code class="language-plaintext highlighter-rouge">start</code> 方法后才会去创建线程。先来看下 <code class="language-plaintext highlighter-rouge">start</code> 方法内部实现:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 </pre></td><td class="rouge-code"><pre> <span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">start</span><span class="o">()</span> <span class="o">{</span> <span class="cm">/** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */</span> <span class="c1">// Android-changed: Replace unused threadStatus field with started field.</span> <span class="c1">// The threadStatus field is unused on Android.</span> <span class="k">if</span> <span class="o">(</span><span class="n">started</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalThreadStateException</span><span class="o">();</span> <span class="cm">/* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */</span> <span class="n">group</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="c1">// Android-changed: Use field instead of local variable.</span> <span class="c1">// It is necessary to remember the state of this across calls to this method so that it</span> <span class="c1">// can throw an IllegalThreadStateException if this method is called on an already</span> <span class="c1">// started thread.</span> <span class="n">started</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="k">try</span> <span class="o">{</span> <span class="c1">// Android-changed: Use Android specific nativeCreate() method to create/start thread.</span> <span class="c1">// start0();</span> <span class="n">nativeCreate</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">stackSize</span><span class="o">,</span> <span class="n">daemon</span><span class="o">);</span> <span class="n">started</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(!</span><span class="n">started</span><span class="o">)</span> <span class="o">{</span> <span class="n">group</span><span class="o">.</span><span class="na">threadStartFailed</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ignore</span><span class="o">)</span> <span class="o">{</span> <span class="cm">/* do nothing. If start0 threw a Throwable then it will be passed up the call stack */</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,最终线程的创建和执行是在 <code class="language-plaintext highlighter-rouge">nativeCreate</code> 方法中,而它是个 native 方法,对应的实现在 <em>/art/runtime/native/java_lang_Thread.cc</em> 文件中,代码如下:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 </pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">Thread_nativeCreate</span><span class="p">(</span><span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">,</span> <span class="n">jclass</span><span class="p">,</span> <span class="n">jobject</span> <span class="n">java_thread</span><span class="p">,</span> <span class="n">jlong</span> <span class="n">stack_size</span><span class="p">,</span> <span class="n">jboolean</span> <span class="n">daemon</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// There are sections in the zygote that forbid thread creation.</span> <span class="n">Runtime</span><span class="o">*</span> <span class="n">runtime</span> <span class="o">=</span> <span class="n">Runtime</span><span class="o">::</span><span class="n">Current</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">runtime</span><span class="o">-&gt;</span><span class="n">IsZygote</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">runtime</span><span class="o">-&gt;</span><span class="n">IsZygoteNoThreadSection</span><span class="p">())</span> <span class="p">{</span> <span class="n">jclass</span> <span class="n">internal_error</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">FindClass</span><span class="p">(</span><span class="s">"java/lang/InternalError"</span><span class="p">);</span> <span class="n">CHECK</span><span class="p">(</span><span class="n">internal_error</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">);</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">ThrowNew</span><span class="p">(</span><span class="n">internal_error</span><span class="p">,</span> <span class="s">"Cannot create threads in zygote"</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="n">Thread</span><span class="o">::</span><span class="n">CreateNativeThread</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">java_thread</span><span class="p">,</span> <span class="n">stack_size</span><span class="p">,</span> <span class="n">daemon</span> <span class="o">==</span> <span class="n">JNI_TRUE</span><span class="p">);</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续跟踪到 <em>/art/runtime/thread.cc</em>:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 </pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="n">Thread</span><span class="o">::</span><span class="n">CreateNativeThread</span><span class="p">(</span><span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">,</span> <span class="n">jobject</span> <span class="n">java_peer</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">stack_size</span><span class="p">,</span> <span class="n">bool</span> <span class="n">is_daemon</span><span class="p">)</span> <span class="p">{</span> <span class="n">CHECK</span><span class="p">(</span><span class="n">java_peer</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">);</span> <span class="n">Thread</span><span class="o">*</span> <span class="n">self</span> <span class="o">=</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="n">JNIEnvExt</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">env</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">GetSelf</span><span class="p">();</span> <span class="p">......</span> <span class="kt">int</span> <span class="n">pthread_create_result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">child_jni_env_ext</span><span class="p">.</span><span class="n">get</span><span class="p">()</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">)</span> <span class="p">{</span> <span class="n">pthread_t</span> <span class="n">new_pthread</span><span class="p">;</span> <span class="n">pthread_attr_t</span> <span class="n">attr</span><span class="p">;</span> <span class="n">child_thread</span><span class="o">-&gt;</span><span class="n">tlsPtr_</span><span class="p">.</span><span class="n">tmp_jni_env</span> <span class="o">=</span> <span class="n">child_jni_env_ext</span><span class="p">.</span><span class="n">get</span><span class="p">();</span> <span class="n">CHECK_PTHREAD_CALL</span><span class="p">(</span><span class="n">pthread_attr_init</span><span class="p">,</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">),</span> <span class="s">"new thread"</span><span class="p">);</span> <span class="n">CHECK_PTHREAD_CALL</span><span class="p">(</span><span class="n">pthread_attr_setdetachstate</span><span class="p">,</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">PTHREAD_CREATE_DETACHED</span><span class="p">),</span> <span class="s">"PTHREAD_CREATE_DETACHED"</span><span class="p">);</span> <span class="n">CHECK_PTHREAD_CALL</span><span class="p">(</span><span class="n">pthread_attr_setstacksize</span><span class="p">,</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">stack_size</span><span class="p">),</span> <span class="n">stack_size</span><span class="p">);</span> <span class="n">pthread_create_result</span> <span class="o">=</span> <span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">new_pthread</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">gUseUserfaultfd</span> <span class="o">?</span> <span class="n">Thread</span><span class="o">::</span><span class="n">CreateCallbackWithUffdGc</span> <span class="o">:</span> <span class="n">Thread</span><span class="o">::</span><span class="n">CreateCallback</span><span class="p">,</span> <span class="n">child_thread</span><span class="p">);</span> <span class="n">CHECK_PTHREAD_CALL</span><span class="p">(</span><span class="n">pthread_attr_destroy</span><span class="p">,</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">),</span> <span class="s">"new thread"</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">pthread_create_result</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// pthread_create started the new thread. The child is now responsible for managing the</span> <span class="c1">// JNIEnvExt we created.</span> <span class="c1">// Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization</span> <span class="c1">// between the threads.</span> <span class="n">child_jni_env_ext</span><span class="p">.</span><span class="n">release</span><span class="p">();</span> <span class="c1">// NOLINT pthreads API.</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="p">......</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">Thread::CreateNativeThread</code> 方法实现比较多,过滤了一些代码,我们重点关注下里面调用了 <code class="language-plaintext highlighter-rouge">pthread_create</code> 方法:</p> <blockquote> <p><em>bionic/libc/bionic/pthread_create.cpp</em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">pthread_create</span><span class="p">(</span><span class="n">pthread_t</span><span class="o">*</span> <span class="n">thread_out</span><span class="p">,</span> <span class="n">pthread_attr_t</span> <span class="k">const</span><span class="o">*</span> <span class="n">attr</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="p">(</span><span class="o">*</span><span class="n">start_routine</span><span class="p">)(</span><span class="kt">void</span><span class="o">*</span><span class="p">),</span> <span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span> <span class="p">{</span> <span class="p">......</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>POSIX 线程(POSIX threads)又简称 Pthreads 是线程的 POSIX 标准,该标准定义了创建和操纵线程的一整套 API,在类 Unix 操作系统(Unix、Linux、Mac OS X等)中都使用 Pthreads 作为操作系统的线程,Windows操作系统也有其移植版 pthreads-win32。简而言之该标准定义内部 API 创建和操纵线程, Pthreads 定义了一套 C 程序语言类型、函数与常量,它以 pthread.h 头文件和一个线程库实现,所以在 Android Studio 使用时直接在 C/C++ 文件中 <em>#include &lt; pthread.h &gt;</em> 引入即可。</p> <p>该函数是一个线程阻塞函数,调用方将一直等待到线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回 0,如果失败则返回一个错误码。</p> <p>该函数参数释义如下:</p> <ul> <li><em>thread_out</em>:线程标识符的指针,pthread_t 类型,即线程 ID(线程创建成功后会将分配的线程 ID 赋值给 thread_out)</li> <li><em>attr</em>:它是一个结构体类型,用于表示创建线程的相关属性信息,如线程优先级、调度策略等等</li> <li><em>start_routine</em>:表示线程运行函数的地址</li> <li><em>arg</em>:代表线程运行函数的参数</li> </ul> <p>看到这里,基本代表 Java 层面的线程创建路径分析完成了,想更多了解关于 pthread 内容可自行查阅资料。此外,Android 中还有一种 native 线程,即 C/C++ 平台的 Thread 特供版。</p> <h3 id="native-线程创建">Native 线程创建</h3> <p>刚刚我们分析了 Java 层面的 Thread 创建流程,下面来简单看下 Android Native 层中的 Thread 是如何创建的。首先我们需要关注一下 <em>system/core/libutils/Threads.cpp</em> 这个文件,里面是针对 Android 平台的 native 层如何创建一个线程的相关封装。</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 </pre></td><td class="rouge-code"><pre><span class="n">status_t</span> <span class="n">Thread</span><span class="o">::</span><span class="n">run</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">priority</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">stack</span><span class="p">)</span> <span class="p">{</span> <span class="n">Mutex</span><span class="o">::</span><span class="n">Autolock</span> <span class="n">_l</span><span class="p">(</span><span class="n">mLock</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">mRunning</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// thread already started</span> <span class="k">return</span> <span class="n">INVALID_OPERATION</span><span class="p">;</span> <span class="p">}</span> <span class="p">...</span> <span class="n">mRunning</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="n">bool</span> <span class="n">res</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">mCanCallJava</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 创建能调用Java代码的Native线程</span> <span class="n">res</span> <span class="o">=</span> <span class="n">createThreadEtc</span><span class="p">(</span><span class="n">_threadLoop</span><span class="p">,</span> <span class="n">this</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">priority</span><span class="p">,</span> <span class="n">stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mThread</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// 创建只能调用C/C++代码的Native线程</span> <span class="n">res</span> <span class="o">=</span> <span class="n">androidCreateRawThreadEtc</span><span class="p">(</span><span class="n">_threadLoop</span><span class="p">,</span> <span class="n">this</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">priority</span><span class="p">,</span> <span class="n">stack</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mThread</span><span class="p">);</span> <span class="p">}</span> <span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">==</span> <span class="nb">false</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">UNKNOWN_ERROR</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="n">NO_ERROR</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">mCanCallJava</code> 是在 Thread 对象创建时的构造参数,在构造函数中默认设置值为 <code class="language-plaintext highlighter-rouge">true</code>。</p> <ul> <li>当 mCanCallJava 为 true 时,则代表创建的是不仅能调用 C/C++ 代码,还能调用 Java 代码的 Native 线程。</li> <li>当 mCanCallJava 为 false 时,则代表创建的是只能调用 C/C++ 代码的 Native 线程。</li> </ul> <p>有关两种创建线程模式的方法实现分别在 createThreadEtc 和 androidCreateRawThreadEtc 中,下面来简单分析一下它们的实现链路。</p> <h5 id="androidcreaterawthreadetc">androidCreateRawThreadEtc</h5> <p>本方法用于创建仅允许调用 C/C++ 代码的线程。</p> <blockquote> <p><em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:system/core/libutils/Threads.cpp;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1;l=117?q=Threads.cpp&amp;ss=android%2Fplatform%2Fsuperproject&amp;gsn=androidCreateRawThreadEtc&amp;gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dsystem%2Fcore%2Flibutils%2FThreads.cpp%2326cI-R-PypD1DZioBVZ2i29KWl0Nhu4SxPFXreBRDGI">Threads.cpp#androidCreateRawThreadEtc</a></em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">androidCreateRawThreadEtc</span><span class="p">(</span><span class="n">android_thread_func_t</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span> <span class="n">__android_unused</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">android_thread_id_t</span> <span class="o">*</span><span class="n">threadId</span><span class="p">)</span> <span class="p">{</span> <span class="n">pthread_attr_t</span> <span class="n">attr</span><span class="p">;</span> <span class="n">pthread_attr_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span> <span class="n">pthread_attr_setdetachstate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">PTHREAD_CREATE_DETACHED</span><span class="p">);</span> <span class="cp">#if defined(__ANDROID__) </span><span class="cm">/* valgrind is rejecting RT-priority create reqs */</span><span class="cp"> </span> <span class="k">if</span> <span class="p">(</span><span class="n">threadPriority</span> <span class="o">!=</span> <span class="n">PRIORITY_DEFAULT</span> <span class="o">||</span> <span class="n">threadName</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Now that the pthread_t has a method to find the associated</span> <span class="c1">// android_thread_id_t (pid) from pthread_t, it would be possible to avoid</span> <span class="c1">// this trampoline in some cases as the parent could set the properties</span> <span class="c1">// for the child. However, there would be a race condition because the</span> <span class="c1">// child becomes ready immediately, and it doesn't work for the name.</span> <span class="c1">// prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was</span> <span class="c1">// proposed but not yet accepted.</span> <span class="n">thread_data_t</span><span class="o">*</span> <span class="n">t</span> <span class="o">=</span> <span class="n">new</span> <span class="n">thread_data_t</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">priority</span> <span class="o">=</span> <span class="n">threadPriority</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">threadName</span> <span class="o">=</span> <span class="n">threadName</span> <span class="o">?</span> <span class="n">strdup</span><span class="p">(</span><span class="n">threadName</span><span class="p">)</span> <span class="o">:</span> <span class="nb">NULL</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">entryFunction</span> <span class="o">=</span> <span class="n">entryFunction</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">userData</span> <span class="o">=</span> <span class="n">userData</span><span class="p">;</span> <span class="n">entryFunction</span> <span class="o">=</span> <span class="p">(</span><span class="n">android_thread_func_t</span><span class="p">)</span><span class="o">&amp;</span><span class="n">thread_data_t</span><span class="o">::</span><span class="n">trampoline</span><span class="p">;</span> <span class="n">userData</span> <span class="o">=</span> <span class="n">t</span><span class="p">;</span> <span class="p">}</span> <span class="cp">#endif </span> <span class="k">if</span> <span class="p">(</span><span class="n">threadStackSize</span><span class="p">)</span> <span class="p">{</span> <span class="n">pthread_attr_setstacksize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">threadStackSize</span><span class="p">);</span> <span class="p">}</span> <span class="n">errno</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">pthread_t</span> <span class="kr">thread</span><span class="p">;</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="p">(</span><span class="n">android_pthread_entry</span><span class="p">)</span><span class="n">entryFunction</span><span class="p">,</span> <span class="n">userData</span><span class="p">);</span> <span class="n">pthread_attr_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGE</span><span class="p">(</span><span class="s">"androidCreateRawThreadEtc failed (entry=%p, res=%d, %s)</span><span class="se">\n</span><span class="s">"</span> <span class="s">"(android threadPriority=%d)"</span><span class="p">,</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">),</span> <span class="n">threadPriority</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Note that *threadID is directly available to the parent only, as it is</span> <span class="c1">// assigned after the child starts. Use memory barrier / lock if the child</span> <span class="c1">// or other threads also need access.</span> <span class="k">if</span> <span class="p">(</span><span class="n">threadId</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">)</span> <span class="p">{</span> <span class="o">*</span><span class="n">threadId</span> <span class="o">=</span> <span class="p">(</span><span class="n">android_thread_id_t</span><span class="p">)</span><span class="kr">thread</span><span class="p">;</span> <span class="c1">// XXX: this is not portable</span> <span class="p">}</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看到,上面代码首先做了一些线程对象的属性赋值,然后分配线程的栈空间,接着是通过 pthread 来创建一个线程,线程创建成功则返回 1。值得注意的是,这里的 <code class="language-plaintext highlighter-rouge">entryFunction</code> 其实就是之前代码中传进来的 <code class="language-plaintext highlighter-rouge">_threadLoop</code> 函数:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="n">Thread</span><span class="o">::</span><span class="n">_threadLoop</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">user</span><span class="p">)</span> <span class="p">{</span> <span class="n">Thread</span><span class="o">*</span> <span class="k">const</span> <span class="n">self</span> <span class="o">=</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="n">Thread</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">user</span><span class="p">);</span> <span class="n">sp</span><span class="o">&lt;</span><span class="n">Thread</span><span class="o">&gt;</span> <span class="n">strong</span><span class="p">(</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">mHoldSelf</span><span class="p">);</span> <span class="n">wp</span><span class="o">&lt;</span><span class="n">Thread</span><span class="o">&gt;</span> <span class="n">weak</span><span class="p">(</span><span class="n">strong</span><span class="p">);</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mHoldSelf</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span> <span class="cp">#if defined(__ANDROID__) </span> <span class="c1">// this is very useful for debugging with gdb</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mTid</span> <span class="o">=</span> <span class="n">gettid</span><span class="p">();</span> <span class="cp">#endif </span> <span class="n">bool</span> <span class="n">first</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="k">do</span> <span class="p">{</span> <span class="n">bool</span> <span class="n">result</span><span class="p">;</span> <span class="k">if</span> <span class="p">(</span><span class="n">first</span><span class="p">)</span> <span class="p">{</span> <span class="n">first</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mStatus</span> <span class="o">=</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">readyToRun</span><span class="p">();</span> <span class="n">result</span> <span class="o">=</span> <span class="p">(</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">mStatus</span> <span class="o">==</span> <span class="n">OK</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">exitPending</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// Binder threads (and maybe others) rely on threadLoop</span> <span class="c1">// running at least once after a successful ::readyToRun()</span> <span class="c1">// (unless, of course, the thread has already been asked to exit</span> <span class="c1">// at that point).</span> <span class="c1">// This is because threads are essentially used like this:</span> <span class="c1">// (new ThreadSubclass())-&gt;run();</span> <span class="c1">// The caller therefore does not retain a strong reference to</span> <span class="c1">// the thread and the thread would simply disappear after the</span> <span class="c1">// successful ::readyToRun() call instead of entering the</span> <span class="c1">// threadLoop at least once.</span> <span class="n">result</span> <span class="o">=</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">threadLoop</span><span class="p">();</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">result</span> <span class="o">=</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">threadLoop</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// establish a scope for mLock</span> <span class="p">{</span> <span class="n">Mutex</span><span class="o">::</span><span class="n">Autolock</span> <span class="n">_l</span><span class="p">(</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">mLock</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="nb">false</span> <span class="o">||</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mExitPending</span><span class="p">)</span> <span class="p">{</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mExitPending</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mRunning</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">// clear thread ID so that requestExitAndWait() does not exit if</span> <span class="c1">// called by a new thread using the same thread ID as this one.</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mThread</span> <span class="o">=</span> <span class="n">thread_id_t</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> <span class="c1">// note that interested observers blocked in requestExitAndWait are</span> <span class="c1">// awoken by broadcast, but blocked on mLock until break exits scope</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">mThreadExitedCondition</span><span class="p">.</span><span class="n">broadcast</span><span class="p">();</span> <span class="k">break</span><span class="p">;</span> <span class="p">}</span> <span class="p">}</span> <span class="c1">// Release our strong reference, to let a chance to the thread</span> <span class="c1">// to die a peaceful death.</span> <span class="n">strong</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span> <span class="c1">// And immediately, re-acquire a strong reference for the next loop</span> <span class="n">strong</span> <span class="o">=</span> <span class="n">weak</span><span class="p">.</span><span class="n">promote</span><span class="p">();</span> <span class="p">}</span> <span class="k">while</span><span class="p">(</span><span class="n">strong</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>通过上述代码不难发现:_threadLoop 内部会循环调用 Thread 对象的成员方法 <code class="language-plaintext highlighter-rouge">threadLoop</code>,该成员方法一般由派生类来自行实现。该线程将会在下面几种情况退出循环:</p> <ul> <li>线程状态错误,即 mStatus != OK</li> <li>线程即将退出,调用 Thread::requestExit() 时会触发</li> <li>线程的强引用被释放,无法继续将弱引用提升为强引用</li> </ul> <h5 id="createthreadetc">createThreadEtc</h5> <p>本方法用于创建允许调用 C/C++ 和 Java 代码的线程。</p> <blockquote> <p><em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:system/core/libutils/include/utils/AndroidThreads.h;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1;l=104?gsn=createThreadEtc&amp;gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dsystem%2Fcore%2Flibutils%2Finclude%2Futils%2FAndroidThreads.h%23tT-K0gHAowaJ7f3TSwiqGCfDqOT3WNg0_49y9_WYVp0">system/core/libutils/include/utils/AndroidThreads.h#createThreadEtc</a></em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 </pre></td><td class="rouge-code"><pre><span class="c1">// Create thread with lots of parameters</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">createThreadEtc</span><span class="p">(</span><span class="n">thread_func_t</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span> <span class="o">=</span> <span class="s">"android:unnamed_thread"</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">threadPriority</span> <span class="o">=</span> <span class="n">PRIORITY_DEFAULT</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">threadStackSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">thread_id_t</span> <span class="o">*</span><span class="n">threadId</span> <span class="o">=</span> <span class="n">nullptr</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">androidCreateThreadEtc</span><span class="p">(</span><span class="n">entryFunction</span><span class="p">,</span> <span class="n">userData</span><span class="p">,</span> <span class="n">threadName</span><span class="p">,</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">threadId</span><span class="p">)</span> <span class="o">?</span> <span class="nb">true</span> <span class="o">:</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>继续跟踪 <code class="language-plaintext highlighter-rouge">androidCreateThreadEtc</code> 函数:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 </pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">android_create_thread_fn</span> <span class="n">gCreateThreadFn</span> <span class="o">=</span> <span class="n">androidCreateRawThreadEtc</span><span class="p">;</span> <span class="kt">int</span> <span class="nf">androidCreateThreadEtc</span><span class="p">(</span><span class="n">android_thread_func_t</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">android_thread_id_t</span> <span class="o">*</span><span class="n">threadId</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">gCreateThreadFn</span><span class="p">(</span><span class="n">entryFunction</span><span class="p">,</span> <span class="n">userData</span><span class="p">,</span> <span class="n">threadName</span><span class="p">,</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">threadId</span><span class="p">);</span> <span class="p">}</span> <span class="kt">void</span> <span class="nf">androidSetCreateThreadFunc</span><span class="p">(</span><span class="n">android_create_thread_fn</span> <span class="n">func</span><span class="p">)</span> <span class="p">{</span> <span class="n">gCreateThreadFn</span> <span class="o">=</span> <span class="n">func</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>看到这里可以发现继续调用了 <code class="language-plaintext highlighter-rouge">gCreateThreadFn</code> 函数,而该函数会被调用方在其他地方赋值代理掉,所以我们需要找到调用 <code class="language-plaintext highlighter-rouge">androidSetCreateThreadFunc</code> 函数的地方,最终锁定在了 <em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/core/jni/AndroidRuntime.cpp;l=1685;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1">frameworks/base/core/jni/AndroidRuntime.cpp</a></em> 这个文件:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 </pre></td><td class="rouge-code"><pre><span class="cm">/* * Register android native functions with the VM. */</span> <span class="cm">/*static*/</span> <span class="kt">int</span> <span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">startReg</span><span class="p">(</span><span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">)</span> <span class="p">{</span> <span class="n">ATRACE_NAME</span><span class="p">(</span><span class="s">"RegisterAndroidNatives"</span><span class="p">);</span> <span class="cm">/* * This hook causes all future threads created in this process to be * attached to the JavaVM. (This needs to go away in favor of JNI * Attach calls.) */</span> <span class="n">androidSetCreateThreadFunc</span><span class="p">((</span><span class="n">android_create_thread_fn</span><span class="p">)</span> <span class="n">javaCreateThreadEtc</span><span class="p">);</span> <span class="n">ALOGV</span><span class="p">(</span><span class="s">"--- registering native functions ---</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span> <span class="cm">/* * Every "register" function calls one or more things that return * a local reference (e.g. FindClass). Because we haven't really * started the VM yet, they're all getting stored in the base frame * and never released. Use Push/Pop to manage the storage. */</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">PushLocalFrame</span><span class="p">(</span><span class="mi">200</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">register_jni_procs</span><span class="p">(</span><span class="n">gRegJNI</span><span class="p">,</span> <span class="n">NELEM</span><span class="p">(</span><span class="n">gRegJNI</span><span class="p">),</span> <span class="n">env</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">PopLocalFrame</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="p">}</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">PopLocalFrame</span><span class="p">(</span><span class="nb">NULL</span><span class="p">);</span> <span class="c1">//createJavaThread("fubar", quickTest, (void*) "hello");</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>由此看来,Android 系统会在向虚拟机中注册 native 方法时将 gCreateThreadFn 指定向 <code class="language-plaintext highlighter-rouge">javaCreateThreadEtc</code> 这个函数。那么我们就来看下 <em>javaCreateThreadEtc</em> 内部实现如何:</p> <blockquote> <p><em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/core/jni/AndroidRuntime.cpp;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1;l=1442?gsn=javaCreateThreadEtc&amp;gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dframeworks%2Fbase%2Fcore%2Fjni%2FAndroidRuntime.cpp%23Hu_OOm1g4JDVMfzDHU98HuMryIeU8hbMIVcQhf8k4vY">frameworks/base/core/jni/AndroidRuntime.cpp#javaCreateThreadEtc</a></em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 </pre></td><td class="rouge-code"><pre><span class="cm">/* * This is invoked from androidCreateThreadEtc() via the callback * set with androidSetCreateThreadFunc(). * * We need to create the new thread in such a way that it gets hooked * into the VM before it really starts executing. */</span> <span class="cm">/*static*/</span> <span class="kt">int</span> <span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">javaCreateThreadEtc</span><span class="p">(</span> <span class="n">android_thread_func_t</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">userData</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">android_thread_id_t</span><span class="o">*</span> <span class="n">threadId</span><span class="p">)</span> <span class="p">{</span> <span class="kt">void</span><span class="o">**</span> <span class="n">args</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span><span class="mi">3</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">));</span> <span class="c1">// javaThreadShell must free</span> <span class="kt">int</span> <span class="n">result</span><span class="p">;</span> <span class="n">LOG_ALWAYS_FATAL_IF</span><span class="p">(</span><span class="n">threadName</span> <span class="o">==</span> <span class="n">nullptr</span><span class="p">,</span> <span class="s">"threadName not provided to javaCreateThreadEtc"</span><span class="p">);</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="n">entryFunction</span><span class="p">;</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">userData</span><span class="p">;</span> <span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="n">strdup</span><span class="p">(</span><span class="n">threadName</span><span class="p">);</span> <span class="c1">// javaThreadShell must free</span> <span class="n">result</span> <span class="o">=</span> <span class="n">androidCreateRawThreadEtc</span><span class="p">(</span><span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">javaThreadShell</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">threadName</span><span class="p">,</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">threadId</span><span class="p">);</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>根据官方给出的注释可以知道:<code class="language-plaintext highlighter-rouge">javaCreateThreadEtc</code> 函数从 <code class="language-plaintext highlighter-rouge">androidCreateThreadEtc</code> 中通过 <code class="language-plaintext highlighter-rouge">androidSetCreateThreadFunc</code> 设置的回调来进行调用的,即在线程真正开始执行之前被 Hook 到虚拟机中。我们接着来看下真正实现的 androidCreateRawThreadEtc 函数:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 </pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">androidCreateRawThreadEtc</span><span class="p">(</span><span class="n">android_thread_func_t</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">userData</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span> <span class="n">__android_unused</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">threadPriority</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">threadStackSize</span><span class="p">,</span> <span class="n">android_thread_id_t</span> <span class="o">*</span><span class="n">threadId</span><span class="p">)</span> <span class="p">{</span> <span class="n">pthread_attr_t</span> <span class="n">attr</span><span class="p">;</span> <span class="n">pthread_attr_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span> <span class="n">pthread_attr_setdetachstate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">PTHREAD_CREATE_DETACHED</span><span class="p">);</span> <span class="cp">#if defined(__ANDROID__) </span><span class="cm">/* valgrind is rejecting RT-priority create reqs */</span><span class="cp"> </span> <span class="k">if</span> <span class="p">(</span><span class="n">threadPriority</span> <span class="o">!=</span> <span class="n">PRIORITY_DEFAULT</span> <span class="o">||</span> <span class="n">threadName</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// Now that the pthread_t has a method to find the associated</span> <span class="c1">// android_thread_id_t (pid) from pthread_t, it would be possible to avoid</span> <span class="c1">// this trampoline in some cases as the parent could set the properties</span> <span class="c1">// for the child. However, there would be a race condition because the</span> <span class="c1">// child becomes ready immediately, and it doesn't work for the name.</span> <span class="c1">// prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was</span> <span class="c1">// proposed but not yet accepted.</span> <span class="n">thread_data_t</span><span class="o">*</span> <span class="n">t</span> <span class="o">=</span> <span class="n">new</span> <span class="n">thread_data_t</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">priority</span> <span class="o">=</span> <span class="n">threadPriority</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">threadName</span> <span class="o">=</span> <span class="n">threadName</span> <span class="o">?</span> <span class="n">strdup</span><span class="p">(</span><span class="n">threadName</span><span class="p">)</span> <span class="o">:</span> <span class="nb">NULL</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">entryFunction</span> <span class="o">=</span> <span class="n">entryFunction</span><span class="p">;</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">userData</span> <span class="o">=</span> <span class="n">userData</span><span class="p">;</span> <span class="n">entryFunction</span> <span class="o">=</span> <span class="p">(</span><span class="n">android_thread_func_t</span><span class="p">)</span><span class="o">&amp;</span><span class="n">thread_data_t</span><span class="o">::</span><span class="n">trampoline</span><span class="p">;</span> <span class="n">userData</span> <span class="o">=</span> <span class="n">t</span><span class="p">;</span> <span class="p">}</span> <span class="cp">#endif </span> <span class="k">if</span> <span class="p">(</span><span class="n">threadStackSize</span><span class="p">)</span> <span class="p">{</span> <span class="n">pthread_attr_setstacksize</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">threadStackSize</span><span class="p">);</span> <span class="p">}</span> <span class="n">errno</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">pthread_t</span> <span class="kr">thread</span><span class="p">;</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="kr">thread</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="p">(</span><span class="n">android_pthread_entry</span><span class="p">)</span><span class="n">entryFunction</span><span class="p">,</span> <span class="n">userData</span><span class="p">);</span> <span class="n">pthread_attr_destroy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="n">ALOGE</span><span class="p">(</span><span class="s">"androidCreateRawThreadEtc failed (entry=%p, res=%d, %s)</span><span class="se">\n</span><span class="s">"</span> <span class="s">"(android threadPriority=%d)"</span><span class="p">,</span> <span class="n">entryFunction</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">),</span> <span class="n">threadPriority</span><span class="p">);</span> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// Note that *threadID is directly available to the parent only, as it is</span> <span class="c1">// assigned after the child starts. Use memory barrier / lock if the child</span> <span class="c1">// or other threads also need access.</span> <span class="k">if</span> <span class="p">(</span><span class="n">threadId</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">)</span> <span class="p">{</span> <span class="o">*</span><span class="n">threadId</span> <span class="o">=</span> <span class="p">(</span><span class="n">android_thread_id_t</span><span class="p">)</span><span class="kr">thread</span><span class="p">;</span> <span class="c1">// XXX: this is not portable</span> <span class="p">}</span> <span class="k">return</span> <span class="mi">1</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>该函数实现在 androidCreateRawThreadEtc 章节就已经分析过了,只不过这里的 <code class="language-plaintext highlighter-rouge">entryFunction</code> 传过来的是 <em>AndroidRuntime::javaThreadShell</em>,我们来看下它内部如何实现的:</p> <blockquote> <p><em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/core/jni/AndroidRuntime.cpp;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1;l=1413?gsn=javaThreadShell&amp;gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dframeworks%2Fbase%2Fcore%2Fjni%2FAndroidRuntime.cpp%232dRQv8aW1yl_ahrbBQXwWUKL7oGOgfdloD9ALdX35aU">frameworks/base/core/jni/AndroidRuntime.cpp</a></em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 </pre></td><td class="rouge-code"><pre><span class="cm">/* * When starting a native thread that will be visible from the VM, we * bounce through this to get the right attach/detach action. * Note that this function calls free(args) */</span> <span class="cm">/*static*/</span> <span class="kt">int</span> <span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">javaThreadShell</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span> <span class="kt">void</span><span class="o">*</span> <span class="n">start</span> <span class="o">=</span> <span class="p">((</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="n">args</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span> <span class="kt">void</span><span class="o">*</span> <span class="n">userData</span> <span class="o">=</span> <span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)</span><span class="n">args</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="p">((</span><span class="kt">void</span> <span class="o">**</span><span class="p">)</span><span class="n">args</span><span class="p">)[</span><span class="mi">2</span><span class="p">];</span> <span class="c1">// we own this storage</span> <span class="n">free</span><span class="p">(</span><span class="n">args</span><span class="p">);</span> <span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">;</span> <span class="kt">int</span> <span class="n">result</span><span class="p">;</span> <span class="cm">/* hook us into the VM */</span> <span class="k">if</span> <span class="p">(</span><span class="n">javaAttachThread</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">env</span><span class="p">)</span> <span class="o">!=</span> <span class="n">JNI_OK</span><span class="p">)</span> <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="cm">/* start the thread running */</span> <span class="n">result</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">android_thread_func_t</span><span class="p">)</span><span class="n">start</span><span class="p">)(</span><span class="n">userData</span><span class="p">);</span> <span class="cm">/* unhook us */</span> <span class="n">javaDetachThread</span><span class="p">();</span> <span class="n">free</span><span class="p">(</span><span class="n">name</span><span class="p">);</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><em>javaThreadShell</em> 函数比较重要,综合上下文不难发现,代码中的局部变量 <code class="language-plaintext highlighter-rouge">start</code> 代表的是 _threadLoop,<code class="language-plaintext highlighter-rouge">userData</code> 代表 Thread 对象,而 <code class="language-plaintext highlighter-rouge">name</code> 指向线程的名称。接着继续调用了 <code class="language-plaintext highlighter-rouge">javaAttachThread</code> 函数,用于将线程 hook 到当前进程的虚拟机中,进而执行 Java 代码。紧接着继续执行线程自身的逻辑,即调用成员函数 <code class="language-plaintext highlighter-rouge">threadLoop</code>;线程执行完毕后调用了 <code class="language-plaintext highlighter-rouge">javaDetachThread</code> 函数用于将线程从虚拟机中剥离。下面重点看下 javaAttachThread 内部是如何将线程 hook 至虚拟机的:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 </pre></td><td class="rouge-code"><pre><span class="cm">/* * Makes the current thread visible to the VM. * * The JNIEnv pointer returned is only valid for the current thread, and * thus must be tucked into thread-local storage. */</span> <span class="k">static</span> <span class="kt">int</span> <span class="nf">javaAttachThread</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">threadName</span><span class="p">,</span> <span class="n">JNIEnv</span><span class="o">**</span> <span class="n">pEnv</span><span class="p">)</span> <span class="p">{</span> <span class="n">JavaVMAttachArgs</span> <span class="n">args</span><span class="p">;</span> <span class="n">JavaVM</span><span class="o">*</span> <span class="n">vm</span><span class="p">;</span> <span class="n">jint</span> <span class="n">result</span><span class="p">;</span> <span class="n">vm</span> <span class="o">=</span> <span class="n">AndroidRuntime</span><span class="o">::</span><span class="n">getJavaVM</span><span class="p">();</span> <span class="n">assert</span><span class="p">(</span><span class="n">vm</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span> <span class="n">args</span><span class="p">.</span><span class="n">version</span> <span class="o">=</span> <span class="n">JNI_VERSION_1_4</span><span class="p">;</span> <span class="n">args</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">threadName</span><span class="p">;</span> <span class="n">args</span><span class="p">.</span><span class="n">group</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">vm</span><span class="o">-&gt;</span><span class="n">AttachCurrentThread</span><span class="p">(</span><span class="n">pEnv</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="o">&amp;</span><span class="n">args</span><span class="p">);</span> <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="n">JNI_OK</span><span class="p">)</span> <span class="n">ALOGI</span><span class="p">(</span><span class="s">"NOTE: attach of thread '%s' failed</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">threadName</span><span class="p">);</span> <span class="k">return</span> <span class="n">result</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>可以看见 javaAttachThread 中又调用了 AttachCurrentThread 函数,该函数可以将 native 线程附加到进程的 VM 中,详细参见官方 JNI 文档:<a href="https://developer.android.com/training/articles/perf-jni?hl=zh-cn#threads">JNI 提示</a></p> <p>接着可以在 runtime 中找到 CheckAttachThread 实现:</p> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 </pre></td><td class="rouge-code"><pre><span class="c1">// Check whether the current thread is attached. This is usually required</span> <span class="c1">// to be the first check, as ScopedCheck needs a ScopedObjectAccess for</span> <span class="c1">// checking heap values (and that will fail with unattached threads).</span> <span class="n">bool</span> <span class="nf">CheckAttachedThread</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">function_name</span><span class="p">)</span> <span class="p">{</span> <span class="n">Thread</span><span class="o">*</span> <span class="n">self</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">::</span><span class="n">Current</span><span class="p">();</span> <span class="k">if</span> <span class="p">(</span><span class="n">UNLIKELY</span><span class="p">(</span><span class="n">self</span> <span class="o">==</span> <span class="n">nullptr</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// Need to attach this thread for a proper abort to work. We prefer this</span> <span class="c1">// to get reasonable stacks and environment, rather than relying on</span> <span class="c1">// tombstoned.</span> <span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">;</span> <span class="n">Runtime</span><span class="o">::</span><span class="n">Current</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetJavaVM</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">AttachCurrentThread</span><span class="p">(</span><span class="o">&amp;</span><span class="n">env</span><span class="p">,</span> <span class="cm">/* thr_args= */</span> <span class="n">nullptr</span><span class="p">);</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">tmp</span> <span class="o">=</span> <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">StringPrintf</span><span class="p">(</span> <span class="s">"a thread (tid %"</span> <span class="n">PRId64</span> <span class="s">" is making JNI calls without being attached"</span><span class="p">,</span> <span class="n">static_cast</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">GetTid</span><span class="p">()));</span> <span class="n">Runtime</span><span class="o">::</span><span class="n">Current</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetJavaVM</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">JniAbort</span><span class="p">(</span><span class="n">function_name</span><span class="p">,</span> <span class="n">tmp</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span> <span class="n">CHECK_NE</span><span class="p">(</span><span class="n">Runtime</span><span class="o">::</span><span class="n">Current</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">GetJavaVM</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">DetachCurrentThread</span><span class="p">(),</span> <span class="n">JNI_ERR</span><span class="p">);</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="p">}</span> <span class="k">return</span> <span class="nb">true</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>好吧,里面又调用了 GetJavaVM()-&gt; AttachCurrentThread,看来我们还得继续跟下去,不过为了节省中间若干个类文件跳转环节,这里直接指出最终目的地在 <code class="language-plaintext highlighter-rouge">Runtime::AttachCurrentThread</code> 函数里面:</p> <blockquote> <p><em><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:art/runtime/runtime.cc;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;bpv=1;bpt=1;l=2428?gsn=AttachCurrentThread&amp;gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%3Flang%3Dc%252B%252B%3Fpath%3Dart%2Fruntime%2Fruntime.cc%23Q56GPTk2CWJsQUdZAdwYiLpyNr6vSrIH3nWc6hCz66k">art/runtime/runtime.cc</a></em></p> </blockquote> <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 </pre></td><td class="rouge-code"><pre><span class="n">bool</span> <span class="n">Runtime</span><span class="o">::</span><span class="n">AttachCurrentThread</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">thread_name</span><span class="p">,</span> <span class="n">bool</span> <span class="n">as_daemon</span><span class="p">,</span> <span class="n">jobject</span> <span class="n">thread_group</span><span class="p">,</span> <span class="n">bool</span> <span class="n">create_peer</span><span class="p">,</span> <span class="n">bool</span> <span class="n">should_run_callbacks</span><span class="p">)</span> <span class="p">{</span> <span class="n">ScopedTrace</span> <span class="n">trace</span><span class="p">(</span><span class="n">__FUNCTION__</span><span class="p">);</span> <span class="n">Thread</span><span class="o">*</span> <span class="n">self</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">::</span><span class="n">Attach</span><span class="p">(</span><span class="n">thread_name</span><span class="p">,</span> <span class="n">as_daemon</span><span class="p">,</span> <span class="n">thread_group</span><span class="p">,</span> <span class="n">create_peer</span><span class="p">,</span> <span class="n">should_run_callbacks</span><span class="p">);</span> <span class="c1">// Run ThreadGroup.add to notify the group that this thread is now started.</span> <span class="k">if</span> <span class="p">(</span><span class="n">self</span> <span class="o">!=</span> <span class="n">nullptr</span> <span class="o">&amp;&amp;</span> <span class="n">create_peer</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">IsAotCompiler</span><span class="p">())</span> <span class="p">{</span> <span class="n">ScopedObjectAccess</span> <span class="n">soa</span><span class="p">(</span><span class="n">self</span><span class="p">);</span> <span class="n">self</span><span class="o">-&gt;</span><span class="n">NotifyThreadGroup</span><span class="p">(</span><span class="n">soa</span><span class="p">,</span> <span class="n">thread_group</span><span class="p">);</span> <span class="p">}</span> <span class="k">return</span> <span class="n">self</span> <span class="o">!=</span> <span class="n">nullptr</span><span class="p">;</span> <span class="p">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这里其实就是将线程在 JavaVM 层面包装成一个 VM 上的线程再返回给 self。接下来就不继续往下深究了,篇幅有限,本文先讲到这里,里面还涉及到很多 JavaVM 相关的细节及原理将在后续文章中做一个补充。</p> <p>顺带提一嘴,Android SDK 中的 Thread 与 JDK 中的 java.lang.Thread 实际上是有区别的,虽然 Android 也使用 Java 语言开发,但Android 基于平台特殊性对 JDK 进行了一些删减和改造。我们都知道 Java 是具有跨平台特性的,同一套代码它完全可以在 Windows、Linux 等操作系统上正常运作,但其实内部的线程创建等细节是基于 OS 的特性进行各自实现的:</p> <p><img src="/img/in-post/post-android/java_thread_creation.png" alt="java_thread_creation" /></p> <blockquote> <p>想了解更多关于 Java 线程的启动过程可以参考<a href="https://zhuanlan.zhihu.com/p/439797698">此文</a>。</p> </blockquote> <h3 id="总结">总结</h3> <p>我们通过在 Java 层面创建线程的时候,VM 会包装成一个 VM 的 Thread,然后启动 pthread,再调用 run 方法,但我们通过 pthread 独立创建的线程,是没有和 VM 里面的线程对象建立关联的,VM 压根不知道它的存在。但是如果该线程想要访问 Java代码,这就得需 VM 帮忙,故而需要将 native 层自己创建的线程包装成一个 VM 层面的 Thread 对象,然后添加到 VM 的 thread 集合中去,如此一来,Android 的虚拟机就可以感知到 native 中这个线程对象的存在了。</p> <h3 id="相关参考">相关参考</h3> <ul> <li><a href="https://cs.android.com/android/platform/superproject/+/master:art/runtime/native/java_lang_Thread.cc"><em>runtime/native/java_lang_Thread.cc</em></a></li> <li><a href="https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/Threads.cpp"><em>system/core/libutils/Threads.cpp</em></a></li> <li><a href="https://cs.android.com/android/platform/superproject/+/refs/heads/master:system/core/libutils/include/utils/Thread.h"><em>system/core/libutils/include/utils/Thread.h</em></a></li> <li><a href="https://blog.csdn.net/u012430727/article/details/115239992"><em>Android native/C++层Thread线程实现源码分析</em></a></li> <li><a href="https://keeplooking.top/2020/05/19/Android/AttachCurrentThread/"><em>JNI为什么要调用AttachCurrentThread</em></a></li> </ul>

2023/4/7
阅读更多