duanqz.github.io
2019-09-06T07:26:55+00:00
https://duanqz.github.io
Duan Qizhi
duanqz@gmail.com
基金数据爬虫
2018-08-01T00:00:00+00:00
https://duanqz.github.io/Fund-Crawler
<p>购买基金可以参考一个法则:</p>
<ul>
<li>最近一年、两年、三年、五年及成立以来收益率排名同类基金的前<code class="highlighter-rouge">1/4</code></li>
<li>最近三个月、六个月收益率排名同类基金的前<code class="highlighter-rouge">1/3</code></li>
</ul>
<p>笔者实现了一个应用,依据上述法则筛选基金,降低广大基民踩雷的风险。</p>
<p>本应用包含<strong>数据爬虫</strong>、<strong>数据存储</strong>和<strong>数据展示</strong>三个模块。本应用并没有采用传统的SQL作为数据库存储,而是直接利用Github作为数据存储服务器,并且利用<a href="https://travis-ci.com/nullpointer/fund-crawler">Travis-ci</a>的持续集成功能定时调度爬虫任务,前端展示的域名和伺服也是利用Github提供的gh-pages功能。因此,本应用是一个前后端都寄生在<strong>Github</strong>和<strong>Travis-ci</strong>的<strong><code class="highlighter-rouge">免费应用</code></strong>。
架构图如下所示:</p>
<div align="center"><img src="/assets/images/fundcrawler/0-fundcrawler-architecture.png" alt="Fund Crawler Architecture" /></div>
<p>几乎每个编程语言都有爬虫框架,初学者大可基于自己熟悉语言的爬虫框架进行开发。笔者选择了JavaScript实现的<a href="https://www.npmjs.com/package/crawler">Crawler</a>,相对于主流的爬虫框架,<strong>Crawler</strong>是比较冷门的,之所以选择它,是因为:</p>
<ul>
<li>本文所要爬取的基金数据并不复杂(数据来源是<a href="http://fund.eastmoney.com/">天天基金</a>,大部分数据可以直接通过调用数据API得到),无需借用Scrapy、WebMagic、Pyspider等重型利器</li>
<li>本文将爬取的数据以JSON格式存储于Github,即后文中要展开的将Github作为云存储服务器,基于现有的JavaScript工具<a href="https://www.npmjs.com/package/github-db">GithubDB</a>可以快速实现存储功能</li>
</ul>
<h1 id="数据爬取">数据爬取</h1>
<p>本文实现的基金数据爬虫<a href="https://github.com/nullpointer/fund-crawler">fund-crawler</a>完全基于NodeJS的,通过运行以下命令就能启动爬虫:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git clone https://github.com/nullpointer/fund-crawler
<span class="nv">$ </span><span class="nb">export </span><span class="nv">TOKEN</span><span class="o">=</span><span class="s1">'Your github personal token'</span>
<span class="nv">$ </span><span class="nb">cd </span>fund-crawler
<span class="nv">$ </span>npm install
<span class="nv">$ </span>npm start
</code></pre></div></div>
<blockquote>
<p><strong>注意:</strong> 运行本爬虫需要引入一个环境变量<strong>TOKEN</strong>,其值为Github的Personal Access Token,因为所爬取的数据会直接写入了一个Github上的代码库,而操作Github代码库的API最便捷的方式就是利用Personal Access Token。</p>
<p>在Github的开发者设置页<a href="https://github.com/settings/tokens">https://github.com/settings/tokens</a>,可以生成一个<strong>TOKEN</strong>:</p>
<div align="center"><img src="/assets/images/fundcrawler/1-fundcrawler-github-token.png" alt="Github Personal
Access Token" /></div>
</blockquote>
<p>本爬虫要爬取的数据有两种:</p>
<ol>
<li>
<p><strong>不同类型的基金排行</strong></p>
<p>天天基金的排行可以通过API直接获取,不需要分析网页的结构,请求不同类型基金的API如下所示:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">rankUri</span> <span class="o">=</span> <span class="s1">'http://fund.eastmoney.com/data/rankhandler.aspx?op=ph&dt=kf&ft=%s&rs=&gs=0&sc=zzf&st=desc&pi=1&pn=10000&dx=1'</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'all'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'all'</span> <span class="p">});</span> <span class="c1">// 全部</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'gp'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'gupiao'</span> <span class="p">});</span> <span class="c1">// 股票型</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'hh'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'hunhe'</span> <span class="p">});</span> <span class="c1">// 混合型</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'zq'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'zhaiquan'</span> <span class="p">});</span> <span class="c1">// 债券型</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'zs'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'zhishu'</span> <span class="p">});</span> <span class="c1">// 指数型</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'qdii'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'qdii'</span> <span class="p">});</span> <span class="c1">// QDII</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'lof'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'lof'</span> <span class="p">});</span> <span class="c1">// LOF</span>
<span class="nx">c</span><span class="p">.</span><span class="nx">queue</span><span class="p">({</span> <span class="na">uri</span><span class="p">:</span> <span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">rankUri</span><span class="p">,</span> <span class="s1">'fof'</span><span class="p">),</span> <span class="na">type</span><span class="p">:</span> <span class="s1">'fof'</span> <span class="p">});</span> <span class="c1">// FOF</span>
</code></pre></div> </div>
<p>请求URL的一个参数为<code class="highlighter-rouge">ft</code>,取不同的值表示不同的的类型。</p>
</li>
<li>
<p><strong>每个基金的历史净值</strong></p>
<p>天天基金的历史净值也可以通过API直接获取,请求一直基金的历史净值的API如下所示:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">uri</span> <span class="o">=</span> <span class="s2">"http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=%s&page=1&per=%s"</span>
<span class="nx">Util</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="nx">uri</span><span class="p">,</span> <span class="nx">code</span><span class="p">,</span> <span class="nx">records</span><span class="p">),</span>
</code></pre></div> </div>
<p>请求参数中的<code class="highlighter-rouge">code</code>为基金的编码,<code class="highlighter-rouge">per</code>为历史净值数据量。</p>
</li>
</ol>
<h1 id="数据存储">数据存储</h1>
<p>Github可以作为数据存储服务器,在Github上新建一个库(Repository),数据便可以文件的形式存储起来。本爬虫设置了默认的数据存储库;如果要更换成其他库,可以在<a href="https://github.com/nullpointer/fund-crawler/blob/master/src/db.js">db.js</a>文件中可以修改以下代码片段:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">owner</span><span class="p">:</span> <span class="s1">'nullpointer'</span><span class="p">,</span> <span class="c1">// 修改为用户名</span>
<span class="na">repo</span><span class="p">:</span> <span class="s1">'fund-data'</span><span class="p">,</span> <span class="c1">// 修改为库的名称</span>
<span class="p">...</span>
<span class="p">};</span>
</code></pre></div></div>
<p>所谓的数据存储,其实就是往Github提交改动。<a href="https://www.npmjs.com/package/github-db">GithubDB</a>实现了一个Github库的增删改查功能,就像操作普通的数据库一样,其内部的实现原理,就是利用Github提供的API来实现对Github库的读写操作。</p>
<p>该爬虫接入了<a href="https://travis-ci.com/nullpointer/fund-crawler">Travis-CI</a>,配置了定时任务,每天都会自动运行,因此每天爬取的基金数据会自动提交到Github上的数据存储库。</p>
<div align="center"><img src="/assets/images/fundcrawler/2-fundcrawler-travis-ci.png" alt="Travis CI" /></div>
<p>当爬虫执行完毕,所爬取的数据就自动提交到了默认的<a href="https://github.com/nullpointer/fund-data">https://github.com/nullpointer/fund-data</a>,截取片段如下所示:</p>
<div align="center"><img src="/assets/images/fundcrawler/3-fundcrawler-fund-data.png" alt="Fund Data" /></div>
<h1 id="数据展示">数据展示</h1>
<p>数据展示基于Angular 6、Bootstrap 4和<a href="https://github.com/akveo/nebular">Nebular</a>实现,是一个部署在github上的前端页面。</p>
<p>点击访问<a href="https://duanqz.github.io/fund-visualizer">https://duanqz.github.io/fund-visualizer</a>,主要展示以下三部分信息:</p>
<ul>
<li><strong>持仓推荐</strong>:对于爬取的基金排行数据,会根据一年、两年、三年、成立以来、三个月、五个月的收益率进行排序,找出位于头部的基金</li>
<li><strong>基金排行</strong>:展示不同类别基金的排行榜</li>
<li><strong>基金详情</strong>:目前仅展示基金的历史净值图表</li>
</ul>
<div align="center"><img src="/assets/images/fundcrawler/4-fundcrawler-fund-recommend.png" alt="Fund Data" /></div>
<div align="center"><img src="/assets/images/fundcrawler/5-fundcrawler-fund-detail.png" alt="Fund Data" /></div>
<h1 id="总结展望">总结展望</h1>
<p>相比于传统的前后端设计,本方案利用github作为后端存储和前端展示服务器,利用travis-ci持续集成作为爬虫任务调度,做到了<strong>服务器投入零费用</strong>,是一款完全架设在现有免费资源上的应用。但由于数据存储没有采用SQL,在数据读取方面的性能并不强。</p>
<p>后续的工作计划:</p>
<ol>
<li>爬虫优化:爬取更多基金信息,包括基金经理、持仓、评价等</li>
<li>数据分析:AI模型分析基金数据</li>
<li>展示优化:根据爬虫数据展示更多的基金信息,展示基于数据分析的推荐基金</li>
</ol>
Java ThreadLocal的演化、实现和场景
2018-03-15T00:00:00+00:00
https://duanqz.github.io/Java-ThreadLocal
<p><code class="highlighter-rouge">ThreadLocal</code>,在没有任何背景知识的情况下,我们从英文单词的意思上理解它:</p>
<ul>
<li><strong>Thread</strong>:跟线程相关。Java语言中,表示线程的类就是Thread,是程序最小的执行单元,多个线程可以<strong>并发</strong>执行。</li>
<li><strong>Local</strong>:本地、局部,与之相对的概念就是远程、全局。Java语言中,通常用Local表示局部变量。</li>
</ul>
<p>这两个概念一组合,拼成了英文单词<strong>ThreadLocal</strong>,线程局部?局部线程?究竟要表达什么意思,为什么不叫<strong>LocalThread</strong>,完全找不着北啊!</p>
<p>笔者搜罗了网上对<strong>ThreadLocal</strong>的一些解读:</p>
<ul>
<li>
<p><strong>ThreadLocal</strong>为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,<strong>ThreadLocal</strong>并不是一个Thread,而是Thread的局部变量,把它命名为ThreadLocalVariable更容易让人理解一些</p>
</li>
<li>
<p><strong>ThreadLocal</strong>并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的变量。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离。从线程的角度看,就好像每一个线程都完全拥有该变量</p>
</li>
<li>
<p><strong>ThreadLocal</strong>类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。<strong>ThreadLocal</strong>实例通常来说都是private static类型的,用于关联线程和线程的上下文</p>
</li>
<li>
<p>引入<strong>ThreadLocal</strong>的初衷是为了提供线程内的局部变量,而不是为了解决共享对象的多线程访问问题。实际上,ThreadLocal根本就不能解决共享对象的多线程访问问题</p>
</li>
</ul>
<p>这些解释都没错,但并不是很好理解,而且大多数读者也没有真正使用过<strong>ThreadLocal</strong>,字面意思看上去理解了,真正到用的时候又不知从何下手。
笔者试图由简入繁,通过生活中的例子,来描述<strong>ThreadLocal</strong>的演化、实现和使用场景。</p>
<h1 id="1-演化过程">1. 演化过程</h1>
<p>以实际生活中的银行业务办理模型,解释ThreadLocal的诞生过程。读者们可以看到:随着业务模型的不断扩展,代码逻辑变得更加复杂,经过不断优化代码结构的过程,演化出了<strong>ThreadLocal</strong>这个编程工具。</p>
<h2 id="11-初始形态">1.1 初始形态</h2>
<blockquote>
<p>大家去银行办理业务时,如果需要排队等候,则会领取一个排队号,直到叫号才能办理业务。</p>
<p>我们把每一笔业务(Transaction)抽象为一个线程,每一笔业务都有一个唯一的标识(id)。</p>
</blockquote>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Transaction</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</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">if</span> <span class="o">(</span><span class="n">wait</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Waiting</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Start transaction</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在这个模型里面,每新来一笔业务,都需要运行一个线程,然后分配一个全局唯一的业务标识(id)给这个新的线程,简化以后的代码逻辑如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">id</span> <span class="o">=</span> <span class="n">nextTransactionId</span><span class="o">();</span>
<span class="k">new</span> <span class="nf">Transaction</span><span class="o">(</span><span class="n">id</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
</code></pre></div></div>
<h2 id="12-扩展形态">1.2 扩展形态</h2>
<blockquote>
<p>现在,需要把业务模型扩展一下,每一笔业务还需要知道等待时间(waitTime),等待人数(waitPeople)等。</p>
<p>于是乎,在原有的线程里面,又增加了一些局部变量和控制逻辑,线程运行以后便会对这些局部变量进行读写操作。</p>
</blockquote>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Transaction</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitPeople</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">serviceWindow</span><span class="o">;</span>
<span class="o">...</span> <span class="c1">// Other extension</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">if</span> <span class="o">(</span><span class="n">wait</span><span class="o">)</span> <span class="o">{</span>
<span class="n">waitTime</span> <span class="n">increasing</span>
<span class="o">...</span> <span class="c1">// Waiting</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">serviceWindow</span> <span class="n">assigned</span>
<span class="o">...</span> <span class="c1">// Start transaction</span>
<span class="n">waitPeople</span> <span class="n">decreasing</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>添加完扩展的代码逻辑之后,我们发现这种编程方法并不好:譬如,要扩展一个业务办理时长(serviceTime),又得新增一个局部变量。可以想象,类似的扩展还有很多。于是乎,我们想到把这些零散的字段封装成一个类,这里我们命名为Session,表示一个事务所需要操作的数据集合:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Transaction</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">Session</span> <span class="n">session</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">if</span> <span class="o">(</span><span class="n">wait</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Waiting</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Start transaction</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">Session</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitPeople</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">serviceWindow</span><span class="o">;</span>
<span class="o">...</span> <span class="c1">// Other extension</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这样一来,每个线程都拥有一个局部变量Session,后续可以在Session的基础上进行扩展,降低Transaction的复杂度,当线程运行时,需要对Session对象进行读写。</p>
<p><strong>注意,银行的业务是多窗口同时办理的,意味着这些线程可以并发执行。以上代码并没有锁控制,因为每个线程都是修改自己的局部变量,并不影响其他线程。</strong></p>
<blockquote>
<p>随着银行的业务变得愈加复杂,譬如:客户可以买卖理财产品,缴纳日常生活费用。</p>
<p>Transaction的代码量变得越来越大,于是乎,又把与理财业务相关的代码封装到FinancialService。</p>
</blockquote>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Transaction</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">Session</span> <span class="n">session</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">if</span> <span class="o">(</span><span class="n">wait</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Waiting</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Start transaction</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">Session</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitTime</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">waitPeople</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">serviceWindow</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">serviceTime</span><span class="o">;</span>
<span class="o">...</span> <span class="c1">// Other extension</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">FinancialService</span> <span class="o">{</span>
<span class="n">Session</span> <span class="n">session</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setSession</span><span class="o">(</span><span class="n">Session</span> <span class="n">session</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">session</span> <span class="o">=</span> <span class="n">session</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doService</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">...</span> <span class="c1">// Do financial service</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>扩展出来的FinancialService需要读写Session中的数据,譬如:获取分配的服务窗口(serviceWindow)、更新服务时间(serviceTime),所以,在FinancialService类中也会有一个局部变量Session,它是外部传入进来的。</p>
<p>可以这么来理解:FinancialService属于一个具体的事务,FinancialService对象仍然属于Transaction这个线程的生命周期,在Transaction线程的生命周期内,需要将Session对象传入FinancialService对象。</p>
<h2 id="13-改良形态">1.3 改良形态</h2>
<blockquote>
<p>Transaction线程的代码逻辑已经很复杂了,涉及到很多类的封装和数据传递,在线程运行时,有一些变量是在整个线程的生命都存在的,如果线程中某些对象需要使用这些变量,就需要封装一些接口进行数据传递。有没有一种便捷的方式来访问这些变量呢?</p>
<p>在Transaction中创建一个Map类型的局部变量,通过一个全局可以访问的Key,便可对Session进行存取操作。在线程生命周期的任何地方,只需要通过Key,就可以获取到Session</p>
</blockquote>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 全局可以访问的Key</span>
<span class="kd">static</span> <span class="n">SessionKey</span> <span class="n">globalKey</span> <span class="o">=</span> <span class="k">new</span> <span class="n">SessionKey</span><span class="o">();</span>
<span class="kd">class</span> <span class="nc">Transaction</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="n">Map</span><span class="o"><</span><span class="n">SessionKey</span><span class="o">,</span> <span class="n">Session</span><span class="o">></span> <span class="n">map</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="c1">// 将Session保存到线程的局部变量map中</span>
<span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">globalKey</span><span class="o">,</span> <span class="n">session</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">wait</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Waiting</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Start transaction</span>
<span class="n">Read</span><span class="o">/</span><span class="n">Write</span> <span class="n">session</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">FinancialService</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">doService</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 获取当前运行的线程</span>
<span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
<span class="c1">// 通过全局的globalKey从线程的Map中取出Session</span>
<span class="n">Session</span> <span class="n">session</span> <span class="o">=</span> <span class="n">t</span><span class="o">.</span><span class="na">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">globalSessionKey</span><span class="o">);</span>
<span class="o">...</span> <span class="c1">// Do financial service</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>注意:此处有两个关键点:</strong></p>
<ol>
<li><strong>全局变量Key</strong>,所有线程都可以访问</li>
<li><strong>局部变量Map</strong>,属于每个线程,这个Map中每一项的Key是全局的,而Value是局部的</li>
</ol>
<p>把上述两者之间的关系图示出来:</p>
<div align="center"><img src="/assets/images/threadlocal/1-threadlocal-transaction.png" /></div>
<p>线程类<strong>Transaction</strong>中定义了一个类型为<strong>Map</strong>的变量,其中每一项的<em>Key</em>为<strong>SessionKey</strong>,<em>Value</em>为<strong>Session</strong>。</p>
<p><strong>读者一定心生疑问了,直接将Session作为全局变量不就可以了吗?为什么还要搞一个线程的局部变量Map?</strong></p>
<p>这就涉及到多线程数据访问了:对于Session而言,每个线程都各自维护自己的,修改了也不需要告诉其他线程。如果将Session直接作为全局变量,那每个线程都改的是同一份数据,还需要进行多线程的锁控制。</p>
<p>演化到这一步,ThreadLocal就呼之欲出了。</p>
<h1 id="2-实现原理">2. 实现原理</h1>
<p>先直接把<strong>Thread</strong>与<strong>ThreadLocal</strong>之间关系图示出来:</p>
<div align="center"><img src="/assets/images/threadlocal/2-threadlocal-structure.png" /></div>
<p>这个结构图跟上面改良形态的Transaction结构图简直如出一辙,只不过<strong>ThreadLocal</strong>做了更多的封装:</p>
<ul>
<li>线程类<strong>Thread</strong>中有一个类型为<strong>ThreadLocalMap</strong>的变量为<em>threadLocals</em></li>
<li><strong>ThreadLocalMap</strong>是一个映射表,内部实现是一个数组,每一个元素的类型为<strong>Entry</strong></li>
<li><strong>Entry</strong>就是一个键值对(<em>Key-Value Pair</em>),其 <em>Key</em> 就是<strong>ThreadLocal</strong>,其 <em>Value</em> 可以是任何对象</li>
</ul>
<p>接下来,我们深入到源码,窥探一下<strong>ThreadLocal</strong>的奥妙。</p>
<h2 id="21-threadlocal的主要接口">2.1 ThreadLocal的主要接口</h2>
<p><strong>ThreadLocal</strong>对外提供的接口并不多:JDK 1.8以前,仅<strong>set()</strong>、<strong>get()</strong>和<strong>remove()</strong>三个接口;JDK 1.8以来,多提供了一个<strong>withInitial()</strong>接口。这些接口其实就是针对线程中<strong>ThreadLocalMap</strong>的增删改查操作。</p>
<ul>
<li>
<p><strong>set()</strong>,表示要往当前线程中设置“本地变量”,最终的结果是将变量设置到了线程的映射表。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// ThreadLocal.set()</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="n">T</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 获取当前线程</span>
<span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
<span class="c1">// 获取线程中的映射表</span>
<span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
<span class="c1">// 设置映射表的Key-Value,Key就是当前ThreadLocal对象</span>
<span class="n">map</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="k">else</span>
<span class="nf">createMap</span><span class="o">(</span><span class="n">t</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ThreadLocalMap</span> <span class="nf">getMap</span><span class="o">(</span><span class="n">Thread</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">t</span><span class="o">.</span><span class="na">threadLocals</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p><strong>get()</strong>,表示要从当前线程中取出“本地变量”,最终的结果是在当前线程的映射表中,以调用get()方法的<strong>ThreadLocal</strong>对象为<em>Key</em>,查询出对应的<em>Value</em>。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// ThreadLocal.get()</span>
<span class="kd">public</span> <span class="n">T</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>
<span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
<span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 获取映射表中当前ThreadLocal对应的Value</span>
<span class="n">ThreadLocalMap</span><span class="o">.</span><span class="na">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">map</span><span class="o">.</span><span class="na">getEntry</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">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span>
<span class="n">T</span> <span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="n">T</span><span class="o">)</span><span class="n">e</span><span class="o">.</span><span class="na">value</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="o">}</span>
<span class="c1">// 如果Map还未初始化或者Map中没有找到Key,则设置一个初始值</span>
<span class="k">return</span> <span class="nf">setInitialValue</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="n">T</span> <span class="nf">setInitialValue</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 获取初始值,这个方法通常由ThreadLocal的泛型实例化类去实现</span>
<span class="n">T</span> <span class="n">value</span> <span class="o">=</span> <span class="n">initialValue</span><span class="o">();</span>
<span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
<span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
<span class="n">map</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="k">else</span>
<span class="nf">createMap</span><span class="o">(</span><span class="n">t</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="k">return</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
</ul>
<p><strong>ThreadLocal</strong>的set()和get()方法的主体逻辑算是比较简单了,围绕主体逻辑,还做了一些特殊处理,譬如:线程中的映射表还未初始化时,调用createMap()进行初始化;在映射表中没有获取到<em>Value</em>时,通过setInitialValue()设置一个初始值,这种场景下,只需要实现initialValue()函数就可以了,这种<strong>ThreadLocal</strong>的使用方式很常见。本文不再展开这些细枝末节的逻辑,读者自行阅读源码即可。</p>
<h2 id="22-threadlocalmap映射表">2.2 ThreadLocalMap映射表</h2>
<p><strong>ThreadLocal</strong>并不是一个存储容器,往ThreadLocal中读(get)和写(set)数据,其实都是将数据保存到了每个线程自己的存储空间。</p>
<p>线程中的存储空间是一个映射表(ThreadLocalMap),<strong>TheadLocal</strong>其实就是这个映射表每一项的<em>Key</em>,通过<strong>ThreadLocal</strong>读写数据,其实就是通过<em>Key</em>在一个映射表中读写数据。</p>
<p>上文中图示中,我们见过映射表的结构,它是一个名为table的数组,每一个元素都是Entry对象,而Entry对象包含key和value两个属性,其代码如下所示:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">class</span> <span class="nc">Entry</span> <span class="kd">extends</span> <span class="n">WeakReference</span><span class="o"><</span><span class="n">ThreadLocal</span><span class="o"><?>></span> <span class="o">{</span>
<span class="cm">/** The value associated with this ThreadLocal. */</span>
<span class="n">Object</span> <span class="n">value</span><span class="o">;</span>
<span class="n">Entry</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o"><?></span> <span class="n">k</span><span class="o">,</span> <span class="n">Object</span> <span class="n">v</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">v</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>ThreadLocalMap</strong>的Entry是WeakReference的子类,这样能保证线程中的映射表的每一个Entry可以被垃圾回收,而不至于发生内存泄露。因为<strong>ThreadLocal</strong>作为全局的<em>Key</em>,其生命周期很可能比一个线程要长,如果Entry是一个强引用,那么线程对象就一直持有<strong>ThreadLocal</strong>的引用,而不能被释放。随着线程越来越多,这些不能被释放的内存也就越来越多。</p>
<p><strong>ThreadLocal</strong>作为映射表的<em>Key</em>,需要具备唯一的标识,每创建一个新的<strong>ThreadLocal</strong>,这个标识就变的跟之前不一样了。
如何保证每一个<strong>ThreadLocal</strong>的唯一性呢?</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ThreadLocal</span><span class="o"><</span><span class="n">T</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="kt">int</span> <span class="n">HASH_INCREMENT</span> <span class="o">=</span> <span class="mh">0x61c88647</span><span class="o">;</span>
<span class="c1">// 每一个ThreadLocal对象的HashCode都不一样</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">threadLocalHashCode</span> <span class="o">=</span> <span class="n">nextHashCode</span><span class="o">();</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">nextHashCode</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 下一个HashCode,是在已有基础上增加0x61c88647</span>
<span class="k">return</span> <span class="n">nextHashCode</span><span class="o">.</span><span class="na">getAndAdd</span><span class="o">(</span><span class="n">HASH_INCREMENT</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>ThreadLocal</strong>内部有一个名为threadLocalHashCode的变量,每创建一个新的<strong>ThreadLocal</strong>对象,这个变量的值就会增加<code class="highlighter-rouge">0x61c88647</code>。
正是因为有这么一个神奇的数字,它能够保证生成的Hash值可以均匀的分布在0~(2^N-1)之间,N是数组长度。
更多关于数字<code class="highlighter-rouge">0x61c88647</code>,可以参考<a href="https://www.javaspecialists.eu/archive/Issue164.html">Why 0x61c88647?</a></p>
<h1 id="3-使用场景">3. 使用场景</h1>
<p>在介绍具体的使用场景之前,我们先来抽象一下:</p>
<div align="center"><img src="/assets/images/threadlocal/3-threadlocal-usage.png" /></div>
<p>这个图表示:多个线程的生命周期不同,当一个线程在其生命周期内的某个时候,调用ThreadLocal.set()方法,其实就在该线程内部启用了一个局部变量,而后这个局部变量可以在该线程生命周期的任何时候被获取,直到调用ThreadLocal.remove()方法或者线程消亡。</p>
<font color="red">线程通过<b>ThreadLocal</b>提供的接口来操作自己内部的映射表,或者可以在语意上这么理解:线程把<b>ThreadLocal</b>当做自己的局部变量,不过对这个变量的赋值操作是set(),读取操作是get(),清空操作是remove()。</font>
<h2 id="31-android-looper">3.1 Android Looper</h2>
<p>Android中有一个很常见的操作:使用Handler将消息抛送到线程的消息队列。控制消息队列的类是<strong>Looper</strong>,每个拥有消息队列的线程,都会有一个独立的<strong>Looper</strong>类,用于处理本线程的消息。
一种实现方式是:在线程类中,声明一个<strong>Looper</strong>类型的局部变量,当线程运行起来时,创建<strong>Looper</strong>对象,并开始进行无限循环,代码示意如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LooperThread</span> <span class="kd">extends</span> <span class="n">Thread</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">Looper</span> <span class="n">mLooper</span><span class="o">;</span>
<span class="nd">@Override</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="c1">// 创建Looper对象(实际上,Looper类的构造器是私有的)</span>
<span class="n">mLooper</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Looper</span><span class="o">();</span>
<span class="c1">// 开始无限循环处理消息</span>
<span class="n">mLooper</span><span class="o">.</span><span class="na">loop</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Looper</span> <span class="nf">getLooper</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">mLooper</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>注意到</strong>,这种实现方式需要增加一个方法:<strong>getLooper()</strong>,因为其他线程可能需要获取<strong>LooperThread</strong>的消息队列。
然而,Android并不是采用的上述实现方式,而是利用<strong>ThreadLocal</strong>来保存<strong>Looper</strong>对象,当一个线程想要拥有消息队列时,调用<strong>Looper.prepare()</strong>方法便可完成消息队列的初始化,然后调用<strong>Looper.loop()</strong>便会开始无限循环,不断从消息队列上取出消息进行处理。先来看<strong>Looper</strong>的代码实现片段:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Looper</span> <span class="o">{</span>
<span class="kd">static</span> <span class="kd">final</span> <span class="n">ThreadLocal</span><span class="o"><</span><span class="n">Looper</span><span class="o">></span> <span class="n">sThreadLocal</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocal</span><span class="o"><</span><span class="n">Looper</span><span class="o">>();</span>
<span class="c1">// 私有构造器,意味着外部不能调用</span>
<span class="kd">private</span> <span class="nf">Looper</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">quitAllowed</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mQueue</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MessageQueue</span><span class="o">(</span><span class="n">quitAllowed</span><span class="o">);</span>
<span class="n">mThread</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">prepare</span><span class="o">()</span> <span class="o">{</span>
<span class="n">prepare</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">prepare</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">quitAllowed</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sThreadLocal</span><span class="o">.</span><span class="na">get</span><span class="o">()</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">RuntimeException</span><span class="o">(</span><span class="s">"Only one Looper may be created per thread"</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 通过ThreadLocal保存新建的Looper对象</span>
<span class="n">sThreadLocal</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">new</span> <span class="n">Looper</span><span class="o">(</span><span class="n">quitAllowed</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">Looper</span> <span class="nf">myLooper</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 返回实际线程的Looper对象</span>
<span class="k">return</span> <span class="n">sThreadLocal</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>Looper</strong>中定义了一个静态变量<em><code class="highlighter-rouge">sThreadLocal</code></em>,构造器都是私有的(private),即外部无法调用,然后提供了一个<strong>prepare()</strong>方法,当该方法被调用时,便往<em><code class="highlighter-rouge">sThreadLocal</code></em>中设置一个<strong>Looper</strong>对象。</p>
<p>上文剖析过<strong>ThreadLocal</strong>的实现,可以知道:哪个线程调用了<strong>prepare()</strong>方法,<strong>Looper</strong>对象就添加到了那个具体线程的<strong>ThreadLocalMap</strong>映射表中,表中每一项的<em>Key</em>是<em><code class="highlighter-rouge">sLocalThread</code></em>,<em>Value</em>是<strong>Looper</strong>对象,这样一来,就等价于线程拥有了<strong>Looper</strong>这个局部变量。如何获取线程中的<strong>Looper</strong>对象呢?在线程中直接调用<strong>ThreadLocal.get()</strong>方法就可以了,所以<strong>Looper</strong>类封装了一个静态方法<strong>myLooper()</strong>,做的就是获取当前线程<strong>Looper</strong>对象的买卖。</p>
<p>Android中,真正带消息队列的线程实现是<strong>HandlerThread</strong>,与上文中模拟的<strong>LooperThread</strong>的实现方式如出一辙,不过是利用了<strong>ThreadLocal</strong>这个编程工具:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HandlerThread</span> <span class="kd">extends</span> <span class="n">Thread</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="n">mTid</span> <span class="o">=</span> <span class="n">Process</span><span class="o">.</span><span class="na">myTid</span><span class="o">();</span>
<span class="n">Looper</span><span class="o">.</span><span class="na">prepare</span><span class="o">();</span> <span class="c1">// 初始化消息队列,即将Looper对象添加到实际线程的ThreadLocalMap中</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="n">mLooper</span> <span class="o">=</span> <span class="n">Looper</span><span class="o">.</span><span class="na">myLooper</span><span class="o">();</span> <span class="c1">// 获取实际线程的Looper对象</span>
<span class="n">notifyAll</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">Process</span><span class="o">.</span><span class="na">setThreadPriority</span><span class="o">(</span><span class="n">mPriority</span><span class="o">);</span>
<span class="n">onLooperPrepared</span><span class="o">();</span>
<span class="n">Looper</span><span class="o">.</span><span class="na">loop</span><span class="o">();</span> <span class="c1">// 开始无限循环处理消息</span>
<span class="n">mTid</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Looper</span> <span class="nf">getLooper</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isAlive</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="c1">// If the thread has been started, wait until the looper has been created.</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="k">while</span> <span class="o">(</span><span class="n">isAlive</span><span class="o">()</span> <span class="o">&&</span> <span class="n">mLooper</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">wait</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</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="k">return</span> <span class="n">mLooper</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>当线程运行起来时,往<strong>ThreadLocal</strong>中添加了一个<strong>Looper</strong>对象,然后开始无限循环处理消息。往<strong>ThreadLocal</strong>中添加对象的行为,就意味着这个对象是属于每个线程的局部变量。</p>
<p>当有多个HandlerThread同时运行时,它们的关系如下图所示:</p>
<div align="center"><img src="/assets/images/threadlocal/4-threadlocal-handlerthread.png" /></div>
<p>每一个HandlerThread线程内部都有<strong>Key-Value Pairs</strong>,<em>Value</em>是不同的Looper对象,而<em>Key</em>是指向同一个静态ThreadLocal对象的弱引用。</p>
<h2 id="32-android-sqlitedatabase">3.2 Android SQLiteDatabase</h2>
<p>Android中进行数据库的事务操作时,通常都会在某个工作线程中调用SQLiteDatabase.beginTransaction()方法,然后开始具体的数据库操作。有些时候,并发操作数据库的线程会存在多个,要操作数据库,是要发起连接的,Android封装了一个类<strong>SQLiteSession</strong>,专门来管理数据库连接,每个线程都需要<strong>SQLiteSession</strong>对象,那线程怎样才能获取到一个独立的<strong>SQLiteSession</strong>对象呢?这种场景下,便有了ThreadLocal的用武之地了。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">SQLiteDatabase</span> <span class="kd">extends</span> <span class="n">SQLiteClosable</span> <span class="o">{</span>
<span class="c1">// 定义ThreadLocal,存储的对象类型是SQLiteSession</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">ThreadLocal</span><span class="o"><</span><span class="n">SQLiteSession</span><span class="o">></span> <span class="n">mThreadSession</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocal</span><span class="o"><</span><span class="n">SQLiteSession</span><span class="o">>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="n">SQLiteSession</span> <span class="nf">initialValue</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">createSession</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="n">SQLiteSession</span> <span class="nf">getThreadSession</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 通过ThreadLocal获取SQLiteSession对象</span>
<span class="k">return</span> <span class="n">mThreadSession</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> <span class="c1">// initialValue() throws if database closed</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">beginTransaction</span><span class="o">(</span><span class="n">SQLiteTransactionListener</span> <span class="n">transactionListener</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">exclusive</span><span class="o">)</span> <span class="o">{</span>
<span class="n">acquireReference</span><span class="o">();</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 获取SQLiteSession对象后,开始数据库的事务操作</span>
<span class="n">getThreadSession</span><span class="o">().</span><span class="na">beginTransaction</span><span class="o">(</span>
<span class="n">exclusive</span> <span class="o">?</span> <span class="n">SQLiteSession</span><span class="o">.</span><span class="na">TRANSACTION_MODE_EXCLUSIVE</span> <span class="o">:</span>
<span class="n">SQLiteSession</span><span class="o">.</span><span class="na">TRANSACTION_MODE_IMMEDIATE</span><span class="o">,</span>
<span class="n">transactionListener</span><span class="o">,</span>
<span class="n">getThreadDefaultConnectionFlags</span><span class="o">(</span><span class="kc">false</span> <span class="cm">/*readOnly*/</span><span class="o">),</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">releaseReference</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>SQLiteDatabase中定义了<strong>ThreadLocal</strong>,所存储对象的类型是SQLiteSession。每当在线程中调用<strong>SQLiteDatabase.beginTransaction()</strong>方法时,表示要开始数据库的事务操作了,这时候会先从<strong>ThreadLocal</strong>中取出属于当前线程的SQLiteSession对象。</p>
<p>在多进程多线程访问数据库的情况下,它们的关系图如下所示:</p>
<div align="center"><img src="/assets/images/threadlocal/5-threadlocal-sqlitedatabase.png" /></div>
<h2 id="34-总结">3.4 总结</h2>
<p>通过上述使用场景可以发现,<strong>ThreadLocal</strong>确实提供了一种编程手段,本来需要在线程中显示声明的局部变量,像是被<strong>ThreadLocal</strong>隐藏了起来,当多个线程运行起来时,每个线程都往相同的<strong>ThreadLocal</strong>中存取所需要的变量就可以了,使用<strong>ThreadLocal</strong>存取的变量,就像是每个线程自己的局部变量,不受其他线程运行状态的影响。</p>
<p>通过<strong>ThreadLocal</strong>可以解决<code class="highlighter-rouge">多线程读</code>共享数据的问题,因为共享数据会被复制到每个线程,不需要加锁便可同步访问。但<strong>ThreadLocal</strong>解决不了<code class="highlighter-rouge">多线程写</code>共享数据的问题,因为每个线程写的都是自己本线程的局部变量,并没将写数据的结果同步到其他线程。理解了这一点,才能理解所谓的:</p>
<ul>
<li><strong>ThreadLocal</strong>以空间换时间,提升多线程并发的效率。什么意思呢?每个线程都有一个<strong>ThreadLocalMap</strong>映射表,正是利用了这个映射表所占用的空间,使得多个线程都可以访问自己的这片空间,不用担心考虑线程同步问题,效率自然会高。</li>
<li><strong>ThreadLocal</strong>并不是为了解决共享数据的<strong>互斥写</strong>问题,而是通过一种编程手段,正好提供了<strong>并行读</strong>的功能。什么意思呢?<strong>ThreadLocal</strong>并不是万能的,它的设计初衷只是提供一个便利性,使得线程可以更为方便地使用局部变量。</li>
<li><strong>ThreadLocal</strong>提供了一种线程全域访问功能,什么意思呢?一旦将一个对象添加到<strong>ThreadLocal</strong>中,只要不移除它,那么,在线程的生命周期内的任何地方,都可以通过<strong>ThreadLocal.get()</strong>方法拿到这个对象。有时候,代码逻辑比较复杂,一个线程的代码可能分散在很多地方,利用<strong>ThreadLocal</strong>这种便利性,就能简化编程逻辑。</li>
</ul>
构建基于ACRA的Android数据采集系统
2018-01-28T00:00:00+00:00
https://duanqz.github.io/ACRA
<p>本文基于MacOS,介绍如何基于ACRA(Application Crash Reporting on Android)构建数据采集系统。 阅读本文之前,读者需要了解:在MacOS下通过brew安装软件包。</p>
<h1 id="1-acra简介">1. ACRA简介</h1>
<p>ACRA(Application Crash Reporting on Android),Android应用程序崩溃上报系统。在客户端集成ACRA的SDK,便可将应用程序的崩溃错误上报到服务端。ACRA内部实现了崩溃上报的策略,客户端采用Annotation的方式进行代码集成,对已有的业务代码影响非常小。</p>
<h1 id="2-服务端acralyzer">2. 服务端Acralyzer</h1>
<p>实际上,ACRA并不限制服务端采用什么实现,现有第三方服务端有很多,譬如:<a href="http://www.cloudant.com/">Cloudant</a>、<a href="http://www.iriscouch.com/">IrisCouch</a>、<a href="https://www.couchappy.com/">Couchhappy</a>、<a href="http://www.tracepot.com/">Tracepot</a>等;
如果不想使用第三方的,也可以自建服务端,<strong>Acralyzer</strong>是ACRA的服务端官方实现案例,除此之外,还有很多其他可用的服务端框架,PHP、Ruby、Go语言实现的都有,譬如:<a href="http://www.brokenopenapp.org/2015/05/24/first-post.html">BrokenOpenApp</a>、<a href="http://livefront.github.com/acracadabra/">Acracadabra</a>、<a href="https://github.com/gen2brain/acra-go">acra-go</a>。</p>
<p>本文采用<strong>Acralyzer</strong>在本地自建服务端,如要选择其他服务端,请参考<a href="https://github.com/ACRA/acra/wiki/Backends">https://github.com/ACRA/acra/wiki/Backends</a></p>
<h2 id="21-安装couchdb">2.1 安装CouchDB</h2>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> brew install couchdb
<span class="c">...
</span><span class="go">To test CouchDB run:
curl http://127.0.0.1:5984/
The reply should look like:
{"couchdb":"Welcome","uuid":"....","version":"1.7.1","vendor":{"version":"1.7.1-1","name":"Homebrew"}}
To have launchd start couchdb now and restart at login:
brew services start couchdb
Or, if you don't want/need a background service you can just run:
couchdb
</span></code></pre></div></div>
<p>安装完成后会有提示:</p>
<ul>
<li>使用<code class="highlighter-rouge">brew services start couchdb</code>命令可以将<strong>CouchDB</strong>作为常驻服务自动启动</li>
<li>使用<code class="highlighter-rouge">couchdb</code>命令可以启动<strong>CouchDB</strong>一次</li>
<li>正常运行<strong>CouchDB</strong>后,可以使用<code class="highlighter-rouge">curl http://127.0.0.1:5984/</code>命令进行测试</li>
<li><strong>CouchDB</strong>的配置文件在<strong>/usr/local/etc/couchdb</strong>目录下</li>
</ul>
<h2 id="22-安装acralyzer">2.2 安装Acralyzer</h2>
<p><strong>CouchDB</strong>不仅是一个数据库,还是Web服务器,在浏览器中打开<a href="http://127.0.0.1:5984/_utils/">http://127.0.0.1:5984/_utils/</a>,便可看到<strong>CouchDB</strong>的管理界面:</p>
<div align="center"><img src="/assets/images/junior/acra/1-acra-couchdb-util.png" /></div>
<p>默认情况下,有名为<strong>_replicator</strong>和<strong>_users</strong>的两个数据库。接下来,需要根据业务逻辑构建新的数据库和Web应用。<strong>Acralyzer</strong>包含两个组件:</p>
<ul>
<li><strong>acra-storage</strong>:用于存储上报数据 <a href="https://github.com/ACRA/acra-storage.git">https://github.com/ACRA/acra-storage.git</a></li>
<li><strong>acralyzer</strong>:用于展示数据报表 <a href="http://github.com/ACRA/acralyzer.git">http://github.com/ACRA/acralyzer.git</a></li>
</ul>
<p>根据<strong>Acralyzer</strong>的<a href="https://github.com/ACRA/acralyzer/wiki/setup">官方介绍</a>,<strong>CouchDB</strong> 1.2.0及以上的版本支持快速安装这两个组件,使用<strong>Replicator</strong>功能即可:</p>
<div align="center"><img src="/assets/images/junior/acra/2-acra-couchdb-replicator.png" /></div>
<p>在<strong>CouchDB</strong>的管理页,点击<strong>Replicator</strong>,便可以复制已有的数据库。</p>
<ul>
<li>
<p>对于<strong>acra-storage</strong>,按照如下参数填写表单后,再点击<strong>Replicate</strong>按钮,稍作等待,便可将远程的数据库复制到本地,本地的数据库名字建议以<strong>acra-</strong>为前缀:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from Remote Database: http://get.acralyzer.com/distrib-acra-storage
to Local Database: acra-<appname>
</code></pre></div> </div>
</li>
<li>
<p>对于<strong>acralyzer</strong>,表单中填入以下参数,按照相同的操作即可:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from Remote Database: http://get.acralyzer.com/distrib-acralyzer
to Local Database: acralyzer
</code></pre></div> </div>
</li>
</ul>
<p>笔者使用<strong>CouchDB</strong> 1.7.0版本,Replicator无法正常工作,需要使用<strong>CouchDB</strong> 2.1.1版本才行,而且Replicate操作执行的时间比较长(笔者的网络环境下需要等待约50分钟),直到<strong>acralyzer</strong>这个数据库构建成功,才能打开<a href="http://127.0.0.1:5984/acralyzer/_design/acralyzer/index.html">http://127.0.0.1:5984/acralyzer/_design/acralyzer/index.html</a>,否则打开此网页会一直出现如下错误:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="s2">"error"</span><span class="p">:</span><span class="s2">"not_found"</span><span class="p">,</span><span class="s2">"reason"</span><span class="p">:</span><span class="s2">"missing"</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>这是因为<strong>acralyzer/_design/acralyzer/index.html</strong>这个页面还没有被复制到本地的数据库中。官网也有手工安装<strong>acra-storage</strong>和<strong>acralyzer</strong>两个组件的<a href="https://github.com/ACRA/acralyzer/wiki/manual-setup">教程</a>,笔者在MacOS下尝试手工安装,却失败了。</p>
<p>正确安装<strong>Acralyzer</strong>后,打开<a href="http://127.0.0.1:5984/_utils/">http://127.0.0.1:5984/_utils/</a>的Database界面,会看到新增的两个数据库:<code class="highlighter-rouge">acra-<appname></code>和<code class="highlighter-rouge">acralyzer</code>,这两个数据库所包含的文件数量(# of Docs)应该都<strong>大于1</strong>:</p>
<div align="center"><img src="/assets/images/junior/acra/3-acra-couchdb-replication-completed.png" /></div>
<h2 id="23-acralyzer配置">2.3 Acralyzer配置</h2>
<p><strong>CouchDB</strong>和<strong>Acralyzer</strong>安装完毕后,打开<a href="http://127.0.0.1:5984/acralyzer/_design/acralyzer/index.html">http://127.0.0.1:5984/acralyzer/_design/acralyzer/index.html</a>,便可以进入<strong>Acralyzer</strong>的主界面了,要采集客户端的上报数据,首先需要创建一个用户:</p>
<div align="center"><img src="/assets/images/junior/acra/4-acra-acralyzer-create-user.png" /></div>
<p>在导航栏的Admin选项卡中,选择Users,输入<strong>User name</strong>和<strong>Password</strong>,点击<strong>Create user</strong>,便可创建用户:</p>
<div align="center"><img src="/assets/images/junior/acra/5-acra-acralyzer-create-user.png" /></div>
<p>这时,会生成一段代码,这段代码需要集成到客户端,后文我们再来解释其含义。此处,只需要理解,<strong>Acralyzer</strong>的一个<strong>User</strong>就是一个客户端的上报标识。</p>
<p>使用127.0.0.1:5984这样的地址,客户端是无法访问的,因此正式环境下,需要进行域名和服务器的部署。在本地的测试环境下,可以使用隧道技术,将127.0.0.1:5984映射成一个外网可以访问的域名。
笔者使用<a href="https://gotunnel.net">gotunnel</a>,下载对应平台的版gotunnel版本后,执行以下命令:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> ./ngrok <span class="nt">-config</span> <span class="s2">"ngrok.conf"</span> <span class="nt">-subdomain</span> duanqz <span class="nt">-proto</span> http 5984
<span class="go">Tunnel Status online
Version 1.7/1.7
</span><span class="gp">Forwarding http://duanqz.gotunnel.net -></span> 127.0.0.1:5984
</code></pre></div></div>
<h2 id="24-常见错误">2.4 常见错误</h2>
<h3 id="241-couchdb没有启动">2.4.1 CouchDB没有启动</h3>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl http://127.0.0.1:5984/
curl: (7) Failed to connect to 127.0.0.1 port 5984: Connection refused
</code></pre></div></div>
<p>在控制台执行<code class="highlighter-rouge">couchdb</code>命令即可启动。</p>
<h3 id="242-replicator不能正常工作">2.4.2 Replicator不能正常工作</h3>
<p>在使用<strong>CouchDB</strong> 1.7.0版本的<strong>Replicator</strong>复制远程数据库时一直失败,可能会出现如下错误:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[error] [<0.16696.0>] ** Generic server <0.16696.0> terminating
** Last message in was {'EXIT',<0.16695.0>,killed}
** When Server state == {state,"http://get.acralyzer.com/distrib-acra-storage/",
20,[],
[<0.16697.0>],
{[],[]}}
** Reason for termination ==
** killed
</code></pre></div></div>
<p>笔者更新<strong>CouchDB</strong>到2.1.1,便可以正常使用<strong>Replicator</strong>复制远程数据库。</p>
<h1 id="3-客户端">3. 客户端</h1>
<h2 id="31-acra使用案例">3.1 ACRA使用案例</h2>
<ol>
<li>
<p>首先,<strong>build.gradle</strong>中加入如下依赖:</p>
<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">dependencies</span> <span class="o">{</span>
<span class="c1">// ACRA(Application Crash Reporting on Android)</span>
<span class="n">implementation</span> <span class="s2">"ch.acra:acra-http:5.0.2"</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div> </div>
<p><strong>注意</strong>:笔者使用的是5.0.2版本,在接口使用上,与4.x的版本存在明显的不同。</p>
</li>
<li>
<p>然后,继承实现Application类,将<strong>@AcraHttpSender</strong>这个注解添加到自定义的类前面:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@AcraHttpSender</span><span class="o">(</span>
<span class="n">uri</span> <span class="o">=</span> <span class="s">"http://duanqz.gotunnel.net/acra-secure/_design/acra-storage/_update/report"</span><span class="o">,</span>
<span class="n">httpMethod</span> <span class="o">=</span> <span class="n">HttpSender</span><span class="o">.</span><span class="na">Method</span><span class="o">.</span><span class="na">PUT</span><span class="o">,</span>
<span class="n">basicAuthLogin</span> <span class="o">=</span> <span class="s">"demo"</span><span class="o">,</span>
<span class="n">basicAuthPassword</span> <span class="o">=</span> <span class="s">"123456"</span>
<span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DemoApplication</span> <span class="kd">extends</span> <span class="n">Application</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div> </div>
<p><strong>@AcraHttpSender</strong>需要填入一些参数:<strong>uri</strong>表示服务端接收上报数据的地址,<strong>httpMethod</strong>可选的有POST和PUT,<strong>basicAuthLogin</strong>和<strong>basicAuthPassword</strong>用于上报的鉴权。这些参数值,其实就是在服务端<strong>Acralyzer</strong>创建一个用户时自动生成的,笔者上文创建的用户和密码<strong>demo/123456</strong>,就是此处需要填入的参数值。</p>
<p><strong>注意</strong>,在ACRA 5.0之前的版本,使用的注解是<strong>@ReportCrashs</strong>,参数的Key也略有区别,譬如:<strong>@ReportCrashs</strong>的参数<strong>fromUri</strong>变成了<strong>@AcraHttpSender</strong>的<strong>uri</strong>。更多区别,请参见<a href="https://github.com/ACRA/acra/wiki/Migrating">https://github.com/ACRA/acra/wiki/Migrating</a>。</p>
</li>
<li>
<p>最后,重写Application类的<strong>attachBaseContext()</strong>方法,来完成ACRA的初始化:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">attachBaseContext</span><span class="o">(</span><span class="n">Context</span> <span class="n">base</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">attachBaseContext</span><span class="o">(</span><span class="n">base</span><span class="o">);</span>
<span class="n">ACRA</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">ACRA</span><span class="o">.</span><span class="na">getErrorReporter</span><span class="o">().</span><span class="na">handleSilentException</span><span class="o">(</span><span class="k">new</span> <span class="n">Exception</span><span class="o">(</span><span class="s">"I am exception."</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>此处,调用了<strong>ACRA.init()</strong>完成了ACRA环境的初始化,其具体实现方式后文在做详细分析;之后,主动上报了一个异常<strong>I am exception</strong>,当应用程序启动时,就会将该异常上报到服务端。</p>
</li>
</ol>
<p>通过以上三步,就完成了客户端上报异常的所有工作,在服务端看到上报的结果如下图:</p>
<div align="center"><img src="/assets/images/junior/acra/6-acra-acralyzer-report-received.png" /></div>
<p>除了异常信息,还有很多与设备相关的信息也会上报,在<strong>Full report</strong>一项中会呈现出来:</p>
<div align="center"><img src="/assets/images/junior/acra/7-acra-acralyzer-full-report.png" /></div>
<p>更多信息,请参考官方的<a href="https://github.com/ACRA/acra/wiki/ReportContent">ReportContent介绍</a>。</p>
<h1 id="4-参考资料">4. 参考资料</h1>
<ol>
<li>ACRA官方WIKI:<a href="https://github.com/ACRA/acra/wiki">https://github.com/ACRA/acra/wiki</a>。</li>
<li>Acra详细分析:<a href="https://www.jianshu.com/p/fd4d6a7c6175">https://www.jianshu.com/p/fd4d6a7c6175</a>。分析了ACRA 4.9的代码逻辑,本文使用ACRA 5.0.2,ACRA并不是向前兼容的,接口使用上存在很大的不同。</li>
</ol>
微擎环境搭建指南
2018-01-13T00:00:00+00:00
https://duanqz.github.io/WeEngine-Start
<p>本文基于<code class="highlighter-rouge">MacOS</code>,介绍如何定制微擎运行环境。
阅读本文之前,读者需要了解:在MacOS下通过brew安装软件包。</p>
<h1 id="1-环境搭建">1. 环境搭建</h1>
<h2 id="11-下载微擎源码">1.1 下载微擎源码</h2>
<p>可以选择下载<strong><a href="http://s.we7.cc/store-static-install.html">微擎官方源码</a></strong>离线版,本文下载的微擎是<code class="highlighter-rouge">1.0稳定版</code>。</p>
<p>同时,笔者将源码保存在到了github上,可以通过以下命令直接下载:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/etutorial/WeEngine.git
</code></pre></div></div>
<h2 id="12-安装nginx">1.2 安装nginx</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install nginx
</code></pre></div></div>
<p>在浏览器中输入<code class="highlighter-rouge">localhost:8080</code>,如果看到如下界面内容,表示<code class="highlighter-rouge">nginx</code>安装成功:</p>
<div align="center"><img src="/assets/images/junior/weengine/1-weengine-nginx-installed.png" /></div>
<p>配置文件<code class="highlighter-rouge">/usr/local/etc/nginx/nginx.conf</code></p>
<h2 id="13-安装mysql">1.3 安装mysql</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install mysql
</code></pre></div></div>
<p>启动mysql,设置初始化密码:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql.server start
mysql_secure_installation
</code></pre></div></div>
<p>然后,根据命令提示输入密码即可。</p>
<h2 id="14-安装微擎">1.4 安装微擎</h2>
<p>微擎依赖于php,MacOS默认自带了php,因此直接执行如下命令即可,其中<strong>/Users/duanqz/Code/WeEngine</strong>是微擎的源码目录:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ php -S 127.0.0.1:8080 -t /Users/duanqz/Code/WeEngine
</code></pre></div></div>
<p>在浏览器中输入<code class="highlighter-rouge">127.0.0.1:8080</code>,出现以下界面表示php环境运行正常:</p>
<div align="center"><img src="/assets/images/junior/weengine/2-weengine-php-installed.png" /></div>
<p>点击<code class="highlighter-rouge">install.php 进入安装</code>,按照操作提示下一步即可。其中参数配置一步,需要配置数据库连接信息和微擎后台管理员账号:</p>
<div align="center"><img src="/assets/images/junior/weengine/3-weegine-installing-config.png" /></div>
<p>在测试环境下:</p>
<ul>
<li>数据库主机输入的是本机(<code class="highlighter-rouge">127.0.0.1</code>)的mysql</li>
<li>数据库账号和密码是之前配置的root和12345678</li>
<li>表前缀和数据库名称选择了默认值ims_和we7</li>
<li>微擎的后台管理员账户/密码配置为admin/admin</li>
</ul>
<p>成功安装微擎后,便会进入微擎后台的登录界面,输入刚才配置的管理员用户,便进入到微擎的主界面:</p>
<div align="center"><img src="/assets/images/junior/weengine/4-weengine-installed.png" /></div>
<h2 id="15-常见问题">1.5 常见问题</h2>
<h3 id="151-mysql没有启动">1.5.1 mysql没有启动</h3>
<p>mysql没有启动,则会报如下错误,按照上述步骤启动mysql即可。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>发生错误: SQLSTATE[HY000] [2002] Connection refused
</code></pre></div></div>
<h3 id="152-msql中存在重名的数据库">1.5.2 msql中存在重名的数据库</h3>
<p>mysql中已经存在相同的数据库或表前缀。在重复安装微擎的运行环境时会出现如下错误。
删除mysql中的重名数据库和数据表即可。可以使用MySQLWorkbench、Navicat、TeamSQL等图形化的客户端。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>发生错误: 您的数据库不为空,请重新建立数据库或是清空该数据库或更改表前缀!
</code></pre></div></div>
<h3 id="153-重新安装微擎">1.5.3 重新安装微擎</h3>
<p>如果需要修改数据库和数据表名,则需要重新安装微擎,删除微擎源码目录下的以下子目录即可:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data/config.php # 微擎运行的配置信息,之前输入的数据库连接信息就保存在此文件中
data/install.lock # 一个锁标识,标识微擎已经正确安装
data/tpl/ # 根据源码生成的最终用户界面
</code></pre></div></div>
<p>使用如下命令,连接数据库,可展示出数据库中微擎默认创建的数据表,删除这些表即可。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mysql --user=root --password we7
Enter password: # 输入密码12345678
mysql> show tables;
+-------------------------------+
| Tables_in_we7 |
+-------------------------------+
| ims_account |
| ims_account_wechats |
| ims_account_wxapp |
| ims_article_category |
| ims_article_news |
... # 此处省略了很多其他表
| ims_wxapp_versions |
| ims_wxcard_reply |
+-------------------------------+
98 rows in set (0.01 sec)
</code></pre></div></div>
<h1 id="2-微擎定制">2. 微擎定制</h1>
<h2 id="21-内网穿透">2.1 内网穿透</h2>
<p>微信公众号的调试需要公网的域名,可以使用利用内网穿透技术,将一个本地的IP和端口映射成公网的域名。
笔者罗列几个可以进行内网穿透的工具:</p>
<ul>
<li><a href="https://natapp.cn">NATAPP</a>:需要注册才能使用。</li>
<li><a href="https://www.ngrok.cc">Sunny-Ngrok</a>:需要注册才能使用,免费版需要实名注册。</li>
<li><a href="https://gotunnel.net">Go-Tunnel</a>:无需注册,免费使用,速度有限。</li>
</ul>
<blockquote>
<p>读者可以从网络上搜罗到的大部分内网穿透工具都是基于ngrok的</p>
</blockquote>
<p>笔者使用的是定制过的Go-Tunnel:<a href="https://github.com/etutorial/WeEngine/tree/master/gotunnel">https://github.com/etutorial/WeEngine/tree/master/gotunnel</a>
在下载的微擎源码根目录下,执行以下命令,便可启动内网穿透:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> <span class="nb">cd </span>gotunnel <span class="o">&&</span> ./nogrok start web
<span class="go">Tunnel Status online
Version 1.7/1.7
</span><span class="gp">Forwarding http://duanqz.gotunnel.net -></span> 127.0.0.1:8080
<span class="gp">Http Server 127.0.0.1:8080 -></span> /Users/duanqz/Code/WeEngine
<span class="go">Web Interface disabled
</span><span class="gp">#</span> Conn 82
<span class="go">Avg Conn Time 245.97ms
</span></code></pre></div></div>
<p>正常启动时,Tunnel Status应该是<strong>online</strong>状态,内网地址<strong>127.0.0.1:8080</strong>被映射到公网的域名<strong>http://duanqz.gotunnel.net</strong>。
这里,还需简单说明一下Go-Tunnel的配置文件:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">server_addr</span><span class="pi">:</span> <span class="s2">"</span><span class="s">gotunnel.net:2333"</span>
<span class="na">tunnels</span><span class="pi">:</span>
<span class="na">web</span><span class="pi">:</span>
<span class="na">subdomain</span><span class="pi">:</span> <span class="s">duanqz</span>
<span class="na">root</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/Users/duanqz/Code/WeEngine"</span>
<span class="na">proto</span><span class="pi">:</span>
<span class="na">http</span><span class="pi">:</span> <span class="s2">"</span><span class="s">:8080"</span>
</code></pre></div></div>
<ul>
<li>配置文件中的服务器地址<strong>server_addr</strong>可能不稳定,导致内网穿透失败,此时,读者可以将其换成其他地址</li>
<li>配置文件中定义了一个隧道<strong>web</strong>,子域名是<strong>duanqz</strong>,端口是<strong>8080</strong>,读者可以根据自己的需要进行修改</li>
</ul>
Android Context的设计思想和源码分析
2017-12-25T00:00:00+00:00
https://duanqz.github.io/Android-Context
<p>做了好些年Android,终于可以聊一聊既熟悉又陌生的Context了,每个刚入门的Android开发人员都会接触到它;然而要读懂Context的设计哲学,却又要经过好多轮的认知升级。很多时候,大家是感知不到Context的存在的,笔者最开始“被迫”使用Context,是在自定义控件的时候,布局中有一个按钮,点击一次就发送一次广播,其代码片段如下所示:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomLayout</span> <span class="kd">extends</span> <span class="n">LinearLayout</span> <span class="kd">implements</span> <span class="n">View</span><span class="o">.</span><span class="na">OnClickListener</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">Context</span> <span class="n">mContext</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">Button</span> <span class="n">mBtnBroadcast</span><span class="o">;</span>
<span class="c1">// 1. 强制传入Context</span>
<span class="kd">public</span> <span class="nf">CustomLayout</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="n">LayoutInflater</span><span class="o">.</span><span class="na">from</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">inflate</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">custom_layout</span><span class="o">,</span> <span class="k">this</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="n">mBtnBroadcast</span> <span class="o">=</span> <span class="o">(</span><span class="n">Button</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">btn_broadcast</span><span class="o">);</span>
<span class="n">mBtnBroadcast</span><span class="o">.</span><span class="na">setOnClickListener</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onClick</span><span class="o">(</span><span class="n">View</span> <span class="n">v</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="c1">// 2. 通过Context发送广播</span>
<span class="n">mContext</span><span class="o">.</span><span class="na">sendBroadcast</span><span class="o">(</span><span class="n">intent</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>之所以说“被迫”使用Context,是因为:1. 构造函数就强制要传入Context,否则会导致编译报错;2. 在点击按钮发送广播时,又必须使用Context,于是乎,又“被迫”设计一个对象属性<code class="highlighter-rouge">mContext</code>来保存外部传入的Context。各种疑问涌上心头:</p>
<ul>
<li>好不容易想实现控件的代码解耦,为什么要把Context传来传去呢?</li>
<li>为什么不能像在Activity中一样,直接调用sendBroadcast()就可以了呢?</li>
<li>通过Context可以调用到很多Android接口,譬如<strong>getString(), getColor(), startActivity()</strong>等等,它到底是何方神圣呢?</li>
</ul>
<p>本文会结合Context的设计思想和源码分析来进行解构。</p>
<h1 id="1-context的设计思想">1. Context的设计思想</h1>
<h2 id="11-面向应用程序的设计">1.1 面向应用程序的设计</h2>
<p>Android有意淡化进程的概念,在开发一个Android应用程序时,通常都不需要关心目标对象运行在哪个进程,只需要表明意图(Intent),譬如拨打电话、查看图片、打开链接等;也不需要关心系统接口是在哪个进程实现的,只需要通过Context发起调用。对于一个Android应用程序而言,Context就像运行环境一样,无处不在。有了Context,一个Linux进程就摇身一变,成为了Android进程,变成了Android世界的公民,享有Android提供的各种服务。那么,一个Android应用程序需要一些什么服务呢?</p>
<ul>
<li>获取应用资源,譬如:drawable、string、assets</li>
<li>操作四大组件,譬如:启动界面、发送广播、绑定服务、打开数据库</li>
<li>操作文件目录,譬如:获取/data/分区的数据目录、获取sdcard目录</li>
<li>检查授予权限,譬如:应用向外提供服务时,可以判定申请者是否具备访问权限</li>
<li>获取其他服务,有一些服务有专门的提供者,譬如:包管理服务、Activity管理服务、窗口管理服务</li>
</ul>
<p>在应用程序中,随处都可访问这些服务,这些服务的访问入口就是Context。Android对Context类的注解是:</p>
<blockquote>
<p>Interface to global information about an application environment. This is
an abstract class whose implementation is provided by the Android system.
It allows access to application-specific resources and classes, as well as
up-calls for application-level operations such as launching activities,
broadcasting and receiving intents, etc.</p>
</blockquote>
<p>其意为:Context是一个抽象类,提供接口,用于访问应用程序运行所需服务,譬如启动Activity、发送广播、接受Intent等。Android是构建在Linux之上的,然而对于Android的应用开发者而言,已经不需要关心Linux进程的概念了,正是因为有了Context,为应用程序的运行提供了一个Android环境,开发者只需要关心Context提供了哪些接口。</p>
<h2 id="12-关于decorator设计模式">1.2 关于Decorator设计模式</h2>
<p>在类的世界里面,要为一个类增加新的功能,最直接的方式就是<strong>继承</strong>,子类可以基于父类进行扩展。然而,当要增加的功能维度有很多,并且功能相互叠加的时候,要扩展的子类会变得非常多。</p>
<blockquote>
<p>举个例子,基类是衣服,需求是分别生产防水、透气和速干的三个不同功能的衣服,便会扩展出三个子类:防水衣服、透气衣服和速干衣服。如果又有新的需求,即防水又速干,便会扩展出一个新的子类:防水速干衣服。然后,又有新的需求,即保暖又速干,这可能会扩展出两个子类:保暖衣服和保暖速干衣服。长此以往,市场需求不断变化,要扩展的类就会越来越多。</p>
</blockquote>
<p>在GOF设计模式里面,把<strong>继承</strong>看成<strong>静态</strong>的类扩展,扩展功能的增多会导致子类膨胀。为了有效缓解这种情况,便产生了<strong>动态</strong>的类扩展:<code class="highlighter-rouge">修饰器模式(Decorator Pattern)</code>,先上UML图:</p>
<div align="center"><img src="/assets/images/context/1-context-decorator-design-pattern-uml.png" /></div>
<p><strong>Decorator</strong>就是所谓的修饰器,包装了(Wrapper)一个<strong>Component</strong>类型对象,修饰器可以在已有<strong>Component</strong>的基础上,增加新的<strong>属性(addedState)</strong>和<strong>行为(addedBehavior)</strong>,从而形成不同的<strong>ConcreteDecorator</strong>。<strong>Decorator</strong>通过包装的手段,在外围扩展了<strong>Component</strong>的功能。
说到这里,读者们一定心生诧异,实现不同扩展功能的<strong>ConcreteDecorator</strong>,还是得<strong>继承</strong>实现多个不同子类啊!确实如此,扩展不同维度的功能需要实现不同的子类,但要实现这些功能的组合,却不需要新的子类了,因为一个修饰器可以修饰另外一个修饰器,通过修饰器的叠加便可实现功能的组合。</p>
<blockquote>
<p>还是上面的例子,基类是衣服,有三个修饰器:防水、透气和速干,在这三个修饰器的包装下,便可生成三种不同的衣服:防水衣服、透气衣服和速干衣服。如果又有新需求,即防水又速干,只需要在防水衣服上再叠加一个速干修饰器,便生成了防水速干衣服。然后,又有新需求,即保暖又速干,这时,只需要增加一个修饰器:保暖,将这个修饰器叠加到速干衣服上,便可生成保暖速干衣服。这样一来,便能有效缓解类的膨胀。</p>
</blockquote>
<p>理解Decorator模式,有助于大家理解Context类簇的设计,前文说过Context是一个抽象类,围绕Context还有很多实现类,这些类的结构设计就是Decorator模式。</p>
<h2 id="13-context类簇的设计">1.3 Context类簇的设计</h2>
<p>先奉上Context类簇的类图如下:</p>
<div align="center"><img src="/assets/images/context/2-context-class-diagram.png" /></div>
<p>一个典型的Decorator模式,基类<strong>Context</strong>定义了各种接口,<strong>ContextImpl</strong>负责实现接口的具体功能。对外提供使用时,<strong>ContextImpl</strong>需要被包装(Wrapper)一下,这就有了<strong>ContextWrapper</strong>这个修饰器。修饰器一般只是一个传递者,修饰器所有的方法实现都是调用具体的实现类<strong>ContextImpl</strong>,所以修饰器<strong>ContextWrapper</strong>需要持有一个<strong>ContextImpl</strong>的引用。</p>
<p>修饰器存在的价值是为了扩展类的功能,<strong>Context</strong>已经提供了丰富的系统功能,但仍不能满足最终应用程序编程的需要,因此Android又扩展了一些修饰器,包括<strong>Application</strong>、<strong>Activity</strong>和<strong>Service</strong>。虎躯一震,这些东西竟然就是<strong>Context</strong>,原来<strong>Context</strong>真的是无处不在啊!在<strong>Activity</strong>中调用startActivity启动另外的界面,原来就是通过父类<strong>Context</strong>发起的调用!</p>
<p><strong>Application</strong>扩展了应用程序的生命周期,<strong>Activity</strong>扩展了界面显示的生命周期,<strong>Service</strong>扩展了后台服务的生命周期,它们在父类<strong>Context</strong>的基础上进行了不同维度的扩展,同时也仍可以将它们作为<strong>Context</strong>使用,这可以解释很多<strong>Applicaiton、Activity和Service</strong>的使用方式,但很多问题也随之而来:</p>
<ul>
<li>为什么四大组件的另外两个<strong>BroadcastReceiver</strong>和<strong>ContentProvider</strong>不是<strong>Context</strong>的子类呢?</li>
<li>为什么<strong>Application、Activity和Service</strong>不直接继承<strong>ContextImpl</strong>呢,不是更直接吗?所谓的Decorator模式,也没看到有多大实际用处啊?</li>
</ul>
<p>看一下ContentProvider的构造函数和<strong>BroadcastReceiver.onReceive()</strong>函数:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ContentProvider</span>
<span class="kd">public</span> <span class="nf">ContentProvider</span><span class="o">(</span>
<span class="n">Context</span> <span class="n">context</span><span class="o">,</span>
<span class="n">String</span> <span class="n">readPermission</span><span class="o">,</span>
<span class="n">String</span> <span class="n">writePermission</span><span class="o">,</span>
<span class="n">PathPermission</span><span class="o">[]</span> <span class="n">pathPermissions</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="n">mReadPermission</span> <span class="o">=</span> <span class="n">readPermission</span><span class="o">;</span>
<span class="n">mWritePermission</span> <span class="o">=</span> <span class="n">writePermission</span><span class="o">;</span>
<span class="n">mPathPermissions</span> <span class="o">=</span> <span class="n">pathPermissions</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// BroadcastReceiver</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">void</span> <span class="nf">onReceive</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="o">);</span>
</code></pre></div></div>
<p>ContentProvider和BroadcastReceiver都需要把Context作为参数传入,虽然它们不继承于Context,但它们都依赖于Context,换个角度看:它们就是修饰器,包装了Context。因为这两个组件在使用上与Activity和Service存在较大的区别,所以它们的实现方式存在较大差异。</p>
<p>往深一步理解,Decorator模式的优势也体现出来了,譬如Application、Activity和Service都可以作为BroadcastReceiver的载体,只需要通过它们各自的Context去注册广播接收器就可以了,将BroadcastReceiver修饰在它们之上,就形成了新的功能扩展,而不是去扩展一个可以接收广播的Applicaiton、Activity或Service类。</p>
<blockquote>
<p><strong>题外话</strong>,Decorator模式在Android中随处可见,除了Context类簇,还有Window类簇。</p>
</blockquote>
<font color="red"><b>
至此,我们已经领会了Context的设计思想,Context无处不在,它是应用进程与系统对话的一个接口:从使用的角度,更是可以将Context理解为应用进程的Android运行环境,想要什么资源,都可以向Context索取;从实现的角度,Context类簇利用Decorator设计模式,Android最核心的四大组件都可以理解为“修饰器”,它们从不同的功能维度扩展了Context的功能。
</b></font>
<h1 id="2-context的源码分析">2. Context的源码分析</h1>
<p><strong>Context</strong>本身作为一个最高层的抽象类,仅仅是定义接口,方法的实现都在<strong>ContextImpl</strong>中。因为<strong>Context</strong>是为应用程序设计的,笔者试图通过两条主线来渗透<strong>Context</strong>的各项知识点:</p>
<ul>
<li><strong>第一条主线</strong>:应用程序Application的Context构建过程</li>
<li><strong>第二条主线</strong>:应用界面Activity的Context构建过程</li>
</ul>
<h2 id="21-application的context的构建过程">2.1 Application的Context的构建过程</h2>
<p>在<a href="/2016-01-29-Activity-IPC">应用进程与系统进程之间的通信</a>一文中,介绍过应用进程启动时,需要和系统进程进行通信:</p>
<ul>
<li>
<p>当应用进程在初始化自己的主线程ActivityThread时,便会发起跨进程调用<strong>IActivityManager.attachApplication()</strong>,告诉系统进程(SystemServer):我已经在Linux的世界诞生了,现在需要增加Android的属性(应用的包信息、四大组件信息、Android进程名等),才能成为一个真正的Android进程。</p>
</li>
<li>
<p>系统进程在进行包解析时,就获取了所有应用程序的静态信息。在AMS中执行一个应用进程的<strong>attachApplication()</strong>时,便会将这些信息的数据结构准备好,发起跨进程调用<strong>IApplicationThread.bindApplication()</strong>,传送应用进程启动所必需的信息。</p>
</li>
</ul>
<p>经过以上的交互,应用进程就进入<strong>ActivityThread.handleBindApplication()</strong>,开始构建自己所需的Android环境了:</p>
<div align="center"><img src="/assets/images/context/3-context-application-context-sequence.png" /></div>
<p>从时序图的第一个函数开始分析:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ActivityThread.handleBindApplication()</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleBindApplication</span><span class="o">(</span><span class="n">AppBindData</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">Application</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">makeApplication</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="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="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>在<strong>ActivityThread.handleBindApplication()</strong>完成大量变量的初始化后,便开始创建一个Application类型的对象了,有了这个对象后便开始调用其<strong>onCreate()</strong>方法,就进入到了大家非常熟悉的一个系统回调函数<strong>Application.onCreate()</strong>。该函数片段的关键点是调用<strong>LoadedApk.makeApplication()</strong>创建Application对象,<strong>data.info</strong>是之前调用<strong>ActivityThread.getPackageInfoNoCheck()</strong>生成的LoadedApk对象,表示一个已经加载解析过的APK文件。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// LoadedApk.makeApplication()</span>
<span class="kd">public</span> <span class="n">Application</span> <span class="nf">makeApplication</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">forceDefaultAppClass</span><span class="o">,</span> <span class="n">Instrumentation</span> <span class="n">instrumentation</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">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="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">getClassLoader</span><span class="o">();</span>
<span class="n">ContextImpl</span> <span class="n">appContext</span> <span class="o">=</span> <span class="n">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="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="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数片段中,创建了一个<strong>ClassLoader</strong>对象和<strong>ContextImpl</strong>对象,连同将要构建的<strong>Application</strong>类名appClass,一起作为参数传送给<strong>Instrumentation.newApplication()</strong>方法,可想而知,最终的<strong>Application</strong>对象是反射构建的。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ContextImpl.createAppContext()</span>
<span class="kd">static</span> <span class="n">ContextImpl</span> <span class="nf">createAppContext</span><span class="o">(</span><span class="n">ActivityThread</span> <span class="n">mainThread</span><span class="o">,</span> <span class="n">LoadedApk</span> <span class="n">packageInfo</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">packageInfo</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalArgumentException</span><span class="o">(</span><span class="s">"packageInfo"</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ContextImpl</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="n">mainThread</span><span class="o">,</span>
<span class="n">packageInfo</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="mi">0</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">Display</span><span class="o">.</span><span class="na">INVALID_DISPLAY</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// ContextImpl.constructor()</span>
<span class="kd">private</span> <span class="nf">ContextImpl</span><span class="o">(</span><span class="n">ContextImpl</span> <span class="n">container</span><span class="o">,</span> <span class="n">ActivityThread</span> <span class="n">mainThread</span><span class="o">,</span>
<span class="n">LoadedApk</span> <span class="n">packageInfo</span><span class="o">,</span> <span class="n">IBinder</span> <span class="n">activityToken</span><span class="o">,</span> <span class="n">UserHandle</span> <span class="n">user</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">,</span>
<span class="n">Display</span> <span class="n">display</span><span class="o">,</span> <span class="n">Configuration</span> <span class="n">overrideConfiguration</span><span class="o">,</span> <span class="kt">int</span> <span class="n">createDisplayWithId</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mOuterContext</span> <span class="o">=</span> <span class="k">this</span><span class="o">;</span> <span class="c1">// 外围包装器,暂时用</span>
<span class="o">...</span>
<span class="n">mMainThread</span> <span class="o">=</span> <span class="n">mainThread</span><span class="o">;</span> <span class="c1">// 主线程</span>
<span class="n">mActivityToken</span> <span class="o">=</span> <span class="n">activityToken</span><span class="o">;</span> <span class="c1">// 关联到系统进程的ActivityRecord</span>
<span class="n">mFlags</span> <span class="o">=</span> <span class="n">flags</span><span class="o">;</span>
<span class="o">...</span>
<span class="n">mPackageInfo</span> <span class="o">=</span> <span class="n">packageInfo</span><span class="o">;</span> <span class="c1">// LoadedApk对象</span>
<span class="n">mResourcesManager</span> <span class="o">=</span> <span class="n">ResourcesManager</span><span class="o">.</span><span class="na">getInstance</span><span class="o">();</span>
<span class="o">...</span>
<span class="n">Resources</span> <span class="n">resources</span> <span class="o">=</span> <span class="n">packageInfo</span><span class="o">.</span><span class="na">getResources</span><span class="o">(</span><span class="n">mainThread</span><span class="o">);</span>
<span class="o">...</span>
<span class="n">mResources</span> <span class="o">=</span> <span class="n">resources</span><span class="o">;</span> <span class="c1">// 通过各种计算得到的资源</span>
<span class="o">...</span>
<span class="n">mContentResolver</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ApplicationContentResolver</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">mainThread</span><span class="o">,</span> <span class="n">user</span><span class="o">);</span> <span class="c1">// 访问ContentProvider的接口</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>ContextImpl</strong>有三种不同的类型:</p>
<ul>
<li><strong>SystemContext</strong>:系统进程SystemServer的Context</li>
<li><strong>AppContext</strong>:应用进程的Context</li>
<li><strong>ActivityContext</strong>:Activity的Context,只有ActivityContext跟界面显示相关,需要传入activityToken和有效的DisplayId</li>
</ul>
<p>该函数片段是创建一个<strong>AppContext</strong>,要初始化的属性其实不多,需要特别注意的是:Context中会初始化一个<strong>ContentResovler</strong>对象,所以,可以通过Context操作数据库。Context创建完毕后,会作为参数传递给Instrumentation对象去构建一个Application对象:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Instrumentation.newApplication()</span>
<span class="kd">static</span> <span class="kd">public</span> <span class="n">Application</span> <span class="nf">newApplication</span><span class="o">(</span><span class="n">Class</span><span class="o"><?></span> <span class="n">clazz</span><span class="o">,</span> <span class="n">Context</span> <span class="n">context</span><span class="o">)</span>
<span class="kd">throws</span> <span class="n">InstantiationException</span><span class="o">,</span> <span class="n">IllegalAccessException</span><span class="o">,</span> <span class="n">ClassNotFoundException</span> <span class="o">{</span>
<span class="n">Application</span> <span class="n">app</span> <span class="o">=</span> <span class="o">(</span><span class="n">Application</span><span class="o">)</span><span class="n">clazz</span><span class="o">.</span><span class="na">newInstance</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="c1">// Application.attach()</span>
<span class="kd">final</span> <span class="kt">void</span> <span class="nf">attach</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="n">attachBaseContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="n">mLoadedApk</span> <span class="o">=</span> <span class="n">ContextImpl</span><span class="o">.</span><span class="na">getImpl</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">mPackageInfo</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// ContextWrapper.attachBaseContext()</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">attachBaseContext</span><span class="o">(</span><span class="n">Context</span> <span class="n">base</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mBase</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">IllegalStateException</span><span class="o">(</span><span class="s">"Base context already set"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mBase</span> <span class="o">=</span> <span class="n">base</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Application对象是通过反射构建的,如果应用程序没有继承实现Application,则默认使用<code class="highlighter-rouge">android.app.Application</code>这个包名进行反射。构建Application对象完成后,便会调用其<strong>attach()</strong>函数绑定一个Context,这一绑定就相当于在<strong>ContextWrapper</strong>中关联了一个<strong>ContextImpl</strong>,这一层Decorator的修饰包装关系这就么套上了。</p>
<p><strong>回顾一下时序图,ActivityThread中发起Application对象的创建操作,然后创建一个真实的ContextImpl对象(AppContext),最后将AppContext包装进Application对象中,才完成整个的修饰动作,在这之后,Application便可作为一个真正的Context使用,可以回调其生命周期的onCreate()方法了。</strong></p>
<h2 id="22-activity的context构建过程">2.2 Activity的Context构建过程</h2>
<p>在<a href="2016-10-23-Activity-LaunchProcess-Part2">Activity的启动过程</a>一文中,介绍过一个Activity是如何从无到有,再到显示状态的,这个过程极其复杂。本节将聚焦在Activity的Context构建时机,当要启动一个Activity时,<strong>ActivityThread.performLaunchActivity()</strong>会被调用,从这以后便会开始构建Activity对象,时序图如下:</p>
<div align="center"><img src="/assets/images/context/4-context-activity-context-sequence.png" /></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="n">Activity</span> <span class="nf">performLaunchActivity</span><span class="o">(</span><span class="n">ActivityClientRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">customIntent</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">packageInfo</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span>
<span class="c1">// 1. 反射构建Activity对象</span>
<span class="n">activity</span> <span class="o">=</span> <span class="n">mInstrumentation</span><span class="o">.</span><span class="na">newActivity</span><span class="o">(</span>
<span class="n">cl</span><span class="o">,</span> <span class="n">component</span><span class="o">.</span><span class="na">getClassName</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="n">StrictMode</span><span class="o">.</span><span class="na">incrementExpectedActivityCount</span><span class="o">(</span><span class="n">activity</span><span class="o">.</span><span class="na">getClass</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">setExtrasClassLoader</span><span class="o">(</span><span class="n">cl</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">prepareToEnterProcess</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">state</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">state</span><span class="o">.</span><span class="na">setClassLoader</span><span class="o">(</span><span class="n">cl</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 2. 获取已有的Application对象</span>
<span class="n">Application</span> <span class="n">app</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">packageInfo</span><span class="o">.</span><span class="na">makeApplication</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="n">mInstrumentation</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">activity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 3. 创建Activity的Context</span>
<span class="n">Context</span> <span class="n">appContext</span> <span class="o">=</span> <span class="n">createBaseContextForActivity</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">activity</span><span class="o">);</span>
<span class="n">CharSequence</span> <span class="n">title</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">.</span><span class="na">loadLabel</span><span class="o">(</span><span class="n">appContext</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">());</span>
<span class="n">Configuration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Configuration</span><span class="o">(</span><span class="n">mCompatConfiguration</span><span class="o">);</span>
<span class="o">...</span>
<span class="c1">// 4. 将Context包装进Activity</span>
<span class="n">activity</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">appContext</span><span class="o">,</span> <span class="k">this</span><span class="o">,</span> <span class="n">getInstrumentation</span><span class="o">(),</span> <span class="n">r</span><span class="o">.</span><span class="na">token</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">ident</span><span class="o">,</span> <span class="n">app</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="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">,</span> <span class="n">title</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">parent</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">embeddedID</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">lastNonConfigurationInstances</span><span class="o">,</span> <span class="n">config</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">referrer</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">voiceInteractor</span><span class="o">,</span> <span class="n">window</span><span class="o">);</span>
<span class="o">...</span>
<span class="c1">// 5. 调用Activity.onCreate()</span>
<span class="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">isPersistable</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mInstrumentation</span><span class="o">.</span><span class="na">callActivityOnCreate</span><span class="o">(</span><span class="n">activity</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">state</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">persistentState</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="na">callActivityOnCreate</span><span class="o">(</span><span class="n">activity</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">state</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="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数片段完成了从一个Activity对象构建到<strong>Activity.onCreate()</strong>函数被回调的过程:</p>
<ol>
<li>
<p>调用<strong>Instrumentation.newActivity()</strong>函数,传入这里通过ClassLoader和Activity的类名,反射构建一个Activity对象。</p>
</li>
<li>
<p>获取已有的Application。<strong>LoadedApk.makeApplication()</strong>这个函数在前文分析过,当已有创建了一个Application时,会直接返回。</p>
</li>
<li>
<p>调用<strong>ActivityThread.createBaseContextForActivity()</strong>函数,该函数内部会继续调用<strong>ContextImpl.createActivityContext()</strong>,创建一个ActivityContext。此处不再展开分析这两个函数,ContextImpl对象的初始化过程与上节中一致,请读者自行参考。</p>
</li>
<li>
<p>以上过程都可以理解为在准备参数,真正将Context包装进Activity是调用<strong>Activity.attach()</strong>函数完成的,这个函数我们在分析Activity与Window的关系时,还会重点介绍。此处只需要理解其中一行,就是调用<strong>ContextWrapper.attachBaseContext()</strong>函数,将之前创建的ContextImpl对象包装到ContextWrapper中:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">attach</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">ActivityThread</span> <span class="n">aThread</span><span class="o">,</span>
<span class="n">Instrumentation</span> <span class="n">instr</span><span class="o">,</span> <span class="n">IBinder</span> <span class="n">token</span><span class="o">,</span> <span class="kt">int</span> <span class="n">ident</span><span class="o">,</span>
<span class="n">Application</span> <span class="n">application</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="o">,</span> <span class="n">ActivityInfo</span> <span class="n">info</span><span class="o">,</span>
<span class="n">CharSequence</span> <span class="n">title</span><span class="o">,</span> <span class="n">Activity</span> <span class="n">parent</span><span class="o">,</span> <span class="n">String</span> <span class="n">id</span><span class="o">,</span>
<span class="n">NonConfigurationInstances</span> <span class="n">lastNonConfigurationInstances</span><span class="o">,</span>
<span class="n">Configuration</span> <span class="n">config</span><span class="o">,</span> <span class="n">String</span> <span class="n">referrer</span><span class="o">,</span> <span class="n">IVoiceInteractor</span> <span class="n">voiceInteractor</span><span class="o">,</span>
<span class="n">Window</span> <span class="n">window</span><span class="o">)</span> <span class="o">{</span>
<span class="n">attachBaseContext</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>Activity的Context构建完成后,便可以回调大家熟悉的<strong>Activity.onCreate()</strong>函数了。</p>
</li>
</ol>
<font color="red"><b>
可见,Activity和Application的Context构建过程极为相似,这也是符合逻辑的,因为它们本质上就是Context,只不过功能维度不同。其实Service的Context构建过程也很相似。无非都是构建ContextImpl对象,构建Decorator对象(Application、Activity和Service),再将Decorator对象包装到ContextImpl对象之上。
</b></font>
<h1 id="3-context的注意事项">3. Context的注意事项</h1>
<h2 id="31-不同类型context的区别">3.1 不同类型Context的区别</h2>
<p>从前文中可以看出,Android中有好几种不同的Context,在一个Activity中,就可以获取到以下几种Context:</p>
<ul>
<li>getApplication()</li>
<li>getApplicationContext()</li>
<li>getBaseContext()</li>
<li>Activity.this</li>
</ul>
<p>它们都什么区别呢?分别在什么时候使用呢?可以在Activity中通过以下代码,将这个Context分别打印出来:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 在Activity.onCreate()中插入以下代码:</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Application: "</span> <span class="o">+</span> <span class="n">getApplication</span><span class="o">());</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"ApplicationContext: "</span> <span class="o">+</span> <span class="n">getApplicationContext</span><span class="o">());</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Activity: "</span> <span class="o">+</span> <span class="k">this</span><span class="o">);</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"ActivityContext:"</span> <span class="o">+</span> <span class="k">this</span><span class="o">);</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Application BaseContext: "</span> <span class="o">+</span> <span class="n">getApplication</span><span class="o">().</span><span class="na">getBaseContext</span><span class="o">());</span>
<span class="n">Log</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Activity BaseContext: "</span> <span class="o">+</span> <span class="n">getBaseContext</span><span class="o">());</span>
<span class="c1">// 得到的运行结果:</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="nl">Application:</span> <span class="n">com</span><span class="o">.</span><span class="na">duanqz</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">DemoApp</span><span class="nd">@cf8644e</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="nl">ApplicationContext:</span> <span class="n">com</span><span class="o">.</span><span class="na">duanqz</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">DemoApp</span><span class="nd">@cf8644e</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="nl">Activity:</span> <span class="n">com</span><span class="o">.</span><span class="na">duanqz</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">MainActivity</span><span class="nd">@bbcadec</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="n">Activity</span> <span class="nl">Context:</span> <span class="n">com</span><span class="o">.</span><span class="na">duanqz</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">MainActivity</span><span class="nd">@bbcadec</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="n">Application</span> <span class="nl">BaseContext:</span> <span class="n">android</span><span class="o">.</span><span class="na">app</span><span class="o">.</span><span class="na">ContextImpl</span><span class="err">@</span><span class="mi">6</span><span class="n">a6a96f</span>
<span class="n">I</span> <span class="nl">MainActivity:</span> <span class="n">Activity</span> <span class="nl">BaseContext:</span> <span class="n">android</span><span class="o">.</span><span class="na">app</span><span class="o">.</span><span class="na">ContextImpl</span><span class="err">@</span><span class="mi">770267</span>
</code></pre></div></div>
<p>可以看到,有以下几点不同:</p>
<ul>
<li>
<p>getApplication()和getApplicationContext()返回的是同一个对象<code class="highlighter-rouge">com.duanqz.github.DemoApp@cf8644e</code>,虽然同一块内存区域,但对象的类型不同:前者是Application,后者是Context。Java是强类型的语言,Application到Context相当于向上转型,会丢失掉一些接口的访问入口。</p>
</li>
<li>
<p>同理,Activity和Activity Context也是同一个对象,不同的类型。</p>
</li>
<li>
<p>Application和Activity的Base Context都是ContextImpl对象,正是这个Context真正的实现类,被外围的修饰器包装了一下,才形成不同功能的类。</p>
</li>
</ul>
<h2 id="32-context导致的内存泄露问题">3.2 Context导致的内存泄露问题</h2>
<p>Context经常会被作为参数传递,很容易导致内存泄露。以下代码片段是一个很常见的Activity泄露问题:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">AppCompatActivity</span> <span class="o">{</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Other codes</span>
<span class="n">Singleton</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Singleton</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">Singleton</span> <span class="n">sMe</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">Singleton</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Do something with context</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">synchronized</span> <span class="n">Singleton</span> <span class="nf">get</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sMe</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">sMe</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Singleton</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">sMe</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>本例中,有一个需要Context才能初始化的单例,在Activity中使用了这个单例,并且传入的Context是Activity对象。这种使用方式很可能导致Activity的泄露,因为MainActivity对象在应用进程的生命周期中可能会存在多个(譬如:多次进入/退出MainActivity界面、横竖屏切换都可能导致Activity对象的创建和销毁),但单例却是存在于整个应用进程的生命周期的,Activity作为Context传送给单例,会导致Activity销毁后,其对象不能被垃圾回收,这样一来Activity对象就泄露了。</p>
<blockquote>
<p>往深一点说:单例的实现都包含一个静态变量,而在Java的垃圾回收机制中,静态变量是GC ROOT,某对象只要存在到达GC ROOT的路径,就不会被回收。其实,所谓的内存泄露,都是生命周期短的对象没有被正确的回收,之所以没有被回收,是因为它们处在到GC ROOT的路径上,像静态变量、类加载器等都是GC ROOT,在使用过程如果关联到了生命周期短的对象,而且没有及时解除关联,就会产生内存泄露。</p>
</blockquote>
<p>写过Android代码的朋友都知道,在单例中使用Context是一种刚需,那怎样才能解决内存泄露的问题呢?其实,只要传入一个生命周期长的Context就可以,自然就想到了与应用程序生命周期一致的ApplicationContext:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="n">AppCompatActivity</span> <span class="o">{</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// Other codes</span>
<span class="n">Singleton</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">getApplicationContext</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>如此一来,就将单例与Application绑定,解决了Activity泄露的问题。这个案例提醒大家ApplicationContext和ActivityContext的使用场景是不同的:</p>
<ul>
<li>使用ApplicationContext的场景:Context的生命周期超出Activity或Service的生命周期时,譬如工具类</li>
<li>使用ActivityContext的场景:Context的生命周期小于Activity,譬如初始化Activity的子控件、弹出对话框</li>
</ul>
<h1 id="4-总结">4. 总结</h1>
<p>本文从设计、源码和使用三个方面剖析了Android Context这一重要的概念,它是应用程序访问Android资源的接口,它是应用进程的运行环境,它是四大组件的基础,它是开发者既熟悉的陌生人。Context采用Decorator模式这一顶层设计,其对象构建/销毁都和四大组件紧密相关,稍有使用不当,便会导致内存泄露。</p>
<p>相信各位开发者在读完本文后,会对Context有一个更加深刻的认识。</p>
Android数字签名机制和应用场景
2017-09-01T00:00:00+00:00
https://duanqz.github.io/Android-Digital-Signature
<p>本文以Android 7.1的代码为蓝本,部分源码在不同Android版本上略有区别。</p>
<h1 id="1-数字签名介绍">1 数字签名介绍</h1>
<p>本节的内容概要如下:</p>
<ul>
<li>
<p>首先,介绍数字签名的产生背景,有什么用途以及怎么用,涉及到经常被拿出来比对的概念:<strong>数字签名(Digital Signature)</strong>和<strong>电子签名(Electronic Signature)</strong>。</p>
</li>
<li>
<p>然后,介绍数字签名的运行机制,数据加密和认证的过程,涉及到的重要概念:<strong>密钥对(Key Pair),即私钥(Private Key)和公钥(Public Key)</strong>。</p>
</li>
<li>
<p>最后,通过介绍两个与数字签名相关的工具<strong>ssh-keygen</strong>和<strong>keytool</strong>,一个密钥管理文件<strong>keystore</strong>,引申出几个重要的概念:<strong>证书(Certificate)</strong>、<strong>证书链(Certificate Chain)</strong>、<strong>证书认证机构(Certificate Authority)</strong>。</p>
</li>
</ul>
<h2 id="11-背景">1.1 背景</h2>
<p>Digital Signature直译成中文就是数字签名,是Whitfield Diffie和Martin Hellman早在1976年就提出的概念,以他们名字命名的Diffie-Hellman Key Exchange算法可以用于在公开网络上传送密钥。
随后,由Ron <strong>R</strong>ivest, Adi <strong>S</strong>hamir和Leonard <strong>A</strong>dleman三人发明的RSA算法,被广泛运用到数字签名中。首个投放到市场的数字签名应用在1989年发布,采用的算法就是RSA。</p>
<p><strong>签名有什么用呢?</strong></p>
<p>生活中,大家一定有签名表身份的经历,譬如:在刷银行卡消费时,需要签上自己的大名,收银员有时还会对比一下你签的名与银行卡上签名栏是否匹配;在签署合同时,往往需要潇洒的挥上自己的大名,甚至手抹红印,按下指纹。这些都是为了确定行为人的身份,作为后续问题追溯的依据。数字签名的<strong>签名</strong>两字正是源于生活,意在确定身份,而<strong>数字</strong>两字表明其是签名的一种类型:基于密码学的签名方式。被数字签名广泛采用的RSA算法就是一种加密算法。</p>
<p><strong>数字签名怎么用呢?</strong></p>
<p>在网络发送信息时,存在安全问题,因为在发送者和接收者之间可能存在第三者,截获发送者信息,进行篡改后,再发送给接收者。为了解决这类安全问题,发送者可以对数据进行数字签名,当接收者拿到一堆签名后的数据时,可以对数据进行校验,一旦校验成功,接收者可以信心满满的说:</p>
<ul>
<li>这些数据一定是某人发送过来的,可以通过其他机构确定发送人的资质。</li>
<li>发送者不可抵赖这是自己发送的,一旦接收者一口咬定数据是来源于某位发送者,那发送者只能心服首肯。</li>
<li>这些数据一定没有被篡改,否则不会通过校验。</li>
</ul>
<p>这其实就是数字签名的三大特性:<strong>可认证性(authentication)、不可抵赖性(non-repudiation)和完整性(integrity)</strong>。即便收到被第三者篡改的数据,接收者也可以确定数据不是来源于受信的发送者,而且数据肯定被动过。</p>
<blockquote>
<p>自古以来,手写签名、按压指纹这种传统的签名方式都是具有法律效应的。在电子信息如此发达的今天,<strong>电子签名(Electronic Signature)</strong>也是被很多国家和地区(欧盟、美国)也被立法保护。读者在接触数字签名的概念时,往往会关联到电子签名的概念,前者属于技术实现的范畴,后者属于人文律法的范畴。</p>
<p>人们认可电子签名在法律上的有效性,而数字签名采用一定的加密算法,来保障电子签名的这种有效性。诚然,加密算法总有过时的一天,譬如广泛应用的低位数的RSA算法就已经被破解了,这时候,电子签名的有效性就消失了,需要进一步更新数字签名的算法,继续维持电子签名的法律有效性。</p>
<p>这里,还需要说明一点,并非所有的电子签名实现都是采用数字签名。</p>
</blockquote>
<h2 id="12-机制">1.2 机制</h2>
<p><strong>数字签名是如何保证可认证、不可抵赖和完整性的呢?</strong>这得从数字签名的算法说起。数字签名的实现包含三个算法:</p>
<ol>
<li>
<p><strong>密钥生成算法</strong>。数字签名采用的是非对称密钥生成算法,即会生成一对密钥:私钥和公钥。用私钥加密的数据,只能用对应的公钥解密。私钥是应该要保护起来,不会泄露给其他人;公钥是完全公开的,会随着加密数据一起在公开网络上传送。</p>
</li>
<li>
<p><strong>签名算法</strong>。给定私钥(private key)和数据(message),可以生成一个签名(signature)。</p>
</li>
<li>
<p><strong>签名验证算法</strong>。给定数据(message)、公钥(public key)和签名(signature),可以解密数据并验证数据的来源和完整性。</p>
</li>
</ol>
<blockquote>
<p>密钥有对称(symmetric)和非对称(public)之分。对称密钥只有一个,同时用于加密和解密,就像我们用同一把钥匙开门和锁门,谁拿到钥匙,谁就掌握了房间的使用权。DES和AES这种加密算法采用的是对称密钥,而上文提到的RSA算法采用的是非对称密钥。</p>
</blockquote>
<p>下图示意了数字签名的运行机制:</p>
<div align="center"><img src="/assets/images/digitalsignature/1-digital-signature-mechanisum.png" alt="数字签名机制" /></div>
<ul>
<li>
<p>对待发送的数据明文进行Hash,通常可采用MD5或SHA算法,然后采用私钥对Hash值进行加密,得到签名。将数据明文和签名一同发送出去。为什么要先对原始数据进行Hash后再用私钥加密呢?因为原数据可能比较大,直接使用私钥加密将会非常耗时。</p>
</li>
<li>
<p>接收数据以后,会经过签名验证,其实就是比较两个Hash值:采用同样的Hash算法对数据明文进行解密,得到一个Hash值;采用公钥对签名进行解密后,得到原始的Hash值。如果两个Hash值相同,则说明数据没有被篡改而且来源可信:</p>
<ul>
<li>
<p>如果用公钥解密成功,则说明公钥与签名的私钥是唯一配对的,即一定是某个私钥的签名,这就保障了来源不可抵赖。</p>
</li>
<li>
<p>如果数据被篡改,则接收到数据的Hash值与解密后的Hash值不会相同,这就保障了完整性。</p>
</li>
<li>
<p>接收到的公钥是可以由第三方权威机构(Certificate Authority)认证的,因此接收方可以验证来源是否可靠,这就可以保障来源的可认证性。关于CA认证我们后文再展开。</p>
</li>
</ul>
</li>
</ul>
<p>说了这么多,其实<strong>数字签名最神奇的地方就在于密钥对:用私钥加密后的数据只能用对应的公钥解密。</strong> RSA算法的理论根基是欧拉定理,可以参见<a href="http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html">RSA算法原理介绍</a>。</p>
<h2 id="13-使用">1.3 使用</h2>
<p>了解数字签名的基本概念之后,就需要进一步了解一些数字签名的使用工具和衍生概念。</p>
<h3 id="131-ssh-keygen">1.3.1 ssh-keygen</h3>
<p>在使用SSH(Secure SHell)进行远程登录或其他网络请求时(譬如下载git库),会接触到<strong>ssh-keygen</strong>。相比Telent, FTP这些协议,SSH是安全的,因为可以使用ssh-keygen生成一对密钥,对通信过程进行加密,防止第三方攻击。</p>
<p>举一个最常见的例子,github上的开源项目均支持通过SSH协议进行下载,这就需要在github上添加一个<strong>SSH Key</strong>,否则无法正常下载github上的项目。</p>
<p>首先,会先在本地使用<strong>ssh-keygen</strong>命令生成一个密钥对:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> ssh-keygen
<span class="go">Generating public/private rsa key pair.
Enter file in which to save the key (~/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
ac:70:ea:54:20:75:ce:8e:ec:ed:36:8f:b2:4a:3d:26 xo@X
The key's randomart image is:
+--[ RSA 2048]----+
| . . |
| . + |
| . . o |
| o + . |
| + + S |
| o * . |
| E B o |
| . =.oo. |
| ..o+oo. |
+-----------------+
</span></code></pre></div></div>
<p>该命令执行完后,会在用户目录下的<strong>.ssh</strong>子目录生成两个文件:<code class="highlighter-rouge">id_rsa</code>和<code class="highlighter-rouge">id_rsa.pub</code>。</p>
<p>然后,便是将id_rsa.pub的文件内容粘贴到github的<strong>SSH Key</strong>中。完成配置后便可通过SSH下载远程代码,知其然却不知其所以然。在理解了数字签名的基本概念后,再来看这个过程:</p>
<ul>
<li>
<p><strong>ssh-keygen</strong>默认采用RSA算法生成了私钥(id_rsa)和公钥(id_rsa.pub)。上文中采用的是RSA 2048位的算法,因为256,512位的RSA算法已经被破解了。这里需要提出的是,除了RSA,还有其他密钥对的生成算法,譬如DSA(Digital Signature Algorithm)、EdDSA(Edwards-curve Digital Signature Algorithm)等,可以通过<strong>ssh-keygen</strong>的<em>-t</em>参数指定密钥对的生成算法。</p>
<blockquote>
<p>使用<strong>ssh-keygen</strong>生成密钥对时,会提示输入一个口令(passphrase),用来保护私钥。口令采用的对称密钥加密算法,简单来说,口令就像银行卡密码一样,需要记在脑子里。</p>
<p>公钥通常比较长,上文中RSA算法生成的公钥是2048位,为了便于描述和比对,采用MD5算法将公钥映射成128位的Hash值,通常用16进制表示,这一串Hash值就叫做<strong>指纹(Fingerprint)</strong>。</p>
</blockquote>
</li>
<li>
<p>将公钥粘贴到github的SSH Key配置项中,能够免去不断输入用户名和密码的烦恼;同时,这说明公钥是完全公开的,但私钥(id_rsa)仍然是保存在本地的。当github收到从客户端发起请求时,会利用客户端的公钥对一串随机数进行加密操作,然后将密文发送给客户端;客户端在收到密文后,会用配对的私钥进行解密,如果解密成功,便可建立一个受信的连接,不用再通过用户名和密码鉴权。</p>
</li>
</ul>
<p>除了github,还有很多代码管理工具都支持SSH,譬如gitlab,oschina,gerrit等,其操作过程都是一致的。因为SSH利用数字签名,建立了安全的连接。</p>
<h3 id="132-keytool">1.3.2 keytool</h3>
<p><strong>keytool</strong>是JDK的一个工具,用于密钥和证书的管理。<strong>keytool</strong>的主要操作对象是<strong>keystore文件</strong>,该文件一般以<em>.keystore或</em>.jks(Java KeyStore)为后缀名,用于密钥和证书的存储,其存储结构如下图所示:</p>
<div align="center"><img src="/assets/images/digitalsignature/2-digital-signature-keystore.png" alt="keystore的存储结构" /></div>
<p><strong>keystore</strong>可以存储多个密钥对(Key Pair),每一个密钥对包含私钥(Private Key)和多个证书(Certificate)。想必诸位读者一定心生怪异了,上文说的密钥对都是一个私钥和一个公钥配对,然而<strong>keystore</strong>中存储的却是私钥和多个证书的配对,到底密钥对是怎么一样对应关系呢?这里有必要把证书和公钥的关系说明清楚了。</p>
<p><strong>证书(Certificate)</strong></p>
<p>如其名,用来证明真伪,就像一家企业的营业执照、大学生的毕业证一样,可用来证明其<strong>标的物</strong>的合法性,数字签名中的证书就是用于证明<strong>公钥</strong>的合法性。前文中提过,数字签名算法具备可认证性,然而公钥是完全公开的,很容易被伪造,这样一来,给定一个公钥是无法认证其合法性的,因此,需要给公钥“颁发”一个<strong>证书</strong>,其实现手段就是给公钥再签名一次,得到的结果就是<strong>证书</strong>。上图中,展示了一个完整的证书所包含的属性:Version、Subject、Issuer、Valid From、Valid Until、Public Key(待签名的公钥)、 Signature Algorithm(对公钥签名采用的数字签名算法)、Fingerprint(对公钥签名的公钥)。</p>
<p><strong>至此,我们知道证书和公钥的关系了:证书就是对公钥再次签名产生的结果,证书包含被签名的公钥、公钥拥有者的信息以及对公钥进行签名的公钥。</strong>
接下来,还有两个重要的概念:<strong>证书认证机构CA(Certificate Authority)</strong>和<strong>证书链(Certificate Chain)</strong>。这两个概念在数字签名体系中经常出现,但并不容易理解。</p>
<p><strong>证书认证机构CA(Certificate Authority)</strong>和<strong>证书链(Certificate Chain)</strong></p>
<p>上述证书的生成过程会导致一个无限的链条:要得到证书,就需要给公钥进行数字签名,这就有两个公钥了:证书本身的公钥(即证书中的Public Key,用公钥A表示)和数字签名算法的公钥(即证书中的Fingerprint,用公钥B表示),要保证公钥B的合法性,我们可以对公钥B再进行数字签名,就又会引入一个新的公钥C,由此类推,还会有公钥D、E、F…,公钥合法性的保证就像链条一样:层层相扣,无穷无尽。怎么办呢?要打破这个无限的链条,就需要有一个权威机构,来统一颁发证书,这个机构就是<strong>CA(Certificate Authority)</strong>,只要是<strong>CA</strong>颁发的证书,就不需要再签名了,就是权威的。然而,全世界需要的证书太多了,光靠一个<strong>CA</strong>是忙不过来的,<strong>CA</strong>得授权一些代理,这些代理也可以颁发证书,代理颁发的证书也被认为是合法可信的,这就形成了一个证书认证的链条,即<strong>证书链</strong>。我们再回过来头来看一下<strong>keystore</strong>文件的结构:一个私钥(Private Key)与多个证书(Certificates)组成密钥对(Key Pair),其实多个证书的结构就是<strong>证书链</strong>。</p>
<blockquote>
<p>要理解<strong>CA</strong>和<strong>证书链</strong>,还是可以举毕业证的例子,我们的政府就可以看做一个<strong>CA</strong>,政府授权给教育部,教育部具备颁发和认证毕业证的职能;教育部再授权给各个高校,高校具备颁发自己学校毕业证的职能,教育部和高校就是不同级别的代理,授权过程就像<strong>链条</strong>一样。当我们要认证一个毕业证的真伪时,先拿到所属高校认证一下,如果高校无法确定合法,就拿到教育部认证一下,这种对毕业证的颁发和逐级认证的过程,就可以理解为<strong>证书链</strong>。</p>
</blockquote>
<p><strong>有了这么多概念储备后,就可以轻松理解keytool这个工具的使用了。如果将keystore看做密钥和证书管理的数据库,那么keytool就是这个数据库增、删、改、查的接口。</strong> 笔者在网上搜索keytool的使用,基本都是罗列keytool命令的各中参数,真心记不住啊!好在Eclipse/Android Studio这些IDE都集成了keysotre的图形化工具,为开发人员带来了在签名的便利。</p>
<p>当然,除了Eclipse/Android Studio里面的keystore操作工具,还有很多其他好用的工具,笔者通过<a href="http://keystore-explorer.org/">KeyStore Explorer</a>工具打开Android的debug.keystore文件(位于~/.android/,存储了Android应用的默认签名),可以看到如下信息:</p>
<div align="center"><img src="/assets/images/digitalsignature/3-digital-signature-keystore-content.png" alt="keystore的存储内容" /></div>
<p>通过<strong>keytool</strong>命令可以显示出相同的内容:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">duanqizhi@xo:~$</span> keytool <span class="nt">-list</span> <span class="nt">-v</span> <span class="nt">-keystore</span> ~/.android/debug.keystore
<span class="gp">Enter keystore password: #</span> 此处需要输入keystore的口令: android
<span class="go">
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: androiddebugkey
Creation date: Jul 1, 2015
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 588a6601
Valid from: Wed Aug 18 08:03:38 CST 2017 until: Fri Aug 11 08:03:38 CST 2047
Certificate fingerprints:
MD5: DD:89:BB:7A:E2:49:66:E0:B5:2F:9C:CF:DB:3B:AB:26
SHA1: 7B:DE:EF:7E:4A:25:8E:F8:08:0B:8D:28:32:0A:0A:3C:C6:50:D5:CB
SHA256:
27:E0:B8:9C:6B:4F:C6:88:25:7E:34:6E:93:7D:6C:F2:58:AC:64:02:2A:44:BB:17:23:19:E8:F4:8C:17:E6:30
Signature algorithm name: SHA256withRSA
Version: 3
Extensions:
</span><span class="gp">#</span>1: ObjectId: 2.5.29.14 <span class="nv">Criticality</span><span class="o">=</span><span class="nb">false</span>
<span class="go">SubjectKeyIdentifier [
KeyIdentifier [
0000: 34 D2 8F 7A D4 AE FB 04 A8 0D 0E 7A D6 8C 3C 00 4..z.......z..<.
0010: 9B 71 5A 61 .qZa
]
]
*******************************************
*******************************************
</span></code></pre></div></div>
<p>可以为<strong>keystore</strong>文件设置一个口令,增强访问的安全限制。Android的debug.keystore口令就是<code class="highlighter-rouge">android</code>,我们通过Eclipse/Android Studio开发应用时,默认就会使用debug.keystore进行签名。该文件描述了以下信息:</p>
<ul>
<li>
<p>keystore的文件格式为JSK(Java Key Store),技术提供商为SUN公司。</p>
</li>
<li>
<p>keystore存了一个记录:名为androiddebugkey的密钥对,其中有一个私钥和一条证书链,证书链中只有一个证书。</p>
</li>
<li>
<p>证书的持有者和发布者都是<code class="highlighter-rouge">CN=Android Debug, O=Android, C=US</code>,Common Name是Android Debug,Organization Name是Android,Country是US。</p>
</li>
<li>
<p>证书的有效期2017-8-18到2047-8-11;分别用MD5,SHA1,SHA256三种不同的Hash算法计算出了证书的指纹。</p>
</li>
<li>
<p>证书中的公钥是采用RSA 1024算法生成的,格式是<strong>X.509</strong>,上述所定义的各种属性字段,就是<strong>X.509</strong>的标准。</p>
</li>
</ul>
<p>本节不再罗列<strong>keytool</strong>命令的其他参数,后文中还会使用keytool查看APK的签名信息。其实,最重要的还是理解几个关键概念:<strong>证书</strong>、<strong>证书链</strong>、<strong>证书认证机构</strong>,在当下的安全体系中,这几个概念出现的相当多,最有名的当属<strong>HTTPS</strong>。</p>
<h1 id="2-android使用数字签名的场景">2 Android使用数字签名的场景</h1>
<p>笔者把Android中数字签名的应用场景分为应用和系统两个层面,先抛出具体的场景,后文中再通过源码分析这些场景的实现方式。</p>
<ul>
<li>
<p>应用层面:Android对APK的签名要求</p>
<ul>
<li>Android拒绝安装没有签名的APK</li>
<li>Android并不校验证书的合法性</li>
<li>当签名不匹配时,APK升级会失败</li>
</ul>
</li>
<li>
<p>系统层面:Android基于数字签名的一些机制</p>
<ul>
<li>编译Android系统时,会根据不同应用的类型进行签名</li>
<li>Android基于数字签名来判定是否给应用授权</li>
<li>Android基于数字签名来标记APK的SELinux Lable</li>
</ul>
</li>
</ul>
<h2 id="21-未签名的apk-vs-签名后的apk">2.1 未签名的APK VS 签名后的APK</h2>
<p>Android中的数字签名主要是针对APK的,因此有必要先介绍APK签名前后的差异。使用JDK的工具<code class="highlighter-rouge">jarsigner</code>便可对APK进行签名。</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">duanqizhi@xo:~$</span> jarsigner <span class="nt">-verbose</span> <span class="se">\</span>
<span class="nt">-keystore</span> ~/.android/debug.keystore <span class="se">\</span>
<span class="nt">-signedjar</span> app-signed.apk app-unsigned.apk <span class="se">\</span>
androiddebugkey
<span class="gp">Enter Passphrase for keystore: #</span>此处输入密钥android
<span class="go"> updating: META-INF/MANIFEST.MF
adding: META-INF/ANDROIDD.SF
adding: META-INF/ANDROIDD.RSA
signing: AndroidManifest.xml
signing: classes.dex
signing: res/anim-v21/design_bottom_sheet_slide_in.xml
signing: res/anim-v21/design_bottom_sheet_slide_out.xml
signing: res/anim/abc_fade_in.xml
</span><span class="gp"> ... #</span>此处省略很多对其他文件的signing日志
<span class="go"> signing: resources.arsc
jar signed.
</span></code></pre></div></div>
<p>该命令使用了前文介绍的Android Studio默认的<strong>keystore</strong>文件,其中存放了一对别名为<strong>androiddebugkey</strong>的密钥对。加上<code class="highlighter-rouge">-verbose</code>参数可以打印出具体对哪些文件进行过签名。</p>
<p>APK文件其实就是压缩包,可以通过zip或tar等工具直接解压打开,对比签名前后的APK内容如下:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app-unsigned app-signed
├── AndroidManifest.xml ├── AndroidManifest.xml
├── classes.dex ├── classes.dex
├── META-INF ├── META-INF
│ │ │ ├── ANDROIDD.RSA
│ │ │ ├── ANDROIDD.SF
│ └── MANIFEST.MF │ └── MANIFEST.MF
├── res ├── res
└── resources.arsc └── resources.arsc
</code></pre></div></div>
<p>由此可见,APK签名前后,只有META-INF目录发生了变化,签名后会更新<strong>MANIFEST.MF</strong>文件,会增加<strong>ANDROIDD.SF</strong>和<strong>ANDROIDD.RSA</strong>这两个文件,这与使用<code class="highlighter-rouge">jarsigner</code>签名时的日志输出是一致的。那么,<strong>META-INF/</strong>目录下,变化的这三个文件到底是什么呢?</p>
<p><strong>MANIFEST.MF</strong>和<strong>ANDROIDD.SF</strong>都是文本文件,直接打开可以看到其文件内容,通过<code class="highlighter-rouge">bcompare</code>对比工具查看其文件差异,截图片段如下图所示:</p>
<div align="center"><img src="/assets/images/digitalsignature/4-digital-signature-manifest-unsigned-vs-signed.png" alt="清单文件" /></div>
<p><strong>MANIFEST.MF</strong>是APK包含文件的清单列表,记录了APK中每一个文件的SHA256摘要,前文介绍数字签名的时候说过,签名算法很耗时,因此不是对原文件执行加密算法,而是先生成原文件的摘要,再对摘要进行签名。由此,可以推断出<strong>ANDROIDD.MF</strong>文件的内容就是摘要密文。</p>
<p><strong>ANDROIDD.RSA</strong>其实是数字证书,RSA的文件后缀表示证书中包含了一个基于RSA算法生成的公钥。通过前文介绍的<code class="highlighter-rouge">keytool</code>命令,便可打印出该证书的信息,这与前文中keystore中打印的证书信息是一致的:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">duanqizhi@xo:~/app-signed/META-INF$</span> keytool <span class="nt">--printcert</span>
<span class="go">-file ANDROIDD.RSA
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 588a6601
Valid from: Wed Aug 18 08:03:38 CST 2017 until: Fri Aug 11 08:03:38 CST 2047
Certificate fingerprints:
MD5: DD:89:BB:7A:E2:49:66:E0:B5:2F:9C:CF:DB:3B:AB:26
SHA1: 7B:DE:EF:7E:4A:25:8E:F8:08:0B:8D:28:32:0A:0A:3C:C6:50:D5:CB
SHA256:
27:E0:B8:9C:6B:4F:C6:88:25:7E:34:6E:93:7D:6C:F2:58:AC:64:02:2A:44:BB:17:23:19:E8:F4:8C:17:E6:30
Signature algorithm name: SHA256withRSA
Version: 3
Extensions:
</span><span class="gp">#</span>1: ObjectId: 2.5.29.14 <span class="nv">Criticality</span><span class="o">=</span><span class="nb">false</span>
<span class="go">SubjectKeyIdentifier [
KeyIdentifier [
0000: 34 D2 8F 7A D4 AE FB 04 A8 0D 0E 7A D6 8C 3C 00 4..z.......z..<.
0010: 9B 71 5A 61 .qZa
]
]
</span></code></pre></div></div>
<p><strong>至此,APK签名前后的差异已经分析完毕。签名会在APK中的META-INF/目录下添加证书ANDROIDD.RSA和密文ANDROIDD.SF,有了这两个文件,就可以对APK进行校验了。</strong></p>
<h2 id="22-应用层面android对apk的签名要求">2.2 应用层面:Android对APK的签名要求</h2>
<h3 id="221-android拒绝安装没有签名的apk">2.2.1 Android拒绝安装没有签名的APK</h3>
<p>通过命令行<code class="highlighter-rouge">adb install</code>安装一个没有签名的APK时,会提示<strong>INSTALL_PARSE_FAILED_NO_CERTIFICATES</strong>错误:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>duanqizhi@xo:$ adb install app-release-unsigned.apk
5927 KB/s (2357317 bytes in 0.388s)
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Package /data/app/vmdl1995237626.tmp/base.apk has no certificates at entry AndroidManifest.xml]
</code></pre></div></div>
<p>同时,会出现如下日志:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">1034 1271 E PackageInstaller: Commit of session 1995237626 failed: Package /data/app/vmdl1995237626.tmp/base.apk has no certificates at entry AndroidManifest.xml
1034 11776 W System.err: java.lang.SecurityException: Caller has no access to session 1995237626
1034 11776 W System.err: at com.android.server.pm.PackageInstallerService.abandonSession(PackageInstallerService.java:737)
</span><span class="gp">1034 11776 W System.err: at android.content.pm.IPackageInstaller$</span>Stub.onTransact<span class="o">(</span>IPackageInstaller.java:97<span class="o">)</span>
<span class="go">1034 11776 W System.err: at android.os.Binder.execTransact(Binder.java:574)
</span></code></pre></div></div>
<p>要深入理解这些日志,需要对adb安装APK的原理有了解,然后才能分析Android是如何在APK安装路径上植入签名检查的。</p>
<p>通过adb安装APK的时序可以分为三步:<strong>创建安装Session</strong>、<strong>拷贝APK到手机上</strong>和<strong>安装APK</strong>。这里引入了Session(会话)的概念,之所以要用Session,是为了保证安装过程中的数据不会丢失:如果一个APK的安装因为某种原因中断,那么下次系统重启后仍可以继续之前的安装过程。了解Session的用意后,我们接着分析adb安装APK的时序:如下图所示:</p>
<div align="center"><img src="/assets/images/digitalsignature/5-digital-signature-install-apk.png" alt="APK的安装过程" /></div>
<ol>
<li>
<p><strong>创建安装的Session</strong>。这里出现的关键类是<a href="">Pm.java</a>,它是包管理相关的命令解析器。通过命令行输入<code class="highlighter-rouge">adb install</code>或<code class="highlighter-rouge">adb shell package</code>命令就会交由Pm进行解析,对输入的命令解析完后,真正创建Session的操作是通过跨进程调用到<a href="">PackageInstallerService.java</a>中完成的。PackageInstallerService是运行在系统进程的,伴随着PackageManagerService的创建而创建,它的主要职责就是为安装APK服务,提供了很多操作Session的接口,其内部会维护所有安装的Sesssion,并将其Session的信息保存到磁盘文件<strong>/data/system/install_sessions.xml</strong>中,如果有安装有中断,则下次从该文件中读取信息便可恢复Session。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Pm.run()
└── Pm.runInstall()
└── Pm.doCreateSession()
└── IPackageInstaller.createSession()
└── Binder.execTransact()
...
└── PackageInstallerService.createSession()
</code></pre></div> </div>
</li>
<li>
<p><strong>拷贝APK到手机</strong>。Session创建完后,要做的第一件事就是将待安装的APK拷贝到手机上,拷贝到手机上的哪个目录呢?创建Session的时候会根据APK的安装属性来做判定:如果安装到内置存储空间(通常就是Data分区),那会将APK拷贝到<strong>/data/app/vmdl+sessionId+.tmp/</strong>目录下;如果安装到外置存储空间(通常就是SD卡),那么将APK拷贝到外置存储空间<strong>/smdl+sessionId+.tmp/</strong>目录下,此处的sessionId是唯一的。在上文的日志中出现的/data/app/vmdl1995237626.tmp/base.apk其实表示当时安装的sessionId是<strong>1995237626</strong>。</p>
</li>
<li>
<p><strong>安装APK</strong>。前面的步骤创建了一个Session,保存了一些安装信息,并将APK拷贝到手机上,这个状态称为<strong>Staged</strong>,是一个临时状态,在安装APK时可以看到很多与<strong>Stage</strong>相关的关键字。对于一个Session而言,需要进行提交(commit),交给系统完成后续的安装操作。上文的时序图并没有把完整的函数调用逻辑示意出来,其实Session的commit操作路径还是较长的,具体如下:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Pm.run()
└── Pm.runInstall()
└── Pm.doCommitSession()
└── PackageInstaller.commit()
└── IPackageInstallerSession.commit()
└── Binder.execTransact()
...
└── PackageInstallerSession.commit()
└── sendMessage(MSG_COMMIT)
Handler.Callback.handleMessage(MSG_COMMIT)
└── PackageInstallerSession.commitLocked()
└── PackageInstallerSession.validateInstallLocked()
└── PackageParser.parseApkLite()
└── PackageParser.collectCertificates()
└── PackageParser.loadCertificates()
└── PackageManagerService.installStage()
</code></pre></div> </div>
<p>本文不再赘述路径上各函数的功能,只提出一点:要想知道APK中是否包含签名,就需要对读取APK文件的内容,这是由<strong>PackageParser.parseApkLite()</strong>完成的,在读取APK时,就会去找签名,这是通过<strong>PackageParser.loadCertificates()</strong>完成的。如果找不到签名,则会<strong>INSTALL_PARSE_FAILED_NO_CERTIFICATES</strong>异常,中断安装过程;如果找到签名,则会继续调用**PackageManagerService.installStage()完成后续的安装操作,更多安装操作的细节,请参考<a href="/2017-01-04-Package-Manage-Mechanism">Android包管理机制</a>一文。</p>
</li>
</ol>
<blockquote>
<p>我们可以从数据库的事务操作来理解一个session,安装一个APK就可以理解为向系统添加一项APK记录的事务操作。创建一个事务(createSession),表达操作意图(writeSession),然后将事务提交(commitSession)。剩下的事情就交由系统完成了,系统进程会进入APK的安装阶段。</p>
</blockquote>
<p>理解了APK安装过程后,我们再回过头来看一下上文中提示安装错误的日志:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">1034 1271 E PackageInstaller: Commit of session 1995237626 failed: Package /data/app/vmdl1995237626.tmp/base.apk has no certificates at entry AndroidManifest.xml
</span></code></pre></div></div>
<p>对于<strong>1995237626</strong>这个Session而言,Commit的时候出现了错误,这表示Session已经进入到了Commit阶段,APK已经拷贝到手机上<strong>/data/app/vmdl1995237626.tmp/base.apk</strong>(取名为base.apk是与APK的拆分有关,如果没有拆分,那base.apk就是待安装的APK)。出错的原因是找不到AndroidManifest.xml这个文件的证书,其实就是没有APK中没有签名信息啦~</p>
<h3 id="222-android不校验证书的合法性">2.2.2 Android不校验证书的合法性</h3>
<p>Android拒绝安装没有签名的APK,但Android并不会校验APK证书的有效性,即只要签名正确,不管证书是不是合法的,Android都会安装。我们可以使用<code class="highlighter-rouge">jarsigner</code>工具对APK的证书进行校验,会出现如下的警告,但APK依旧可以正常安装。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">duanqizhi</span><span class="nd">@xo</span><span class="o">:~</span><span class="err">$</span> <span class="n">jarsigner</span> <span class="o">-</span><span class="n">verify</span> <span class="n">app</span><span class="o">-</span><span class="n">signed</span><span class="o">.</span><span class="na">apk</span>
<span class="n">jar</span> <span class="n">verified</span><span class="o">.</span>
<span class="nl">Warning:</span>
<span class="n">This</span> <span class="n">jar</span> <span class="n">contains</span> <span class="n">entries</span> <span class="n">whose</span> <span class="n">certificate</span> <span class="n">chain</span> <span class="n">is</span> <span class="n">not</span> <span class="n">validated</span><span class="o">.</span>
<span class="n">This</span> <span class="n">jar</span> <span class="n">contains</span> <span class="n">signatures</span> <span class="n">that</span> <span class="n">does</span> <span class="n">not</span> <span class="n">include</span> <span class="n">a</span> <span class="n">timestamp</span><span class="o">.</span>
<span class="n">Without</span> <span class="n">a</span> <span class="n">timestamp</span><span class="o">,</span> <span class="n">users</span> <span class="n">may</span> <span class="n">not</span> <span class="n">be</span> <span class="n">able</span> <span class="n">to</span> <span class="n">validate</span> <span class="k">this</span> <span class="n">jar</span> <span class="n">after</span> <span class="n">the</span> <span class="n">signer</span> <span class="n">certificate</span><span class="err">'</span><span class="n">s</span> <span class="n">expiration</span> <span class="nf">date</span> <span class="o">(</span><span class="mi">2047</span><span class="o">-</span><span class="mi">08</span><span class="o">-</span><span class="mi">11</span><span class="o">)</span> <span class="n">or</span> <span class="n">after</span> <span class="n">any</span> <span class="n">future</span> <span class="n">revocation</span> <span class="n">date</span><span class="o">.</span>
</code></pre></div></div>
<p>由于Android并不校验证书的合法性,因此我们无法判定一个安装的应用是否经由正规的公司发布。譬如,我们可以给微信进行重新签上自己生成的签名,还是可以正常安装,但这已经不是腾讯发布的微信了,Android系统对此并不感知。当然,微信自己会进行签名校验,发现签名不对会选择自行退出,但对于市面上绝大多数应用而言,都没有自校验签名。因此也就出现了一些无良的开发者,窃取别人的劳动成果,在已有APK中植入广告,重签名进行发布。</p>
<blockquote>
<p>数字签名的证书是可以交由第三方机构认证的,即由<strong>CA(Certifiacte Authoriy)</strong>颁发的证书是得到认可的。基于此,应用市场(譬如应用宝、豌豆荚等)可以对上架的应用进行证书的校验。</p>
</blockquote>
<h3 id="223-当签名不匹配时apk升级会失败">2.2.3 当签名不匹配时,APK升级会失败</h3>
<p>如果一个APK的前后两个版本签名不一致,Android是拒绝升级的,这是一种基于数字签名对应用的保护机制。在此限定下,应用开发者需保持其签名一直不变。</p>
<p>本文不再细述APK升级安装的过程,这个过程由PMS处理,读者其实可以猜到,无非就是在安装多了一个环节:与已安装的同包名应用进行签名比对。具体的细节可以参考<a href="/2017-01-04-Package-Manage-Mechanism/">Android包管理机制</a>文中的应用安装一节。</p>
<p>举一个例子,对一个应用签两次不同的名,通过<code class="highlighter-rouge">adb install -r</code>命令重复安装,便会升级签名不匹配的错误:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">duanqizhi</span><span class="nd">@xo</span><span class="o">:~</span><span class="err">$</span> <span class="n">adb</span> <span class="n">install</span> <span class="o">-</span><span class="n">r</span> <span class="n">app</span><span class="o">-</span><span class="n">signed</span><span class="o">-</span><span class="n">changed</span><span class="o">.</span><span class="na">apk</span>
<span class="mi">1904</span> <span class="n">KB</span><span class="o">/</span><span class="n">s</span> <span class="o">(</span><span class="mi">2407277</span> <span class="n">bytes</span> <span class="n">in</span> <span class="mf">1.234</span><span class="n">s</span><span class="o">)</span>
<span class="n">Failure</span> <span class="o">[</span><span class="nl">INSTALL_FAILED_UPDATE_INCOMPATIBLE:</span> <span class="n">Package</span> <span class="n">com</span><span class="o">.</span><span class="na">xo</span><span class="o">.</span><span class="na">demo</span> <span class="n">signatures</span> <span class="k">do</span> <span class="n">not</span> <span class="n">match</span> <span class="n">the</span> <span class="n">previously</span> <span class="n">installed</span> <span class="n">version</span><span class="o">;</span> <span class="n">ignoring</span><span class="o">!]</span>
</code></pre></div></div>
<h2 id="23-系统层面android基于数字签名的一些机制">2.3 系统层面:Android基于数字签名的一些机制</h2>
<h3 id="231-四种不同类型的签名">2.3.1 四种不同类型的签名</h3>
<p>Android预置的系统应用都是需要签名的,包括联系人、相机、设置等。Android设计了四种不同类型的签名:<strong>platform</strong>, <strong>share</strong>, <strong>media</strong>和<strong>testkey</strong>,默认置于在源码的<strong>build/target/product/security</strong>目录下,分别用于给不同类型的系统应用进行签名。从Android Lollipop开始,Android还对boot.img和system.img进行签名以防被篡改,所以在原来四组签名的基础上又增加了<strong>verity</strong>。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build/target/product/security
├── (media.pk8, media.x509.pem) #用于给MediaProvider, Gallery等签名
├── (platform.pk8, platform.x509.pem) #用于给Settings, Phone等签名
├── (shared.pk8, shared.x509.pem) #用于给Launcher, Dailer等签名
├── (testkey.pk8, testkey.x509.pem) #用于给一般应用签名
└── (verity.pk8, verity.x509.pem) #用于给boot.img和system.img签名
</code></pre></div></div>
<p>一共有五组密钥对,<strong>.pk8</strong>为私钥, <strong>.509.pem</strong>为证书(包含公钥),本质上,它们都是密钥对,并没有区别,只不过Android做了逻辑上的区分,后文我们再来介绍不同类型的签名各有什么用。先来看如何给系统应用设定签名类型,以下是<a href="">packages/apps/Settings/Android.mk</a>的内容片段,表示要编译Settings这个模块:</p>
<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">LOCAL_PATH</span><span class="o">:=</span> <span class="err">$</span><span class="o">(</span>call my-dir<span class="o">)</span>
<span class="err">include</span> <span class="err">$(CLEAR_VARS)</span>
<span class="err">...</span>
<span class="nv">LOCAL_PACKAGE_NAME</span> <span class="o">:=</span> Settings
<span class="nv">LOCAL_CERTIFICATE</span> <span class="o">:=</span> platform <span class="c">#表示Settings需要采用platform密钥对进行签名</span>
<span class="nv">LOCAL_PRIVILEGED_MODULE</span> <span class="o">:=</span> <span class="nb">true</span>
<span class="err">...</span>
<span class="err">include</span> <span class="err">$(BUILD_PACKAGE)</span>
</code></pre></div></div>
<p>设置Makefile的<strong>LOCAL_CERTIFICATE</strong>变量,就可以指定APK的签名类型了,编译系统会根据设定的变量值选取密钥对。</p>
<blockquote>
<p>在AOSP的源码中,上述的密钥对是完全公开的,只是为了完成签名操作,并没有安全性可言。OEM设备厂商都会自行生成密钥对,并将私钥(.pk8文件)保护起来。</p>
</blockquote>
<h3 id="232-应用的授权">2.3.2 应用的授权</h3>
<p>在<a href="/2017-01-04-Package-Manage-Mechanism/">Android包管理机制</a>一文中,详细介绍了系统如何给应用授权,本文不再赘述,仅把与数字签名相关的应用授权提炼出来。</p>
<p>先来看一个特殊的APK:framework-res.apk,其包名为<strong>android</strong>,在它的AndroidManifest.xml文件中,定义了一些系统权限:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"><!-- 安装应用的权限 --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.INSTALL_PACKAGES"</span>
<span class="na">android:protectionLevel=</span><span class="s">"signature|privileged"</span> <span class="nt">/></span>
<span class="c"><!-- 卸载应用的权限 --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.DELETE_PACKAGES"</span>
<span class="na">android:protectionLevel=</span><span class="s">"signature|privileged"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>所有定义的权限都有一个<strong>protectionLevel</strong>属性,它表示一个权限的保护级别。其中部分权限的<strong>protectionLevel</strong>值包含signature,表示该权限的申请者必须与framework-res.apk的签名相同,系统才会授予申请者该权限。从framework-res.apk的Android.mk中,可以看出它签名类型为platform,这也就意味着如果想要获取系统中protectionLevel为signature的权限,就必须使用platform密钥对来签名应用。</p>
<p>除了framework-res.apk,其他应用也可以定义属于自己的权限,同样可以设定<strong>protectionLevel</strong>,通过Android的权限授予机制来保护API,防止滥用。譬如在<a href="">packages/apps/Launcer3/AndroidManifest.xml</a>中,就定义了如下权限:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><permission</span>
<span class="na">android:name=</span><span class="s">"com.android.launcher3.permission.WRITE_SETTINGS"</span>
<span class="na">android:permissionGroup=</span><span class="s">"android.permission-group.SYSTEM_TOOLS"</span>
<span class="na">android:protectionLevel=</span><span class="s">"signatureOrSystem"</span>
<span class="na">android:label=</span><span class="s">"@string/permlab_write_settings"</span>
<span class="na">android:description=</span><span class="s">"@string/permdesc_write_settings"</span><span class="nt">/></span>
</code></pre></div></div>
<p>这就表明要想获取<strong>com.android.launcher3.permission.WRITE_SETTINGS</strong>这个权限,申请者要么与Launcher3具有相同的签名,要么是一个系统应用。</p>
<p>正常情况下,普通的应用是无法获取一些受保护系统权限的,因为无法获得与系统应用相同的签名。试想如果一个恶意的应用获取了静默安装的系统权限,每天偷偷的下载安装应用,这是一件多么恐怖的事情。</p>
<blockquote>
<p>Android基于数字签名设计的应用授权,是一种灵活的授权机制,因为仅仅通过是否为系统应用来判定是否授权,有很多局限,譬如一些受信的应用没有安装在系统分区,就无法获取系统权限;两个普通的应用之间也无法定义受保护的权限。
有了数字签名,就相当于多了一种受信机制。两个不同的APK,签名相同,意味着来源相同,彼此是受信的。</p>
</blockquote>
<h3 id="233-selinux根据签名给apk打标签labling">2.3.3 SELinux根据签名给APK打标签(Labling)</h3>
<p>Android Lollipop强制使用SELinux后,极大的增强了Android系统的安全性,关于SELinux的运行机制,各位读者可以从网上搜罗很多,本节意不在此,仅是为了介绍Android如何结合SELinux和数字签名为APK打上标签(所谓“标签”,是为了后文中描述的方便,在SELinux的概念中,其实叫SELinux Context)。有一些必要基础的背景知识:</p>
<ul>
<li>
<p>在SELinux环境下,所有的文件和进程都有标签(通过<code class="highlighter-rouge">ls -Z</code>和<code class="highlighter-rouge">ps -Z</code>命令可以查看),这个标签称为SELinux Context,是SELinux的权限控制的基础。</p>
</li>
<li>
<p>部分标签是事先定义好的,譬如<a href="">system/sepolicy/file_contexts</a>文件中所定义的一些系统文件和目录的标签;部分标签是根据规则生成的,譬如在fork子进程时,会根据TE(Type Enforcement)文件中定义的规则,为子进程打上标签。</p>
</li>
</ul>
<p>以Settings为例,手机上的系统分区会存在Settings.apk这个静态的文件,运行<code class="highlighter-rouge">ls -Z</code>命令可以查看其SELinux标签:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">angler:/system/priv-app/Settings #</span> <span class="nb">ls</span> <span class="nt">-Z</span>
<span class="go">-rw-r--r-- 1 root root u:object_r:system_file:s0 Settings.apk
</span></code></pre></div></div>
<p>Settings.apk的标签为<strong>u:object_r:system_file:s0</strong>,表示:</p>
<ul>
<li>
<p>该文件属于SELinux的用户u,Android下的SELinux只定义了一个用户,就是u</p>
</li>
<li>
<p>该文件的角色为object_r,SELinux的规则文件中为不同的角色定义了不同的权限</p>
</li>
<li>
<p>该文件的域为system_file,域的概念不是很好理解,姑且将其理解为操作对象,还有其他的域,譬如system_data_file, apk_data_file等,SELinux的规则文件中定义了一个角色对不同对象的操作权限</p>
</li>
<li>
<p>MLS(Multi-Level Security)级别0,目前Android中所有Label的级别都是S0,此处不展开讨论</p>
</li>
</ul>
<p>这个静态的Settings.apk文件的SELinux标签是怎么打上去的呢?在<a href="">system/sepolicy/file_contexts</a>文件中做了如下定义:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
#############################
# System files
#
/system(/.*)? u:object_r:system_file:s0
/system/bin/atrace u:object_r:atrace_exec:s0
/system/bin/e2fsck -- u:object_r:fsck_exec:s0
/system/bin/fsck\.f2fs -- u:object_r:fsck_exec:s0
/system/bin/fsck_msdos -- u:object_r:fsck_exec:s0
/system/bin/toolbox -- u:object_r:toolbox_exec:s0
...
</code></pre></div></div>
<p>上面的代码片段表示对于<strong>/system/</strong>目录下的所有文件,如果没有特殊的指定,其SELinux标签就为<strong>u:object_r:system_file:s0</strong>。<strong>/system/</strong>子目录也可以再重新指定,譬如<strong>/system/bin/toolbox</strong>,虽然也在<strong>/system</strong>目录下,但具体指定了其标签为<strong>u:object_r:toolbox_exec:s0</strong>。</p>
<p>当Settings运行起来后,系统中就存在一个Settings进程,通过运行<code class="highlighter-rouge">ps -Z</code>命令查看:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>angler:/system/priv-app/Settings # ps -Z | grep settings
u:r:system_app:s0 system S com.android.settings
</code></pre></div></div>
<p>对于Settings进程而言,SELinux标签的角色为r,域为system_app,这个标签与静态的Settings.apk文件不同,它是依据APK的签名打上去的,Android设计了一套为应用进程打标签的机制:维护签名到SELinux标签的映射表,记录在<a href="">system/sepolicy/mac_permissions.xml</a>中,其初始内容如下所示:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><policy></span>
<span class="nt"><signer</span> <span class="na">signature=</span><span class="s">"@PLATFORM"</span> <span class="nt">></span>
<span class="nt"><seinfo</span> <span class="na">value=</span><span class="s">"platform"</span> <span class="nt">/></span>
<span class="nt"></signer></span>
<span class="nt"></policy></span>
</code></pre></div></div>
<p>以上内容只是一个模板,表达的意思是:签名为<strong>@PLATFORM</strong>的APK所在的进程,其seinfo为platform。<strong>@PLATFORM</strong>编译后会被替换成真实的platfrom类型的签名,以下是mac_permissions.xml经过编译后的结果:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="iso-8859-1"?></span>
<span class="nt"><policy></span>
<span class="nt"><signer</span> <span class="na">signature=</span><span class="s">"308204a83...8b1b357"</span><span class="nt">></span>
<span class="nt"><seinfo</span> <span class="na">value=</span><span class="s">"platform"</span><span class="nt">/></span>
<span class="nt"></signer></span>
<span class="nt"></policy></span>
</code></pre></div></div>
<p>对于在Android.mk中声明<strong>LOCAL_CERTIFICATE := platform</strong>的应用来说,其seinfo就被标记为<strong>platform</strong>了,那么<strong>seinfo</strong>又是什么呢?<a href="">frameworks/base/core/java/android/content/pm/ApplicationInfo.java</a>文件中有其定义:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cm">/**
* String retrieved from the seinfo tag found in selinux policy. This value
* can be overridden with a value set through the mac_permissions.xml policy
* construct. This value is useful in setting an SELinux security context on
* the process as well as its data directory. The String default is being used
* here to represent a catchall label when no policy matches.
*
* {@hide}
*/</span>
<span class="kd">public</span> <span class="n">String</span> <span class="n">seinfo</span> <span class="o">=</span> <span class="s">"default"</span><span class="o">;</span>
</code></pre></div></div>
<p><strong>ApplicationInfo</strong>这个对象是表示一个应用程序的信息,其中一个属性就是<strong>seinfo</strong>,默认取值为default,最终取值会依据<strong>mac_permissions.xml</strong>而定。在<strong>system/sepolicy/seinfo_contexts</strong>文件中,定义了不同<strong>seinfo</strong>映射到的SELinux标签:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>isSystemServer=true domain=system_server
user=system seinfo=platform domain=system_app type=system_app_data_file
user=bluetooth seinfo=platform domain=bluetooth type=bluetooth_data_file
user=nfc seinfo=platform domain=nfc type=nfc_data_file
user=radio seinfo=platform domain=radio type=radio_data_file
user=shared_relro domain=shared_relro
user=shell seinfo=platform domain=shell type=shell_data_file
user=_isolated domain=isolated_app levelFrom=user
user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user
user=_app isAutoPlayApp=true domain=autoplay_app type=autoplay_data_file levelFrom=all
user=_app isPrivApp=true domain=priv_app type=app_data_file levelFrom=user
user=_app domain=untrusted_app type=app_data_file levelFrom=user
</code></pre></div></div>
<p><strong>Settings</strong>进程的seinfo为platform,进程所属的用户为system,那就匹配到了<strong>“user=system seinfo=platform domain=system_app type=system_app_data_file”</strong>这一条,表示Settings进程的域为<strong>system_app</strong>,其可操作的文件类型为<strong>system_app_data_file</strong>。</p>
<p>当APK的签名在<strong>mac_permissions.xml</strong>中没有匹配成功时,会默认设置seinfo为default,这样最终APK的应用进程的域就为<strong>untrusted_app</strong>。感兴趣的读者可以自行通过上文中的命令查看。</p>
<p>用下图总结一下为应用进程打标签的过程:</p>
<div align="center"><img src="/assets/images/digitalsignature/6-digital-signature-selinux-labeling.png" alt="APK打标签的过程" /></div>
<ul>
<li>
<p>对APK进行扫描的时候,根据<strong>mac_permissions.xml</strong>中定义的<code class="highlighter-rouge">签名=>seinfo</code>映射,设置APK所对应的应用进程的seinfo属性</p>
</li>
<li>
<p>当APK的应用进程启动时,根据<strong>seapp_contexts</strong>中定义的<code class="highlighter-rouge">seinfo=>SELinux Context</code>映射,设置进程的SELinux标签</p>
</li>
</ul>
<h1 id="3-总结">3 总结</h1>
<p>本文的目的是为了介绍数字签名在Android中的应用场景。</p>
<p>首先,介绍了数字签名的背景,机制和常见的使用工具。对于数字签名(Digitial Signature)、电子签名(Electronic Signature)、证书(Certificate)、证书链(Certificate Chain)、证书认证机构(Certificate Authority)等概念做了比较通俗的解释。数字签名的实现基于非对称密钥算法,主要有签名和校验两个机制。使用数字签名可以保障可认证性(Authentication)、不可抵赖性(Non-repudiation)和完整性(Integrity),因此应用场景非常之广泛,譬如SSH、HTTPS、网银U盾、电子合同等。</p>
<p>然后,分析了Android基于数字签名实现的一些安全机制,包括拒绝安装没有签名的应用、拒绝升级签名不匹配的应用、基于签名给应用授权、SELinux环境下基于签名打标签。在Android源码中,都能够找到这些机制的实现,读者可以重点学习Android是如何将数字签名机制嵌入到已有的代码流程中的。</p>
<p>最后,数字签名还可以衍生出一些有意思的变种,譬如如何做到可抵赖(Repudiation),这在间谍领域是很有必要的,基于本文一开始提到的<strong>Diffie-Hellman Key Exchange</strong>算法就可以实现可抵赖性,有兴趣的读者可以继续研究。</p>
Android包管理机制
2017-01-04T00:00:00+00:00
https://duanqz.github.io/Package-Manage-Mechanism
<h1 id="1-概要">1 概要</h1>
<p>每一个社会群落都有管理机制,其中有三个要素:被管理者、管理者以及管理机制的运转。在Android的世界中,有一处群落叫“包管理”,要研究Android的包管理机制,同样可以从以下几个角度来思考:</p>
<ol>
<li>被管理的对象是什么?</li>
<li>管理者的职能是什么?</li>
<li>管理机制是如何运转的?</li>
</ol>
<p>所谓包,其实就是一种文件格式,譬如APK包、JAR包等。在Android中存活着很多包,所有的应用程序都是APK包,很多构成Android运行环境的都是JAR包,还有一些以so为后缀的库文件,包管理者很重要的一个职能就是识别不同的包,统一维护这些包的信息。当有一个包进入或离开Android世界,都需要向包管理者申报,其他管理部门要获取包的具体信息,也都需要向包管理者申请。</p>
<p>如同社会是由人与人的协作形成,不同的包之间也需要进行协作。既然有协作,自然就有协作的规范,一个包可以干什么,不可以干什么,都需要有一个明确的范围界定,这就是包管理中的权限设计。涉及到的内容非常广泛,Linux的UGO(User Group Other)和ACL(Access Control List,访问控制列表)权限管理、数字签名与验证、Android授权机制、Selinux,都是包管理中权限设计的组成部分。</p>
<p>Android的世界就如同一个井然有序的人类社会,除了包管理部门,还有其他各种管理部门,譬如电源管理、窗口管理、活动管理等等,大家不仅各司其职,而且也有交流往来。从APK的安装到Activity的显示这么一个看似简单的过程,却需要大量管理部门参与进来,不断地进行数据解析、封装、传递、呈现,内部机理十分复杂。</p>
<blockquote>
<p>PackageManagerService是包管理中最重要的服务,为了描述方便,本文会简写成<strong>PMS</strong>。</p>
<p>PMS的部分函数带有<strong>LI</strong>后缀,表示需要获取<strong>mInstalllock</strong>这个锁时才能执行;部分函数带有<strong>LP</strong>后缀,表示需要获取<strong>mPackages</strong>这个锁才能执行。</p>
</blockquote>
<h1 id="2-被管理对象的形态">2 被管理对象的形态</h1>
<p>Android中的APK和JAR包都以静态文件的形式分布在不同的硬件分区,包管理者面临的第一个任务就是将这些静态的文件转化成内存的数据结构,这样才能将其管理起来。Android中最重要的包管理对象就是APK,APK可以包含so文件,负责将静态文件转换内存中数据结构的工具就是PackageParser,包解析器。</p>
<div align="center"><img src="/assets/images/packagemanager/1-packagemanager-package-from-static-to-dynamic.png" alt="Package from static to dynamic" /></div>
<p>Android L(5.0)以后,支持APK拆分,即一个APK可以分割成很多部分,位于相同的目录下,每一个部分都是一个单独的APK文件,所有的APK文件具备相同的签名,在APK解析过程中,会将拆分的APK重新组合成内存中的一个Package。对于一个完整的APK,Android称其为<strong>Monolithic</strong>;对于拆分后的APK,Android称其为<strong>Cluster</strong>。</p>
<blockquote>
<p>在Android L(5.0)以前,APK文件都是直接位于<strong>app</strong>或<strong>priv-app</strong>目录下,譬如短彩信APK的目录就是<strong>/system/priv-app/Mms.apk</strong>;到了Android L(5.0)之后,多了一级目录结构,譬如短彩信APK的目录是<strong>/system/priv-app/Mms/Mms.apk</strong>,这是Android为了支持APK拆分而做的改动,如果要将短彩信APK进行拆分,那所有被拆出来的APK都位于<strong>/system/priv-app/Mms/</strong>即可,这样在包解析时,就会变成以<strong>Cluster</strong>的方式解析目录。</p>
</blockquote>
<p>一个包在内存中的数据结构就是<strong>Package</strong>,那么,Package有一些什么属性?是怎么从APK文件中获取数据的呢? 这就涉及到包解析器的工作原理。</p>
<h2 id="21-包解析器">2.1 包解析器</h2>
<p>为了先让读者对被管理对象有一个初步的认识,我们先把一个包最终在内存中的数据结构拎出来。其实生成这个数据结构,需要包管理者进行大量的调度工作,调度中心是PMS,包解析的过程也都是由PMS驱动的。在分析包解析过程之前,我们先上包解析的结果:</p>
<div align="center"><img src="/assets/images/packagemanager/2-packagemanager-packageparser.png" alt="Package Parser" /></div>
<p>这个类图,示意了一个包最终在内存中的数据结构<strong>Package</strong>,它包含很多属性,部分属性还是包解析器中的子数据结构。我们可以从设计的角度来理解这个类图:</p>
<ul>
<li>
<p>一个包中有很多组件,为此设计了一个高层的基类<strong>Component</strong>,所有具体的组件都是<strong>Component</strong>的子类。什么是组件呢?<strong>AndroidManifest.xml</strong>文件中所定义的的一些标签,就是组件,譬如<activity>,<service>,<provider>,<permission>等,这些标签分别对应到包解析器中的一个数据结构,它们各自有自身的属性。</p>
</li>
<li>
<p>诸如<activity>,<service>标签,都可以配置<intent-filter>,来过滤其可以接收的Intent,这些信息也需要在包解析器中体现出来,为此组件<strong>Component</strong>依赖于<strong>IntentInfo</strong>这个数据结构。每一个具体的组件所依赖的<strong>IntentInfo</strong>不同,所以<strong>Component</strong>和<strong>IntentInfo</strong>之间的依赖关系采用了桥接(Bridge)这种设计模式,通过泛型的手段实现。</p>
</li>
<li>
<p>各种组件最终聚合到<strong>Package</strong>这个数据结构中,形成了最终包解析器的输出。当然,在解析的过程中,还有利用了一些数据结构来优化设计,<strong>PackageLite</strong>和<strong>ApkLite</strong>就是一些很简单的数据封装。</p>
</li>
</ul>
<p>要得到以上的数据结构,包解析器<strong>PackageParser</strong>功不可没,从接收一个静态的文件(File类型)开始,会经过一个漫长的包解析过程,直到生成最终的<strong>Package</strong>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>parsePackages(File file...)
└── parseClusterPackage(File packageDir...)
└── parseClusterPackageLite(File packageDir...)
| └── parseApkLite(File apkFile...)
| └── parseApkLite(String codePath...)
└── parseBaseApk()
└── parseBaseApplication()
| └── parseActivity()
| └── parseService()
| └── ...
└── parseInstrumentation()
└── ...
</code></pre></div></div>
<p>这些函数的具体逻辑本文不予分析,仅把关键的流程捋出来:</p>
<ol>
<li>
<p><strong>PackageParser.parsePackages()</strong>是包解析器的入口函数,它首先会判定给定的输入是否为一个目录,如果是目录,则以为着目录下可能存在多个拆分后的APK,这就需要以<strong>Cluster</strong>的方式进行解析;如果仅仅是一个APK文件,就以<strong>Monolithic</strong>的方式解析;</p>
</li>
<li>
<p>解析APK,需要先得到一个中间数据结构<strong>PacakgeLite</strong>,包名、版本、拆分包等信息都会保存在这个数据结构中;由于一个包可能有多个拆分的APK,所以<strong>PackageLite</strong>可能关联到多个APK,每一个APK都对应到<strong>ApkLite</strong>这个数据结构,也是一些基本信息的封装。之所以以<strong>Lite</strong>为后缀命名,是因为这两个数据结构都比较轻量,只保存APK中很少信息;</p>
</li>
<li>
<p>一个APK真正的信息都写在<strong>AndroidManifest.xml</strong>这个文件中,<strong>PackageParser.parseBaseApk()</strong>这个函数就是用来解析该文件。其解析过程与<strong>AndroidManifest.xml</strong>的文件结构一一对应,譬如先解析<application>标签的内容,然后解析其下的<activity>,<service>等标签。由于<strong>AndroidManifest.xml</strong>文件的结构非常复杂,所以该函数逻辑也非常庞大,读者们可以自行分析源码。</p>
</li>
</ol>
<p><strong>至此,包解析器PackageParser就将一个静态的文件,转换成了内存中的数据结构Package,它包含了一个包的所有信息,如包名、包路径、权限、四大组件等,其数据来源主要就是AndroidManifest.xml文件。</strong></p>
<h2 id="22-包信息体">2.2 包信息体</h2>
<p>包解析器从静态文件中获取的数据,很多都是需要用于跨进程传递的,譬如初次启动Activity时,就需要把包信息从系统进程传递到应用进程,先完成应用进程的启动。在包解析器的类图中,我们看到Activity、Service、Provider、Permission、Instrumentaion这些类都有一个共同的特征:都具备<strong>info</strong>这个属性,其实这些类的结构非常简单,就是对<strong>info</strong>的一次封装,<strong>info</strong>这个结构体才是真正的包数据,笔者暂且称之为“包信息体”:</p>
<div align="center"><img src="/assets/images/packagemanager/3-packagemanager-packageparser-parcelable.png" alt="Package Parser Parcelable" /></div>
<p>所有的<strong>info</strong>都实现了<strong>Parcelable</strong>接口,意图很明显,<strong>info</strong>是可以进行跨进程传递的。不同组件的<strong>info</strong>类型是不同的,除了实现了<strong>Parcelable</strong>接口,它们之间又构成了一个庞大的数据结构,把这些具体的<strong>info</strong>类型展开,就是以下的类图:</p>
<div align="center"><img src="/assets/images/packagemanager/4-packagemanager-packageparser-parcelable.png" alt="Package Parser Parcelable" /></div>
<p>可以看到,这个类图与<strong>PackageParser</strong>中的类图在结构上很相似,我们依旧是从设计的角度来理解这个类图:</p>
<ul>
<li>
<p><strong>PackageItemInfo</strong>作为包每一项信息的高层基类:</p>
<ul>
<li>针对<strong>permission</strong>,<strong>permission</strong>,<strong>instrumentation</strong>等,分别为其设计了一个类,都继承自<strong>PackageItemInfo</strong></li>
<li>针对<strong>activity</strong>,<strong>service</strong>,<strong>provider</strong>等四大组件,在<strong>PackageItemInfo</strong>之下又多设计了一层:<strong>ComponentInfo</strong>作为四大组件的基类</li>
<li><strong>ApplicationInfo</strong>也是包信息中的一项,但与四大组件紧密相连,四大组件肯定都属于某个<strong>Application</strong>,所以<strong>ComponentInfo</strong>与<strong>Application</strong>存在依赖关系,继而,具体到每个组件都与Application存在依赖关系</li>
</ul>
</li>
<li>
<p>所有的包信息都聚合到<strong>PackageInfo</strong>这个类中,<strong>PackageInfo</strong>就是一个包向外提供的所有信息。其实除了上图列出来的类,还有一些类没有示意出来,譬如ConfigurationInfo,FeatureInfo,它们都可以对应到AndroidManifest.xml中的标签。</p>
</li>
</ul>
<p>这些结构体中的数据,都是在包解析器时初始化的,譬如<strong>Activity</strong>依赖于<strong>ActivityInfo</strong>,在解析<strong>Activity</strong>时,就会创建一个<strong>ActivityInfo</strong>对象,把<activity>所定义的数据全都填充到<strong>ActivityInfo</strong>中。读者可以思考一下<strong>PackageParser</strong>中的<strong>Activity</strong>与此处的<strong>ActivityInfo</strong>的分开设计的目的和好处是什么?</p>
<blockquote>
<p>在分析包的形态时,我们见到了很多类,类的命名方式还有点相似,初读代码的时候,很容易陷入各个类之间复杂的关系网之中。不得不说,包在内存中的数据结构是比较庞大的,因为它蕴含的信息大多了。</p>
</blockquote>
<h1 id="3-pms的启动过程">3 PMS的启动过程</h1>
<h2 id="31-对象构建">3.1 对象构建</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 代码片段1:PMS部分属性的初始化</span>
<span class="kd">public</span> <span class="nf">PackageManagerService</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">Installer</span> <span class="n">installer</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">factoryTest</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">onlyCore</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 关键的Event日志,PMS开始启动了</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_PMS_START</span><span class="o">,</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</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="n">mFactoryTest</span> <span class="o">=</span> <span class="n">factoryTest</span><span class="o">;</span>
<span class="n">mOnlyCore</span> <span class="o">=</span> <span class="n">onlyCore</span><span class="o">;</span>
<span class="c1">// 如果是eng版,则延迟做DexOpt</span>
<span class="n">mLazyDexOpt</span> <span class="o">=</span> <span class="s">"eng"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">SystemProperties</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"ro.build.type"</span><span class="o">));</span>
<span class="n">mMetrics</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DisplayMetrics</span><span class="o">();</span>
<span class="c1">// Settings是包管理中一个很重要的数据结构,用于维护所有包的信息</span>
<span class="n">mSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Settings</span><span class="o">(</span><span class="n">mPackages</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.system"</span><span class="o">,</span> <span class="n">Process</span><span class="o">.</span><span class="na">SYSTEM_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.phone"</span><span class="o">,</span> <span class="n">RADIO_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.log"</span><span class="o">,</span> <span class="n">LOG_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.nfc"</span><span class="o">,</span> <span class="n">NFC_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.bluetooth"</span><span class="o">,</span> <span class="n">BLUETOOTH_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">addSharedUserLPw</span><span class="o">(</span><span class="s">"android.uid.shell"</span><span class="o">,</span> <span class="n">SHELL_UID</span><span class="o">,</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">,</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">PRIVATE_FLAG_PRIVILEGED</span><span class="o">);</span>
<span class="kt">long</span> <span class="n">dexOptLRUThresholdInMinutes</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mLazyDexOpt</span><span class="o">)</span> <span class="o">{</span>
<span class="n">dexOptLRUThresholdInMinutes</span> <span class="o">=</span> <span class="mi">30</span><span class="o">;</span> <span class="c1">// only last 30 minutes of apps for eng builds.</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">dexOptLRUThresholdInMinutes</span> <span class="o">=</span> <span class="mi">7</span> <span class="o">*</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span><span class="o">;</span> <span class="c1">// apps used in the 7 days for users.</span>
<span class="o">}</span>
<span class="n">mDexOptLRUThresholdInMills</span> <span class="o">=</span> <span class="n">dexOptLRUThresholdInMinutes</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="o">;</span>
<span class="o">...</span>
<span class="n">mInstaller</span> <span class="o">=</span> <span class="n">installer</span><span class="o">;</span>
<span class="c1">// DexOpt工具类</span>
<span class="n">mPackageDexOptimizer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageDexOptimizer</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">mMoveCallbacks</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MoveCallbacks</span><span class="o">(</span><span class="n">FgThread</span><span class="o">.</span><span class="na">get</span><span class="o">().</span><span class="na">getLooper</span><span class="o">());</span>
<span class="n">mOnPermissionChangeListeners</span> <span class="o">=</span> <span class="k">new</span> <span class="n">OnPermissionChangeListeners</span><span class="o">(</span>
<span class="n">FgThread</span><span class="o">.</span><span class="na">get</span><span class="o">().</span><span class="na">getLooper</span><span class="o">());</span>
<span class="n">getDefaultDisplayMetrics</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">mMetrics</span><span class="o">);</span>
<span class="c1">// 读取系统默认的权限</span>
<span class="n">SystemConfig</span> <span class="n">systemConfig</span> <span class="o">=</span> <span class="n">SystemConfig</span><span class="o">.</span><span class="na">getInstance</span><span class="o">();</span>
<span class="n">mGlobalGids</span> <span class="o">=</span> <span class="n">systemConfig</span><span class="o">.</span><span class="na">getGlobalGids</span><span class="o">();</span>
<span class="n">mSystemPermissions</span> <span class="o">=</span> <span class="n">systemConfig</span><span class="o">.</span><span class="na">getSystemPermissions</span><span class="o">();</span>
<span class="n">mAvailableFeatures</span> <span class="o">=</span> <span class="n">systemConfig</span><span class="o">.</span><span class="na">getAvailableFeatures</span><span class="o">();</span>
<span class="c1">// 这里上了两把锁: mInstallLock是安装APK时需要用到的锁;mPackage是更新APK信息时需要的锁</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mInstallLock</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 构建一个后台线程,并将线程的消息队列绑定到Handler</span>
<span class="n">mHandlerThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ServiceThread</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span>
<span class="n">Process</span><span class="o">.</span><span class="na">THREAD_PRIORITY_BACKGROUND</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/*allowIo*/</span><span class="o">);</span>
<span class="n">mHandlerThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="n">mHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageHandler</span><span class="o">(</span><span class="n">mHandlerThread</span><span class="o">.</span><span class="na">getLooper</span><span class="o">());</span>
<span class="c1">// 将PMS加入Watchdog的监控列表</span>
<span class="n">Watchdog</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">addThread</span><span class="o">(</span><span class="n">mHandler</span><span class="o">,</span> <span class="n">WATCHDOG_TIMEOUT</span><span class="o">);</span>
<span class="c1">// 初始化一些文件目录</span>
<span class="n">File</span> <span class="n">dataDir</span> <span class="o">=</span> <span class="n">Environment</span><span class="o">.</span><span class="na">getDataDirectory</span><span class="o">();</span>
<span class="n">mAppDataDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"data"</span><span class="o">);</span>
<span class="n">mAppInstallDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"app"</span><span class="o">);</span>
<span class="n">mAppLib32InstallDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"app-lib"</span><span class="o">);</span>
<span class="n">mAsecInternalPath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"app-asec"</span><span class="o">).</span><span class="na">getPath</span><span class="o">();</span>
<span class="n">mUserAppDataDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"user"</span><span class="o">);</span>
<span class="n">mDrmAppPrivateInstallDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"app-private"</span><span class="o">);</span>
<span class="c1">// 初始化系统权限</span>
<span class="n">ArrayMap</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">SystemConfig</span><span class="o">.</span><span class="na">PermissionEntry</span><span class="o">></span> <span class="n">permConfig</span>
<span class="o">=</span> <span class="n">systemConfig</span><span class="o">.</span><span class="na">getPermissions</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">permConfig</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">SystemConfig</span><span class="o">.</span><span class="na">PermissionEntry</span> <span class="n">perm</span> <span class="o">=</span> <span class="n">permConfig</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="n">BasePermission</span> <span class="n">bp</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPermissions</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">perm</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">bp</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">bp</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BasePermission</span><span class="o">(</span><span class="n">perm</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="s">"android"</span><span class="o">,</span> <span class="n">BasePermission</span><span class="o">.</span><span class="na">TYPE_BUILTIN</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">mPermissions</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">perm</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">bp</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">perm</span><span class="o">.</span><span class="na">gids</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">bp</span><span class="o">.</span><span class="na">setGids</span><span class="o">(</span><span class="n">perm</span><span class="o">.</span><span class="na">gids</span><span class="o">,</span> <span class="n">perm</span><span class="o">.</span><span class="na">perUser</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 初始化PMS中的ShareLibraries</span>
<span class="n">ArrayMap</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">libConfig</span> <span class="o">=</span> <span class="n">systemConfig</span><span class="o">.</span><span class="na">getSharedLibraries</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">libConfig</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">mSharedLibraries</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">libConfig</span><span class="o">.</span><span class="na">keyAt</span><span class="o">(</span><span class="n">i</span><span class="o">),</span>
<span class="k">new</span> <span class="nf">SharedLibraryEntry</span><span class="o">(</span><span class="n">libConfig</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">),</span> <span class="kc">null</span><span class="o">));</span>
<span class="o">}</span>
<span class="c1">// 读取mac_permission.xml文件的内容</span>
<span class="n">mFoundPolicyFile</span> <span class="o">=</span> <span class="n">SELinuxMMAC</span><span class="o">.</span><span class="na">readInstallPolicy</span><span class="o">();</span>
<span class="n">mRestoredSettings</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">readLPw</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">sUserManager</span><span class="o">.</span><span class="na">getUsers</span><span class="o">(</span><span class="kc">false</span><span class="o">),</span>
<span class="n">mSdkVersion</span><span class="o">,</span> <span class="n">mOnlyCore</span><span class="o">);</span>
<span class="n">String</span> <span class="n">customResolverActivity</span> <span class="o">=</span> <span class="n">Resources</span><span class="o">.</span><span class="na">getSystem</span><span class="o">().</span><span class="na">getString</span><span class="o">(</span>
<span class="n">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">config_customResolverActivity</span><span class="o">);</span>
<span class="c1">// 未完接代码片段2</span>
</code></pre></div></div>
<p>【代码片段1】完成了很多PMS的属性初始化操作,几个重要的属性如下:</p>
<ul>
<li>
<p><strong>mSettings</strong>:PMS内部有一个Settings数据结构,用于维护所有包的信息。写过Android应用程序的朋友可能知道两个APK可以运行在相同的进程中,前提是两个APK具有相同的签名和ShareUid。Android系统中定义了一些默认的ShareUid,譬如<strong>android.uid.system</strong>表示系统进程的UID,如果有一个APK想要运行在系统进程中,则其需要在AndroidManifest.xml文件中声明ShareUid为<strong>android.uid.system</strong>,并且该APK的签名必须与framework-res.apk的签名一致,即platform签名。</p>
<p>PMS在创建完Settings对象之后,便把很多系统默认的ShareUid加入其中。</p>
</li>
<li>
<p><strong>mInstaller</strong>:Installer是一个系统服务,它封装了很多PMS进行包管理需要用到的函数,譬如install()、 dexopt()、rename()等。在Installer内部,其实是通过Socket连接<strong>installd</strong>,将执行指令发送到<strong>installd</strong>完成具体的操作。</p>
</li>
<li>
<p><strong>mPackageDexOptimizer</strong>: 进行Dex优化的工具类。对于一个APK而言,编译后其APK包中可执行文件的格式dex,安装到了手机上以后,需要经过文件格式转化才能运行,譬如APK需要转换成oat格式才能在ART虚拟机上运行,文件格式转换的过程就叫DexOpt。</p>
<p>DalvikVM的时代,Android可执行文件的格式是dex,有一种进一步优化的格式叫odex;ART虚拟机的时代,Android可执行文件的格式是oat。虽然都叫做DexOpt,但在DalvikVM和ART两种不同虚拟机的时代分别有不同的内涵。</p>
</li>
<li>
<p><strong>SystemConfig</strong>: 系统全局的配置信息的数据结构。原始的数据来源于<strong>/system/etc/sysconfig</strong>和<strong>/system/etc/permissions</strong>目录下的XML文件,在SystemConfig对象构建时,会读取这两个目录下所有XML文件的内容,主要有以下几个维度:</p>
<ul>
<li>
<p>权限与GID的映射关系,譬如,以下内容表示属于<strong>inet</strong>这个组的用户都拥有<strong>android.permission.INTERNET</strong>权限:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><permission</span> <span class="na">name=</span><span class="s">"android.permission.INTERNET"</span> <span class="nt">></span>
<span class="nt"><group</span> <span class="na">git=</span><span class="s">"inet"</span><span class="nt">></span>
<span class="nt"></permission></span>
</code></pre></div> </div>
</li>
<li>
<p>权限与UID的映射关系,譬如,以下内容表示UID为<strong>meida</strong>用户拥有<strong>android.permission.CAMERA</strong>这个权限:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><assign-permission</span> <span class="na">name=</span><span class="s">"android.permission.CAMERA"</span> <span class="na">uid=</span><span class="s">"media"</span> <span class="nt">/></span>
</code></pre></div> </div>
</li>
<li>
<p>公共库的定义。Android中有很多公共库,除了BOOTCLASSPATH中定义的,框架层还支持额外的扩展,譬如,以下内容表示公共库的包名和其路径的关系:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><library</span> <span class="na">name=</span><span class="s">"android.test.runner"</span>
<span class="na">file=</span><span class="s">"/system/framework/android.test.runner.jar"</span> <span class="nt">/></span>
</code></pre></div> </div>
</li>
</ul>
</li>
<li>
<p><strong>mHandler</strong>: 创建PackageHandler对象,将其绑定到一个后台线程的消息队列。可想而知,一些厚重的活,譬如安装APK,就交由这个后台线程完成了。由于PMS是一个重要的系统服务,这个后台线程的消息队列如果过于忙碌,则会导致系统一直卡住,所以需要将这个消息队列加入Watchdog的监控列表,以便在这种情况下,Watchdog可以做出一些应急操作。</p>
</li>
<li>
<p><strong>初始化一些/data文件目录</strong>:应用程序的安装和运行都需要用到Data分区,PMS会在Data分区新建一些子目录。</p>
</li>
<li>
<p><strong>初始化系统权限</strong>:在SystemConfig初始化的时候,从<strong>/system/etc/permissions</strong>和<strong>/system/etc/sysconfig</strong>目录下读取了XML文件,这些信息要添加到Settings这个数据结构中。Android设计了一个BasePermission的数据结构,主要用于保存权限与包名之间的映射关系,此处,添加的权限是从SystemConfig中取出,包名是android,也就是先将系统权限添加到Settings中。</p>
</li>
<li>
<p><strong>mFoundPolicyFile</strong>: 有了SeLinux以后,Android会为每个文件打上SE Label,对于APK而言,打SE Label的准则就是签名,即根据签名信息打上不同的SE Label。Android将签名分类成为platform,testkey, media等,签名与类别的映射关系就存在一个叫<strong>mac_permission.xml</strong>的文件中。此处,需要读取该文件的内容。</p>
</li>
</ul>
<p>在完成部分属性的初始化之后,PMS要进入扫描安装阶段了。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//代码片段2:DexOpt处理,扫描系统文件</span>
<span class="c1">// 关键日志,开始扫描系统APP</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_PMS_SYSTEM_SCAN_START</span><span class="o">,</span>
<span class="n">startTime</span><span class="o">);</span>
<span class="c1">// 设置扫描参数</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">scanFlags</span> <span class="o">=</span> <span class="n">SCAN_NO_PATHS</span> <span class="o">|</span> <span class="n">SCAN_DEFER_DEX</span> <span class="o">|</span> <span class="n">SCAN_BOOTING</span> <span class="o">|</span> <span class="n">SCAN_INITIAL</span><span class="o">;</span>
<span class="c1">// 1. 构建数组变量用于保存已经做过DexOpt的文件,后文中,会往这个数组变量中添加元素</span>
<span class="kd">final</span> <span class="n">ArraySet</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">alreadyDexOpted</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArraySet</span><span class="o"><</span><span class="n">String</span><span class="o">>();</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">bootClassPath</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">getenv</span><span class="o">(</span><span class="s">"BOOTCLASSPATH"</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">systemServerClassPath</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">getenv</span><span class="o">(</span><span class="s">"SYSTEMSERVERCLASSPATH"</span><span class="o">);</span>
<span class="c1">// BOOTCLASSPATH环境变量所定义的文件已经做过DexOpt</span>
<span class="k">if</span> <span class="o">(</span><span class="n">bootClassPath</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">bootClassPathElements</span> <span class="o">=</span> <span class="n">splitString</span><span class="o">(</span><span class="n">bootClassPath</span><span class="o">,</span> <span class="sc">':'</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">element</span> <span class="o">:</span> <span class="n">bootClassPathElements</span><span class="o">)</span> <span class="o">{</span>
<span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">element</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// SYSTEMSERVERCLASSPATH环境变量所定义的文件已经做过了DexOpt</span>
<span class="k">if</span> <span class="o">(</span><span class="n">systemServerClassPath</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">systemServerClassPathElements</span> <span class="o">=</span> <span class="n">splitString</span><span class="o">(</span><span class="n">systemServerClassPath</span><span class="o">,</span> <span class="sc">':'</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">element</span> <span class="o">:</span> <span class="n">systemServerClassPathElements</span><span class="o">)</span> <span class="o">{</span>
<span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">element</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 获取指令集,以便后续进行DexOpt</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">allInstructionSets</span> <span class="o">=</span> <span class="n">InstructionSets</span><span class="o">.</span><span class="na">getAllInstructionSets</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">dexCodeInstructionSets</span> <span class="o">=</span>
<span class="n">getDexCodeInstructionSets</span><span class="o">(</span>
<span class="n">allInstructionSets</span><span class="o">.</span><span class="na">toArray</span><span class="o">(</span><span class="k">new</span> <span class="n">String</span><span class="o">[</span><span class="n">allInstructionSets</span><span class="o">.</span><span class="na">size</span><span class="o">()]));</span>
<span class="c1">// 公共库是定义在 etc/sysconfig 和 etc/permissions 文件夹下的XML文件中</span>
<span class="c1">// 需要这些公共库进行DexOpt处理</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mSharedLibraries</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="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">dexCodeInstructionSet</span> <span class="o">:</span> <span class="n">dexCodeInstructionSets</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">SharedLibraryEntry</span> <span class="n">libEntry</span> <span class="o">:</span> <span class="n">mSharedLibraries</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">lib</span> <span class="o">=</span> <span class="n">libEntry</span><span class="o">.</span><span class="na">path</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">lib</span> <span class="o">==</span> <span class="kc">null</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">try</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">dexoptNeeded</span> <span class="o">=</span> <span class="n">DexFile</span><span class="o">.</span><span class="na">getDexOptNeeded</span><span class="o">(</span><span class="n">lib</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">dexCodeInstructionSet</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">dexoptNeeded</span> <span class="o">!=</span> <span class="n">DexFile</span><span class="o">.</span><span class="na">NO_DEXOPT_NEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">lib</span><span class="o">);</span>
<span class="n">mInstaller</span><span class="o">.</span><span class="na">dexopt</span><span class="o">(</span><span class="n">lib</span><span class="o">,</span> <span class="n">Process</span><span class="o">.</span><span class="na">SYSTEM_UID</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">dexCodeInstructionSet</span><span class="o">,</span> <span class="n">dexoptNeeded</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(...)</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">File</span> <span class="n">frameworkDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getRootDirectory</span><span class="o">(),</span> <span class="s">"framework"</span><span class="o">);</span>
<span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">frameworkDir</span><span class="o">.</span><span class="na">getPath</span><span class="o">()</span> <span class="o">+</span> <span class="s">"/framework-res.apk"</span><span class="o">);</span>
<span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">frameworkDir</span><span class="o">.</span><span class="na">getPath</span><span class="o">()</span> <span class="o">+</span> <span class="s">"/core-libart.jar"</span><span class="o">);</span>
<span class="c1">// system/framework目录下,除了framework-res.apk和core-libart.jar这两个文件外</span>
<span class="c1">// 其他的APK和JAR文件都需要进行DexOpt处理</span>
<span class="n">String</span><span class="o">[]</span> <span class="n">frameworkFiles</span> <span class="o">=</span> <span class="n">frameworkDir</span><span class="o">.</span><span class="na">list</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">frameworkFiles</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">dexCodeInstructionSet</span> <span class="o">:</span> <span class="n">dexCodeInstructionSets</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">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">frameworkFiles</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="n">File</span> <span class="n">libPath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">frameworkDir</span><span class="o">,</span> <span class="n">frameworkFiles</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
<span class="n">String</span> <span class="n">path</span> <span class="o">=</span> <span class="n">libPath</span><span class="o">.</span><span class="na">getPath</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">alreadyDexOpted</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">path</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">path</span><span class="o">.</span><span class="na">endsWith</span><span class="o">(</span><span class="s">".apk"</span><span class="o">)</span> <span class="o">&&</span> <span class="o">!</span><span class="n">path</span><span class="o">.</span><span class="na">endsWith</span><span class="o">(</span><span class="s">".jar"</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">try</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">dexoptNeeded</span> <span class="o">=</span> <span class="n">DexFile</span><span class="o">.</span><span class="na">getDexOptNeeded</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">dexCodeInstructionSet</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">dexoptNeeded</span> <span class="o">!=</span> <span class="n">DexFile</span><span class="o">.</span><span class="na">NO_DEXOPT_NEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mInstaller</span><span class="o">.</span><span class="na">dexopt</span><span class="o">(</span><span class="n">path</span><span class="o">,</span> <span class="n">Process</span><span class="o">.</span><span class="na">SYSTEM_UID</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">dexCodeInstructionSet</span><span class="o">,</span> <span class="n">dexoptNeeded</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span><span class="o">(...)</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 2. Android M的APK授权机制有了变化,此处是与授权相关的版本兼容处理</span>
<span class="kd">final</span> <span class="n">VersionInfo</span> <span class="n">ver</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getInternalVersion</span><span class="o">();</span>
<span class="n">mIsUpgrade</span> <span class="o">=</span> <span class="o">!</span><span class="n">Build</span><span class="o">.</span><span class="na">FINGERPRINT</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">ver</span><span class="o">.</span><span class="na">fingerprint</span><span class="o">);</span>
<span class="n">mPromoteSystemApps</span> <span class="o">=</span>
<span class="n">mIsUpgrade</span> <span class="o">&&</span> <span class="n">ver</span><span class="o">.</span><span class="na">sdkVersion</span> <span class="o"><=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">LOLLIPOP_MR1</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mPromoteSystemApps</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Iterator</span><span class="o"><</span><span class="n">PackageSetting</span><span class="o">></span> <span class="n">pkgSettingIter</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">values</span><span class="o">().</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span> <span class="o">(</span><span class="n">pkgSettingIter</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
<span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">pkgSettingIter</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">))</span> <span class="o">{</span>
<span class="n">mExistingSystemPackages</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 3. 扫描系统文件</span>
<span class="n">File</span> <span class="n">vendorOverlayDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">VENDOR_OVERLAY_DIR</span><span class="o">);</span>
<span class="c1">// 扫描 /vendor/overlay 目录下的文件</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">vendorOverlayDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span><span class="o">,</span> <span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_TRUSTED_OVERLAY</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 扫描 /system/framework 目录下的文件</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">frameworkDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_PRIVILEGED</span><span class="o">,</span>
<span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_NO_DEX</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 扫描 /system/priv-app 目录下的文件</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">privilegedAppDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getRootDirectory</span><span class="o">(),</span> <span class="s">"priv-app"</span><span class="o">);</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">privilegedAppDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_PRIVILEGED</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 扫描 /system/app 目录下的文件</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">systemAppDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getRootDirectory</span><span class="o">(),</span> <span class="s">"app"</span><span class="o">);</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">systemAppDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 扫描 /vendor/app 目录下的文件</span>
<span class="n">File</span> <span class="n">vendorAppDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s">"/vendor/app"</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">vendorAppDir</span> <span class="o">=</span> <span class="n">vendorAppDir</span><span class="o">.</span><span class="na">getCanonicalFile</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{}</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">vendorAppDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 扫描 /oem/app 目录下的文件</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">oemAppDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getOemDirectory</span><span class="o">(),</span> <span class="s">"app"</span><span class="o">);</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">oemAppDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">mInstaller</span><span class="o">.</span><span class="na">moveFiles</span><span class="o">();</span>
<span class="c1">// 4. 对扫描到的系统文件善后处理</span>
<span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">possiblyDeletedUpdatedSystemApps</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">String</span><span class="o">>();</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mOnlyCore</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Iterator</span><span class="o"><</span><span class="n">PackageSetting</span><span class="o">></span> <span class="n">psit</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">values</span><span class="o">().</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span> <span class="o">(</span><span class="n">psit</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
<span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">psit</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="k">if</span> <span class="o">((</span><span class="n">ps</span><span class="o">.</span><span class="na">pkgFlags</span> <span class="o">&</span> <span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</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="kd">final</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">scannedPkg</span> <span class="o">=</span> <span class="n">mPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">scannedPkg</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">mSettings</span><span class="o">.</span><span class="na">isDisabledSystemPackageLPr</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">removePackageLI</span><span class="o">(</span><span class="n">ps</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">mExpectingBetter</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">codePath</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">mSettings</span><span class="o">.</span><span class="na">isDisabledSystemPackageLPr</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">psit</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
<span class="n">removeDataDirsLI</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">PackageSetting</span> <span class="n">disabledPs</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getDisabledSystemPkgLPr</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">disabledPs</span><span class="o">.</span><span class="na">codePath</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">disabledPs</span><span class="o">.</span><span class="na">codePath</span><span class="o">.</span><span class="na">exists</span><span class="o">())</span> <span class="o">{</span>
<span class="n">possiblyDeletedUpdatedSystemApps</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">ArrayList</span><span class="o"><</span><span class="n">PackageSetting</span><span class="o">></span> <span class="n">deletePkgsList</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getListOfIncompleteInstallPackagesLPr</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">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">deletePkgsList</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">cleanupInstallFailedPackage</span><span class="o">(</span><span class="n">deletePkgsList</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="o">}</span>
<span class="n">deleteTempPackageFiles</span><span class="o">();</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">pruneSharedUsersLPw</span><span class="o">();</span>
<span class="c1">// 未完接代码片段3</span>
</code></pre></div></div>
<p>【代码片段2】的主体逻辑如下:</p>
<ol>
<li>
<p>通过不断往alreadyDexOpted数组中填充元素,来略过不需要做DexOpt的文件:BOOTCLASSPATH和SYSTEMSERVERPATH这两个环境变量中定义的文件、system/framework-res.apk、system/core-libart.jar。除略过的文件外,其他APK和JAR文件都是需要做DexOpt处理的,通过调用<strong>Installer.dexopt()</strong>函数完成,这个函数只是将dexopt命令发送给installd。</p>
</li>
<li>
<p>由于Android M的APK授权机制发生了变化,在扫描系统文件之前,做了一些简单的记录,以便后续的授权处理:</p>
<ul>
<li><strong>mIsUpgrade</strong>:如果当前版本的指纹与历史版本的指纹信息不一致,表示当前版本是一次OTA升级上来更新版本</li>
<li><strong>mPromoteSystemApps</strong>:如果历史版本是Android M之前的版本(ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1),当前又有版本升级,则需要用一个布尔变量,表示当前需要对系统应用的授权做特殊处理,此时会先把已有的系统应用都保存在<strong>mExistingSystemPackages</strong>这个数组中</li>
</ul>
</li>
<li>
<p>扫描系统文件,PMS中所有的文件扫描都是调用<strong>scanDirLI()</strong>函数,扫描系统文件重要的参数就是 <strong>PackageParser.PARSE_IS_SYSTEM</strong>和<strong>PackageParser.PARSE_IS_SYSTEM_DIR</strong>,在后文中我们会剖析这个函数。此处,需要注意的是被扫描目录的顺序,这个顺序意味着:先被扫描到的文件,就是最终被用到的文件。</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /vendor/overlay >> /system/framework >> /system/priv-app >> /system/app >> /vendor/app >> /oem/app
</code></pre></div> </div>
</li>
<li>
<p><strong>possiblyDeletedUpdatedSystemApps</strong>这个变量表示“可能被删除的系统APP”,这是一个什么概念呢?除了<strong>possiblyDeletedUpdatedSystemApps</strong>,还有<strong>mExpectingBetter</strong>,表示当前这个APK有更好的选择,这又是什么概念呢?对于一个系统APP而言,在一次OTA升级的过程中,有三种可能:</p>
<ul>
<li>保持原状。即这个系统APP没有任何更新。</li>
<li>更新版本。即新的OTA版本中,这个系统APP有更新。</li>
<li>不复存在。在新的OTA版本中已经删除了这个系统APP。</li>
</ul>
<p>当系统APP升级过后,PMS的Settings中会将原来的系统APP标识为Disable状态,这时候通过<strong>Settings.isDisabledSystemPackageLPr()</strong>函数调用便返回了false。因此,如果系统APP有更新版本,则属于<strong>mExpectingBetter</strong>这一类,接下来会扫描Data分区的文件,更新的系统APP就安装在Data分区。</p>
<p>如果一个系统APP不复存在,而且也没有被标记为Disable状态,说明这个系统APP已经彻底不存在了,需要把其在Data分区下的数据删除;如果不复存在的系统APP被标记为Disable状态,那还不能确定该系统APP是否已经被删除,因为还没有扫描Data分区的文件,所以,只能暂时将其放到<strong>possiblyDeletedUpdatedSystemApps</strong>变量中,表示“可能被删除”,在扫描Data分区之前,这是不能确定的。</p>
</li>
</ol>
<p>扫描完系统文件之后,接下来会扫描Data分区的文件。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 代码片段3:扫描Data分区文件,更新公共库信息</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mOnlyCore</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 关键日志,PMS对Data分区的文件扫描开始了</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_PMS_DATA_SCAN_START</span><span class="o">,</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">());</span>
<span class="c1">// 1. 扫描Data分区的文件目录</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">mAppInstallDir</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_REQUIRE_KNOWN</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">scanDirLI</span><span class="o">(</span><span class="n">mDrmAppPrivateInstallDir</span><span class="o">,</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_FORWARD_LOCK</span><span class="o">,</span>
<span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_REQUIRE_KNOWN</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="c1">// 2. 扫描完Data分区后,处理“可能被删除的系统应用”</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">deletedAppName</span> <span class="o">:</span> <span class="n">possiblyDeletedUpdatedSystemApps</span><span class="o">)</span> <span class="o">{</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">deletedPkg</span> <span class="o">=</span> <span class="n">mPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">deletedAppName</span><span class="o">);</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">removeDisabledSystemPackageLPw</span><span class="o">(</span><span class="n">deletedAppName</span><span class="o">);</span>
<span class="n">String</span> <span class="n">msg</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">deletedPkg</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">removeDataDirsLI</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="n">deletedAppName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">deletedPkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">flags</span> <span class="o">&=</span> <span class="o">~</span><span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">;</span>
<span class="n">PackageSetting</span> <span class="n">deletedPs</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">deletedAppName</span><span class="o">);</span>
<span class="n">deletedPs</span><span class="o">.</span><span class="na">pkgFlags</span> <span class="o">&=</span> <span class="o">~</span><span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 3. 处理有版本更新的系统应用</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">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">mExpectingBetter</span><span class="o">.</span><span class="na">size</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="n">String</span> <span class="n">packageName</span> <span class="o">=</span> <span class="n">mExpectingBetter</span><span class="o">.</span><span class="na">keyAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mPackages</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">scanFile</span> <span class="o">=</span> <span class="n">mExpectingBetter</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">reparseFlags</span><span class="o">;</span>
<span class="c1">// 设置重新扫描的解析参数</span>
<span class="k">if</span> <span class="o">(</span><span class="n">FileUtils</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">privilegedAppDir</span><span class="o">,</span> <span class="n">scanFile</span><span class="o">))</span> <span class="o">{</span>
<span class="n">reparseFlags</span> <span class="o">=</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span>
<span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_PRIVILEGED</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{...}</span>
<span class="c1">// 将原来的系统应用重新置为Enable状态</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">enableSystemPackageLPw</span><span class="o">(</span><span class="n">packageName</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">scanPackageLI</span><span class="o">(</span><span class="n">scanFile</span><span class="o">,</span> <span class="n">reparseFlags</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageManagerException</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="o">}</span>
<span class="o">}</span> <span class="c1">// end of "if (!mOnlyCore)"</span>
<span class="n">mExpectingBetter</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="c1">// 4. 处理公共库</span>
<span class="n">updateAllSharedLibrariesLPw</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">SharedUserSetting</span> <span class="n">setting</span> <span class="o">:</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getAllSharedUsersLPw</span><span class="o">())</span> <span class="o">{</span>
<span class="n">adjustCpuAbisForSharedUserLPw</span><span class="o">(</span><span class="n">setting</span><span class="o">.</span><span class="na">packages</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* scanned package */</span><span class="o">,</span>
<span class="kc">false</span> <span class="cm">/* force dexopt */</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* defer dexopt */</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mPackageUsage</span><span class="o">.</span><span class="na">readLP</span><span class="o">();</span>
<span class="c1">// 关键日志,PMS扫描结束了</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_PMS_SCAN_END</span><span class="o">,</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">());</span>
<span class="c1">// 未完接代码片段4</span>
</code></pre></div></div>
<p>Data分区文件的扫描都被<strong>mOnlyCore</strong>这个布尔变量笼罩,当其为true时,表示只需要扫描系统文件;当其为false时,才会扫描Data分区文件。【代码片段3】的主体逻辑如下:</p>
<ol>
<li>
<p>调用<strong>PMS.scanDirLI()</strong>函数扫描 <strong>/data/app</strong> 和 <strong>/data/app-private</strong>两个目录的文件。后文会详细剖析该函数。</p>
</li>
<li>
<p>扫描完Data分区的文件后,需要对之前系统文件的余孽做一些处理,第一类是<strong>possiblyDeletedUpdatedSystemApps</strong>,因为在扫描Data分区文件之前,不能确定系统应用有没有被彻底删除,如果在Data分区也无法找到了不复存在的系统应用,则需要彻底删除该系统应用;如果在Data分区找到了不复存在的系统应用,则需要去除其系统应用的标识。</p>
</li>
<li>
<p>另外一类系统应用的余孽是<strong>mExpectingBetter</strong>,表示系统应用已经升级过。如果在Data分区无法找到这些升级过的系统应用,那很可能是用户在OTA升级时,清除了Data分区的数据,对于这种场景,需要重新扫描一下该应用原来位于系统分区的文件。</p>
</li>
<li>
<p>SystemConfig中定义了公共库,在APK的AndroidManifest.xml文件中,会通过<use-library>标签标记该APK动态依赖的公共库,此处的逻辑就是将APK与SystemConfig中的公共库关联起来。如果APK使用的公共库并不存在,则会抛出异常(INSTALL_FAILED_MISSING_SHARED_LIBRARY)。</p>
</li>
</ol>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 代码片段4:收尾工作</span>
<span class="c1">// 授权</span>
<span class="kt">int</span> <span class="n">updateFlags</span> <span class="o">=</span> <span class="n">UPDATE_PERMISSIONS_ALL</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ver</span><span class="o">.</span><span class="na">sdkVersion</span> <span class="o">!=</span> <span class="n">mSdkVersion</span><span class="o">)</span> <span class="o">{</span>
<span class="n">updateFlags</span> <span class="o">|=</span> <span class="n">UPDATE_PERMISSIONS_REPLACE_PKG</span> <span class="o">|</span> <span class="n">UPDATE_PERMISSIONS_REPLACE_ALL</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">updatePermissionsLPw</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">updateFlags</span><span class="o">);</span>
<span class="n">ver</span><span class="o">.</span><span class="na">sdkVersion</span> <span class="o">=</span> <span class="n">mSdkVersion</span><span class="o">;</span>
<span class="c1">// 多用户场景下的版本兼容处理</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">onlyCore</span> <span class="o">&&</span> <span class="o">(</span><span class="n">mPromoteSystemApps</span> <span class="o">||</span> <span class="o">!</span><span class="n">mRestoredSettings</span><span class="o">))</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">UserInfo</span> <span class="n">user</span> <span class="o">:</span> <span class="n">sUserManager</span><span class="o">.</span><span class="na">getUsers</span><span class="o">(</span><span class="kc">true</span><span class="o">))</span> <span class="o">{</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">applyDefaultPreferredAppsLPw</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">);</span>
<span class="n">applyFactoryDefaultBrowserLPw</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">);</span>
<span class="n">primeDomainVerificationsLPw</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 如果是升级新版本,则需要清除已有的Code cache目录</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mIsUpgrade</span> <span class="o">&&</span> <span class="o">!</span><span class="n">onlyCore</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">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">size</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="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">StorageManager</span><span class="o">.</span><span class="na">UUID_PRIVATE_INTERNAL</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">))</span> <span class="o">{</span>
<span class="n">deleteCodeCacheDirsLI</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">ver</span><span class="o">.</span><span class="na">fingerprint</span> <span class="o">=</span> <span class="n">Build</span><span class="o">.</span><span class="na">FINGERPRINT</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">checkDefaultBrowser</span><span class="o">();</span>
<span class="n">mExistingSystemPackages</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="n">mPromoteSystemApps</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">ver</span><span class="o">.</span><span class="na">databaseVersion</span> <span class="o">=</span> <span class="n">Settings</span><span class="o">.</span><span class="na">CURRENT_DATABASE_VERSION</span><span class="o">;</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">writeLPr</span><span class="o">();</span>
<span class="c1">// 关键日志,PMS已经启动完毕了</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_PMS_READY</span><span class="o">,</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">());</span>
<span class="n">mRequiredVerifierPackage</span> <span class="o">=</span> <span class="n">getRequiredVerifierLPr</span><span class="o">();</span>
<span class="n">mRequiredInstallerPackage</span> <span class="o">=</span> <span class="n">getRequiredInstallerLPr</span><span class="o">();</span>
<span class="c1">// 初始化包安装服务</span>
<span class="n">mInstallerService</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageInstallerService</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="n">mIntentFilterVerifierComponent</span> <span class="o">=</span> <span class="n">getIntentFilterVerifierComponentNameLPr</span><span class="o">();</span>
<span class="n">mIntentFilterVerifier</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IntentVerifierProxy</span><span class="o">(</span><span class="n">mContext</span><span class="o">,</span>
<span class="n">mIntentFilterVerifierComponent</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// synchronized (mPackages)</span>
<span class="o">}</span> <span class="c1">// synchronized (mInstallLock)</span>
<span class="n">Runtime</span><span class="o">.</span><span class="na">getRuntime</span><span class="o">().</span><span class="na">gc</span><span class="o">();</span>
<span class="n">LocalServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="n">PackageManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">new</span> <span class="n">PackageManagerInternalImpl</span><span class="o">());</span>
<span class="o">}</span> <span class="c1">// PMS构造函数完结</span>
</code></pre></div></div>
<p>【代码片段4】主要进行一些收尾工作,有几个关键点:</p>
<ul>
<li>授权,通过调用<strong>PMS.updatePermissionsLPw()</strong>函数,后文会详细分析</li>
<li>版本兼容处理,Android M引入了多用户,需要更新每个APK关联到的userid</li>
<li>将PMS的Settings信息写入<strong>/system/packages.xml</strong>文件中,Settings是PMS中所有包信息的汇总的数据结构,PMS对包的管理极其依赖于这个数据结构。</li>
<li>初始化包安装服务PackageInstallerService。</li>
</ul>
<p><strong>至此,PMS对象的构建过程已经分析完毕,整个逻辑还是较为清晰的,但其实这是一个非常耗时的过程,开机时间大部分都耗在文件扫描上。</strong></p>
<h2 id="32-文件扫描">3.2 文件扫描</h2>
<p><strong>scanDirLI()</strong>只是文件扫描的起点,由此引发出一串的与文件扫描相关的函数。</p>
<div align="center"><img src="/assets/images/packagemanager/5-packagemanager-scan-seq.png" alt="Package Scan Sequence Diagram" /></div>
<p>接下来,笔者会按照调用时序,对关键函数进行分析。PMS对象构建时,待扫描的目录有很多,不同目录的文件扫描,只是在扫描参数上略有区别,整体逻辑上并无不同。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">scanDirLI</span><span class="o">(</span><span class="n">File</span> <span class="n">dir</span><span class="o">,</span> <span class="kt">int</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="kt">long</span> <span class="n">currentTime</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">File</span><span class="o">[]</span> <span class="n">files</span> <span class="o">=</span> <span class="n">dir</span><span class="o">.</span><span class="na">listFiles</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ArrayUtils</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">(</span><span class="n">files</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">for</span> <span class="o">(</span><span class="n">File</span> <span class="n">file</span> <span class="o">:</span> <span class="n">files</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isPackage</span> <span class="o">=</span> <span class="o">(</span><span class="n">isApkFile</span><span class="o">(</span><span class="n">file</span><span class="o">)</span> <span class="o">||</span> <span class="n">file</span><span class="o">.</span><span class="na">isDirectory</span><span class="o">())</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">PackageInstallerService</span><span class="o">.</span><span class="na">isStageName</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isPackage</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">try</span> <span class="o">{</span>
<span class="n">scanPackageLI</span><span class="o">(</span><span class="n">file</span><span class="o">,</span> <span class="n">parseFlags</span> <span class="o">|</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_MUST_BE_APK</span><span class="o">,</span>
<span class="n">scanFlags</span><span class="o">,</span> <span class="n">currentTime</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageManagerException</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">parseFlags</span> <span class="o">&</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span>
<span class="n">e</span><span class="o">.</span><span class="na">error</span> <span class="o">==</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FAILED_INVALID_APK</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果扫描Data分区的APK失败,则删除Data分区扫描失败的文件</span>
<span class="k">if</span> <span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">isDirectory</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mInstaller</span><span class="o">.</span><span class="na">rmPackageDir</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">file</span><span class="o">.</span><span class="na">delete</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>
</code></pre></div></div>
<p>对于待扫描目录<strong>/system/priv-app</strong>,该函数会针对每一个子目录调用<strong>PMS.scanPackageLI()</strong>函数,限定了解析参数<strong>PackageParser.PARSE_MUST_BE_APK</strong>,表示只解析APK文件。这个函数如果抛出异常,则说明解析出错,如果是扫描Data分区的APK出错,则需要删除扫描失败的文件。</p>
<p>下面,就开始扫描APK文件了:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="nf">scanPackageLI</span><span class="o">(</span><span class="n">File</span> <span class="n">scanFile</span><span class="o">,</span> <span class="kt">int</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">scanFlags</span><span class="o">,</span>
<span class="kt">long</span> <span class="n">currentTime</span><span class="o">,</span> <span class="n">UserHandle</span> <span class="n">user</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">PackageManagerException</span> <span class="o">{</span>
<span class="n">parseFlags</span> <span class="o">|=</span> <span class="n">mDefParseFlags</span><span class="o">;</span>
<span class="c1">// 初始化PackageParser对象,用于解析包</span>
<span class="n">PackageParser</span> <span class="n">pp</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageParser</span><span class="o">();</span>
<span class="n">pp</span><span class="o">.</span><span class="na">setSeparateProcesses</span><span class="o">(</span><span class="n">mSeparateProcesses</span><span class="o">);</span>
<span class="n">pp</span><span class="o">.</span><span class="na">setOnlyCoreApps</span><span class="o">(</span><span class="n">mOnlyCore</span><span class="o">);</span>
<span class="n">pp</span><span class="o">.</span><span class="na">setDisplayMetrics</span><span class="o">(</span><span class="n">mMetrics</span><span class="o">);</span>
<span class="k">if</span> <span class="o">((</span><span class="n">scanFlags</span> <span class="o">&</span> <span class="n">SCAN_TRUSTED_OVERLAY</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">parseFlags</span> <span class="o">|=</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_TRUSTED_OVERLAY</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 2. 解析包得到一个PackageParser.Package对象</span>
<span class="n">pkg</span> <span class="o">=</span> <span class="n">pp</span><span class="o">.</span><span class="na">parsePackage</span><span class="o">(</span><span class="n">scanFile</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span><span class="o">(...)</span>
<span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">PackageSetting</span> <span class="n">updatedPkg</span><span class="o">;</span>
<span class="c1">// 3. 判定系统APK是否需要更新</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">oldName</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mRenamedPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">mOriginalPackages</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">pkg</span><span class="o">.</span><span class="na">mOriginalPackages</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">oldName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">peekPackageLPr</span><span class="o">(</span><span class="n">oldName</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ps</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ps</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">peekPackageLPr</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">updatedPkg</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getDisabledSystemPkgLPr</span><span class="o">(</span><span class="n">ps</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">ps</span><span class="o">.</span><span class="na">name</span> <span class="o">:</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="o">}</span>
<span class="kt">boolean</span> <span class="n">updatedPkgBetter</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">updatedPkg</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">(</span><span class="n">parseFlags</span><span class="o">&</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</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">ps</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">!</span><span class="n">ps</span><span class="o">.</span><span class="na">codePath</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">scanFile</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">mVersionCode</span> <span class="o"><=</span> <span class="n">ps</span><span class="o">.</span><span class="na">versionCode</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">PackageManagerException</span><span class="o">(</span><span class="n">INSTALL_FAILED_DUPLICATE_PACKAGE</span><span class="o">,</span>
<span class="s">"Package "</span> <span class="o">+</span> <span class="n">ps</span><span class="o">.</span><span class="na">name</span> <span class="o">+</span> <span class="s">" at "</span> <span class="o">+</span> <span class="n">scanFile</span>
<span class="o">+</span> <span class="s">" ignored: updated version "</span> <span class="o">+</span> <span class="n">ps</span><span class="o">.</span><span class="na">versionCode</span>
<span class="o">+</span> <span class="s">" better than this "</span> <span class="o">+</span> <span class="n">pkg</span><span class="o">.</span><span class="na">mVersionCode</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">InstallArgs</span> <span class="n">args</span> <span class="o">=</span> <span class="n">createInstallArgsForExisting</span><span class="o">(</span><span class="n">packageFlagsToInstallFlags</span><span class="o">(</span><span class="n">ps</span><span class="o">),</span>
<span class="n">ps</span><span class="o">.</span><span class="na">codePathString</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">resourcePathString</span><span class="o">,</span> <span class="n">getAppDexInstructionSets</span><span class="o">(</span><span class="n">ps</span><span class="o">));</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mInstallLock</span><span class="o">)</span> <span class="o">{</span>
<span class="n">args</span><span class="o">.</span><span class="na">cleanUpResourcesLI</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">enableSystemPackageLPw</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">updatedPkgBetter</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="o">...</span>
<span class="c1">// 4. 获取APK的签名信息</span>
<span class="n">collectCertificatesLI</span><span class="o">(</span><span class="n">pp</span><span class="o">,</span> <span class="n">ps</span><span class="o">,</span> <span class="n">pkg</span><span class="o">,</span> <span class="n">scanFile</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">);</span>
<span class="c1">// 5. 在Data分区存在一个应用,与当前扫描的系统APK包名相同</span>
<span class="kt">boolean</span> <span class="n">shouldHideSystemApp</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">updatedPkg</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">ps</span> <span class="o">!=</span> <span class="kc">null</span>
<span class="o">&&</span> <span class="o">(</span><span class="n">parseFlags</span> <span class="o">&</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">PARSE_IS_SYSTEM_DIR</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="o">!</span><span class="n">isSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">compareSignatures</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">signatures</span><span class="o">.</span><span class="na">mSignatures</span><span class="o">,</span> <span class="n">pkg</span><span class="o">.</span><span class="na">mSignatures</span><span class="o">)</span>
<span class="o">!=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">SIGNATURE_MATCH</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 签名不匹配</span>
<span class="n">deletePackageLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">true</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="mi">0</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="n">ps</span> <span class="o">=</span> <span class="kc">null</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">pkg</span><span class="o">.</span><span class="na">mVersionCode</span> <span class="o"><=</span> <span class="n">ps</span><span class="o">.</span><span class="na">versionCode</span><span class="o">)</span> <span class="o">{</span>
<span class="n">shouldHideSystemApp</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="n">InstallArgs</span> <span class="n">args</span> <span class="o">=</span> <span class="n">createInstallArgsForExisting</span><span class="o">(</span><span class="n">packageFlagsToInstallFlags</span><span class="o">(</span><span class="n">ps</span><span class="o">),</span>
<span class="n">ps</span><span class="o">.</span><span class="na">codePathString</span><span class="o">,</span> <span class="n">ps</span><span class="o">.</span><span class="na">resourcePathString</span><span class="o">,</span> <span class="n">getAppDexInstructionSets</span><span class="o">(</span><span class="n">ps</span><span class="o">));</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mInstallLock</span><span class="o">)</span> <span class="o">{</span>
<span class="n">args</span><span class="o">.</span><span class="na">cleanUpResourcesLI</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="c1">// 6. 调用另外一个scanPackageLI()函数,对包进行扫描</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">scannedPkg</span> <span class="o">=</span> <span class="n">scanPackageLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="n">scanFlags</span>
<span class="o">|</span> <span class="n">SCAN_UPDATE_SIGNATURE</span><span class="o">,</span> <span class="n">currentTime</span><span class="o">,</span> <span class="n">user</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">shouldHideSystemApp</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">disableSystemPackageLPw</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">scannedPkg</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ol>
<li>
<p>初始化包解析器<strong>PackageParser</strong>,在前文中,我们已经见识过它了,现在我们知道,包解析器是在PMS扫描文件时构建的;</p>
</li>
<li>
<p>有了包解析器,对静态的APK文件进行解析,最终就是为了得一个包在内存中的数据结构<strong>Package</strong>,我们已经前文中提前见到了这个结构的全貌,一个包的所有信息都在其中;</p>
</li>
<li>
<p>系统应用升级后会安装在Data分区,之前在System分区的应用会被标记为Disable状态。这些状态信息记录在PMS的Settings中,需要通过包名获取。包解析完成以后,就能得到一个APK的包名了。这一部分代码中有两个变量:pkg是Package类型,表示当前扫描的APK;ps是PackageSettings类型,表示PMS的Settings中保存的APK信息,即上一次安装的APK信息。通过比对这两个变量,就能知道当前扫描的APK与已经安装的历史APK的差异,如果当前扫描的系统APK版本比已经安装的系统APK版本要高,这就需要重新将系统APK设置为Enable状态;否则,中断扫描过程,抛出异常;</p>
</li>
<li>
<p>之前构建<strong>Package</strong>对象时,还没有APK的签名信息,现在正是把APK签名信息填进去的时候,因为到这一步已经确定要安装APK了,APK能安装的前提就是一定要有签名信息;如果是对已有APK进行升级,那签名必须与已有APK匹配。<strong>PMS.collectCertificatesLI()</strong>函数就是从APK包中<strong>META-INF</strong>目录读取签名信息;</p>
</li>
<li>
<p>处理系统APK已经被安装过的场景,已经被安装过的APK位于Data分区。<strong>shouldHideSystemApp</strong>表示是否需要将系统APK设置为Disable状态,默认情况下为false;如果安装过的APK的版本比当前扫描的系统APK的版本要高,则意味着要使用Data分区的APK,隐藏系统APK,<strong>shouldHideSystemApp</strong>被置为true;</p>
</li>
<li>
<p>到这一步,已经通过包解析器完成了对APK文件的解析,并且做了一些安装场景的判断。接下来,需要对解析出来的<strong>Package</strong>进行处理,这交由另外一个<strong>scanPackageLI()</strong>函数完成。</p>
</li>
</ol>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="nf">scanPackageLI</span><span class="o">(</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">,</span> <span class="kt">int</span> <span class="n">parseFlags</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="kt">long</span> <span class="n">currentTime</span><span class="o">,</span> <span class="n">UserHandle</span> <span class="n">user</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">PackageManagerException</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">success</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="kd">final</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">res</span> <span class="o">=</span> <span class="n">scanPackageDirtyLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span>
<span class="n">currentTime</span><span class="o">,</span> <span class="n">user</span><span class="o">);</span>
<span class="n">success</span> <span class="o">=</span> <span class="kc">true</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="k">finally</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">success</span> <span class="o">&&</span> <span class="o">(</span><span class="n">scanFlags</span> <span class="o">&</span> <span class="n">SCAN_DELETE_DATA_ON_FAILURES</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">removeDataDirsLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">,</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>以上函数就是做了一层封装调用,如果扫描失败,且SCAN_DELETE_DATA_ON_FAILURES标志位被置上,则需要删除Data分区的APK文件。真正干活的是<strong>scanPackageDirtyLI()</strong>函数,这个函数逻辑非常庞大,随着Andorid版本的升级,函数体也越来越庞大,笔者仅仅挑出几个主干逻辑进行分析,省略大量分支逻辑:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="nf">scanPackageDirtyLI</span><span class="o">(</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">,</span> <span class="kt">int</span> <span class="n">parseFlags</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">scanFlags</span><span class="o">,</span> <span class="kt">long</span> <span class="n">currentTime</span><span class="o">,</span> <span class="n">UserHandle</span> <span class="n">user</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">PackageManagerException</span> <span class="o">{</span>
<span class="o">...</span>
<span class="c1">// 1. 针对包名为“android”的APK进行处理</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"android"</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">mPlatformPackage</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">;</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">mVersionCode</span> <span class="o">=</span> <span class="n">mSdkVersion</span><span class="o">;</span>
<span class="n">mAndroidApplication</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">;</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 2. 锁上mPacakges对象,意味着要对这个数据结构进行写操作,里面保存的就是已经解析出来的包信息</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果有定义ShareUserId,则创建一个ShareUserSetting对象</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">mSharedUserId</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">suid</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getSharedUserLPw</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">mSharedUserId</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">0</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">suid</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">PackageManagerException</span><span class="o">(</span><span class="n">INSTALL_FAILED_INSUFFICIENT_STORAGE</span><span class="o">,</span>
<span class="s">"Creating application package "</span> <span class="o">+</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span>
<span class="o">+</span> <span class="s">" for shared user failed"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 生成PackageSetting对象,对应的数据结构将序列化在/data/system/packags.xml文件中</span>
<span class="n">pkgSetting</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">getPackageLPw</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">origPackage</span><span class="o">,</span> <span class="n">realName</span><span class="o">,</span> <span class="n">suid</span><span class="o">,</span> <span class="n">destCodeFile</span><span class="o">,</span>
<span class="n">destResourceFile</span><span class="o">,</span> <span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">nativeLibraryRootDir</span><span class="o">,</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">primaryCpuAbi</span><span class="o">,</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">secondaryCpuAbi</span><span class="o">,</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">flags</span><span class="o">,</span> <span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">privateFlags</span><span class="o">,</span>
<span class="n">user</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">...</span>
<span class="c1">// 打上SELinux标签</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mFoundPolicyFile</span><span class="o">)</span> <span class="o">{</span>
<span class="n">SELinuxMMAC</span><span class="o">.</span><span class="na">assignSeinfoValue</span><span class="o">(</span><span class="n">pkg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 签名验证</span>
<span class="k">if</span> <span class="o">(</span><span class="n">shouldCheckUpgradeKeySetLP</span><span class="o">(</span><span class="n">pkgSetting</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">checkUpgradeKeySetLP</span><span class="o">(</span><span class="n">pkgSetting</span><span class="o">,</span> <span class="n">pkg</span><span class="o">))</span> <span class="o">{</span>
<span class="n">pkgSetting</span><span class="o">.</span><span class="na">signatures</span><span class="o">.</span><span class="na">mSignatures</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">mSignatures</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">verifySignaturesLP</span><span class="o">(</span><span class="n">pkgSetting</span><span class="o">,</span> <span class="n">pkg</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">fixProcessName</span><span class="o">(</span>
<span class="n">pkg</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">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span>
<span class="n">pkg</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="c1">// 3. 生成APK的data目录</span>
<span class="n">File</span> <span class="n">dataPath</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mPlatformPackage</span> <span class="o">==</span> <span class="n">pkg</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// framework-res.apk的data目录是 /data/system</span>
<span class="n">dataPath</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">Environment</span><span class="o">.</span><span class="na">getDataDirectory</span><span class="o">(),</span> <span class="s">"system"</span><span class="o">);</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">dataDir</span> <span class="o">=</span> <span class="n">dataPath</span><span class="o">.</span><span class="na">getPath</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 其他APK的data目录是 /data/data/packageName</span>
<span class="n">dataPath</span> <span class="o">=</span> <span class="n">Environment</span><span class="o">.</span><span class="na">getDataUserPackageDirectory</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">,</span>
<span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">,</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">uidError</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">dataPath</span><span class="o">.</span><span class="na">exists</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// APK的data目录已经存在</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// APK的data目录不存在,则通过Installd创建之</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">createDataDirsLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">,</span> <span class="n">pkgName</span><span class="o">,</span> <span class="n">pkg</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="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">seinfo</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ret</span> <span class="o"><</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">PackageManagerException</span><span class="o">(</span><span class="n">INSTALL_FAILED_INSUFFICIENT_STORAGE</span><span class="o">,</span>
<span class="s">"Unable to create data dirs [errorCode="</span> <span class="o">+</span> <span class="n">ret</span> <span class="o">+</span> <span class="s">"]"</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="c1">// 4. 设置Native Library</span>
<span class="k">if</span> <span class="o">((</span><span class="n">scanFlags</span> <span class="o">&</span> <span class="n">SCAN_NEW_INSTALL</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">derivePackageAbi</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">scanFile</span><span class="o">,</span> <span class="n">cpuAbiOverride</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/* extract libs */</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isSystemApp</span><span class="o">(</span><span class="n">pkg</span><span class="o">)</span> <span class="o">&&</span> <span class="o">!</span><span class="n">pkg</span><span class="o">.</span><span class="na">isUpdatedSystemApp</span><span class="o">()</span> <span class="o">&&</span>
<span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">primaryCpuAbi</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setBundledAppAbisAndRoots</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">pkgSetting</span><span class="o">);</span>
<span class="n">setNativeLibraryPaths</span><span class="o">(</span><span class="n">pkg</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="n">setNativeLibraryPaths</span><span class="o">(</span><span class="n">pkg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 5. 填充PMS中的数据</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">insertPackageSettingLPw</span><span class="o">(</span><span class="n">pkgSetting</span><span class="o">,</span> <span class="n">pkg</span><span class="o">);</span>
<span class="n">mPackages</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">pkg</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">pkg</span><span class="o">);</span>
<span class="o">...</span>
<span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">providers</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Provider</span> <span class="n">p</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">providers</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="n">p</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">fixProcessName</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span>
<span class="n">p</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">pkg</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="n">mProviders</span><span class="o">.</span><span class="na">addProvider</span><span class="o">(</span><span class="n">p</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">services</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Service</span> <span class="n">s</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">services</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="n">s</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">fixProcessName</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span>
<span class="n">s</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">pkg</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="n">mServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="n">s</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">receivers</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Activity</span> <span class="n">a</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">receivers</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="n">a</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">fixProcessName</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span>
<span class="n">a</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">pkg</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="n">mReceivers</span><span class="o">.</span><span class="na">addActivity</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="s">"receiver"</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">activities</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Activity</span> <span class="n">a</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">activities</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="n">a</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span> <span class="o">=</span> <span class="n">fixProcessName</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span>
<span class="n">a</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">pkg</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="n">mActivities</span><span class="o">.</span><span class="na">addActivity</span><span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="s">"activity"</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">pkg</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<ul>
<li>
<p>1 包名为“android”的APK就是<strong>system/framework/framework-res.apk</strong>,PMS中有几个专门的变量用于保存这个APK的信息:mPlatfromPackage用于保存该APK的<strong>Package</strong>数据结构、mAndroidApplicationInfo用于保存该APK的<strong>ApplicationInfo</strong>。</p>
</li>
<li>
<p>2 当<strong>Package</strong>对象创建以后,就需要将其纳入PMS的管辖范围,PMS有一个<strong>mPackages</strong>对象,保存的是包名到<strong>Package</strong>对象的映射,当一个APK顺利通过扫描过程之后,其<strong>Package</strong>对象便被添加到<strong>mPackages</strong>这个映射表中。在这一步,锁上<strong>mPackages</strong>对象,意味着需要对其相关的内容进行写操作,主要涉及以下方面:</p>
<ul>
<li>
<p>处理ShareUserId,如果APK有定义”android:ShareUserId”,则会为其创建一个ShareUserSetting对象,并将其纳入PMS的mShareUsers中</p>
</li>
<li>
<p>生成PackageSetting对象,每一个包名都会对应一个PacageSetting对象,这个映射关系保存在PMS的<strong>Settings.mPackages</strong>中。PMS的Settings最终会序列化到<strong>/data/system/packages.xml</strong>文件中,前文分析的系统应用升级替换的逻辑,需要获取已经安装应用的信息,就是从<strong>/data/system/packages.xml</strong>文件中读取的</p>
</li>
<li>
<p>打上SELinux标签,这是在Android引入SELinux以后才有的逻辑,每一个的静态的APK文件都会被打上一个SE Label,一个APK该打上什么类型的SE Label是由其签名信息决定的。在前文中,我们提到过<strong>mFoundPolicyFile</strong>就是<strong>mac_permission.xml</strong>文件,在这个文件中,保存了签名到签名类型的映射。笔者在AOSP源码上编译生成的 <strong>out\target\product\generic_arm64\obj\ETC\mac_permissions.xml_intermediates\mac_permissions.xml</strong>文件内容如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><policy></span>
<span class="nt"><signer</span> <span class="na">signature=</span><span class="s">"308204a83082039..."</span><span class="nt">></span>
<span class="nt"><seinfo</span> <span class="na">value=</span><span class="s">"platform"</span><span class="nt">/></span>
<span class="nt"></signer></span>
<span class="nt"><default></span>
<span class="nt"><seinfo</span> <span class="na">value=</span><span class="s">"default"</span><span class="nt">/></span>
<span class="nt"></default></span>
<span class="nt"></policy></span>
</code></pre></div> </div>
<p>其中,signature就是签名,不同签名的值不一样,本例中,该签名的类型就是platform,其他签名的类型都是default。SELinxu在打标签的时候,就是根据seinfo来打上不同的标签。</p>
</li>
</ul>
</li>
<li>
<p>3 生成APK的data目录,framework-res.apk比较特殊,它的data目录位于/data/system/,其他APK的data目录都位于/data/data/packageName下,packageName就是APK的包名,譬如com.yourname.test。</p>
</li>
<li>
<p>4 设置APK依赖的Native库,即so文件的目录。这一块逻辑比较复杂,笔者不予展开,在分析安装APK的过程时,再回过来理解如果处理APK所依赖的so文件。</p>
</li>
<li>
<p>5 该函数的最后一部分,就是填充PMS中的一些数据结构。每一个包的PackageSetting会填充到PMS.mPackages中,activity、provider、service、receiver四大组件的信息会填充PMS对应的数组中,当然还有permission、instrument等会填充到PMS中。</p>
</li>
</ul>
<p><strong>至此,由scanDir()函数引发的一系列包扫描的过程就完成了,通过对包的解析得到一个包在内存中的数据结构Package,然后再对这个数据结构进行加工处理,将所有包的信息汇集到PMS这个包管理中心。</strong></p>
<blockquote>
<p>本文中列举的包文件扫描,仅仅是一个大体的流程,包文件扫描的细节远不止这些,诸如包扫描的参数、特殊的扫描场景都是本文中没有涉及到的。读者在理解PMS的功能定位时,也可以适当地跳出细节,笼统来看,包扫描就是包管理者对管理对象设定的一套认证系统,只有管理对象通过认证,才能被纳入管理范围。同时,包管理者也是一个信息中心,它维护所有包的信息。</p>
</blockquote>
<h2 id="33-应用授权">3.3 应用授权</h2>
<p>包解析过后,PMS的Settings这个数据结构中聚合了所有包的信息以及所有权限信息,此时,需要对所有应用授权。整个授权机制的原理如下图所示:</p>
<div align="center"><img src="/assets/images/packagemanager/6-packagemanager-permission-grant-mechanism.png" alt="Package Permission Grant Mechanism" /></div>
<p>APK在清单文件中声明需要使用的权限,PMS设计了一套授权机制,用于判定是否授权以及授权类型。</p>
<p>Android中,有<strong>权限持有者</strong>和<strong>权限申请者</strong>两个角色,一个Android包可以扮演其中一种角色,或两种角色兼备。持有者通过设计权限来保护接口和数据,申请者如果要访问受保护的接口和数据时,需要事先声明,然后交由包管理者来判断是否要授权。</p>
<p>对于权限持有者而言,可以通过protectionLevel属性定义了权限的受保护级别,其取值主要有以下四种:</p>
<ul>
<li>
<p><strong>normal(0)</strong>: 最普通的一类权限,只要申请使用这一类权限就授予。</p>
</li>
<li>
<p><strong>dangerous(1)</strong>: 较为危险的一类权限,譬如访问联系人、获取位置服务等权限,需要经过用户允许才授予。</p>
</li>
<li>
<p><strong>signature(2)</strong>: 如果申请者与该权限的持有者签名相同,则授予这类权限。</p>
</li>
<li>
<p><strong>signatureOrPrivileged(18)</strong>: 对<strong>singature</strong>的一个补充,权限申请者与持有者签名相同,或者申请者是位于/system/priv-app目录下的应用,则授予这类权限。在早期的Android版本,所有系统应用都位于/system/app目录下,其定义为<strong>signatureOrSystem(3)</strong>,但该定义已经过时了;当Android引入了/system/priv-app目录以后,就将这一类保护级别重新定义为<strong>signatureOrPrivileged(18)</strong>。</p>
</li>
</ul>
<p>除了以上最主要的四类保护级别,android.content.pm.PermissionInfo中还定义了其他保护级别,读者可以自行参考。笔者截取了framework/base/core/res/AndroidManifest.xml文件中的部分权限定义:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"><!-- 读取联系人,权限保护级别为dangerous --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.READ_CONTACTS"</span>
<span class="na">android:permissionGroup=</span><span class="s">"android.permission-group.CONTACTS"</span>
<span class="na">android:label=</span><span class="s">"@string/permlab_readContacts"</span>
<span class="na">android:description=</span><span class="s">"@string/permdesc_readContacts"</span>
<span class="na">android:protectionLevel=</span><span class="s">"dangerous"</span> <span class="nt">/></span>
<span class="c"><!-- 网络访问, 权限保护级别为normal --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.INTERNET"</span>
<span class="na">android:description=</span><span class="s">"@string/permdesc_createNetworkSockets"</span>
<span class="na">android:label=</span><span class="s">"@string/permlab_createNetworkSockets"</span>
<span class="na">android:protectionLevel=</span><span class="s">"normal"</span> <span class="nt">/></span>
<span class="c"><!-- USB管理,权限保护界别为signatureOrPrivileged --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.MANAGE_USB"</span>
<span class="na">android:protectionLevel=</span><span class="s">"signature|privileged"</span> <span class="nt">/></span>
<span class="c"><!-- 账户管理,权限保护级别为signature --></span>
<span class="nt"><permission</span> <span class="na">android:name=</span><span class="s">"android.permission.ACCOUNT_MANAGER"</span>
<span class="na">android:protectionLevel=</span><span class="s">"signature"</span> <span class="nt">/></span>
</code></pre></div></div>
<p>以上这些权限的持有者其实就是<strong>framework-res.apk</strong>,这个APK的签名是<strong>platform</strong>,运行在系统进程(system_process)之中,可以理解为系统权限,几乎所有应用都要申请其中若干项权限。当然,一个应用可以自定义权限,设计其权限保护级别,供其他申请者所用。</p>
<p>有了上面的权限级别限制,就可以理解,部分权限需要申请者满足一定的条件才能被授予,从Android M(6.0)开始,对最终授予的权限进行了分类:</p>
<ul>
<li>
<p><strong>install</strong>:安装时授予的权限。<strong>normal</strong>、<strong>signature</strong>、<strong>signatureOrPrivilege</strong>的授予都属于这一类。</p>
</li>
<li>
<p><strong>runtime</strong>:运行时由用户决定是否授予的权限。在Android M(6.0)以前,<strong>dangerous</strong>的权限属于<strong>install</strong>类型,但从Android M(6.0)以后,<strong>dangerous</strong>的权限改为属于<strong>runtime</strong>一类了。在使用这类权限时,会弹出一个对话框,让用户选择是否授权。</p>
</li>
</ul>
<p><strong>注意</strong>,授权类型是<strong>Android M(6.0)</strong>才引进的,之前只有是否授权的区分。之所以做授权类型的区分,是为了适应多用户使用的场景,<strong>install</strong>类型的授权,对所有用户都是一样的;<strong>runtime</strong>类型的授权,不同用户使用的选择不一样,譬如一个用户在使用的会授予某个应用访问联系人数据的权限,但另一个用户使用时会选择拒绝授权。</p>
<p>在深入分析应用授权的逻辑之前,我们先介绍一下授权机制涉及到的数据结构:</p>
<div align="center"><img src="/assets/images/packagemanager/7-packagemanager-permission-grant-class-diagram.png" alt="Package Permission Grant Class Diagram" /></div>
<ul>
<li>
<p><strong>BasePermission</strong>:权限持有者所定义的每一项权限都会生成一个BasePermission,所有BasePermission都聚合在PMS的Settings中。BasePermission有三种类型: NORMAL、BUILTIN、DYNAMIC。</p>
</li>
<li>
<p><strong>PackageSetting</strong>: 每一个解析成功的包都会生成一个PackageSetting,所有的PackageSetting都聚合在PMS的Settings中。PackageSetting继承了其祖宗<strong>SettingBase</strong>的属性<strong>mPermissionsState</strong>,这个属性表示一个包的授权状态。</p>
</li>
<li>
<p><strong>PermissionsState(注意,这是复数)</strong>:聚合了包中所有权限的授予状态,在多用户的场景下,不同用户的授权情况不同,因此要区分出一个权限针对每个用户的授予情况,为此设计了一个数据封装类<strong>PermissionData(注意,这是单数)</strong>,记录了一个BasePermission与多个用户mUserState之间的关系。每一个用户的权限授予状态用<strong>PermissionState(注意,这是单数)</strong>来记录。</p>
</li>
</ul>
<blockquote>
<p>理解应用授权机制的数据结构设计,可以结合日常生活中的门禁系统。一个大厦里面有很多房间,进入不同的房间需要获得通过门禁的权限,每一个房间的门禁就可以理解为BasePermission,所有的门禁权限都记录在大厦管理者的数据中心,这个数据中心就可以理解为PMS的Settings。每一个用户来到大厦,都会被登记,登记的记录就是PackageSetting。</p>
<p>用户会申请要进入哪些房间,待管理中心分配,最后,用户会拿到的门禁卡,表示可以进入哪些房间。不同用户需要进入和有权进入的房间通常是不同的,譬如涉及大厦安全的监控房间,就不让访客进入,这就决定了对不同用户而言,门禁卡开通的权限不一样。门禁卡所对应的就是PermissionsState。</p>
<p>假设大厦临时换了一套管理规定,之前某个用户有权进入某个房间,在临时的管理规定下变成了无权进入。同一个房间,在不同管理规定下,对一个用户开放的权限是不同的,这个信息被记录下来,就是PermissionData。这样一来,同一张门禁卡,面对同一张门,在不同的管理规定下,打开门的情况是不一样的。</p>
<p>不同的管理规定,其实就是Android中的多用户使用情况的体现,不同用户的使用规则是不同的,对于同一个应用而言,不同用户使用下的授权情况会有差异。</p>
</blockquote>
<p>有了以上应用授权的一些基础知识,我们再来分析代码会稍微轻松一点。</p>
<p>在包扫描完成以后,PMS会调用<strong>updatePermissionsLPw()</strong>函数,来更新所有APK的授权状态。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">updatePermissionsLPw</span><span class="o">(</span><span class="n">String</span> <span class="n">changingPkg</span><span class="o">,</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkgInfo</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 此处省略大段逻辑。需要删除没有归属的BasePermission,</span>
<span class="c1">// 如果一个BasePermission找不到其关联的包,则需要删除之</span>
<span class="c1">// 遍历包扫描的结果,依次对每个包进行授权。</span>
<span class="c1">// PMS初始化调用该函数时,传入的pkgInfo为null。</span>
<span class="k">if</span> <span class="o">((</span><span class="n">flags</span><span class="o">&</span><span class="n">UPDATE_PERMISSIONS_ALL</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">for</span> <span class="o">(</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span> <span class="o">:</span> <span class="n">mPackages</span><span class="o">.</span><span class="na">values</span><span class="o">())</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span> <span class="o">!=</span> <span class="n">pkgInfo</span><span class="o">)</span> <span class="o">{</span>
<span class="n">grantPermissionsLPw</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="o">(</span><span class="n">flags</span><span class="o">&</span><span class="n">UPDATE_PERMISSIONS_REPLACE_ALL</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">,</span>
<span class="n">changingPkg</span><span class="o">);</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">pkgInfo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">grantPermissionsLPw</span><span class="o">(</span><span class="n">pkgInfo</span><span class="o">,</span> <span class="o">(</span><span class="n">flags</span><span class="o">&</span><span class="n">UPDATE_PERMISSIONS_REPLACE_PKG</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">changingPkg</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数的调用场景比较多,譬如安装、卸载、更新系统应用时,都会调用到。此处分析流程是处于PMS构造时调用的,在开机扫描完所有的包后,便会掉用该函数,一次性来更新所有包的权限。</p>
<p>其实,该函数仅仅是完成授权的准备工作,需要保证所有的扫描出来的权限都有归属,才能开始授权。真正的授权函数是<strong>grantPermissionsLPw()</strong>,带上<strong>LP</strong>后缀表示需要获取<strong>mPackages</strong>这个锁,多了一个<strong>w</strong>表示需要对<strong>mPackages</strong>进行写操作。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">grantPermissionsLPw</span><span class="o">(</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">replace</span><span class="o">,</span>
<span class="n">String</span> <span class="n">packageOfInterest</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 1. 初始化授权相关的数据</span>
<span class="kd">final</span> <span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="o">(</span><span class="n">PackageSetting</span><span class="o">)</span> <span class="n">pkg</span><span class="o">.</span><span class="na">mExtras</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ps</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="c1">// 申请者的授权状态PermissionsState,在后文的逻辑中,会以origPermissions这个变量来表示</span>
<span class="n">PermissionsState</span> <span class="n">permissionsState</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="na">getPermissionsState</span><span class="o">();</span>
<span class="n">PermissionsState</span> <span class="n">origPermissions</span> <span class="o">=</span> <span class="n">permissionsState</span><span class="o">;</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(</span><span class="n">replace</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果replace为true,表示包权限需要更新</span>
<span class="c1">// PackageSettings.installPermissionsFixed这个布尔变量表示</span>
<span class="c1">// 包的**install**类型的权限已经确定下来,在安装成功后,该变量会被置为true</span>
<span class="c1">// 此处,需要重新更新权限,故又将其置为false</span>
<span class="n">ps</span><span class="o">.</span><span class="na">installPermissionsFixed</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">ps</span><span class="o">.</span><span class="na">isSharedUser</span><span class="o">())</span> <span class="o">{</span>
<span class="n">origPermissions</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PermissionsState</span><span class="o">(</span><span class="n">permissionsState</span><span class="o">);</span>
<span class="n">permissionsState</span><span class="o">.</span><span class="na">reset</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 2. 遍历所有申请的权限,依次判断是否授权</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">requestedPermissions</span><span class="o">.</span><span class="na">size</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="c1">// name表示待申请的权限名</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">requestedPermissions</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="c1">// bp表示待申请权限的数据结构,bp根据name从已有的权限列表中获取的</span>
<span class="kd">final</span> <span class="n">BasePermission</span> <span class="n">bp</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPermissions</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">bp</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">bp</span><span class="o">.</span><span class="na">packageSetting</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果没有匹配到bp,则说明当前系统中还不存在name指定的权限</span>
<span class="k">if</span> <span class="o">(</span><span class="n">packageOfInterest</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">packageOfInterest</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Unknown permission "</span> <span class="o">+</span> <span class="n">name</span>
<span class="o">+</span> <span class="s">" in package "</span> <span class="o">+</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">perm</span> <span class="o">=</span> <span class="n">bp</span><span class="o">.</span><span class="na">name</span><span class="o">;</span>
<span class="kt">boolean</span> <span class="n">allowedSig</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_DENIED</span><span class="o">;</span>
<span class="o">...</span>
<span class="c1">// 根据所申请权限的保护级别,确定授权类型</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">level</span> <span class="o">=</span> <span class="n">bp</span><span class="o">.</span><span class="na">protectionLevel</span> <span class="o">&</span> <span class="n">PermissionInfo</span><span class="o">.</span><span class="na">PROTECTION_MASK_BASE</span><span class="o">;</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">level</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="n">PermissionInfo</span><span class="o">.</span><span class="na">PROTECTION_NORMAL</span><span class="o">:</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_INSTALL</span><span class="o">;</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="n">PermissionInfo</span><span class="o">.</span><span class="na">PROTECTION_DANGEROUS</span><span class="o">:</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">targetSdkVersion</span> <span class="o"><=</span> <span class="n">Build</span><span class="o">.</span><span class="na">VERSION_CODES</span><span class="o">.</span><span class="na">LOLLIPOP_MR1</span><span class="o">)</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_INSTALL_LEGACY</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">origPermissions</span><span class="o">.</span><span class="na">hasInstallPermission</span><span class="o">(</span><span class="n">bp</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_UPGRADE</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">mPromoteSystemApps</span>
<span class="o">&&</span> <span class="n">isSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">)</span>
<span class="o">&&</span> <span class="n">mExistingSystemPackages</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">ps</span><span class="o">.</span><span class="na">name</span><span class="o">))</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_UPGRADE</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_RUNTIME</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="n">PermissionInfo</span><span class="o">.</span><span class="na">PROTECTION_SIGNATURE</span><span class="o">:</span> <span class="o">{</span>
<span class="n">allowedSig</span> <span class="o">=</span> <span class="n">grantSignaturePermission</span><span class="o">(</span><span class="n">perm</span><span class="o">,</span> <span class="n">pkg</span><span class="o">,</span> <span class="n">bp</span><span class="o">,</span> <span class="n">origPermissions</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">allowedSig</span><span class="o">)</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_INSTALL</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 3. 根据授权类型,更新包的PermissionsState</span>
<span class="k">if</span> <span class="o">(</span><span class="n">grant</span> <span class="o">!=</span> <span class="n">GRANT_DENIED</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 小插曲,对于Data分区的应用而言,原则上是不会授予新的install类型的权限</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">)</span> <span class="o">&&</span> <span class="n">ps</span><span class="o">.</span><span class="na">installPermissionsFixed</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">allowedSig</span> <span class="o">&&</span> <span class="o">!</span><span class="n">origPermissions</span><span class="o">.</span><span class="na">hasInstallPermission</span><span class="o">(</span><span class="n">perm</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isNewPlatformPermissionForPackage</span><span class="o">(</span><span class="n">perm</span><span class="o">,</span> <span class="n">pkg</span><span class="o">))</span> <span class="o">{</span>
<span class="n">grant</span> <span class="o">=</span> <span class="n">GRANT_DENIED</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">grant</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">GRANT_INSTALL:</span> <span class="o">{</span>
<span class="c1">// 收回所有已授予的runtime权限。因为runtime可能在新版本中变成install类型</span>
<span class="c1">// runtime是面向多用户的,所以涉及到runtime授权时,都会有一个循环遍历所有的用户</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">userId</span> <span class="o">:</span> <span class="n">UserManagerService</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getUserIds</span><span class="o">())</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">origPermissions</span><span class="o">.</span><span class="na">getRuntimePermissionState</span><span class="o">(</span>
<span class="n">bp</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">userId</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">origPermissions</span><span class="o">.</span><span class="na">revokeRuntimePermission</span><span class="o">(</span><span class="n">bp</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="n">origPermissions</span><span class="o">.</span><span class="na">updatePermissionFlags</span><span class="o">(</span><span class="n">bp</span><span class="o">,</span> <span class="n">userId</span><span class="o">,</span>
<span class="n">PackageManager</span><span class="o">.</span><span class="na">MASK_PERMISSION_FLAGS</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">changedRuntimePermissionUserIds</span> <span class="o">=</span> <span class="n">ArrayUtils</span><span class="o">.</span><span class="na">appendInt</span><span class="o">(</span>
<span class="n">changedRuntimePermissionUserIds</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 授予install类型的权限</span>
<span class="k">if</span> <span class="o">(</span><span class="n">permissionsState</span><span class="o">.</span><span class="na">grantInstallPermission</span><span class="o">(</span><span class="n">bp</span><span class="o">)</span> <span class="o">!=</span>
<span class="n">PermissionsState</span><span class="o">.</span><span class="na">PERMISSION_OPERATION_FAILURE</span><span class="o">)</span> <span class="o">{</span>
<span class="n">changedInstallPermission</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="nl">GRANT_INSTALL_LEGACY:</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">permissionsState</span><span class="o">.</span><span class="na">grantInstallPermission</span><span class="o">(</span><span class="n">bp</span><span class="o">)</span> <span class="o">!=</span>
<span class="n">PermissionsState</span><span class="o">.</span><span class="na">PERMISSION_OPERATION_FAILURE</span><span class="o">)</span> <span class="o">{</span>
<span class="n">changedInstallPermission</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="nl">GRANT_RUNTIME:</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">userId</span> <span class="o">:</span> <span class="n">UserManagerService</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getUserIds</span><span class="o">())</span> <span class="o">{</span>
<span class="n">PermissionState</span> <span class="n">permissionState</span> <span class="o">=</span> <span class="n">origPermissions</span>
<span class="o">.</span><span class="na">getRuntimePermissionState</span><span class="o">(</span><span class="n">bp</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">permissionState</span> <span class="o">!=</span> <span class="kc">null</span>
<span class="o">?</span> <span class="n">permissionState</span><span class="o">.</span><span class="na">getFlags</span><span class="o">()</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">origPermissions</span><span class="o">.</span><span class="na">hasRuntimePermission</span><span class="o">(</span><span class="n">bp</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">userId</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">permissionsState</span><span class="o">.</span><span class="na">grantRuntimePermission</span><span class="o">(</span><span class="n">bp</span><span class="o">,</span> <span class="n">userId</span><span class="o">)</span> <span class="o">==</span>
<span class="n">PermissionsState</span><span class="o">.</span><span class="na">PERMISSION_OPERATION_FAILURE</span><span class="o">)</span> <span class="o">{</span>
<span class="n">changedRuntimePermissionUserIds</span> <span class="o">=</span> <span class="n">ArrayUtils</span><span class="o">.</span><span class="na">appendInt</span><span class="o">(</span>
<span class="n">changedRuntimePermissionUserIds</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">permissionsState</span><span class="o">.</span><span class="na">updatePermissionFlags</span><span class="o">(</span><span class="n">bp</span><span class="o">,</span> <span class="n">userId</span><span class="o">,</span> <span class="n">flags</span><span class="o">,</span> <span class="n">flags</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="nl">GRANT_UPGRADE:</span> <span class="o">{</span>
<span class="c1">// 先回收之前授予的install权限,再重新授予runtime权限</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="k">default</span><span class="o">:</span> <span class="o">{</span>
<span class="c1">// 如果进入这个分支,表示并没有拒绝授权,但权限又没有真正授予给申请者</span>
<span class="c1">// 因为当时申请的权限还不存在,即便后来有了待申请的权限,也不会授予给申请者</span>
<span class="k">if</span> <span class="o">(</span><span class="n">packageOfInterest</span> <span class="o">==</span> <span class="kc">null</span>
<span class="o">||</span> <span class="n">packageOfInterest</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Not granting permission "</span> <span class="o">+</span> <span class="n">perm</span>
<span class="o">+</span> <span class="s">" to package "</span> <span class="o">+</span> <span class="n">pkg</span><span class="o">.</span><span class="na">packageName</span>
<span class="o">+</span> <span class="s">" because it was previously installed without"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">else</span> <span class="o">{</span>
<span class="c1">// 拒绝授权,则需要回收已经授予的权限</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">changedInstallPermission</span> <span class="o">||</span> <span class="n">replace</span><span class="o">)</span> <span class="o">&&</span> <span class="o">!</span><span class="n">ps</span><span class="o">.</span><span class="na">installPermissionsFixed</span> <span class="o">&&</span>
<span class="o">!</span><span class="n">isSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">)</span> <span class="o">||</span> <span class="n">isUpdatedSystemApp</span><span class="o">(</span><span class="n">ps</span><span class="o">)){</span>
<span class="n">ps</span><span class="o">.</span><span class="na">installPermissionsFixed</span> <span class="o">=</span> <span class="kc">true</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">userId</span> <span class="o">:</span> <span class="n">changedRuntimePermissionUserIds</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSettings</span><span class="o">.</span><span class="na">writeRuntimePermissionsForUserLPr</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>先从整体来思考一下这个函数,该函数完成对一个包授权,接收三个输入参数:</p>
<table>
<thead>
<tr>
<th>函数参数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>pkg</strong></td>
<td>包解析器解析出来的对象PackageParser.Package。这就是待授权的包。</td>
</tr>
<tr>
<td><strong>replace</strong></td>
<td>是否需要替换已有包的权限。初始化PMS时,如果不是OTA升级系统版本,则该参数为false。</td>
</tr>
<tr>
<td><strong>packageOfInterest</strong></td>
<td>感兴趣的包,主要是为了打印日志。不影响函数的主体逻辑。</td>
</tr>
</tbody>
</table>
<p>在调用这个函数之前,所有包的信息都写入了PMS的Settings中,每一个包都有一个<strong>PermissionsState</strong>对象,用于记录授权状态。在调用这个函数之后,包的<strong>PermissionsState</strong>对象的数据会更新。该函数其实就是Android的包授权规则的实现函数,要更新的数据就是<strong>PermissionsState</strong>对象。授权规则体现在函数的逻辑中:</p>
<ol>
<li>
<p>初始化授权相关的数据:</p>
<ul>
<li><strong>origPermissions</strong>:表示一个包已有的授权状态,包扫描完后,这个数据结构就已经构建完毕了。</li>
<li><strong>changedRuntimePermissionUserIds</strong>:表示已经授予的<strong>runtime</strong>类型的权限发生了变化的那些用户。</li>
<li><strong>changedInstallPermission</strong>:表示已经授予的<strong>install</strong>类型的权限发生的变化,默认为false。</li>
<li><strong>PackageSettings.installPermissionsFixed</strong>: 表示包的<strong>install</strong>类型的权已经确定。如果传入的函数参数<strong>replace</strong>为true,则意味着需要对<strong>install</strong>类型的授权进行调整,故会将此变量重新置为false。</li>
</ul>
</li>
<li>
<p>遍历所有申请的权限,根据所申请权限的保护级别(protectionLevel),会得到几种结果:</p>
<ul>
<li>
<p><strong>GRANT_INSTALL</strong>:对于Normal级别的权限而言,都是在安装时授予给申请者的。对于Signature级别的权限,如果签名匹配或者满足Previlege系统应用的要求,则也是在安装时授予给申请者的。</p>
</li>
<li>
<p><strong>GRANT_INSTALL_LEGACY</strong>:带上<strong>LEGACY</strong>表示这属于遗留的安装权限,在Android M(6.0)之前,Dangerous级别的权限都是授予<strong>install</strong>类型,到Android M(6.0)之后,Dangerous级别的权限都是以<strong>runtime</strong>类型来授予。这是Android为了保证向下兼容的设计:对于一个应用程序而言,如果是基于Android M(6.0)之前的SDK进行开发,而且申请了Dangerous级别的权限,这一类权限就是以<strong>GRANT_INSTALL_LEGACY</strong>类型来授予。</p>
</li>
<li>
<p><strong>GRANT_UPGRADE</strong>: 与<strong>GRANT_INSTALL_LEGACY</strong>一样,也是为了向下兼容。如果应用程序没有指明一定要运行在Android M(6.0)以前的版本,那申请的Dangerous级别的权限,将会以<strong>GRANT_UPGRADE</strong>来授权,表示对于Dangerous权限,之前授权为<strong>install</strong>类型的,已经记录在PMS的Settings中,现在整个系统通过OTA升级到Android M(6.0)了,需要将<strong>install</strong>类型的授权升级为<strong>runtime</strong>类型。</p>
</li>
<li>
<p><strong>GRNAT_RUNTIME</strong>:如果运行在Android M(6.0)上的应用程序申请了Dangerous权限,则以<strong>runtime</strong>类型来授权。</p>
</li>
<li>
<p><strong>GRANT_DENIED</strong>:拒绝授权。有两大类情况会拒绝授权:其中一类是申请保护级别为SignatureOrPrivelege的权限,但不满足签名匹配或为系统应用的约束条件,则拒绝授权。另一类,在下文中见。</p>
</li>
</ul>
</li>
<li>
<p>根据授权类型来更新授权状态PermissionsState。如果需要授予权限,则会根据授权类型区别对待,最开始初始化的两个变量changedInstallPermission和changedRuntimePermissionUserIds在这一个步骤中可能会被更新。</p>
<p>如果拒绝授权,则需要收回。这里就出现了另一类拒绝授权的场景:对于一个Data分区的普通应用而言,之前申请的权限不存在,但现在申请的权限又有了,则仍然拒绝授权给这个普通应用,除非这个普通应用申请的权限时平台新增的权限。这一点,其实不难理解,譬如一个普通应用A,申请了普通应用B定义的权限,A先于B安装,那么,在A安装的时候,所申请的权限还不存在。B安装以后,即便权限有了,也不会再重新授予给A。如果普通应用A申请的是一个Android平台自身添加的权限,则会授予。</p>
</li>
</ol>
<blockquote>
<p>如果读者还没有接触到Android M(6.0)之后的代码,那应用授权机制还不至于这么复杂,因为Android M(6.0)对扩展了授权类型,并做了大量向下兼容的设计。所以,这段代码读起来总是不那么利索。其实,越往Android高版本学习,就越能看到这些向下兼容的代码,设计上总是不那么优美,但又无处不体验向下兼容的精神。</p>
<p>没有一个系统一开始就兼顾到了所有后续发展策略,绝对的优美设计或许只是停留在一个时间段。或许更加好的,只是那些活下来的、不断发展的代码。</p>
</blockquote>
<h2 id="34-小结">3.4 小结</h2>
<p>PMS是伴随系统进程启动而启动的,最终会构造一个PMS对象,此后,PMS便作为Android世界中的包管理者,对外提供包的增/删/改/查操作。</p>
<p>在PMS的启动过程中,最重要的是对所有的静态APK文件进行扫描,生成一个包在内存中的数据结构Package,PMS实际上就是维护着所有包在内存中的数据结构。已有包的历史信息会写入磁盘,PMS的Settings专门来管理写入磁盘的包信息。</p>
<p>所有包的信息扫描完以后,需要对应用进行授权,这是Android权限管理的一部分。随着Android版本的升级,授权机制略有区别,总体的框架是:每个APK都可以声明权限,并为权限设定保护级别,其他APk需要使用这些权限的时候,需要先申请,再由系统判定是否进行授权。</p>
<h1 id="4-包查询服务">4 包查询服务</h1>
<p>在管理所有包的同时,包管理者需要对外提供服务,诸如获取包的信息、安装或删除包、解析Intent等,都是包管理者在Android世界的职能。</p>
<p>本节先介绍包管理者的服务方式,再分析一个最常见的包查询实例。</p>
<h2 id="41-服务方式">4.1 服务方式</h2>
<p>包管理者以什么形式对外提供服务呢?在写应用程序时,我们通常会利用应用自身的上下文环境Context来获取包管理服务:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 获取一个PackageManager的对象实例</span>
<span class="n">PackageManager</span> <span class="n">pm</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">();</span>
<span class="c1">// 通过PackageManager对象获取指定包名的包信息</span>
<span class="n">PackageInfo</span> <span class="n">pi</span> <span class="o">=</span> <span class="n">pm</span><span class="o">.</span><span class="na">getPackageInfo</span><span class="o">(</span><span class="s">"com.android.contacts"</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
</code></pre></div></div>
<p>这么一段简单的代码,其实蕴含着很多深意:</p>
<ol>
<li>
<p>前文介绍的PMS和其管理的各种数据结构,都是运行在系统进程之中。在应用进程中获取的PackageManager对象,只是PMS在应用进程中的一个代理,不同的应用进程都不同的代理,意味着不同应用进程中的PackageManager对象是不同的,但管理者PMS只有一个。</p>
</li>
<li>
<p>运行在应用进程中的PackageManager要与运行在系统进程中的PMS进行通信,通信的手段是什么吗?自然是Android中最常见的Binder机制。因此,会有一个IPackageManager.aidl文件,用于描述两者通信的接口。
另外,应用进程中的PackageInfo对象,在上文分析“包信息体”的时候,我们已经见过它,还记得吗?包解析后,需要跨进程传递的数据结构都实现了Parcelable接口,PackageInfo其实就是由系统进程传递到应用进程的对象。</p>
</li>
</ol>
<p>不同应用进程通过Binder机制与PMS通信的过程如下图:</p>
<div align="center"><img src="/assets/images/packagemanager/8-packagemanager-ipackagemanager-aidl.png" alt="PMS Binder Transaction" /></div>
<p>PMS作为包管理的最核心组成部分,伴随着系统的启动而创建,并一直运行系统进程中。当应用程序需要获取包管理服务时,会生成一个PackageManager对象与PMS进行通信。在包解析时就会生成包信息,即XXXInfo这一类数据结构,PMS会将这些数据传递给需要的应用进程。</p>
<blockquote>
<p>管理者对内设计了复杂的管理机制,对外封装了简单的使用接口。这种设计在Android中大量出现,ActivityManagerService、WindowManagerService、PowerManagerService等,基本所有的系统服务都遵循这种设计规范。对于应用程序而言,不需要关心管理者的实现原理,只需要理解接口的使用场景。</p>
</blockquote>
<p>应用进程通过Binder接口获取包管理服务,这仅仅是包管理者提供服务的概貌,接下来可以更深入地思考一个问题:通过Context就能获取到PackageManager,那么PackageManager对象是如何生成到应用进程的运行环境中去的呢?我们通过源码来回答。</p>
<p><strong>Context.getPackageManager()</strong>函数的实现如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// frameworks/base/core/java/android/app/ContextImpl.java</span>
<span class="kd">public</span> <span class="n">PackageManager</span> <span class="nf">getPackageManager</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mPackageManager</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">mPackageManager</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 从ActivityThread获取了PackageManager对象</span>
<span class="n">IPackageManager</span> <span class="n">pm</span> <span class="o">=</span> <span class="n">ActivityThread</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pm</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 通过ApplicationPackageManager对进行了一层封装</span>
<span class="k">return</span> <span class="o">(</span><span class="n">mPackageManager</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ApplicationPackageManager</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">pm</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>
</code></pre></div></div>
<p><strong>ActivityThread.getPackageManager()</strong>函数实现如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// frameworks/base/core/java/android/app/ActivityThread.java</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">IPackageManager</span> <span class="nf">getPackageManager</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sPackageManager</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">sPackageManager</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 早在系统进程(SystemServer)启动PMS时,就将PMS添加到了系统服务中</span>
<span class="c1">// 因此,通过ServiceManager便可以获取到包服务。</span>
<span class="n">IBinder</span> <span class="n">b</span> <span class="o">=</span> <span class="n">ServiceManager</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="s">"package"</span><span class="o">);</span>
<span class="n">sPackageManager</span> <span class="o">=</span> <span class="n">IPackageManager</span><span class="o">.</span><span class="na">Stub</span><span class="o">.</span><span class="na">asInterface</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
<span class="k">return</span> <span class="n">sPackageManager</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>通过Context获取PackageManager最终得到的对象是ApplicationPackageManager,它是PackageManager的子类,其实就是对PackageManager的一个封装,目的是为了方便应用程序编程。</p>
<p><strong>现在,可以完整的梳理一下包管理的服务方式了:</strong></p>
<ol>
<li>
<p>全局定义了<strong>IPackageManager</strong>接口,描述了包管理者对外提供的功能;运行在系统进程中的PMS实现了<strong>IPackageManager</strong>接口,作为包管理的服务端;客户端通过<strong>IPackageManager</strong>接口请求包服务;</p>
</li>
<li>
<p>为了方便客户端使用包服务,Android做了多层封装。应用进程作为客户端,通过<strong>PackageManager</strong>便可使用包服务,客户端实际存在的对象是<strong>ApplicationPackageManager</strong>,它封装了<strong>IPackageManager</strong>的所有接口;</p>
</li>
<li>
<p><strong>在应用进程看来,客户端和服务端的概念是模糊的,明确的只有运行环境的概念,即Context。包服务就存在于应用进程的运行环境中,需要时直接拿出来使用即可</strong>。</p>
</li>
</ol>
<blockquote>
<p>“运行环境(Context)”是Android的设计哲学之一,Android有意弱化进程,强化运行环境,这是面向应用开发者的设计。运行环境是什么并不是一个好回答的问题,可以将其类比为我们的工作环境,当我们需要办公设备时,只需要向管理部门申请,并不需要关心办公设备如何采购,办公设备对一般的工作人员而言,就像是工作环境中天然存在的东西。</p>
</blockquote>
<h2 id="42-intent的解析">4.2 Intent的解析</h2>
<p>Android中,使用Intent来表达意图,最终会有一个响应者。当系统产生一个Intent后,如何找到它的响应者呢?这需要对Intent进行解析。作为所有包信息管理者的中枢,PMS自然有义务承担解析Intent的责任。要解析Intent,就需要先了解Intent的结构,标识一个Intent身份的信息由两部分构成:</p>
<ul>
<li>
<p><strong>主要信息</strong>:Action和Data。Action用于表明Intent所要执行的操作,譬如ACTION_VIEW,ACTION_EDIT; Data用于表明执行操作的数据,譬如联系人数据,数据是以URI来表达的。再举两个Action和Data成对出现的例子:</p>
<p>ACTION_VIEW: content://contacts/people/1 # 表示查看联系人数据库中,ID为1的联系人信息</p>
<p>ACTION_DIAL: tel:119 # 表示拨打电话给119</p>
<p>以上例子中的URI并不一样,完整的URI格式为<code class="highlighter-rouge">scheme://host:port/path</code>。</p>
</li>
<li>
<p><strong>次要信息</strong>:除了主要的标记信息,Intent还可以附加很多额外的信息,Category,Type,Component和Extra:</p>
<ul>
<li>Category表示Intent的类别,譬如CATEGORY_LAUNCHER表示要对属于桌面图标这一类的对象执行操作;</li>
<li>Type表示Intent所操作的数据类型,就是MIMEType,譬如要操作PNG图片,那Type就可以设置为<em>png</em>;</li>
<li>Component表示Intent要操作的对象;</li>
<li>Extra表示Intent所传递的数据,这些数据都实现了Parcelable接口。</li>
</ul>
</li>
</ul>
<p>Intent的身份信息,其实就是Android的一种设计语言,譬如“打电话给119”,只需要发出Action为<em>ACTION_DIAL</em>,URI为<em>tel:119</em>的Intent即可,剩下的就交由Android系统去理解这个意图。任何组件只要按照规则发声,都会被Android系统正确的理解。</p>
<p>根据解析Intent方式的不同,可以将Intent分为两类:</p>
<ul>
<li>
<p><strong>显式(Explicit)</strong>: 明确指名需要谁来响应Intent。这一类Intent的解析过程比较简单。</p>
</li>
<li>
<p><strong>隐式(Implicit)</strong>:由系统找到合适的目标来响应Intent。这一类Intent的解析过程比较复杂,由于目标不明确,所以需要经过层层筛选才能找到最合适的响应者。</p>
</li>
</ul>
<blockquote>
<p>之所以Intent的信息有<strong>主次之分</strong>,是因为解析Intent的规则需要有一个依据,主要信息是最能表达意图的,而次要信息则是解析规则的一个补充。这就像大家在做自我介绍的时候,总是先说姓名、籍贯这些主要的信息,再额外补充爱好、特长这些次要信息,这样一来在和其他人交朋友的时候,其他人就可以先根据姓名、籍贯直接锁定的我。如果我们只介绍爱好、特长,那别人锁定的范围就比较广,因为有相同爱好或特长的人比较多。</p>
<p>之所以Intent有<strong>显隐之分</strong>,是因为解析Intent的方式不同,如果我指定要和某某人交朋友,那发出的这一类请求,就是<strong>显式</strong>的Intent;如果没有指定交朋友的对象,只是说找到跟我爱好相同的人,那发出的这一类请求,就是<strong>隐式</strong>的Intent。对待这两种Intent显然有不同的解析方式。</p>
<p>如同“运行环境(Context)”一样,Intent也是面向应用程序的设计,同样是弱化了进程的概念。应用程序只需表明“我想要什么”,不需要关心所要的东西在什么地方,如何找到所要的东西。Intent是Android通信的手段之一,可以承载要传递的信息,至于信息怎么从发起进程传递到目标进程,应用程序可以毫不关心。</p>
</blockquote>
<p>Intent最后的响应者是一个Android组件,Android的组件都可以定义IntentFilter,还记得前文中包解析器的类图结构吗?每一个Component类中都有一个IntentInfo对象的数组,而IntentInfo则是IntentFilter的子类。既然一个Android组件可以定义多个IntentFilter,那么,Intent想要匹配到最终的组件,则需要通过组件所定义的所有IntentFilter:</p>
<div align="center"><img src="/assets/images/packagemanager/9-packagemanager-intent-and-intentfilter.png" alt="Intent and IntentFilter" /></div>
<p>多个IntentFilter之间是<strong>“或”</strong>的关系,哪怕其他所有的IntentFilter都匹配失败,只要有一个IntentFilter通过,最终Intent还是找到了可以响应的组件。</p>
<p>每一个IntentFilter就像是一个定义了白名单规则的过滤器,只有满足白名单的要求才会放行。IntentFilter的过滤规则,其实就是针对Intent的身份信息的匹配规则,当Intent的身份信息与IntentFilter所规定的要求匹配上,则允许通过;否则,Intent就被过滤掉了。IntentFilter的过滤规则包含以下三个方面:</p>
<ul>
<li>
<p><strong>Action</strong>: 每一个IntentFilter可以定义零个或多个<action>标签,如果Intent想要通过这个IntentFilter,则Intent所辖的Action需要匹配其中至少一个。</p>
</li>
<li>
<p><strong>Category</strong>: 每一个IntentFilter可以定义零个或多个<category>标签,如果Intent想要通过这个IntentFilter,则Intent所辖的Category必须是IntentFilter所定义的Category的子集,才能通过IntentFilter。譬如Intent设置了两个Category:CATEGORY_LAUNCHER和CATEGORY_MAIN,只有那些至少定义了这两项Category的IntentFilter,才会放行该Intent。</p>
<p>启动Activity时,会为Intent设置默认的Category,即CATEGORY_DEFAULT。目标Activity需要添加一个category为CATEGORY_DEFAULT的IntentFilter来匹配这一类隐式的Intent。</p>
</li>
<li>
<p><strong>Data</strong>:每一个IntentFilter可以定义零个或多个<data>,数据可以通过类型(MIMEType)和位置(URI)来描述,如果Intent想要通过这个IntentFilter,则Intent所辖的Data需要匹配其中至少一个。</p>
</li>
</ul>
<p>在了解了Intent的身份信息和IntentFilter的规则定义之后,就可以介绍Intent解析的过程了,这里借用类图来示例一下Intent解析过程中相关的数据结构:</p>
<div align="center"><img src="/assets/images/packagemanager/10-packagemanager-intent-resolver-class-diagram.png" alt="Intent Resolver Class Diagram" /></div>
<p>PMS中有四大组件的Intent解析器,分别是<strong>ActivityIntentResolver</strong>用于解析发往Activity或Broadcast的Intent,<strong>ServiceIntentResolver</strong>用于解析发往Service的Intent,<strong>ProviderIntentResolver</strong>用于解析发往Provider的Intent,系统每收到一个Intent的解析请求时,就会使用应的解析器,它们都是<strong>IntentResolver</strong>的子类。</p>
<p><strong>IntentResolver</strong>的职能就是解析Intent,它包含了所有的<strong>IntentFilter</strong>,同时有一个重要的成员函数<strong>queryIntent()</strong>,接收Intent作为参数,返回查询结果:一个ResolveInfo对象的数组。因为可能有多个组件来响应一个Intent,所以返回结果是一个数组。可想而知,该函数就是针对输入的Intent,按照前文所述的过滤规则,逐个与IntentFilter进行匹配,直到找到最终的响应者,便加入返回结果的列表。</p>
<p><strong>ResolveInfo</strong>是最终的Intent解析结果的数据结构,并不复杂,就是各类组件信息的一个包装。需要注意的是,其中的组件信息ActivityInfo、ProviderInfo、ServiceInfo只有一个不为空,这样就可以区分不同组件的解析结果。</p>
<p>前文中提到包查询服务的形式,应用进程通过PackageManager提供的接口,发起跨进程调用,最终接口实现是在系统进程的PMS中。下面我们就分析<strong>PMS.queryIntentActivities()</strong>函数:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="n">List</span><span class="o"><</span><span class="n">ResolveInfo</span><span class="o">></span> <span class="nf">queryIntentActivities</span><span class="o">(</span><span class="n">Intent</span> <span class="n">intent</span><span class="o">,</span>
<span class="n">String</span> <span class="n">resolvedType</span><span class="o">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">sUserManager</span><span class="o">.</span><span class="na">exists</span><span class="o">(</span><span class="n">userId</span><span class="o">))</span> <span class="k">return</span> <span class="n">Collections</span><span class="o">.</span><span class="na">emptyList</span><span class="o">();</span>
<span class="n">enforceCrossUserPermission</span><span class="o">(</span><span class="n">Binder</span><span class="o">.</span><span class="na">getCallingUid</span><span class="o">(),</span> <span class="n">userId</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="s">"query intent activities"</span><span class="o">);</span>
<span class="c1">// 1. 解析“显式”的Intent</span>
<span class="n">ComponentName</span> <span class="n">comp</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">comp</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">intent</span><span class="o">.</span><span class="na">getSelector</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">intent</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getSelector</span><span class="o">();</span>
<span class="n">comp</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getComponent</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">comp</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="n">List</span><span class="o"><</span><span class="n">ResolveInfo</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">ResolveInfo</span><span class="o">>(</span><span class="mi">1</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">ActivityInfo</span> <span class="n">ai</span> <span class="o">=</span> <span class="n">getActivityInfo</span><span class="o">(</span><span class="n">comp</span><span class="o">,</span> <span class="n">flags</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ai</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="n">ResolveInfo</span> <span class="n">ri</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ResolveInfo</span><span class="o">();</span>
<span class="n">ri</span><span class="o">.</span><span class="na">activityInfo</span> <span class="o">=</span> <span class="n">ai</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">ri</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">list</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 2. 解析“隐式”的Intent</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略多用户情况下CrossProfileIntentFilter相关的代码</span>
<span class="n">List</span><span class="o"><</span><span class="n">ResolveInfo</span><span class="o">></span> <span class="n">result</span> <span class="o">=</span> <span class="n">mActivities</span><span class="o">.</span><span class="na">queryIntent</span><span class="o">(</span>
<span class="n">intent</span><span class="o">,</span> <span class="n">resolvedType</span><span class="o">,</span> <span class="n">flags</span><span class="o">,</span> <span class="n">userId</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="o">}</span>
</code></pre></div></div>
<ol>
<li>
<p>解析“显式”的Intent,如果Intent中有设置Component,则说明已经显式的指名由谁来响应Intent。根据Component可以获取到对应的ActivityInfo,再在封装成ResolveInfo便可作为结果返回了。</p>
</li>
<li>
<p>解析“隐式”的Intent,该处省略了大段与多用户相关的Intent解析逻辑,仅展示了核心的函数调用<strong>mActivities.queryIntent()</strong>,mActivities是ActivityIntentResolver类型的对象,它负责完成对Intent的解析。</p>
</li>
</ol>
<p>其他类似的查询有<strong>queryIntentReceivers(), queryIntentServices(), queryIntentContentProviders()</strong>。</p>
<h2 id="43-小结">4.3 小结</h2>
<p>包管理对外提供服务的形式基于Binder机制,服务端是运行在系统进程中的PMS。包查询服务是使用范围很广的一类服务,很多其他系统服务都需要用到包的信息,都是通过PMS获取的。</p>
<p>包查询服务的核心是Intent的解析,PMS中实现了不同组件的解析器。针对一个输入的Intent,解析得到可以响应的组件。Android为此设计了IntentFilter机制,定义了Intent的匹配规则,最终的解析实现在IntentResolver.queryIntent()函数中。</p>
<h1 id="5-apk的安装过程">5 APK的安装过程</h1>
<p>APK安装是一个比较耗时的操作,PMS将这项工作放到了一个服务进程<strong>com.android.defcontainer</strong>,通过消息传递和跨进程调用的方式来驱动整个安装过程,如下图所示:</p>
<div align="center"><img src="/assets/images/packagemanager/11-packagemanager-default-container-service.png" alt="com.android.defcontainer" /></div>
<p>运行在系统进程中的PMS控制了整个安装流程,具体的安装任务由运行在<strong>com.android.defconainer</strong>进程的<strong>DefaultContainerService</strong>来完成。</p>
<blockquote>
<p>在Android最终的编译产物中,/system/priv-app/目录下,有一个DefaultContainerService.apk,对应的就是<strong>com.android.defconainer</strong>进程,它只运行一个<service>,就是DefaultContainerService。</p>
<p>在Android ICS(4.0)以前的版本中,并没有DefaultContainerService.apk,整个安装过程是耦合在PMS中的,直到Android ICS之后的版本,才将其解耦出来。</p>
</blockquote>
<p>下面,我们就来分析APK安装相关的数据结构和整体的安装流程。</p>
<h2 id="51-数据结构">5.1 数据结构</h2>
<p>PMS为APK的安装设计了一个庞大的数据结构,各个数据结构的类图如下所示:</p>
<div align="center"><img src="/assets/images/packagemanager/12-packagemanager-install-package-class-diagram.png" alt="Install Package Class Diagram" /></div>
<ul>
<li>
<p>APK的安装是<strong>PackageHandler</strong>这个消息处理器来驱动的,通过<strong>HandlerParams</strong>封装了消息所承载的数据。在PMS对象构造时,<strong>PackageHandler</strong>对象就会随之构造,它绑定到一个后台的工作线程,线程名为PackageManager;</p>
</li>
<li>
<p><strong>HandlerParams</strong>是<strong>PackageHandler</strong>所处理的消息承载的数据,有两类:InstallParams对应包安装的数据;MeasureParams对应到包测量的数据,譬如包的大小;</p>
</li>
<li>
<p>APK可以安装在内部存储空间或SD卡上,已经安装的APK也可以在内部存储和SD卡之间进行移动,PMS为此设计了<strong>InstallArgs</strong>这个数据结构,它有不同的子类:<strong>FileInstallArgs</strong>对应将包安装到内部存储空间,即Data分区;<strong>AsecInstallArgs</strong>对应到将包安装到外部存储空间,即SD卡;<strong>MoveInstallArgs</strong>对应将包在内外存储空间移动,譬如将包从Data分区挪到SD卡。</p>
</li>
</ul>
<p>对包安装相关的数据结构有一个初步认识后,就可以深入具体的安装流程,看这些数据结构是怎么串联起来的。</p>
<h2 id="52-安装流程">5.2 安装流程</h2>
<p>安装APK这个动作可以由具备<strong>android.Manifest.permission.INSTALL_PACKAGES</strong>授权的进程发起,譬如应用商店,系统进程等。通过<code class="highlighter-rouge">adb install</code>命令来安装APK,其实也是通过一个进程发起调用,最终都是PMS来响应。下图是APK安装的调用时序:</p>
<div align="center"><img src="/assets/images/packagemanager/13-packagemanager-install-package-sequence.png" alt="Install Package Sequence" /></div>
<p>安装APK都是通过跨进程调用到PMS中的,PMS的响应函数是<strong>installPackageAsUser()</strong>:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">installPackageAsUser</span><span class="o">(</span><span class="n">String</span> <span class="n">originPath</span><span class="o">,</span> <span class="n">IPackageInstallObserver2</span> <span class="n">observer</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">installFlags</span><span class="o">,</span> <span class="n">String</span> <span class="n">installerPackageName</span><span class="o">,</span> <span class="n">VerificationParams</span> <span class="n">verificationParams</span><span class="o">,</span>
<span class="n">String</span> <span class="n">packageAbiOverride</span><span class="o">,</span> <span class="kt">int</span> <span class="n">userId</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 判断是否具备包安装权限:INSTALL_PACKAGES</span>
<span class="n">mContext</span><span class="o">.</span><span class="na">enforceCallingOrSelfPermission</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">INSTALL_PACKAGES</span><span class="o">,</span> <span class="kc">null</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">Binder</span><span class="o">.</span><span class="na">getCallingUid</span><span class="o">();</span>
<span class="n">enforceCrossUserPermission</span><span class="o">(</span><span class="n">callingUid</span><span class="o">,</span> <span class="n">userId</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="s">"installPackageAsUser"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isUserRestricted</span><span class="o">(</span><span class="n">userId</span><span class="o">,</span> <span class="n">UserManager</span><span class="o">.</span><span class="na">DISALLOW_INSTALL_APPS</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// 如果用户安装受限,则退出安装过程</span>
<span class="c1">// 在多用户的场景下,有些用户可能被禁止安装</span>
<span class="o">...</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 根据安装来源修正安装参数</span>
<span class="k">if</span> <span class="o">((</span><span class="n">callingUid</span> <span class="o">==</span> <span class="n">Process</span><span class="o">.</span><span class="na">SHELL_UID</span><span class="o">)</span> <span class="o">||</span> <span class="o">(</span><span class="n">callingUid</span> <span class="o">==</span> <span class="n">Process</span><span class="o">.</span><span class="na">ROOT_UID</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// 通过adb install来安装,就会带上INSTALL_FROM_ADB这个安装参数</span>
<span class="n">installFlags</span> <span class="o">|=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FROM_ADB</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">installFlags</span> <span class="o">&=</span> <span class="o">~</span><span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FROM_ADB</span><span class="o">;</span>
<span class="n">installFlags</span> <span class="o">&=</span> <span class="o">~</span><span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_ALL_USERS</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">UserHandle</span> <span class="n">user</span><span class="o">;</span>
<span class="k">if</span> <span class="o">((</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_ALL_USERS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">ALL</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">user</span> <span class="o">=</span> <span class="k">new</span> <span class="n">UserHandle</span><span class="o">(</span><span class="n">userId</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">((</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_GRANT_RUNTIME_PERMISSIONS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="n">mContext</span><span class="o">.</span><span class="na">checkCallingOrSelfPermission</span><span class="o">(</span><span class="n">Manifest</span><span class="o">.</span><span class="na">permission</span>
<span class="o">.</span><span class="na">INSTALL_GRANT_RUNTIME_PERMISSIONS</span><span class="o">)</span> <span class="o">==</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">PERMISSION_DENIED</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">"You need the "</span>
<span class="o">+</span> <span class="s">"android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "</span>
<span class="o">+</span> <span class="s">"to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">verificationParams</span><span class="o">.</span><span class="na">setInstallerUid</span><span class="o">(</span><span class="n">callingUid</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">originFile</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">originPath</span><span class="o">);</span>
<span class="c1">// 生成OriginInfo对象,包含APK的原始路径等信息</span>
<span class="kd">final</span> <span class="n">OriginInfo</span> <span class="n">origin</span> <span class="o">=</span> <span class="n">OriginInfo</span><span class="o">.</span><span class="na">fromUntrustedFile</span><span class="o">(</span><span class="n">originFile</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">Message</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">mHandler</span><span class="o">.</span><span class="na">obtainMessage</span><span class="o">(</span><span class="n">INIT_COPY</span><span class="o">);</span>
<span class="c1">// 将安装信息封装成一个InstallParams对象</span>
<span class="n">msg</span><span class="o">.</span><span class="na">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="n">InstallParams</span><span class="o">(</span><span class="n">origin</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">observer</span><span class="o">,</span> <span class="n">installFlags</span><span class="o">,</span> <span class="n">installerPackageName</span><span class="o">,</span>
<span class="kc">null</span><span class="o">,</span> <span class="n">verificationParams</span><span class="o">,</span> <span class="n">user</span><span class="o">,</span> <span class="n">packageAbiOverride</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="c1">// 向消息队列发出**INIT_COPY**消息</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">sendMessage</span><span class="o">(</span><span class="n">msg</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数的逻辑较为简单,完成安装权限检查后,便开始构造APK安装信息的数据结构,发出<strong>INIT_COPY</strong>消息。</p>
<p>接下来的流程转入到了<strong>PackageHandler</strong>对消息的处理中,就像一个状态机,<strong>INIT_COPY</strong>是安装APK的初始状态:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">doHandleMessage</span><span class="o">(</span><span class="n">Message</span> <span class="n">msg</span><span class="o">)</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">INIT_COPY:</span> <span class="o">{</span>
<span class="n">HandlerParams</span> <span class="n">params</span> <span class="o">=</span> <span class="o">(</span><span class="n">HandlerParams</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="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">mPendingInstalls</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mBound</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">connectToService</span><span class="o">())</span> <span class="o">{</span>
<span class="n">params</span><span class="o">.</span><span class="na">serviceError</span><span class="o">();</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">mPendingInstalls</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">idx</span><span class="o">,</span> <span class="n">params</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">mPendingInstalls</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">idx</span><span class="o">,</span> <span class="n">params</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">idx</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">sendEmptyMessage</span><span class="o">(</span><span class="n">MCS_BOUND</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>INIT_COPY</strong>这个状态下,首先需要做的工作是连接DefaultContainerServcie,有一个标识位mBound,用于表示是否已经连接上。当连接成功后,便将APK加入安装队列mPendingInstalls,发出<strong>MCS_BOUND</strong>消息:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">case</span> <span class="nl">MCS_BOUND:</span> <span class="o">{</span>
<span class="k">if</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="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mContainerService</span> <span class="o">=</span> <span class="o">(</span><span class="n">IMediaContainerService</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="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mContainerService</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">mBound</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// DefaultContainerService连接失败,清除安装队列</span>
<span class="n">mPendingInstalls</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 继续等待连接</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">mPendingInstalls</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="n">HandlerParams</span> <span class="n">params</span> <span class="o">=</span> <span class="n">mPendingInstalls</span><span class="o">.</span><span class="na">get</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">params</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">params</span><span class="o">.</span><span class="na">startCopy</span><span class="o">())</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mPendingInstalls</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="n">mPendingInstalls</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="mi">0</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">mPendingInstalls</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="k">if</span> <span class="o">(</span><span class="n">mBound</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 所有APK已经安装完成,需要断开与DefaultContainerService连接</span>
<span class="n">removeMessages</span><span class="o">(</span><span class="n">MCS_UNBIND</span><span class="o">);</span>
<span class="n">Message</span> <span class="n">ubmsg</span> <span class="o">=</span> <span class="n">obtainMessage</span><span class="o">(</span><span class="n">MCS_UNBIND</span><span class="o">);</span>
<span class="n">sendMessageDelayed</span><span class="o">(</span><span class="n">ubmsg</span><span class="o">,</span> <span class="mi">10000</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">// 还有待安装的APK,继续处理MSC_BOUND消息</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">sendEmptyMessage</span><span class="o">(</span><span class="n">MCS_BOUND</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>MCS_BOUND</strong>表示MediaContainerService这个服务已经连接上,服务端的实现是DefaultContainerService,其内部实现了IMediaContainerService这个AIDL接口。在该状态下,需要从安装队列中取出一个待安装的APK,进行安装操作。安装完一个APK后,又会循环发出MSC_BOUND消息,继续安装下一个APK,知道安装队列为空,才断开与DefaultContainerService的连接。</p>
<p>在<strong>MCS_BOUND</strong>状态下,会针一个待安装的APK发起<strong>HandlerParam.startCopy()</strong>调用:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kt">boolean</span> <span class="nf">startCopy</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">res</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">mRetries</span> <span class="o">></span> <span class="n">MAX_RETRIES</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">sendEmptyMessage</span><span class="o">(</span><span class="n">MCS_GIVE_UP</span><span class="o">);</span>
<span class="n">handleServiceError</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">else</span> <span class="o">{</span>
<span class="n">handleStartCopy</span><span class="o">();</span>
<span class="n">res</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">{...}</span>
<span class="n">handleReturnCode</span><span class="o">();</span>
<span class="k">return</span> <span class="n">res</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数的主干逻辑是调用<strong>handleStartCopy()</strong>和<strong>handleReturnCode()</strong>,另外有一个检查逻辑,如果尝试安装的次数达到了上线MAX_TRETRIES(4),则会放弃安装过程。</p>
<p><strong>HandlerParams</strong>是一个抽象类,<strong>handleStartCopy()</strong>,<strong>handleReturnCode()</strong>和<strong>handleServiceError()</strong>都是抽象函数,安装APK的实现类是<strong>InstallParams</strong>,我们先来看<strong>InstallParams.handleStartCopy()</strong>函数:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleStartCopy</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">RemoteException</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">staged</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 安装一个新APK时,staged为false</span>
<span class="o">}</span>
<span class="c1">// 确定APK的安装位置,onSd表示安装到SD卡上,onInt表示安装到内部存储,即Data分区</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">onSd</span> <span class="o">=</span> <span class="o">(</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_EXTERNAL</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="kt">boolean</span> <span class="n">onInt</span> <span class="o">=</span> <span class="o">(</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_INTERNAL</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>
<span class="n">PackageInfoLite</span> <span class="n">pkgLite</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">onInt</span> <span class="o">&&</span> <span class="n">onSd</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// APK不能同时安装在SD卡和Data分区</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FAILED_INVALID_INSTALL_LOCATION</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 1. 获取APK的包信息PackageInfoLite,此处还只是获取一些少量的包信息,所以叫Lite</span>
<span class="n">pkgLite</span> <span class="o">=</span> <span class="n">mContainerService</span><span class="o">.</span><span class="na">getMinimalPackageInfo</span><span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">resolvedPath</span><span class="o">,</span> <span class="n">installFlags</span><span class="o">,</span>
<span class="n">packageAbiOverride</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">origin</span><span class="o">.</span><span class="na">staged</span> <span class="o">&&</span> <span class="n">pkgLite</span><span class="o">.</span><span class="na">recommendedInstallLocation</span>
<span class="o">==</span> <span class="n">PackageHelper</span><span class="o">.</span><span class="na">RECOMMEND_FAILED_INSUFFICIENT_STORAGE</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 存储空间不足,则需要释放Cache的一些空间</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">loc</span> <span class="o">=</span> <span class="n">pkgLite</span><span class="o">.</span><span class="na">recommendedInstallLocation</span><span class="o">;</span>
<span class="c1">// 2. 判定安装位置</span>
<span class="k">if</span> <span class="o">(</span><span class="n">loc</span> <span class="o">==</span> <span class="n">PackageHelper</span><span class="o">.</span><span class="na">RECOMMEND_FAILED_INVALID_LOCATION</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FAILED_INVALID_INSTALL_LOCATION</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 3. 根据安装位置创建InstallArgs对象</span>
<span class="kd">final</span> <span class="n">InstallArgs</span> <span class="n">args</span> <span class="o">=</span> <span class="n">createInstallArgs</span><span class="o">(</span><span class="k">this</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="k">if</span> <span class="o">(</span><span class="n">ret</span> <span class="o">==</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 重新调整INSTALL_FROM_ADB的user标识,用做APK的检查</span>
<span class="kt">int</span> <span class="n">userIdentifier</span> <span class="o">=</span> <span class="n">getUser</span><span class="o">().</span><span class="na">getIdentifier</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">userIdentifier</span> <span class="o">==</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_ALL</span>
<span class="o">&&</span> <span class="o">((</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FROM_ADB</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">))</span> <span class="o">{</span>
<span class="n">userIdentifier</span> <span class="o">=</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">requiredUid</span> <span class="o">=</span> <span class="n">mRequiredVerifierPackage</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="o">-</span><span class="mi">1</span>
<span class="o">:</span> <span class="n">getPackageUid</span><span class="o">(</span><span class="n">mRequiredVerifierPackage</span><span class="o">,</span> <span class="n">userIdentifier</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">origin</span><span class="o">.</span><span class="na">existing</span> <span class="o">&&</span> <span class="n">requiredUid</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span>
<span class="o">&&</span> <span class="n">isVerificationEnabled</span><span class="o">(</span><span class="n">userIdentifier</span><span class="o">,</span> <span class="n">installFlags</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 此处省略大段APK的检查逻辑。</span>
<span class="c1">// 目前,Android只是定义了检查逻辑,并没实现真正的检查器,所以改段逻辑都不会运行</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 4. 调用InstallArgs.copyApk函数</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="na">copyApk</span><span class="o">(</span><span class="n">mContainerService</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">mRet</span> <span class="o">=</span> <span class="n">ret</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数的主要逻辑如下:</p>
<ol>
<li>
<p>获取待安装APK的信息,这时候只需要少量的信息即可,所以创建了一个PackageInfoLite的对象。通过跨进程调用getMinimalPackageInfo()后,在DefaultContainerService所在的进程中,会进行一次简单的包解析操作,得到待安装APK的包名、版本号和安装路径等基本信息;</p>
</li>
<li>
<p>调整安装位置,InstallParams类的<strong>installLocationPolicy()</strong>函数用于确定最终APK的安装位置,本文不展开分析这个函数,读者可自行参考源码;</p>
</li>
<li>
<p>根据安装位置创建InstallArgs对象,前文说过,InstallArgs有多个子类,分别对应不同的安装位置,<strong>FileIntallArgs</strong>对应的安装位置是内部存储,即Data分区。</p>
</li>
<li>
<p>在安装之前会进行APK的检查,不过Android一直还没有检查器的实现者,所有APK的安装都会直接到<strong>InstallArgs.copyApk()</strong>函数。</p>
</li>
</ol>
<p>以安装到Data分区的APK为例,实现类是<strong>FileInstallArgs</strong>,其copyApk()函数的逻辑如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">copyApk</span><span class="o">(</span><span class="n">IMediaContainerService</span> <span class="n">imcs</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">temp</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">RemoteException</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">staged</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 对于新安装的APK,staged为false</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 1. 生成临时目录,譬如: /data/app/vmdl239812321.tmp</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">tempDir</span> <span class="o">=</span> <span class="n">mInstallerService</span><span class="o">.</span><span class="na">allocateStageDirLegacy</span><span class="o">(</span><span class="n">volumeUuid</span><span class="o">);</span>
<span class="n">codeFile</span> <span class="o">=</span> <span class="n">tempDir</span><span class="o">;</span>
<span class="n">resourceFile</span> <span class="o">=</span> <span class="n">tempDir</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 空间不足,退出安装流程</span>
<span class="k">return</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_FAILED_INSUFFICIENT_STORAGE</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 2. 创建一个用于跨进程传递的文件描述符</span>
<span class="kd">final</span> <span class="n">IParcelFileDescriptorFactory</span> <span class="n">target</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IParcelFileDescriptorFactory</span><span class="o">.</span><span class="na">Stub</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="n">ParcelFileDescriptor</span> <span class="nf">open</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">mode</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">RemoteException</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">FileUtils</span><span class="o">.</span><span class="na">isValidExtFilename</span><span class="o">(</span><span class="n">name</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">"Invalid filename: "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">codeFile</span><span class="o">,</span> <span class="n">name</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">FileDescriptor</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">Os</span><span class="o">.</span><span class="na">open</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">(),</span>
<span class="n">O_RDWR</span> <span class="o">|</span> <span class="n">O_CREAT</span><span class="o">,</span> <span class="mo">0644</span><span class="o">);</span>
<span class="n">Os</span><span class="o">.</span><span class="na">chmod</span><span class="o">(</span><span class="n">file</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">(),</span> <span class="mo">0644</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ParcelFileDescriptor</span><span class="o">(</span><span class="n">fd</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">ErrnoException</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">RemoteException</span><span class="o">(</span><span class="s">"Failed to open: "</span> <span class="o">+</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">;</span>
<span class="c1">// 3. 跨进程调用DefaultContainerService进程中的copyPackage()函数</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">imcs</span><span class="o">.</span><span class="na">copyPackage</span><span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">file</span><span class="o">.</span><span class="na">getAbsolutePath</span><span class="o">(),</span> <span class="n">target</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">ret</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 4. 拷贝Native库</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">libraryRoot</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">codeFile</span><span class="o">,</span> <span class="n">LIB_DIR_NAME</span><span class="o">);</span>
<span class="n">NativeLibraryHelper</span><span class="o">.</span><span class="na">Handle</span> <span class="n">handle</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">handle</span> <span class="o">=</span> <span class="n">NativeLibraryHelper</span><span class="o">.</span><span class="na">Handle</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">codeFile</span><span class="o">);</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">NativeLibraryHelper</span><span class="o">.</span><span class="na">copyNativeBinariesWithOverride</span><span class="o">(</span><span class="n">handle</span><span class="o">,</span> <span class="n">libraryRoot</span><span class="o">,</span>
<span class="n">abiOverride</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">IOException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="k">return</span> <span class="n">ret</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数的主体逻辑如下:</p>
<ol>
<li>
<p>生成一个临时目录,命名如/data/app/vmdl29300388.tmp,其中数字部分<strong>29300388</strong>在每一次安装过程都不同,是安装时的SessionId。由于PMS监控了/data/app目录,如果该目录下有后缀名为.apk的文件生成,便会触发PMS扫描,为了避免这种情况,安装APK时,先用了临时的文件名。</p>
</li>
<li>
<p>基于临时目录创建拷贝目标target,一个可以跨进程传递的文件描述符;</p>
</li>
<li>
<p>发起跨进程调用,DefaultContainerService所在的进程,实现了IMediaContainerService.aidl中所定义的接口,其中<strong>copyPackage()</strong>函数接收两个参数:</p>
<ul>
<li>
<p>一个是APK的源文件路径,如果通过<code class="highlighter-rouge">adb install</code>安装APK,会将APK先拷贝到<strong>/data/local/tmp</strong>目录下,源文件的路径就是<strong>/data/local/tmp/Test.apk</strong>,就作为第一个参数; 如果通过Google Play这个应用市场来安装APK,那么Google Play会将APK下载到Cache分区,源文件的路径就是<strong>/cache/Test.apk</strong>;</p>
</li>
<li>
<p>一个是拷贝的目的路径,即刚刚创建的target,路径为<strong>/data/app/vmdl29300388.tmp</strong>,在DefaultContainerService进程中,会完成实际的拷贝操作,将APK的源文件拷贝到<strong>/data/app/vmdl29300388.tmp/base.apk</strong>。</p>
</li>
</ul>
</li>
<li>
<p>完成Native库的拷贝,此处不展开分析。</p>
</li>
</ol>
<p>通过以上的过程,就已经将APK拷贝到/data/app目录下,不过,此时还是临时的文件,后续还需要重命名。如果以上过程没有异常产生,那最终会返回一个INSTALL_SUCCEEDED(1)这个整数;否则,会返回对应的错误码。</p>
<p>接下来,就会交由<strong>InstallParams.handleReturnCode()</strong>函数来针对上面的返回值进行处理:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">handleReturnCode</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mArgs</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">processPendingInstall</span><span class="o">(</span><span class="n">mArgs</span><span class="o">,</span> <span class="n">mRet</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">processPendingInstall</span><span class="o">(</span><span class="kd">final</span> <span class="n">InstallArgs</span> <span class="n">args</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">currentStatus</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 往消息队列抛出一个可执行任务,完成接下来的安装过程</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">post</span><span class="o">(</span><span class="k">new</span> <span class="n">Runnable</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="n">mHandler</span><span class="o">.</span><span class="na">removeCallbacks</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">PackageInstalledInfo</span> <span class="n">res</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageInstalledInfo</span><span class="o">();</span>
<span class="n">res</span><span class="o">.</span><span class="na">returnCode</span> <span class="o">=</span> <span class="n">currentStatus</span><span class="o">;</span>
<span class="n">res</span><span class="o">.</span><span class="na">uid</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="n">res</span><span class="o">.</span><span class="na">pkg</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">res</span><span class="o">.</span><span class="na">removedInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageRemovedInfo</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="na">returnCode</span> <span class="o">==</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="n">args</span><span class="o">.</span><span class="na">doPreInstall</span><span class="o">(</span><span class="n">res</span><span class="o">.</span><span class="na">returnCode</span><span class="o">);</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mInstallLock</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 安装一个包</span>
<span class="n">installPackageLI</span><span class="o">(</span><span class="n">args</span><span class="o">,</span> <span class="n">res</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">args</span><span class="o">.</span><span class="na">doPostInstall</span><span class="o">(</span><span class="n">res</span><span class="o">.</span><span class="na">returnCode</span><span class="o">,</span> <span class="n">res</span><span class="o">.</span><span class="na">uid</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>
</code></pre></div></div>
<p><strong>handleReturnCode()</strong>将直接调用了<strong>processPendingInstall()</strong>,表示需要继续处理APK的过程。</p>
<p>在此之前,我们见到的很多函数名都与<strong>copy</strong>相关,其实就是要将APK拷贝的安装目录。到了<strong>handleReturnCode()</strong>这个函数之后,就正式要将APK纳入包管理的范围了,如何纳入呢?前文介绍过开机时的包扫描过程:将静态的APK文件解析成动态的数据结构,便完成了Android对一个APK的识别,从而可以方便的管理这个APK。安装APK时,也需要经过包解析的过程。</p>
<p>在<strong>processPendingInstall()</strong>函数中,有几处关键调用:<strong>doPreInstall()</strong>安装之前的检查工作;<strong>installPackageLI()</strong>实际的安装过程,下文重点分析;<strong>dePostInstall()</strong>安装之后的检查工作。
正常安装完之后,还有与APK备份相关的操作,本文不与分析,下面,我们深入<strong>installPackageLI()</strong>,看看一个APK是如何装载到系统中去的:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">installPackageLI</span><span class="o">(</span><span class="n">InstallArgs</span> <span class="n">args</span><span class="o">,</span> <span class="n">PackageInstalledInfo</span> <span class="n">res</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 1. 准备安装参数</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">installFlags</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="na">installFlags</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">installerPackageName</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="na">installerPackageName</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">volumeUuid</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="na">volumeUuid</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">File</span> <span class="n">tmpPackageFile</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">getCodePath</span><span class="o">());</span>
<span class="o">...</span>
<span class="c1">// 2. 解析APK</span>
<span class="n">PackageParser</span> <span class="n">pp</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PackageParser</span><span class="o">();</span>
<span class="n">pp</span><span class="o">.</span><span class="na">setSeparateProcesses</span><span class="o">(</span><span class="n">mSeparateProcesses</span><span class="o">);</span>
<span class="n">pp</span><span class="o">.</span><span class="na">setDisplayMetrics</span><span class="o">(</span><span class="n">mMetrics</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">pkg</span> <span class="o">=</span> <span class="n">pp</span><span class="o">.</span><span class="na">parsePackage</span><span class="o">(</span><span class="n">tmpPackageFile</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageParserException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">res</span><span class="o">.</span><span class="na">setError</span><span class="o">(</span><span class="s">"Failed parse during installPackageLI"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 3. 获取签名和MD5值</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">pp</span><span class="o">.</span><span class="na">collectCertificates</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">);</span>
<span class="n">pp</span><span class="o">.</span><span class="na">collectManifestDigest</span><span class="o">(</span><span class="n">pkg</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageParserException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">res</span><span class="o">.</span><span class="na">setError</span><span class="o">(</span><span class="s">"Failed collect during installPackageLI"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 4. 判定是否需要覆盖安装</span>
<span class="k">if</span> <span class="o">((</span><span class="n">installFlags</span> <span class="o">&</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_REPLACE_EXISTING</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 如果待安装的APK已经存在,则会将replace变量设置为true,表示需要覆盖安装</span>
<span class="o">}</span>
<span class="c1">// 5. 如果Settings中已经记录了待安装的APK信息,需要验证APK的签名</span>
<span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">pkgName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ps</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="c1">// 6. 检查待安装的APK是否有定义新的权限</span>
<span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="n">pkg</span><span class="o">.</span><span class="na">permissions</span><span class="o">.</span><span class="na">size</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">N</span><span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</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="c1">// 7. 将之前的临时文件名vmdl239812321.tmp重名为正式的名字</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">args</span><span class="o">.</span><span class="na">doRename</span><span class="o">(</span><span class="n">res</span><span class="o">.</span><span class="na">returnCode</span><span class="o">,</span> <span class="n">pkg</span><span class="o">,</span> <span class="n">oldCodePath</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="c1">// 8. 通过PackageHandler发起START_INTENT_FILTER_VERIFICATIONS消息</span>
<span class="n">startIntentFilterVerifications</span><span class="o">(</span><span class="n">args</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">getIdentifier</span><span class="o">(),</span> <span class="n">replace</span><span class="o">,</span> <span class="n">pkg</span><span class="o">);</span>
<span class="c1">// 9. 替换升级或者安装一个新的APK</span>
<span class="k">if</span> <span class="o">(</span><span class="n">replace</span><span class="o">)</span> <span class="o">{</span>
<span class="n">replacePackageLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_REPLACING</span><span class="o">,</span> <span class="n">args</span><span class="o">.</span><span class="na">user</span><span class="o">,</span>
<span class="n">installerPackageName</span><span class="o">,</span> <span class="n">volumeUuid</span><span class="o">,</span> <span class="n">res</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">installNewPackageLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="n">scanFlags</span> <span class="o">|</span> <span class="n">SCAN_DELETE_DATA_ON_FAILURES</span><span class="o">,</span>
<span class="n">args</span><span class="o">.</span><span class="na">user</span><span class="o">,</span> <span class="n">installerPackageName</span><span class="o">,</span> <span class="n">volumeUuid</span><span class="o">,</span> <span class="n">res</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 10. 更新APK的所属用户</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mPackages</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">PackageSetting</span> <span class="n">ps</span> <span class="o">=</span> <span class="n">mSettings</span><span class="o">.</span><span class="na">mPackages</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">pkgName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">ps</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">res</span><span class="o">.</span><span class="na">newUsers</span> <span class="o">=</span> <span class="n">ps</span><span class="o">.</span><span class="na">queryInstalledUsers</span><span class="o">(</span><span class="n">sUserManager</span><span class="o">.</span><span class="na">getUserIds</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>
</code></pre></div></div>
<p>该函数实现了这个逻辑:在安装一个APK时,需要判断系统中是否已经存在同包名的APK,如果存在,则需要判断新旧APK的签名以及版本信息,来决定是否需要升级安装;如果不存在,则安装一个新的APK。</p>
<p>具体的细节本文不展开分析了,挑几个关键点:</p>
<ul>
<li>
<p>如果相同包名的APK已经安装过,则在PMS的Settings中,可以根据包名获取到该APK的信息,否则获取到的APK信息为空。因此,包名可以视为APK的唯一关键字。</p>
</li>
<li>
<p>再次安装相同包名的APK,需要判断签名是否匹配,这对应的很大一类场景就是已有APK的升级。试想,如果签名不匹配就能完成APK的替换升级,那已有的APK岂不是全都可以被替换为同包名的其他APK吗?那整个系统毫无安全性可言;</p>
</li>
<li>
<p>之前拷贝APK时用的临时文件名需要改成正式的名字,譬如 /data/app/vmdl239817273.tmp/base.apk 需要更名成 /data/app/packagename-1/base.apk。新名字会带上一个后缀,如果我们不断的升级一个已有的APK,那这个数字会从1开始不断累加。这部分逻辑在PMS.getNextCodePath()函数中,读者可自行查阅;</p>
</li>
<li>
<p>该函数执行到最后,会根据<strong>replace</strong>变量判断是否需要替换已有的APK,还是安装一个新的APK。<strong>replace</strong>变量值是之前确定下来的,这一步有不同的两个函数调用:<strong>replacePackageLI()</strong>和<strong>installNewPackageLI()</strong>。</p>
</li>
</ul>
<p>下文以安装一个全新的APK为例,分析<strong>installNewPackageLI()</strong>函数:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">installNewPackageLI</span><span class="o">(</span><span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">pkg</span><span class="o">,</span> <span class="kt">int</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="kt">int</span> <span class="n">scanFlags</span><span class="o">,</span>
<span class="n">UserHandle</span> <span class="n">user</span><span class="o">,</span> <span class="n">String</span> <span class="n">installerPackageName</span><span class="o">,</span> <span class="n">String</span> <span class="n">volumeUuid</span><span class="o">,</span>
<span class="n">PackageInstalledInfo</span> <span class="n">res</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="c1">// 判断是否存在APK的数据。如果一个APK不是经过正常的卸载流程,那其历史数据是可能还保留下来的</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">dataDirExists</span> <span class="o">=</span> <span class="n">Environment</span>
<span class="o">.</span><span class="na">getDataUserPackageDirectory</span><span class="o">(</span><span class="n">volumeUuid</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">,</span> <span class="n">pkgName</span><span class="o">).</span><span class="na">exists</span><span class="o">();</span>
<span class="o">...</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// APK扫描</span>
<span class="n">PackageParser</span><span class="o">.</span><span class="na">Package</span> <span class="n">newPackage</span> <span class="o">=</span> <span class="n">scanPackageLI</span><span class="o">(</span><span class="n">pkg</span><span class="o">,</span> <span class="n">parseFlags</span><span class="o">,</span> <span class="n">scanFlags</span><span class="o">,</span>
<span class="n">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">(),</span> <span class="n">user</span><span class="o">);</span>
<span class="c1">// 更新PMS的Settings</span>
<span class="n">updateSettingsLI</span><span class="o">(</span><span class="n">newPackage</span><span class="o">,</span> <span class="n">installerPackageName</span><span class="o">,</span> <span class="n">volumeUuid</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">res</span><span class="o">,</span> <span class="n">user</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="na">returnCode</span> <span class="o">!=</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">INSTALL_SUCCEEDED</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果安装失败,则需要删除APK的数据目录</span>
<span class="n">deletePackageLI</span><span class="o">(</span><span class="n">pkgName</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">ALL</span><span class="o">,</span> <span class="kc">false</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">dataDirExists</span> <span class="o">?</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">DELETE_KEEP_DATA</span> <span class="o">:</span> <span class="mi">0</span><span class="o">,</span>
<span class="n">res</span><span class="o">.</span><span class="na">removedInfo</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageManagerException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>该函数调用了前文分析过的包扫描<strong>scanPackageLI()</strong>函数,这样一来APK的各种信息都会记录在PMS中;描完以后,会调用<strong>updateSettingsLI()</strong>函数来更新APK的权限,设置安装状态等,如果一切顺利,那最终APK的安装状态是PKG_INSTALL_COMPLETE(1)。最终APK的信息会持久化到PMS的Settings中。</p>
<h2 id="53-小结">5.3 小结</h2>
<p>APK的安装过程可能比大家预想的要复杂。粗略来看,可以分成两个阶段:</p>
<ol>
<li>
<p>拷贝APK到安装目录。譬如通过<code class="highlighter-rouge">adb install</code>命令安装APK,APK文件会先拷贝到手机的<strong>/data/local/tmp</strong>目录,然后拷贝到手机的<strong>/data/app</strong>目录。这个过程是由PMS消息驱动的,<strong>INIT_COPY</strong>这个消息会触发PMS连接DefaultContainerService,<strong>MCS_BOUND</strong>这个消息表示已经连接上DefaultContainerService,实际的拷贝操作会在DefaultContainerService所在的进程中完成;</p>
</li>
<li>
<p>APK拷贝到安装目录后,便可以扫描APK文件。这个过程同开机时的包扫描过程相似,不同的仅仅是扫描一个APK文件,相同的是需要检查APK的合法性,判断APK的签名是否匹配,更新APK的权限,更新PMS的Settings等。</p>
</li>
</ol>
<h1 id="6-总结">6 总结</h1>
<p>包管理涉及到的数据结构非常多,在分析源码时,很容易陷入各种数据结构之间的关系,难以自拔,以至于看不到包管理的全貌。作为本文最后的汇总,总结一下各数据结构的职能:</p>
<ul>
<li>PackageManagerService #包管理的核心服务</li>
<li>com.android.server.pm.Settings # 所有包的管理信息
<ul>
<li>PackageSetting # 每一个包的信息</li>
<li>BasePermission # 系统中已有的权限</li>
<li>PermissionsState # 授权状态</li>
</ul>
</li>
<li>PackageParser # 包解析器
<ul>
<li>Package # 解析得到的包信息</li>
<li>Component # 组件的基类,其子类对应到<AndroidManifest.xml>中定义的不同组件
<ul>
<li>Activity</li>
<li>Provider</li>
<li>Service</li>
<li>Instrumentation</li>
<li>Permission</li>
<li>PermissionGroup</li>
</ul>
</li>
<li>PackageInfo # 跨进程传递的包数据,包解析时生成
<ul>
<li>PackageItemInfo
<ul>
<li>ApplicationInfo</li>
<li>InstrumentationInfo</li>
<li>PermissionInfo</li>
<li>PermissionGroupInfo</li>
<li>ComponentInfo
<ul>
<li>ActivityInfo</li>
<li>ServiceInfo</li>
<li>ProviderInfo</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>PackageLite # 轻量的包信息</li>
<li>ApkLite</li>
</ul>
</li>
<li>IntentFilter # Intent过滤器
<ul>
<li>IntentInfo # 组件所定义的<intent-filter>信息
<ul>
<li>ActivityIntentInfo</li>
<li>ServiceIntentInfo</li>
<li>ProviderIntentInfo</li>
</ul>
</li>
</ul>
</li>
<li>Intent
<ul>
<li>ResolveInfo</li>
<li>IntentResolver # Intent解析器,其子类用于不同组件的Intent解析
<ul>
<li>ActivityIntentResolver</li>
<li>ServiceIntentResolver</li>
<li>ProviderIntentResolver</li>
</ul>
</li>
</ul>
</li>
<li>PackageHandler # 包管理的消息处理器
<ul>
<li>HandlerParams # 消息的数据载体
<ul>
<li>InstallParams</li>
<li>MeasureParams</li>
</ul>
</li>
<li>InstallArgs # APK的安装参数
<ul>
<li>FileInstallArgs</li>
<li>AsecInstallArgs</li>
<li>MoveInfoArgs</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>如果读者肯花时间同这些繁杂的数据结构周旋,那对于包管理的细节一定可以拿捏的很准确。但猛然一下要理解这么庞大的数据结构设计,实在不是学习包管理机制的上策,毕竟Android也不是一开始就是这么庞大的,譬如包的拆分机制就是较高版本的Android才引入的。随着使用场景的不断丰富,包管理的机制还会更加复杂,建议各位读者还是抓住包管理的几条主线:</p>
<ul>
<li><strong>包扫描的过程</strong>:经过这个过程,Android就能将一个APK文件的静态信息转化为可以管理的数据结构</li>
<li><strong>包查询的过程</strong>:Intent的定义和解析是包查询的核心,通过包查询服务可以获取到一个包的信息</li>
<li><strong>包安装的过程</strong>:这个过程是包管理者接纳一个新入成员的体现</li>
</ul>
<p>诚然,本文不可能涵盖整个包管理的内容,诸如包的删除过程、SELinux等相关的内容,本文都没有涉及。</p>
Android四大组件之Activity--启动过程(下)
2016-10-23T00:00:00+00:00
https://duanqz.github.io/Activity-LaunchProcess-Part2
<p>在<a href="2016-07-29-Activity-LaunchProcess-Part1">Activity的启动过程(上)</a>一文中,我们介绍了Activity启动过程的上半部分,
按照Activity的启动时序,涉及内容到多达11个函数,最终落脚点在创建一个应用进程。Activity启动过程的上半部分都还是在系统进程中完成,是系统进程内部数据结构和状态的调整。本文分析Activity启动过程的下半部分,涉及到系统进程和应用进程的通信,建议读者先读完<a href="2016-01-29-Activity-IPC">应用进程与系统进程的通信</a>,了解两个进程的通信方式。</p>
<p>本文还是像Activity启动过程(上)一样,以Activity的启动时序为主线,以函数为段落进行分析。</p>
<h1 id="概览">概览</h1>
<p>当Zygote创建完一个应用进程之后,得到的仅仅是一个可以运行的载体,Android还没有侵入到这个新创的进程之中。在<a href="2016-07-15-AMS-LaunchProcess">ActivityManagerService的启动过程</a>一文中,我们介绍过,当系统进程创建以后,还需要创建一个运行环境,就是Context,然后再装载Provider信息,这才是一个可以完整的Android进程。对于应用进程而言,也需要经历这个过程,本文分析的起点,就是Android应用进程的创建。</p>
<p>先上最上层的时序图:</p>
<div align="center"><img src="/assets/images/activity/launchprocess/3-activity-launchprocess-sequence-diagram.png" alt="Sequence Diagram" /></div>
<p>在这个时序图中,ActivityThread运行在应用进程,代表主线程;AMS、ASS、ProcessRecord都是系统进程中的对象,两个进程需相互通信,配合完成Activity启动接下来的过程。下面,我们就一一来解析时序图中重要的函数。</p>
<h1 id="1-activitythreadmain">1. ActivityThread.main()</h1>
<p>该函数是一个静态方法,在创建应用进程时,会被反射调用。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><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="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略一些初始化代码</span>
<span class="n">Process</span><span class="o">.</span><span class="na">setArgV0</span><span class="o">(</span><span class="s">"<pre-initialized>"</span><span class="o">);</span>
<span class="c1">// 初始化主线程的消息队列</span>
<span class="n">Looper</span><span class="o">.</span><span class="na">prepareMainLooper</span><span class="o">();</span>
<span class="n">ActivityThread</span> <span class="n">thread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">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="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">// 消息队列开始循环</span>
<span class="n">Looper</span><span class="o">.</span><span class="na">loop</span><span class="o">();</span>
<span class="c1">// 如果消息队列退出循环,则抛出异常</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></code></pre></figure>
<p>该函数的职能是初始化应用进程的主线程,主线程会有一个消息队列,当消息队列开始循环时,便不断从获取消息处理。</p>
<p>我们通常说,ActivityThread就是应用进程的主线程,这其实是一种笼统的说法,其实ActivityThread并非真正意义上的线程,它不是Thread的子类,只不过ActivityThread充当了主线程的职能,它初始化了一个消息队列。在ActivityThread对象构建时,会创建一个Handler对象,这个Handler对象所绑定的消息队列就是主线程的消息队列。ActivityThread对象构建后,会调用自身的<strong>attach()</strong>函数,发起一个绑定操作。</p>
<h1 id="2-activitythreadattachfalse">2. ActivityThread.attach(false)</h1>
<p>在<a href="2016-07-15-AMS-LaunchProcess">ActivityManagerService的启动过程</a>一文中,曾经见过这个函数,系统进程启动时,也会调用该函数,不过系统进程给的参数system是true,而这里给的参数是false,表示为应用进程发起的绑定操作。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><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="o">{</span>
<span class="n">sCurrentActivityThread</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="k">if</span> <span class="o">(!</span><span class="n">system</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 设置进程名。此时,还没有ApplicationInfo,所以用<pre-initialized>来命名应用进程</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">"<pre-initialized>"</span><span class="o">,</span>
<span class="n">UserHandle</span><span class="o">.</span><span class="na">myUserId</span><span class="o">());</span>
<span class="n">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="n">IActivityManager</span> <span class="n">mgr</span> <span class="o">=</span> <span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</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="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// Ingore }</span>
<span class="n">BinderInternal</span><span class="o">.</span><span class="na">addGcWatcher</span><span class="o">(...)</span>
<span class="n">DropBox</span><span class="o">.</span><span class="na">setReporter</span><span class="o">(</span><span class="k">new</span> <span class="n">DropBoxReporter</span><span class="o">());</span>
<span class="n">ViewRootImpl</span><span class="o">.</span><span class="na">addConfigCallback</span><span class="o">(...)</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略系统进程的绑定操作</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>该函数完成一些简单的初始化操作,关键点在于通过IActivityManager发起跨进程调用attachApplication(mAppThread),这里传递的参数mAppThread是一个Binder类型的对象,因此可以作为跨进程传递的参数。mAppThread对象存在于应用进程,但会被传递到系统进程,在系统进程看来,mAppThread就是操作应用进程的一个工具。后续,系统进程如果想要向应用进程发起跨进程调用,也都需要通过mAppThread这个对象。</p>
<h1 id="3-amsattachapplicationlocked">3. AMS.attachApplicationLocked()</h1>
<p>系统进程中,响应<strong>IActivityManager.attachApplication(mAppThread)</strong>的服务就是AMS了,该函数会在一个Binder线程中执行。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">final</span> <span class="kt">boolean</span> <span class="nf">attachApplicationLocked</span><span class="o">(</span><span class="n">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="o">{</span>
<span class="n">ProcessRecord</span> <span class="n">app</span><span class="o">;</span>
<span class="c1">// 根据PID映射应用进程的ProcessRecord对象</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pid</span> <span class="o">!=</span> <span class="n">MY_PID</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="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="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="k">if</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="c1">// 获取ProcessRecord对象失败,则做一些清理操作后退出</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="n">pid</span> <span class="o">!=</span> <span class="n">MY_PID</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Process</span><span class="o">.</span><span class="na">killProcessQuiet</span><span class="o">(</span><span class="n">pid</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">scheduleExit</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">app</span><span class="o">.</span><span class="na">thread</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ProcessRecord对象之前绑定的进程还则,而当下需要将ProcessRecord绑定到一个新的进程</span>
<span class="c1">// 所以需要将之前ProcessRecord所绑定的进程信息清除</span>
<span class="n">handleAppDiedLocked</span><span class="o">(</span><span class="n">app</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="kd">final</span> <span class="n">String</span> <span class="n">processName</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="k">try</span> <span class="o">{</span>
<span class="c1">// 注册应用进程的DeathRecipient,当应用进程崩溃时,系统进程可以收到通知</span>
<span class="n">AppDeathRecipient</span> <span class="n">adr</span> <span class="o">=</span> <span class="k">new</span> <span class="n">AppDeathRecipient</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="n">thread</span><span class="o">);</span>
<span class="n">thread</span><span class="o">.</span><span class="na">asBinder</span><span class="o">().</span><span class="na">linkToDeath</span><span class="o">(</span><span class="n">adr</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">app</span><span class="o">.</span><span class="na">deathRecipient</span> <span class="o">=</span> <span class="n">adr</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">app</span><span class="o">.</span><span class="na">resetPackageList</span><span class="o">(</span><span class="n">mProcessStats</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="s">"link fail"</span><span class="o">,</span> <span class="n">processName</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">// 将ProcessRecord对象绑定到应用进程,这是ProcessRecord就变成了“激活”状态</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">app</span><span class="o">.</span><span class="na">curAdj</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">setAdj</span> <span class="o">=</span> <span class="o">-</span><span class="mi">100</span><span class="o">;</span>
<span class="n">app</span><span class="o">.</span><span class="na">curSchedGroup</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="na">setSchedGroup</span> <span class="o">=</span> <span class="n">Process</span><span class="o">.</span><span class="na">THREAD_GROUP_DEFAULT</span><span class="o">;</span>
<span class="n">app</span><span class="o">.</span><span class="na">forcingToForeground</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">updateProcessForegroundLocked</span><span class="o">(</span><span class="n">app</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">hasShownUi</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">debugging</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">cached</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">killedByAm</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">removeMessages</span><span class="o">(</span><span class="n">PROC_START_TIMEOUT_MSG</span><span class="o">,</span> <span class="n">app</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">normalMode</span> <span class="o">=</span> <span class="n">mProcessesReady</span> <span class="o">||</span> <span class="n">isAllowedWhileBooting</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="c1">// 获取应用进程的所有Provider</span>
<span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</span> <span class="o">=</span> <span class="n">normalMode</span> <span class="o">?</span> <span class="n">generateApplicationProvidersLocked</span><span class="o">(</span><span class="n">app</span><span class="o">)</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略debug和性能相关的代码</span>
<span class="c1">// 发起跨进程调用,将一堆的信息传递给应用进程</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">providers</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">instrumentationClass</span><span class="o">,</span>
<span class="n">profilerInfo</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">instrumentationArguments</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">instrumentationWatcher</span><span class="o">,</span>
<span class="n">app</span><span class="o">.</span><span class="na">instrumentationUiAutomationConnection</span><span class="o">,</span> <span class="n">testMode</span><span class="o">,</span> <span class="n">enableOpenGlTrace</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">persistent</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">Configuration</span><span class="o">(</span><span class="n">mConfiguration</span><span class="o">),</span> <span class="n">app</span><span class="o">.</span><span class="na">compat</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">updateLruProcessLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="n">mPersistentStartingProcesses</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">app</span><span class="o">);</span>
<span class="n">mProcessesOnHold</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">app</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">badApp</span> <span class="o">=</span> <span class="kc">false</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="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="c1">// 调度Activity</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mStackSupervisor</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="o">{</span>
<span class="n">didSomething</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</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="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="c1">// 调度Service</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="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</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="k">if</span> <span class="o">(!</span><span class="n">badApp</span> <span class="o">&&</span> <span class="n">isPendingBroadcastProcessLocked</span><span class="o">(</span><span class="n">pid</span><span class="o">))</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 调度Broadcast</span>
<span class="n">didSomething</span> <span class="o">|=</span> <span class="n">sendPendingBroadcastsLocked</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="n">Exception</span> <span class="n">e</span><span class="o">)</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="k">if</span> <span class="o">(!</span><span class="n">badApp</span> <span class="o">&&</span> <span class="n">mBackupTarget</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">mBackupTarget</span><span class="o">.</span><span class="na">appInfo</span><span class="o">.</span><span class="na">uid</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="o">{</span>
<span class="o">...</span> <span class="c1">// 省略Backup相关的代码</span>
<span class="o">}</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="n">app</span><span class="o">.</span><span class="na">kill</span><span class="o">(</span><span class="s">"error during init"</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">handleAppDiedLocked</span><span class="o">(</span><span class="n">app</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="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">didSomething</span><span class="o">)</span> <span class="o">{</span>
<span class="n">updateOomAdjLocked</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></code></pre></figure>
<p>该函数的实现逻辑如下:</p>
<ul>
<li>
<p>获取ProcessRecord对象。通过Binder.getCallingPid()可以获取Binder接口的调用者所在进程的PID
进一步,通过PID,就能获取到应用进程对应的ProcessRecord。如果ProcessRecord对象获取失败,则表示应用进程已经被杀掉,需要清除应用进程的数据;如果ProcessRecord之前所绑定的进程信息还在,则需要清除这些信息;</p>
</li>
<li>
<p>为应用进程注册AppDeathRecipient,它是存在于系统进程的对象,当应用进程被杀掉的时候,系统进程会收到通知;</p>
</li>
<li>
<p>激活ProcessRecord对象。所谓“激活”,就是ProcessRecord已经绑定到了一个应用进程,绑定的标识就是:应用进程的ApplicationThread对象赋值给ProcessRecord.thread变量;</p>
</li>
<li>
<p>获取应用进程中所有注册的Provider,这需要通过PackageManager来扫描进程所关联的包名,所有静态的Provider信息,即ProviderInfo对象,都会保存到<strong>ProcessRecord.pubProviders</strong>变量中;</p>
<blockquote>
<p>在系统进程启动时,也曾经历过这个过程,系统进程对应的包名是”android”,扫描的是framework-res.apk的这个应用的信息。</p>
</blockquote>
</li>
<li>
<p>进行一些调试与性能相关的变量设置之后,通过<strong>IApplicationThread.bindApplication()</strong>发起跨进程调用,这样一来,诸如进程名、ApplicationInfo等信息就传递给应用进程了;</p>
</li>
<li>
<p>将信息传递给应用程序以后,就可以进行调度了。这里通过两个标识来记录调度的情况:badApp标识是否调度失败,默认为false,在依次调度Activity、Service和Broadcast的过程中,根据实际的情况,可能将其调整为true,表示调度失败了。一旦调度失败,则需要杀掉应用进程;didSomething表示确有调度发生。在后文中,我们将着重分析Activity的调度,即<strong>ASS.attachApplicationLocked()</strong>函数。</p>
</li>
</ul>
<h1 id="4-activitythreadhandlebindapplication">4. ActivityThread.handleBindApplication()</h1>
<p>系统进程通过<strong>IApplicationThread.bindApplication()</strong>向应用进程传递数据,应用进程中,ApplicationThread会在Binder线程中响应这个跨进程调用,进行一些简单的数据封装后,便向主线程抛出一个<strong>BIND_APPLICATION</strong>消息,这样一来,真正完成进程绑定的操作是在主线程的<strong>handleBindApplication()</strong>函数中。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleBindApplication</span><span class="o">(</span><span class="n">AppBindData</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略部分数据初始化代码</span>
<span class="c1">// 虽然应用进程早就已经创建,但直到这时,才知道进程名是什么</span>
<span class="n">Process</span><span class="o">.</span><span class="na">setArgV0</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">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="n">data</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">myUserId</span><span class="o">());</span>
<span class="o">...</span> <span class="c1">// 省略部分进程运行信息设置代码</span>
<span class="c1">// 创建应用进程的Android运行环境:Context</span>
<span class="kd">final</span> <span class="n">ContextImpl</span> <span class="n">appContext</span> <span class="o">=</span> <span class="n">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="k">if</span> <span class="o">(!</span><span class="n">Process</span><span class="o">.</span><span class="na">isIsolated</span><span class="o">())</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略与缓冲目录设置相关的代码</span>
<span class="o">}</span>
<span class="o">...</span> <span class="c1">// 省略应用进程相关的初始化代码,包含时区、StrictMode、调试模式等相关的设置</span>
<span class="c1">// 根据情况初始化Intrumentation对象</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="o">...</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">instrContext</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span>
<span class="n">mInstrumentation</span> <span class="o">=</span> <span class="o">(</span><span class="n">Instrumentation</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">data</span><span class="o">.</span><span class="na">instrumentationName</span><span class="o">.</span><span class="na">getClassName</span><span class="o">()).</span><span class="na">newInstance</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">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">mInstrumentation</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">instrContext</span><span class="o">,</span> <span class="n">appContext</span><span class="o">,</span>
<span class="k">new</span> <span class="nf">ComponentName</span><span class="o">(</span><span class="n">ii</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">ii</span><span class="o">.</span><span class="na">name</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">data</span><span class="o">.</span><span class="na">instrumentationUiAutomationConnection</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">mInstrumentation</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Instrumentation</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 创建Application对象</span>
<span class="n">Application</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">makeApplication</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="n">mInitialApplication</span> <span class="o">=</span> <span class="n">app</span><span class="o">;</span>
<span class="c1">// 装载Providers</span>
<span class="k">if</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="o">{</span>
<span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</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="k">if</span> <span class="o">(</span><span class="n">providers</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">installContentProviders</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="n">providers</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">mInstrumentation</span><span class="o">.</span><span class="na">onCreate</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="c1">// 调用Application.onCreate()函数</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="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>系统进程已经将很多与应用程序相关的信息都传递给到此,所以该函数的职能就是初始化一个Android应用程序的运行信息。以下过程与系统进程的初始化一样:</p>
<ul>
<li>
<p>创建Android运行环境Context;</p>
</li>
<li>
<p>创建Instrumentation对象;</p>
</li>
<li>
<p>创建Application对象。通过<strong>LoadedApk.makeApplication()</strong>函数,就能创建一个Application对象;</p>
</li>
<li>
<p>装载Providers。有了一个静态的ProviderInfo列表,但应用进程的ContentProvider还不能真正工作,因为ContentProvider对象还未创建。<strong>ActivityThread.installContentProviders()</strong>函数就是用来创建ContentProvider对象的。由此可见,在<strong>Application.onCreate()</strong>函数调用之前,进程的<strong>ContentProvider</strong>都已经创建完毕了;</p>
</li>
<li>
<p>调用<strong>Application.onCreate()</strong>函数。这个函数就是我们通常要实现的系统回调函数。</p>
</li>
</ul>
<blockquote>
<p>Android应用程序需要一个可以运行的进程,这个进程的创建需要通过某种手段通知系统进程,譬如启动Activity,从而引发Zygote孵化出一个应用进程;</p>
<p>刚出生的应用进程来到Android的世界,还什么都不懂,甚至连个正经儿的名字都没有,这时,应用进程极需要将自己加入到Android的社会关系中。应用进程知道,在Android世界中,有一个中心进程,即系统进程,运行在系统进程中有一个管理者,即AMS。所以,应用进程就向AMS发起了“绑定”请求;</p>
<p>AMS在收到“绑定”请求后,迅速了解到情况,知道应用进程因何而来,为何而去,把应用进程需要生存下去的信息传递给它,譬如ApplicationInfo,PrivderInfo等;</p>
<p>应用进程在收到系统进程的反馈之后,开始自我成长,有了进程名,构建出Android的运行环境,真正有了Android应用程序的概念,即Application,这时候应用进程才真正在Android的世界立足。</p>
</blockquote>
<p><b><font color="red">至此,第一个阶段已经分析完毕,完成了一个普通的进程到Android进程的蜕变。</font></b></p>
<h1 id="5-assattachapplicationlocked">5. ASS.attachApplicationLocked()</h1>
<p>该函数取名为<strong>attachApplication()</strong>,意在将ASS绑定到应用进程,那么,ASS有什么需要被绑定到一个应用进程呢?当然是AcivityRecord了。</p>
<p>启动HomeActivity创建了一个新的ActivityRecord,并将其挪到了HomeStack的栈顶位置,当时,ActivityRecord还没有关联到任何进程相关的信息,还不能被迁移到显示状态。当应用进程被创建之后,Activity才有了运行的机会,这时候才会真正调度ActivityRecord。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">boolean</span> <span class="nf">attachApplicationLocked</span><span class="o">(</span><span class="n">ProcessRecord</span> <span class="n">app</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">RemoteException</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">processName</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="kt">boolean</span> <span class="n">didSomething</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">displayNdx</span> <span class="o">=</span> <span class="n">mActivityDisplays</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">displayNdx</span> <span class="o">>=</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">ArrayList</span><span class="o"><</span><span class="n">ActivityStack</span><span class="o">></span> <span class="n">stacks</span> <span class="o">=</span> <span class="n">mActivityDisplays</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">displayNdx</span><span class="o">).</span><span class="na">mStacks</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">stackNdx</span> <span class="o">=</span> <span class="n">stacks</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">stackNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">stackNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">stacks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">stackNdx</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isFrontStack</span><span class="o">(</span><span class="n">stack</span><span class="o">))</span> <span class="o">{</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">ActivityRecord</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">stack</span><span class="o">.</span><span class="na">topRunningActivityLocked</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">hr</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">hr</span><span class="o">.</span><span class="na">app</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">uid</span> <span class="o">==</span> <span class="n">hr</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="n">processName</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">hr</span><span class="o">.</span><span class="na">processName</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="n">realStartActivityLocked</span><span class="o">(</span><span class="n">hr</span><span class="o">,</span> <span class="n">app</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="n">didSomething</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</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="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">didSomething</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ensureActivitiesVisibleLocked</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">didSomething</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>该函数的实现逻辑与很多ASS的其他函数都类似:遍历寻找所有ActivityStack和TaskRecord,对栈顶的ActivityRecord进行操作。这里其实就是需要启动应用进程中还未启动的Activity。</p>
<h1 id="6-assrealstartactivitylocked">6. ASS.realStartActivityLocked()</h1>
<p>其实该函数并没有展开分析的必要,从其函数取名来看,是要动真格的了,什么才叫真正启动一个Activity呢?
我们可以理解为:调度<strong>Activity.onCreate()</strong>函数执行了,就算是真正启动Activity。</p>
<p>这里我们只把该函数的关键代码展示出来:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">boolean</span> <span class="nf">realStartActivityLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">,</span>
<span class="n">ProcessRecord</span> <span class="n">app</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="kd">throws</span> <span class="n">RemoteException</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">r</span><span class="o">.</span><span class="na">app</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">waitingToKill</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">r</span><span class="o">.</span><span class="na">launchCount</span><span class="o">++;</span>
<span class="n">r</span><span class="o">.</span><span class="na">lastLaunchTime</span> <span class="o">=</span> <span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">();</span>
<span class="o">...</span>
<span class="n">app</span><span class="o">.</span><span class="na">thread</span><span class="o">.</span><span class="na">scheduleLaunchActivity</span><span class="o">(</span><span class="k">new</span> <span class="n">Intent</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="n">r</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span>
<span class="n">System</span><span class="o">.</span><span class="na">identityHashCode</span><span class="o">(</span><span class="n">r</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="k">new</span> <span class="n">Configuration</span><span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">mConfiguration</span><span class="o">),</span>
<span class="k">new</span> <span class="nf">Configuration</span><span class="o">(</span><span class="n">stack</span><span class="o">.</span><span class="na">mOverrideConfig</span><span class="o">),</span> <span class="n">r</span><span class="o">.</span><span class="na">compat</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromPackage</span><span class="o">,</span>
<span class="n">task</span><span class="o">.</span><span class="na">voiceInteractor</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">repProcState</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">icicle</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">persistentState</span><span class="o">,</span> <span class="n">results</span><span class="o">,</span>
<span class="n">newIntents</span><span class="o">,</span> <span class="o">!</span><span class="n">andResume</span><span class="o">,</span> <span class="n">mService</span><span class="o">.</span><span class="na">isNextTransitionForward</span><span class="o">(),</span> <span class="n">profilerInfo</span><span class="o">);</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(</span><span class="n">andResume</span><span class="o">)</span> <span class="o">{</span>
<span class="n">stack</span><span class="o">.</span><span class="na">minimalResumeActivityLocked</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>关键点有三:</p>
<ul>
<li>
<p>将ProcessRecord和ActivityRecord关联。ActivityRecord对象的app属性,就是ProcessRecord类型;</p>
</li>
<li>
<p>跨进程调用<strong>IApplicationThread.scheduleLaunchActivity()</strong>,调度启动Activity。很多参数都会通过这个函数传递到应用进程;</p>
</li>
<li>
<p>调用<strong>AS.minimalResumeActivityLocked()</strong>来显示Activity。</p>
</li>
</ul>
<h1 id="7-activitythreadhandlelaunchactivity">7. ActivityThread.handleLaunchActivity()</h1>
<p>应用进程在收到系统进程发起的scheduleLaunchActivity()请求后,便开始驱动Activity生命周期的运转了。</p>
<p>该函数的主要职能就是触发Activity生命周期的开始。代码片段如下:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleLaunchActivity</span><span class="o">(</span><span class="n">ActivityClientRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">customIntent</span><span class="o">)</span> <span class="o">{</span>
<span class="n">unscheduleGcIdler</span><span class="o">();</span>
<span class="n">mSomeActivitiesChanged</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">r</span><span class="o">.</span><span class="na">profilerInfo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mProfiler</span><span class="o">.</span><span class="na">setProfiler</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">profilerInfo</span><span class="o">);</span>
<span class="n">mProfiler</span><span class="o">.</span><span class="na">startProfiling</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">handleConfigurationChanged</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">WindowManagerGlobal</span><span class="o">.</span><span class="na">initialize</span><span class="o">();</span>
<span class="n">Activity</span> <span class="n">a</span> <span class="o">=</span> <span class="n">performLaunchActivity</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">customIntent</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">a</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">createdConfig</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Configuration</span><span class="o">(</span><span class="n">mConfiguration</span><span class="o">);</span>
<span class="n">Bundle</span> <span class="n">oldState</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">state</span><span class="o">;</span>
<span class="n">handleResumeActivity</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">token</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">isForward</span><span class="o">,</span>
<span class="o">!</span><span class="n">r</span><span class="o">.</span><span class="na">activity</span><span class="o">.</span><span class="na">mFinished</span> <span class="o">&&</span> <span class="o">!</span><span class="n">r</span><span class="o">.</span><span class="na">startsNotResumed</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">activity</span><span class="o">.</span><span class="na">mFinished</span> <span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">startsNotResumed</span><span class="o">)</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="k">try</span> <span class="o">{</span>
<span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</span><span class="o">()</span>
<span class="o">.</span><span class="na">finishActivity</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">token</span><span class="o">,</span> <span class="n">Activity</span><span class="o">.</span><span class="na">RESULT_CANCELED</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="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">ex</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></code></pre></figure>
<p>在进行了一些参数设置后,便调用<strong>ActivityThread.performLaunchActivity()</strong>函数,可以猜到初始化一个Activity,真正干活的是它。如果初始化成功,便调用<strong>ActivityThread.handleResumeActivity()</strong>来处理Activity进入显示状态时需要完成的操作;如果初始化失败,则发起跨进程调用<strong>IActivityManager.finishActivity()</strong>,来通报结束一个Activity的生命周期。</p>
<h1 id="8-activitythreadperformlaunchactivity">8. ActivityThread.performLaunchActivity()</h1>
<p>该函数负责实际执行应用进程中Activity的启动,是系统进程调度启动一个Activity的落脚点,是Activity生命周期的开始。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="n">Activity</span> <span class="nf">performLaunchActivity</span><span class="o">(</span><span class="n">ActivityClientRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">customIntent</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 准备新建一个Activity对象的参数</span>
<span class="n">ActivityInfo</span> <span class="n">aInfo</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">activityInfo</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">packageInfo</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">packageInfo</span> <span class="o">=</span> <span class="n">getPackageInfo</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="n">r</span><span class="o">.</span><span class="na">compatInfo</span><span class="o">,</span>
<span class="n">Context</span><span class="o">.</span><span class="na">CONTEXT_INCLUDE_CODE</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ComponentName</span> <span class="n">component</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="k">if</span> <span class="o">(</span><span class="n">component</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">component</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">resolveActivity</span><span class="o">(</span>
<span class="n">mInitialApplication</span><span class="o">.</span><span class="na">getPackageManager</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">setComponent</span><span class="o">(</span><span class="n">component</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">activityInfo</span><span class="o">.</span><span class="na">targetActivity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">component</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ComponentName</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">.</span><span class="na">targetActivity</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 反射新建一个Activity对象</span>
<span class="n">Activity</span> <span class="n">activity</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">packageInfo</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span>
<span class="n">activity</span> <span class="o">=</span> <span class="n">mInstrumentation</span><span class="o">.</span><span class="na">newActivity</span><span class="o">(</span>
<span class="n">cl</span><span class="o">,</span> <span class="n">component</span><span class="o">.</span><span class="na">getClassName</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="o">...</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">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="c1">// 创建Activity的Context,并将数据绑定到Activity对象</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">Application</span> <span class="n">app</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">packageInfo</span><span class="o">.</span><span class="na">makeApplication</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="n">mInstrumentation</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">activity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Context</span> <span class="n">appContext</span> <span class="o">=</span> <span class="n">createBaseContextForActivity</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">activity</span><span class="o">);</span>
<span class="n">CharSequence</span> <span class="n">title</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">.</span><span class="na">loadLabel</span><span class="o">(</span><span class="n">appContext</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">());</span>
<span class="n">Configuration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Configuration</span><span class="o">(</span><span class="n">mCompatConfiguration</span><span class="o">);</span>
<span class="n">activity</span><span class="o">.</span><span class="na">attach</span><span class="o">(</span><span class="n">appContext</span><span class="o">,</span> <span class="k">this</span><span class="o">,</span> <span class="n">getInstrumentation</span><span class="o">(),</span> <span class="n">r</span><span class="o">.</span><span class="na">token</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">ident</span><span class="o">,</span> <span class="n">app</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="n">r</span><span class="o">.</span><span class="na">activityInfo</span><span class="o">,</span> <span class="n">title</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">parent</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">embeddedID</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">lastNonConfigurationInstances</span><span class="o">,</span> <span class="n">config</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">referrer</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">voiceInteractor</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 回调Activity.onCreate()函数</span>
<span class="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">isPersistable</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mInstrumentation</span><span class="o">.</span><span class="na">callActivityOnCreate</span><span class="o">(</span><span class="n">activity</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">state</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">persistentState</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="na">callActivityOnCreate</span><span class="o">(</span><span class="n">activity</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">state</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 回调Activity.onStart()函数</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">r</span><span class="o">.</span><span class="na">activity</span><span class="o">.</span><span class="na">mFinished</span><span class="o">)</span> <span class="o">{</span>
<span class="n">activity</span><span class="o">.</span><span class="na">performStart</span><span class="o">();</span>
<span class="n">r</span><span class="o">.</span><span class="na">stopped</span> <span class="o">=</span> <span class="kc">false</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="o">{}</span>
<span class="k">return</span> <span class="n">activity</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>关键代码逻辑如下:</p>
<ul>
<li>
<p>初始化一些参数。一个新的Activity对象,是通过反射创建的,所以需要包名、类名等信息,部分信息已经从系统进程传递到应用进程了,部分信息也都可以通过PackageManager再向系统进程索取;</p>
</li>
<li>
<p>新建一个Activity对象。有了参数以后,便可以通过ClassLoader加载到Activity对应的类,反射构建之。以上代码片省略了一个细节,就是Activity的Context、Theme会在Activity对象构建之后被初始化;</p>
</li>
<li>
<p>调用<strong>Activity.onCreate()</strong>函数。Activity对象有了,便可以开始完成其使命,于是大家耳熟能详的<strong>Activity.onCreate()</strong>函数此刻便被唤起,Actiivty进入其生命周期的开始。</p>
</li>
<li>
<p>调用<strong>Activity.onStart()</strong>函数。Activity生命周期紧锣密鼓的进入下一个阶段,另外一个重要的函数也随之而来;</p>
</li>
</ul>
<blockquote>
<p>读到Activity启动过程这个函数,想必各位读者都叹为观止,这是经历了多长一段路,才总算到<strong>Activity.onCreate()</strong>这个函数啊!原来我们在这个函数里面加几行代码,是经过了这样一番曲折的过程才会被执行!</p>
<p>Activity生命函数就像是Activity调度过程的冰山一角,对于做应用层的开发人员而言,只需要看到最外层的冰山,就能栩栩如生地把冰山的样子转述出来;然而,意想不到的是,冰山底下宏大的世界,撑起整座冰山的根基却是如此的复杂。</p>
</blockquote>
<h1 id="9-activitythreadhandleresumeactivity">9. ActivityThread.handleResumeActivity()</h1>
<p>在Activity对象构建成功,并成功走完onCreate(), onStart()两个生命周期函数之后,便进入到这个阶段:Activity要进入onResume()的这个生命周期:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">handleResumeActivity</span><span class="o">(</span><span class="n">IBinder</span> <span class="n">token</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">clearHide</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">isForward</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">reallyResume</span><span class="o">)</span> <span class="o">{</span>
<span class="n">unscheduleGcIdler</span><span class="o">();</span>
<span class="n">mSomeActivitiesChanged</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="c1">// 实际执行应用进程一侧的Activity.onResume()</span>
<span class="n">ActivityClientRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">performResumeActivity</span><span class="o">(</span><span class="n">token</span><span class="o">,</span> <span class="n">clearHide</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="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略与WindownManager相关的窗口操作</span>
<span class="k">if</span> <span class="o">(</span><span class="n">reallyResume</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 通知系统进程,Activity已经处于Resumed状态</span>
<span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</span><span class="o">().</span><span class="na">activityResumed</span><span class="o">(</span><span class="n">token</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>真正调用生命周期的是<strong>performResumeActivity()</strong>函数,本文不展开分析了。有一点需要读者注意:在调用<strong>onResume()</strong>之前,如果Activity已经处于Stopped状态,则会先调用<strong>onRestart()</strong>函数,在<strong>performResumeActivity()</strong>函数中,读者可以找到这部分逻辑。</p>
<p>当应用进程已经走完<strong>onResme()</strong>之后,便可通知系统进程:Activity已经进入Resumed状态了,这是通过调用<strong>IActivityManager.activityResumed()</strong>函数实现的,我们后文会再分析此函数。</p>
<p>如果没有进一步的用户输入,那Activity就会在Resumed状态驻留了。</p>
<p><b><font color="red">至此,第二个部分分析完毕,干了一个大事:启动Activity到显示状态。</font></b></p>
Android四大组件之Activity--启动过程(上)
2016-07-29T00:00:00+00:00
https://duanqz.github.io/Activity-LaunchProcess-Part1
<p>Activity的启动过程涉及到的逻辑非常庞大,很难做到单点突破,建议读者在读本文之前,把<a href="2016-01-21-Activity-LaunchMode">Activity的四种启动模式</a>、<a href="2016-07-15-AMS-LaunchProcess.md">ActivityManagerService的启动过程</a>、<a href="2016-02-01-Activity-Maintenance">Activity的管理方式</a>都再读一遍,储存一些背景知识。也是由于内容太多,笔者把启动过程分为上、下两部分介绍,另外一部分在<a href="2016-10-23-Activity-LaunchProcess-Part2.md">Activity的启动过程(下)</a>一文中。</p>
<p>为了尽量能够把Activity启动的主线流程呈现出来,本文选择HomeActivity的启动过程进行分析,按照启动过程中函数的调用时序,以每个关键函数为段落进行分析。
之所以要挑选HomeActivity,是因为它是起机后第一个要启动的Activity,很多状态都处于初始条件,能够简化分析过程。
之所以要以函数为段落进行分析,是因为启动过程本来就是一个时序,哪个时间点,进入哪个函数,完成哪些事,是本文想要分析重点。</p>
<p>通过阅读本文,能够深入理解Activity启动过程中的关键节点以及关键事件。但不得不说,Activity启动的场景非常多,远非本文的篇幅能够涵盖,读者应该结合自身的工作环境,来探究在不同场景下Activity启动所经过的路径。</p>
<h1 id="概述">概述</h1>
<p>HomeActivity就是桌面的主屏,其Category为CATEGORY_HOME。在<a href="2016-07-15-AMS-LaunchProcess">ActivityManagerService的启动过程</a>一文中,我们分析过在ActivityManagerService准备就绪的最后,即systemReady()函数的最后部分,就会启动桌面,实际上就是要求启动HomeActivity。</p>
<p>本章分析的对象就是HomeActivity的启动过程,先上一个最上层的时序图:</p>
<div align="center"><img src="/assets/images/activity/launchprocess/1-activity-launchprocess-sequence-diagram.png" alt="Sequence Diagram" /></div>
<p>我们先树立个顶层的概念:Activity的调度由AMS完成,AMS以栈式结构管理者所有的Activity,直接管理Activity的栈称为TaskRecord,后文中会用“任务”来表述这个栈;管理任务的栈称为ActivityStack;在ActivityStack之上还有一个管理者ActivityStackSupervisor。Activity的启动过程就是AMS对栈结构进行调整的过程,Activity的各级管理者都会参与进来,各司其职。</p>
<blockquote>
<p>阅读过早期Android源码的读者可能知道,Android最开始对Activity调度的设计并没有像现在这么复杂,例如ActivityStackSupervisor是到支持多屏显示才出现的,还有诸如多窗口的实现,也是直到Android 6.0才出现的。Android不断在更迭,保持自身活力,适应新的需求。</p>
</blockquote>
<h1 id="1-amsstarthomeactivitylocked">1. AMS.startHomeActivityLocked()</h1>
<p>这是启动HomeActivity的第一个函数,这时候,系统进程(system_server)才刚刚启动完毕(systemReady),第一个要启动的Activity就是HomeActivity,意味着要让用户看到手机的主屏,又称之为桌面。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">boolean</span> <span class="nf">startHomeActivityLocked</span><span class="o">(</span><span class="kt">int</span> <span class="n">userId</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mFactoryTest</span> <span class="o">==</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_LOW_LEVEL</span>
<span class="o">&&</span> <span class="n">mTopAction</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span><span class="mi">1</span><span class="o">-</span><span class="n">activity</span><span class="o">-</span><span class="n">launchprocess</span><span class="o">-</span><span class="n">sequence</span><span class="o">-</span><span class="n">diagram</span><span class="o">.</span><span class="na">png</span>
<span class="c1">// 工厂测试模式,而且找到ACTION_FACTORY_TEST,则启动失败</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 获取一个Home Intent,交由PackageManager解析出ActivityInfo</span>
<span class="n">Intent</span> <span class="n">intent</span> <span class="o">=</span> <span class="n">getHomeIntent</span><span class="o">();</span>
<span class="n">ActivityInfo</span> <span class="n">aInfo</span> <span class="o">=</span> <span class="n">resolveActivityInfo</span><span class="o">(</span><span class="n">intent</span><span class="o">,</span> <span class="n">STOCK_PM_FLAGS</span><span class="o">,</span> <span class="n">userId</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="o">{</span>
<span class="c1">// 找到目标Activity,设置Activity启动参数</span>
<span class="n">intent</span><span class="o">.</span><span class="na">setComponent</span><span class="o">(</span><span class="k">new</span> <span class="n">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">aInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityInfo</span><span class="o">(</span><span class="n">aInfo</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="n">getAppInfoForUser</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="n">userId</span><span class="o">);</span>
<span class="n">ProcessRecord</span> <span class="n">app</span> <span class="o">=</span> <span class="n">getProcessRecordLocked</span><span class="o">(</span><span class="n">aInfo</span><span class="o">.</span><span class="na">processName</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="kc">true</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">||</span> <span class="n">app</span><span class="o">.</span><span class="na">instrumentationClass</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 桌面进程还未启动,则启动HomeActivity</span>
<span class="n">intent</span><span class="o">.</span><span class="na">setFlags</span><span class="o">(</span><span class="n">intent</span><span class="o">.</span><span class="na">getFlags</span><span class="o">()</span> <span class="o">|</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">);</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">startHomeActivity</span><span class="o">(</span><span class="n">intent</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">reason</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></code></pre></figure>
<p>以上函数的主体逻辑是:</p>
<ul>
<li>
<p>通过PackageManager解析出可以处理HomeIntent的Activity,这里的HomeIntent其实就是一个设置了Category为CATEGORY_HOME的Intent;</p>
</li>
<li>
<p>如果找到,则需要通过Intent启动目标Activity。这时候,需要把包名信息设置到Intent中。启动Activity之前,会从解析到ActivityInfo中获取Activity所属的进程信息,在第一次启动HomeActivity时,宿主进程显然还没有创建,所以,接下来的逻辑转到了ActivityStackSupervisor(下文简称ASS)中,这会经过一个无比的漫长的旅途。</p>
</li>
</ul>
<h1 id="2-assstarthomeactivity">2. ASS.startHomeActivity()</h1>
<p>ASS接收Activity启动的控制权后,就开始彰显其精密的调度手段了。
到这一步,HomeActivity的静态信息(Intent, ActivityInfo)已经准备好了,接下来,就是要把Activity的静态信息注入到一个动态的运行环境中。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">startHomeActivity</span><span class="o">(</span><span class="n">Intent</span> <span class="n">intent</span><span class="o">,</span> <span class="n">ActivityInfo</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="n">moveHomeStackTaskToTop</span><span class="o">(</span><span class="n">HOME_ACTIVITY_TYPE</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
<span class="n">startActivityLocked</span><span class="o">(</span><span class="kc">null</span> <span class="cm">/* caller */</span><span class="o">,</span> <span class="n">intent</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* resolvedType */</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">,</span>
<span class="kc">null</span> <span class="cm">/* voiceSession */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* voiceInteractor */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* resultTo */</span><span class="o">,</span>
<span class="kc">null</span> <span class="cm">/* resultWho */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* requestCode */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* callingPid */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* callingUid */</span><span class="o">,</span>
<span class="kc">null</span> <span class="cm">/* callingPackage */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* realCallingPid */</span><span class="o">,</span> <span class="mi">0</span> <span class="cm">/* realCallingUid */</span><span class="o">,</span>
<span class="mi">0</span> <span class="cm">/* startFlags */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* options */</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/* ignoreTargetSecurity */</span><span class="o">,</span>
<span class="kc">false</span> <span class="cm">/* componentSpecified */</span><span class="o">,</span>
<span class="kc">null</span> <span class="cm">/* outActivity */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* container */</span><span class="o">,</span> <span class="kc">null</span> <span class="cm">/* inTask */</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inResumeTopActivity</span><span class="o">)</span> <span class="o">{</span>
<span class="n">scheduleResumeTopActivities</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>以上函数有牵扯到3个函数调用:</p>
<ul>
<li>
<p><strong>moveHomeStackTaskToTop()</strong>: 目的是为了把HomeActivity挪到任务顶;HomeStack就是HomeActivity所在的任务;</p>
</li>
<li>
<p><strong>startActivityLocked()</strong>:这会进入启动Activity的漫长过程;注意这里给的参数大部分为null或者0,表示HomeActivity是系统中启动的第一个Activity,它由系统进程启动;如果是由普通的应用进程启动一个Activity,那这些参数都应该赋值为当前Activity的唤起者信息;</p>
</li>
<li>
<p><strong>resumeTopActivityLocked()</strong>:在<strong>ASS.inResumeTopActivity</strong>这个变量的控制下调用,因为resumeTopActivityLocked()可能被多次调用,所以通过该变量来避免重复执行函数逻辑。对于本例中第一次进入,<strong>ASS.inResumeTopActivity</strong>为false,所以并不会触发调度resumeTopActivityLocked()函数,在后文,我们会再次见到这个函数,届时再深入分析。</p>
</li>
</ul>
<p>在HomeActivity启动之前,就需要将,来看<strong>moveHomeStackTaskToTop()</strong>这个函数的具体实现:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">boolean</span> <span class="nf">moveHomeStackTaskToTop</span><span class="o">(</span><span class="kt">int</span> <span class="n">homeStackTaskType</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">homeStackTaskType</span> <span class="o">==</span> <span class="n">RECENTS_ACTIVITY_TYPE</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果要显示的类型是最近任务列表,则调用WindowManagerService将其显示</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">showRecentApps</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">// 调用ActivityStack的函数,完成实际的HomeStack挪动</span>
<span class="c1">// 想必读者心中已生疑问,在HomeActivity启动之前就已经有了HomeStack,</span>
<span class="c1">// 那么,HomeStack是在什么时候创建的呢?</span>
<span class="n">mHomeStack</span><span class="o">.</span><span class="na">moveHomeStackTaskToTop</span><span class="o">(</span><span class="n">homeStackTaskType</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">top</span> <span class="o">=</span> <span class="n">getHomeActivity</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">top</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="n">mService</span><span class="o">.</span><span class="na">setFocusedActivityLocked</span><span class="o">(</span><span class="n">top</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>该函数仅仅是做一些上层的调用,牵扯到两个概念:HomeStack和HomeTask。 HomeTask就是HomeActivity的宿主任务,HomeStack就是HomeTask所在的栈。该函数的目的就是为了将HomeTask挪到HomeStack的栈顶位置,为后面的HomeActivity启动做准备。</p>
<p><strong>然而,这里有一个值得读者思考的地方,HomeStack是在什么时候创建的?此刻HomeStack里面有些什么内容呢?</strong></p>
<p>其实,早在系统进程启动时候,就已经创建了HomeStack对象:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SystemServer.startOtherService()
└── AMS.setWindowManager()
└── ASS.setWindowManager()
└── ASS.createStackOnDisplay(HOME_STACK_ID, Display.DEFAULT_DISPLAY);
</code></pre></div></div>
<ul>
<li>SystemServer中启动时会创建WindowManagerService(后文简称WMS);</li>
<li>AMS的很多调度逻辑都需要与WMS进行交互,所以需要将WMS对象设置到AMS中;</li>
<li>ASS也需要WMS对象,所以进一步将WMS设置到ASS中;</li>
<li>ASS在获取到WMS对象之后,就可以创建ActivityStack了,因为ActivityStack需要绑定到显示设备上。
每一个ActivityStack都有一个编号,HomeStack的编号是HOME_STACK_ID(0),它是绑定到默认的显示设备上的,即手机屏幕。</li>
</ul>
<p>下面的函数调用本意是将HomeStack中,类型为HOME_ACTIVITY_TYPE(1)的任务挪到HomeStack的栈顶位置:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">moveHomeStackTaskToTop</span><span class="o">(</span><span class="kt">int</span> <span class="n">homeStackTaskType</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">top</span> <span class="o">=</span> <span class="n">mTaskHistory</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">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">taskNdx</span> <span class="o">=</span> <span class="n">top</span><span class="o">;</span> <span class="n">taskNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">taskNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">TaskRecord</span> <span class="n">task</span> <span class="o">=</span> <span class="n">mTaskHistory</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">taskNdx</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">taskType</span> <span class="o">==</span> <span class="n">homeStackTaskType</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mTaskHistory</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">taskNdx</span><span class="o">);</span>
<span class="n">mTaskHistory</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">top</span><span class="o">,</span> <span class="n">task</span><span class="o">);</span>
<span class="n">updateTaskMovement</span><span class="o">(</span><span class="n">task</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>ActivityStack中用TaskHistory数组记录了所有的任务,要将HomeTask挪到栈顶的位置,就是先找到HomeTask,然后将其删除,再在栈顶的位置添加一个新的HomeTask。</p>
<p>这里还调用了updateTaskMovement()函数,这个函数在后文还会见到很多次,其实并没有什么特别的地方,只是针对Persistable的任务,记录一个时间,这个时间在恢复任务的时候会用到。</p>
<p>实际上,此时的HomeStack,栈中什么都没有,TaskHistory数组的长度为0,HomeTask这个时候还没创建呢,所以,以上分析的函数逻辑并不会执行。正是由于HomeStack中还什么都没有,接下来的getHomeActivity()也会返回null,也就不会调用<strong>AMS.setFocusedActivityLocked()</strong>,但该函数非常重要,会在后文中被调用的。</p>
<p>该函数的逻辑其实是为其他Activity切换到HomeActivity设计的,第一次启动HomeActivity的时候,大部分逻辑都不会走到。接下来,才是Activitiy启动的重头戏。</p>
<p><b><font color="red">至此,HomeActivity启动过程的第一个大部分已经分析完毕,完成两件大事:其一是构建HomeIntent,其二是调度HomeStack。</font></b></p>
<hr />
<h1 id="3-assstartactivitylocked">3. ASS.startActivityLocked()</h1>
<p>Activity启动的旅途由此就开启了,这个过程无比的繁杂,涉及到的函数逻辑都很庞大。每一个Activity的启动都会经过这个函数调用,由于Activity本身的启动模式、宿主任务的状态、宿主进程的状态都会影响到Activity的启动过程,所以中间的判断分支非常多,就本例中的HomeActivity启动而言,相比一般的Activity启动会简单很多,是一个比较好的入门案例。</p>
<p>先来介绍一下函数入参的意义:</p>
<table>
<thead>
<tr>
<th>函数入参</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>caller</td>
<td>调用者进程的操作接口。本例中,该参数被设置为null。</td>
</tr>
<tr>
<td>intent</td>
<td>启动当前Activity的Intent。</td>
</tr>
<tr>
<td>revolvedType</td>
<td>Intent的解析类型。本例中为null。</td>
</tr>
<tr>
<td>aInfo</td>
<td>Activity的静态信息类,解析AndroidManifest.xml的<activity>得到的数据结构。</td>
</tr>
<tr>
<td>voiceSession,voiceInteractor</td>
<td>语音相关的接口。本例中均为null。</td>
</tr>
<tr>
<td>resultTo</td>
<td>调用者Activity。本例为null,但通常情况下,该参数都不为null,因为一个Activity通常由另外一个Activity启动。HomeActivity相当于一个初始的Activity。</td>
</tr>
<tr>
<td>resultWho</td>
<td>描述调用者Activity的字符串。与resultTo是配套存在的,本例为null。</td>
</tr>
<tr>
<td>requestCode</td>
<td>请求码。当调用者Activity需要获取待启动Activity的数据时,可以设置该参数来启动Activity,通过Activity.startActivityForResult()便可传入requestCode参数,取一个大于或等于0的整数即可。默认情况下为”-1”,启动HomeActivity时,该参数为”0”。</td>
</tr>
<tr>
<td>callingPid,callingUid</td>
<td>调用者的进程号和用户号。本例中均为0,表示从系统进程中启动Activity</td>
</tr>
<tr>
<td>callingPackage</td>
<td>调用者包名。本例中为null,表示系统进程。</td>
</tr>
<tr>
<td>realCallingPid,realCallingUid</td>
<td> </td>
</tr>
<tr>
<td>startFlags</td>
<td>启动Activity的Flag</td>
</tr>
<tr>
<td>options</td>
<td> </td>
</tr>
<tr>
<td>ignoreTargetSecurity</td>
<td> </td>
</tr>
<tr>
<td>componentSpecified</td>
<td> </td>
</tr>
<tr>
<td>outActivity</td>
<td>作为该函数的输出参数,记录启动的ActivityRecord</td>
</tr>
<tr>
<td>container</td>
<td> </td>
</tr>
<tr>
<td>inTask</td>
<td>指定待启动Activity所在的任务。本例为null</td>
</tr>
</tbody>
</table>
<p>如此多的函数入参,意味着该函数的执行场景非常多。从一个Activity启动另外一个Activity,总要考虑前一个Activity的状态,陷入复杂的数据传递过程,好在HomeActivity的启动过程场景相对简单,很多参数都置为null。</p>
<p>有了输入,我们再提前来看函数的返回值是些什么取值:</p>
<table>
<thead>
<tr>
<th>返回值</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>START_SUCCESS(0)</td>
<td>启动成功</td>
</tr>
<tr>
<td>START_INTENT_NOT_RESOLVED(-1)</td>
<td>无法解析Intent所请求的Activity</td>
</tr>
<tr>
<td>START_CLASS_NOT_FOUND(-2)</td>
<td>无法找到目标Activity的类</td>
</tr>
<tr>
<td>START_FORWARD_AND_REQUEST_CONFLICT(-3)</td>
<td>带RequestCode启动Activity时,存在的一种与resultTo冲突的场景</td>
</tr>
<tr>
<td>START_PERMISSION_DENIED(-4)</td>
<td>调用者没有获取启动Activity的授权</td>
</tr>
<tr>
<td>START_NOT_VOICE_COMPATIBLE(-7)</td>
<td>当前并不支持语音</td>
</tr>
<tr>
<td>START_NOT_CURRENT_USER_ACTIVITY(-8)</td>
<td>目标Activity并非对当前用户可见</td>
</tr>
<tr>
<td>START_SWITCHES_CANCELED(4)</td>
<td>App Switch功能关闭时,需要将待启动的Activity推入Pending状态</td>
</tr>
</tbody>
</table>
<p>总结一下,小于零的返回值,表示启动失败;大于等于零的返回值,表示启动成功,通过一些大于零的整数来区分不同的启动场景。还有一些返回值没有在这里列出,因为还依赖于后续函数调用的返回值。</p>
<p>下面,我们就一层层深入函数,分析代码逻辑了。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">int</span> <span class="nf">startActivityLocked</span><span class="o">(</span><span class="n">IApplicationThread</span> <span class="n">caller</span><span class="o">,</span>
<span class="n">Intent</span> <span class="n">intent</span><span class="o">,</span> <span class="n">String</span> <span class="n">resolvedType</span><span class="o">,</span> <span class="n">ActivityInfo</span> <span class="n">aInfo</span><span class="o">,</span>
<span class="n">IVoiceInteractionSession</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="n">IVoiceInteractor</span> <span class="n">voiceInteractor</span><span class="o">,</span>
<span class="n">IBinder</span> <span class="n">resultTo</span><span class="o">,</span> <span class="n">String</span> <span class="n">resultWho</span><span class="o">,</span> <span class="kt">int</span> <span class="n">requestCode</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">callingPid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">callingUid</span><span class="o">,</span> <span class="n">String</span> <span class="n">callingPackage</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">realCallingPid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">realCallingUid</span><span class="o">,</span> <span class="kt">int</span> <span class="n">startFlags</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">ignoreTargetSecurity</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">componentSpecified</span><span class="o">,</span> <span class="n">ActivityRecord</span><span class="o">[]</span> <span class="n">outActivity</span><span class="o">,</span>
<span class="n">ActivityContainer</span> <span class="n">container</span><span class="o">,</span> <span class="n">TaskRecord</span> <span class="n">inTask</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">err</span> <span class="o">=</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span><span class="o">;</span>
<span class="n">ProcessRecord</span> <span class="n">callerApp</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">caller</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 本例中caller为null,省略该分支的代码</span>
<span class="o">}</span>
<span class="c1">// 获取userId。在单用户的场景下,只有一个userId,就是0;</span>
<span class="c1">// 在多用户的场景下,会根据uid的值计算出来userId。</span>
<span class="c1">// uid与userId并不相同,uid是分配给应用程序的编号,userId代表多用户场景下的用户ID。</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">userId</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">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="mi">0</span><span class="o">;</span>
<span class="c1">// sourceRecord和resultRecord都表示调用者,但两者有区别:</span>
<span class="c1">// 在某些场景下sourceRecord与resultRecord并不相同,见下面的FLAG_ACTIVITY_FORWARD_RESULT</span>
<span class="n">ActivityRecord</span> <span class="n">sourceRecord</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">ActivityRecord</span> <span class="n">resultRecord</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="c1">// 本例中,resultTo为null,所以,sourceRecord和resultRecord都还是null</span>
<span class="k">if</span> <span class="o">(</span><span class="n">resultTo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 从已有的任务中,找到resultTo所标示的ActivityRecord</span>
<span class="n">sourceRecord</span> <span class="o">=</span> <span class="n">isInAnyStackLocked</span><span class="o">(</span><span class="n">resultTo</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sourceRecord</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">requestCode</span> <span class="o">>=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="o">!</span><span class="n">sourceRecord</span><span class="o">.</span><span class="na">finishing</span><span class="o">)</span> <span class="o">{</span>
<span class="n">resultRecord</span> <span class="o">=</span> <span class="n">sourceRecord</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// launchFlags就是Activity的启动参数,</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">launchFlags</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getFlags</span><span class="o">();</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_FORWARD_RESULT</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">sourceRecord</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 如果启动参数包含FLAG_ACTIVITY_FORWARD_RESULT,则需要对resultRecord进行调整</span>
<span class="o">}</span>
<span class="c1">// 以下代码都是进行一些常规检查,如果不符合条件,则给定一个错误码,标记启动失败</span>
<span class="k">if</span> <span class="o">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span> <span class="o">&&</span> <span class="n">intent</span><span class="o">.</span><span class="na">getComponent</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">err</span> <span class="o">=</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_INTENT_NOT_RESOLVED</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</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="o">{</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_CLASS_NOT_FOUND</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">isCurrentProfileLocked</span><span class="o">(</span><span class="n">userId</span><span class="o">)</span>
<span class="o">&&</span> <span class="o">(</span><span class="n">aInfo</span><span class="o">.</span><span class="na">flags</span> <span class="o">&</span> <span class="n">FLAG_SHOW_FOR_ALL_USERS</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_NOT_CURRENT_USER_ACTIVITY</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span> <span class="c1">// 省略与语音相关的常规检查</span>
<span class="c1">// 如果错误码被标记上,表示上面的常规检查没有通过,需要结束Activity的启动过程。</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">resultStack</span> <span class="o">=</span> <span class="n">resultRecord</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">resultRecord</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">err</span> <span class="o">!=</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">resultRecord</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 此处,如果resultRecord不为null,则需要告诉调用者启动失败了</span>
<span class="n">resultStack</span><span class="o">.</span><span class="na">sendActivityResultLocked</span><span class="o">(-</span><span class="mi">1</span><span class="o">,</span>
<span class="n">resultRecord</span><span class="o">,</span> <span class="n">resultWho</span><span class="o">,</span> <span class="n">requestCode</span><span class="o">,</span>
<span class="n">Activity</span><span class="o">.</span><span class="na">RESULT_CANCELED</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="k">return</span> <span class="n">err</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 常规检查通过,又要开始进行一系列的权限检查。上面是通过错误码err来标记了,</span>
<span class="c1">// 权限检查是通过abort这个变量来标记的</span>
<span class="kt">boolean</span> <span class="n">abort</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">startAnyPerm</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">checkPermission</span><span class="o">(</span>
<span class="n">START_ANY_ACTIVITY</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="o">...</span> <span class="c1">// 省略大量权限检查的代码</span>
<span class="k">if</span> <span class="o">(</span><span class="n">abort</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">resultRecord</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">resultStack</span><span class="o">.</span><span class="na">sendActivityResultLocked</span><span class="o">(-</span><span class="mi">1</span><span class="o">,</span> <span class="n">resultRecord</span><span class="o">,</span> <span class="n">resultWho</span><span class="o">,</span> <span class="n">requestCode</span><span class="o">,</span> <span class="n">Activity</span><span class="o">.</span><span class="na">RESULT_CANCELED</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="c1">// 如果权限检查失败,会中断Activity的启动过程,但返回的却是START_SUCCESS</span>
<span class="k">return</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 主角出场了,ActivityRecord在此创建。创建一个ActivityRecord要传入的参数基本都还是函数入参</span>
<span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityRecord</span><span class="o">(</span><span class="n">mService</span><span class="o">,</span> <span class="n">callerApp</span><span class="o">,</span> <span class="n">callingUid</span><span class="o">,</span> <span class="n">callingPackage</span><span class="o">,</span>
<span class="n">intent</span><span class="o">,</span> <span class="n">resolvedType</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">mService</span><span class="o">.</span><span class="na">mConfiguration</span><span class="o">,</span> <span class="n">resultRecord</span><span class="o">,</span> <span class="n">resultWho</span><span class="o">,</span>
<span class="n">requestCode</span><span class="o">,</span> <span class="n">componentSpecified</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="k">this</span><span class="o">,</span> <span class="n">container</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">outActivity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">outActivity</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">r</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 对于HomeActivity的而言,appTimeTracker一直保持为null</span>
<span class="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">appTimeTracker</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">sourceRecord</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">appTimeTracker</span> <span class="o">=</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">appTimeTracker</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// mFocusedStack在之前将HomeTask挪到栈顶时,已经被置为HomeStack</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">mFocusedStack</span><span class="o">;</span>
<span class="k">if</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="n">stack</span><span class="o">.</span><span class="na">mResumedActivity</span> <span class="o">==</span> <span class="kc">null</span>
<span class="o">||</span> <span class="n">stack</span><span class="o">.</span><span class="na">mResumedActivity</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="n">callingUid</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// AppSwitch相关的处理逻辑,暂不分析</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">mDidAppSwitch</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mService</span><span class="o">.</span><span class="na">mAppSwitchesAllowedTime</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">mService</span><span class="o">.</span><span class="na">mDidAppSwitch</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 先启动一些之前被阻塞的Activity,对于启动HomeActivity而言,显然之前没有被阻塞的Activity</span>
<span class="n">doPendingActivityLaunchesLocked</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="c1">// 启动Activity过程的下一阶段</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">startActivityUncheckedLocked</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="kc">true</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="k">if</span> <span class="o">(</span><span class="n">err</span> <span class="o"><</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">notifyActivityDrawnForKeyguard</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">err</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>以上大片函数逻辑,关键节点在在于创建一个ActivityRecord:</p>
<ul>
<li>
<p>在创建之前要进行参数设置(sourceRecord和resultRecord等)、常规检查(譬如调用者的包名是否存在)、权限检查;</p>
</li>
<li>
<p>在创建ActivityRecord之后,也有一些处理,包括AppSwitch,优先启动之前被阻塞的Activity,然后,进入了Activity过程的下一阶段: <strong>startActivityUncheckedLocked()</strong>,从函数命名可以看出,该做的检查已经做完了,剩下的函数调用就不需要进行额外的检查了(<strong>Unchecked</strong>)。</p>
</li>
</ul>
<blockquote>
<p>启动Activity过程的第一个大函数分析下来,想必读者内心是崩溃的,如此复杂的判定,竟然还只是创建了一个ActivityRecord!接下来的分析,只会更加崩溃,越往后的处理逻辑就越复杂。读者在后文中如果有阅读障碍,一定要再回过头来看前面的函数分析,多次重复阅读,知识是螺旋式积累的。</p>
</blockquote>
<h1 id="4-assstartactivityuncheckedlocked">4. ASS.startActivityUncheckedLocked()</h1>
<p>先描述一下几个新出来的函数入参:</p>
<table>
<thead>
<tr>
<th>函数入参</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>r</td>
<td>待启动的ActivityRecord,这是在上一个函数中刚刚新建的一个ActivityRecord对象。</td>
</tr>
<tr>
<td>sourceRecord</td>
<td>调用者,即当前还处于显示状态的ActivityRecord。本例中由于是启动HomeActivity,所以该参数为null。</td>
</tr>
<tr>
<td>doResume</td>
<td>表示是否要将Activity推入Resume状态,从上一个函数传入进来的参数值为true。其实,该参数为flase的情况我们也已经见过了:上个函数中,在调用ASS.startActivityUncheckedLocked()之前,会调用<strong>doPendingActivityLaunchesLocked(false)</strong>,表示要优先处理一些等待被启动的Activity,在函数内部也会调用startActivityUncheckedLocked(),给定doResume的值就是false。</td>
</tr>
</tbody>
</table>
<p>上面的函数还有一些返回值没有列出,有一部分返回值是由该函数提供的:</p>
<table>
<thead>
<tr>
<th>返回值</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>START_CLASS_NOT_FOUND(-2)</td>
<td>无法找到目标Activity的类</td>
</tr>
<tr>
<td>START_SUCCESS(0)</td>
<td>启动成功</td>
</tr>
<tr>
<td>START_RETURN_INTENT_TO_CALLER(1)</td>
<td>当启动参数中带有START_FLAG_ONLY_IF_NEEDED标志时,如果目标Activity就是当前的调用者,则返回该值,启动结束</td>
</tr>
<tr>
<td>START_TASK_TO_FRONT(2)</td>
<td>在已有任务中找到了目标Activity,则只需要把目标Activity挪到前台即可,启动结束</td>
</tr>
<tr>
<td>START_DELIVERED_TO_TOP(3)</td>
<td>目标Activity位于任务栈顶,则只需要将Intent派送到栈顶的Activity即可,启动结束</td>
</tr>
<tr>
<td>START_RETURN_LOCK_TASK_MODE_VIOLATION(5)</td>
<td>目标Activity的宿主任务处于LockTaskMode模式,且目标Activity的启动方式违背了LockTaskMode的规则,则不能启动目标Activity</td>
</tr>
</tbody>
</table>
<p>该函数逻辑庞大,笔者将其分为3个代码片段。在分析过程中,笔者会适当的省略一些不影响主干逻辑的代码。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段1:Activity启动参数(launchMode, lauchFlags)的组合修正</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="nf">startActivityUncheckedLocked</span><span class="o">(</span><span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">ActivityRecord</span> <span class="n">sourceRecord</span><span class="o">,</span>
<span class="n">IVoiceInteractionSession</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="n">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="n">Bundle</span> <span class="n">options</span><span class="o">,</span> <span class="n">TaskRecord</span> <span class="n">inTask</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">Intent</span> <span class="n">intent</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="kd">final</span> <span class="kt">int</span> <span class="n">callingUid</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromUid</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">!</span><span class="n">inTask</span><span class="o">.</span><span class="na">inRecents</span><span class="o">)</span> <span class="o">{</span>
<span class="n">inTask</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 获取Activity的启动模式,这些值是从<activity>标签中读取的,即目标Activity所定义的启动方式</span>
<span class="c1">// 除了目标Activity定义的启动模式外,调用者也可以设置Activity的启动模式</span>
<span class="c1">// 这些参数都体现在Intent的Flags中。</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">launchSingleTop</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchMode</span> <span class="o">==</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">LAUNCH_SINGLE_TOP</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">launchSingleInstance</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchMode</span> <span class="o">==</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">LAUNCH_SINGLE_INSTANCE</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">launchSingleTask</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchMode</span> <span class="o">==</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">LAUNCH_SINGLE_TASK</span><span class="o">;</span>
<span class="c1">// 处理启动参数FLAG_ACTIVITY_NEW_DOCUMENT。读者可以参阅SDK文档后来了解这个参数。</span>
<span class="kt">int</span> <span class="n">launchFlags</span> <span class="o">=</span> <span class="n">intent</span><span class="o">.</span><span class="na">getFlags</span><span class="o">();</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_DOCUMENT</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&&</span>
<span class="o">(</span><span class="n">launchSingleInstance</span> <span class="o">||</span> <span class="n">launchSingleTask</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="c1">// 是否为后台启动任务的标志位</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">launchTaskBehind</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">mLaunchTaskBehind</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">launchSingleTask</span> <span class="o">&&</span> <span class="o">!</span><span class="n">launchSingleInstance</span>
<span class="o">&&</span> <span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_DOCUMENT</span><span class="o">)</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">r</span><span class="o">.</span><span class="na">resultTo</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">resultTo</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果Activity是在一个新的任务中启动,则resultTo是失效的</span>
<span class="n">r</span><span class="o">.</span><span class="na">resultTo</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</span><span class="o">.</span><span class="na">sendActivityResultLocked</span><span class="o">(-</span><span class="mi">1</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">resultTo</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">resultWho</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">requestCode</span><span class="o">,</span>
<span class="n">Activity</span><span class="o">.</span><span class="na">RESULT_CANCELED</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">r</span><span class="o">.</span><span class="na">resultTo</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 如果启动参数包含FLAG_ACTIVITY_NEW_DOCUMENT,而且又没有resultTo,则意味着要在一个新的任务中启动Activity。</span>
<span class="c1">// 后面还会出现这种类似的参数修正:在某些启动条件的组合下,就隐性要求在一个新的任务中启动Activity</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_DOCUMENT</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">resultTo</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</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">if</span> <span class="o">(</span><span class="n">launchTaskBehind</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">documentLaunchMode</span> <span class="o">==</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">DOCUMENT_LAUNCH_ALWAYS</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="c1">// 对于后台启动的新任务,可以多任务运行</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_MULTIPLE_TASK</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// Activity.onUserLeavingHint()是个回调函数,在onPause()之前会被调用,</span>
<span class="c1">// 如果设置了FLAG_ACTIVITY_NO_USER_ACTION,则该回调函数不会被调用</span>
<span class="n">mUserLeaving</span> <span class="o">=</span> <span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NO_USER_ACTION</span><span class="o">)</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">doResume</span><span class="o">)</span> <span class="o">{</span>
<span class="n">r</span><span class="o">.</span><span class="na">delayedResume</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// FLAG_ACTIVITY_PREVIOUS_IS_TOP用于一些特殊的场景:待启动的Activity不会被作为栈顶,</span>
<span class="c1">// 换句话说,调用者希望待启动的Activity马上销毁,就会使用该启动参数。</span>
<span class="c1">// 通常情况下,notTop都为null。</span>
<span class="n">ActivityRecord</span> <span class="n">notTop</span> <span class="o">=</span>
<span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_PREVIOUS_IS_TOP</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</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="c1">// 省略 START_FLAG_ONLY_IF_NEEDED的处理逻辑</span>
<span class="kt">boolean</span> <span class="n">addingToTask</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">// 表示是否在传入的inTask中启动Actiivty,后面会根据实际情况重新设置该变量</span>
<span class="n">TaskRecord</span> <span class="n">reuseTask</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="c1">// 表示复用的任务。如果inTask存在,则inTask就可以作为reuseTask。</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sourceRecord</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">inTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">inTask</span><span class="o">.</span><span class="na">stack</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 这一段代码逻辑比较长,都是与inTask相关的处理。</span>
<span class="c1">// 对于HomeActivity而言,传入的inTask为null,所以不会经过这段处理逻辑。</span>
<span class="c1">// 需要说明的是:如果inTask不为空,则表示调用者希望目标Activity在指定的任务中运行</span>
<span class="c1">// 这一段代码逻辑会根据inTask的实际情况完成一些设置,如果addingTask被设置为true,</span>
<span class="c1">// 则表示目标Activity可以投放到inTask中</span>
<span class="n">reuseTask</span> <span class="o">=</span> <span class="n">inTask</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">inTask</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 根据调用者和目标Activity的启动模式,来调整目标Activity的启动参数。</span>
<span class="c1">// 一些场景下,并没有设置目标Activity要在新的任务中启动,然而,实际情况又需要在新的任务中</span>
<span class="c1">// 启动,譬如singleInstance就隐含了要新建一个任务的意思,这时候,就需要在启动参数中加上</span>
<span class="c1">// FLAG_ACTIVITY_NEW_TASK。前面出现的调整FLAG_ACTIVITY_NEW_TASK读者们还记得吗?</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inTask</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">sourceRecord</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">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">inTask</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// sourceRecord为null,表示调用者不是Activity,</span>
<span class="c1">// 则强制在新的任务中启动Activity。</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</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">sourceRecord</span><span class="o">.</span><span class="na">launchMode</span> <span class="o">==</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">LAUNCH_SINGLE_INSTANCE</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果调用者的启动模式为LAUNCH_SINGLE_INSTANCE,则需要在新的任务中启动新的Activity</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</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">launchSingleInstance</span> <span class="o">||</span> <span class="n">launchSingleTask</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果目标Activity的启动模式为singleInstance或SingleTask,则需要在新的任务中启动</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 未完接片段2</span></code></pre></figure>
<p>【代码片段1】主要是Activity各种启动参数的组合修正,读者最好借助于SDK文档理解这些参数的意义。
如果暂时不理解,也可以先忽略这些细节。对于HomeActivity的启动而言,不会涉及到上述的代码逻辑。</p>
<p>当启动参数修正后,就需要启动Activity了:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段2:处理目标Activity已经存在的情况</span>
<span class="n">ActivityInfo</span> <span class="n">newTaskInfo</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">Intent</span> <span class="n">newTaskIntent</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">ActivityStack</span> <span class="n">sourceStack</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sourceRecord</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">sourceRecord</span><span class="o">.</span><span class="na">finishing</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果调用者已经处于销毁状态,那意味着其所在的任务也可能被销毁了。</span>
<span class="c1">// 待启动的Activity需要保留已有任务的信息,并强制在新的任务中启动</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">launchFlags</span> <span class="o">|=</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">;</span>
<span class="n">newTaskInfo</span> <span class="o">=</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">info</span><span class="o">;</span>
<span class="n">newTaskIntent</span> <span class="o">=</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">intent</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">sourceRecord</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">sourceStack</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">sourceStack</span> <span class="o">=</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</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">sourceStack</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kt">boolean</span> <span class="n">movedHome</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">ActivityStack</span> <span class="n">targetStack</span><span class="o">;</span> <span class="c1">// 目标Activity的宿主栈,注意:宿主栈是ActivityStack,元素为TaskRecord的栈结构,而宿主任务是TaskRecord,元素为ActivityRecord的栈结构</span>
<span class="n">intent</span><span class="o">.</span><span class="na">setFlags</span><span class="o">(</span><span class="n">launchFlags</span><span class="o">);</span> <span class="c1">// 重新设置将修正后的launchFlags</span>
<span class="c1">// 是否有动画是由启动参数FLAG_ACTIVITY_NO_ANIMATION决定的</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">noAnimation</span> <span class="o">=</span> <span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NO_ANIMATION</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>
<span class="c1">// 重头戏来了!</span>
<span class="k">if</span> <span class="o">(((</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&&</span>
<span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_MULTIPLE_TASK</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span>
<span class="o">||</span> <span class="n">launchSingleInstance</span> <span class="o">||</span> <span class="n">launchSingleTask</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 进入这个分支,意味着不是在当前任务中启动Activity,那就需要先找一下</span>
<span class="c1">// 目标任务是否存在?</span>
<span class="k">if</span> <span class="o">(</span><span class="n">inTask</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">resultTo</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 找目标Activity有两个重要的函数,我们在后文会分析</span>
<span class="n">ActivityRecord</span> <span class="n">intentActivity</span> <span class="o">=</span> <span class="o">!</span><span class="n">launchSingleInstance</span> <span class="o">?</span>
<span class="n">findTaskLocked</span><span class="o">(</span><span class="n">r</span><span class="o">)</span> <span class="o">:</span> <span class="n">findActivityLocked</span><span class="o">(</span><span class="n">intent</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="c1">// intentActivity如果不为空,则意味着找到了目标Activity,</span>
<span class="c1">// 那么目标Activity所在的任务(TaskRecord)和任务所在的栈(ActivityRecord)就是宿主任务和宿主栈</span>
<span class="k">if</span> <span class="o">(</span><span class="n">intentActivity</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">isLockTaskModeViolation</span><span class="o">(</span><span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">,</span>
<span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_CLEAR_TASK</span><span class="o">))</span>
<span class="o">==</span> <span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_CLEAR_TASK</span><span class="o">)))</span> <span class="o">{</span>
<span class="c1">// LockTask相关的处理。LockTask是Android Lollipop引入了新功能,</span>
<span class="c1">// 具体介绍可以查阅:https://developer.android.com/work/cosu.html</span>
<span class="n">showLockTaskToast</span><span class="o">();</span>
<span class="k">return</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_RETURN_LOCK_TASK_MODE_VIOLATION</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">task</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">task</span> <span class="o">=</span> <span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">intentActivity</span><span class="o">.</span><span class="na">task</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="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">setIntent</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 设置目标栈targetStack</span>
<span class="n">targetStack</span> <span class="o">=</span> <span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</span><span class="o">;</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">mLastPausedActivity</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="c1">// 获取当前的焦点栈,这个栈是什么时候创建的呢?</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">focusStack</span> <span class="o">=</span> <span class="n">getFocusedStack</span><span class="o">();</span>
<span class="c1">// 获取前可见的ActivityRecord,即curTop,这是在当前的焦点栈中找到的</span>
<span class="n">ActivityRecord</span> <span class="n">curTop</span> <span class="o">=</span> <span class="o">(</span><span class="n">focusStack</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>
<span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="n">focusStack</span><span class="o">.</span><span class="na">topRunningNonDelayedActivityLocked</span><span class="o">(</span><span class="n">notTop</span><span class="o">);</span>
<span class="kt">boolean</span> <span class="n">movedToFront</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">curTop</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">(</span><span class="n">curTop</span><span class="o">.</span><span class="na">task</span> <span class="o">!=</span> <span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span> <span class="o">||</span>
<span class="n">curTop</span><span class="o">.</span><span class="na">task</span> <span class="o">!=</span> <span class="n">focusStack</span><span class="o">.</span><span class="na">topTask</span><span class="o">()))</span> <span class="o">{</span>
<span class="c1">// 进入这个条件,意味着当前可见的Activity与待启动的Activity不在同一个任务</span>
<span class="c1">// 这种情况下,需要将宿主栈挪到前台。</span>
<span class="n">r</span><span class="o">.</span><span class="na">intent</span><span class="o">.</span><span class="na">addFlags</span><span class="o">(</span><span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_BROUGHT_TO_FRONT</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sourceRecord</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">(</span><span class="n">sourceStack</span><span class="o">.</span><span class="na">topActivity</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span>
<span class="n">sourceStack</span><span class="o">.</span><span class="na">topActivity</span><span class="o">().</span><span class="na">task</span> <span class="o">==</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">task</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">launchTaskBehind</span> <span class="o">&&</span> <span class="n">sourceRecord</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">intentActivity</span><span class="o">.</span><span class="na">setTaskToAffiliateWith</span><span class="o">(</span><span class="n">sourceRecord</span><span class="o">.</span><span class="na">task</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">movedHome</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="c1">// 将目标Activity所在的任务挪到栈顶</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">moveTaskToFrontLocked</span><span class="o">(</span><span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">,</span> <span class="n">noAnimation</span><span class="o">,</span>
<span class="n">options</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">appTimeTracker</span><span class="o">,</span> <span class="s">"bringingFoundTaskToFront"</span><span class="o">);</span>
<span class="n">movedToFront</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">launchFlags</span> <span class="o">&</span>
<span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_TASK_ON_HOME</span><span class="o">))</span>
<span class="o">==</span> <span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_TASK_ON_HOME</span><span class="o">))</span> <span class="o">{</span>
<span class="n">intentActivity</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">setTaskToReturnTo</span><span class="o">(</span><span class="n">HOME_ACTIVITY_TYPE</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">options</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">if</span> <span class="o">(!</span><span class="n">movedToFront</span><span class="o">)</span> <span class="o">{</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">moveToFront</span><span class="o">(</span><span class="s">"intentActivityFound"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span><span class="o">&</span><span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_RESET_TASK_IF_NEEDED</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">if</span> <span class="o">((</span><span class="n">startFlags</span> <span class="o">&</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_FLAG_ONLY_IF_NEEDED</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">...</span> <span class="c1">// 省略针对launchFlags的调整。</span>
<span class="o">}</span> <span class="c1">// END of "if (intentActivity != null)"</span>
<span class="o">}</span> <span class="c1">// END of "if (inTask == null && r.resultTo == null)"</span>
<span class="o">}</span> <span class="c1">// END of "if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 ..."</span>
<span class="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">packageName</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// If the activity being launched is the same as the one currently</span>
<span class="c1">// at the top, then we need to check if it should only be launched</span>
<span class="c1">// once.</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 包名为null,则表示找不到待启动的Activity</span>
<span class="o">...</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="k">return</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_CLASS_NOT_FOUND</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 未完接片段3</span></code></pre></figure>
<p>【代码片段2】主要处理在一个新的任务中启动Activity,而且目标Activity存在的情况,这意味着Activity之前已经启动过,只需要把其找出来,重新挪到前台显示即可。目标Activity所在的宿主栈之前可能在前台(Foreground)也可能在后台(Background),如果在后台,则需要将宿主栈挪动前台。</p>
<p>对于HomeActivity的启动而言,显然不需要经过寻找和处理目标Activity的过程,因为开机时,HomeActivity之前还从未被启动过。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段3:处理目标Activity不存在的情况</span>
<span class="kt">boolean</span> <span class="n">newTask</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kt">boolean</span> <span class="n">keepCurTransition</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">TaskRecord</span> <span class="n">taskToAffiliate</span> <span class="o">=</span> <span class="n">launchTaskBehind</span> <span class="o">&&</span> <span class="n">sourceRecord</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span>
<span class="n">sourceRecord</span><span class="o">.</span><span class="na">task</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">r</span><span class="o">.</span><span class="na">resultTo</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">inTask</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="o">!</span><span class="n">addingToTask</span>
<span class="o">&&</span> <span class="o">(</span><span class="n">launchFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_ACTIVITY_NEW_TASK</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 该类情况表示要在一个新的任务中启动Activity</span>
<span class="n">newTask</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">targetStack</span> <span class="o">=</span> <span class="n">computeStackFocus</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">newTask</span><span class="o">);</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">moveToFront</span><span class="o">(</span><span class="s">"startingNewTask"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">reuseTask</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果不存在一个可以复用的任务,则新建一个。</span>
<span class="n">r</span><span class="o">.</span><span class="na">setTask</span><span class="o">(</span><span class="n">targetStack</span><span class="o">.</span><span class="na">createTaskRecord</span><span class="o">(</span><span class="n">getNextTaskId</span><span class="o">(),</span>
<span class="n">newTaskInfo</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">newTaskInfo</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="n">newTaskIntent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">newTaskIntent</span> <span class="o">:</span> <span class="n">intent</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="o">!</span><span class="n">launchTaskBehind</span> <span class="cm">/* toTop */</span><span class="o">),</span>
<span class="n">taskToAffiliate</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">setTask</span><span class="o">(</span><span class="n">reuseTask</span><span class="o">,</span> <span class="n">taskToAffiliate</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isLockTaskModeViolation</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_RETURN_LOCK_TASK_MODE_VIOLATION</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">movedHome</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">((</span><span class="n">launchFlags</span> <span class="o">&</span>
<span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_TASK_ON_HOME</span><span class="o">))</span>
<span class="o">==</span> <span class="o">(</span><span class="n">FLAG_ACTIVITY_NEW_TASK</span> <span class="o">|</span> <span class="n">FLAG_ACTIVITY_TASK_ON_HOME</span><span class="o">))</span> <span class="o">{</span>
<span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">setTaskToReturnTo</span><span class="o">(</span><span class="n">HOME_ACTIVITY_TYPE</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">sourceRecord</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 该类情况下,宿主栈就是调用者Activity所在的ActivityStack。</span>
<span class="c1">// 处理方式也大同小异,把宿主任务挪到栈顶,针对launchFlags做一些调整</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">inTask</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 在指定的任务栈中启动Activity</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 该类情况表示既不是从一个Activity启动,也不是在新的任务中启动,</span>
<span class="c1">// 目前,代码漏记并不会走到该类场景</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="n">mService</span><span class="o">.</span><span class="na">grantUriPermissionFromIntentLocked</span><span class="o">(</span><span class="n">callingUid</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span>
<span class="n">intent</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">getUriPermissionsLocked</span><span class="o">(),</span> <span class="n">r</span><span class="o">.</span><span class="na">userId</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">sourceRecord</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">sourceRecord</span><span class="o">.</span><span class="na">isRecentsActivity</span><span class="o">())</span> <span class="o">{</span>
<span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">setTaskToReturnTo</span><span class="o">(</span><span class="n">RECENTS_ACTIVITY_TYPE</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">newTask</span><span class="o">)</span> <span class="o">{</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">AM_CREATE_TASK</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">userId</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">taskId</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">ActivityStack</span><span class="o">.</span><span class="na">logStartActivity</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">AM_CREATE_ACTIVITY</span><span class="o">,</span> <span class="n">r</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">);</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">mLastPausedActivity</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="c1">// 启动一个新建的Activity</span>
<span class="n">targetStack</span><span class="o">.</span><span class="na">startActivityLocked</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">newTask</span><span class="o">,</span> <span class="n">doResume</span><span class="o">,</span> <span class="n">keepCurTransition</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">launchTaskBehind</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mService</span><span class="o">.</span><span class="na">setFocusedActivityLocked</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="s">"startedActivity"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">ActivityManager</span><span class="o">.</span><span class="na">START_SUCCESS</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 完</span></code></pre></figure>
<p>【代码片段3】主要处理目标Activity不存在的场景,即该Activity没有被启动过,或者启动过后已经销毁的场景。既然Activity没有被启动过,那就需要在宿主栈继续启动一个新的Activity,转入<strong>ActivityStack.startActivityLocked()</strong>进行处理。</p>
<blockquote>
<p>启动Activity的第二个大函数<strong>startstartActivityUncheckedLocked()</strong>比前文分析的第一个大函数<strong>startstartActivityLocked()</strong>多了<strong>Unchecked</strong>关键字,意图很明显:在调用<strong>Unchecked</strong>这个函数的时候,不会再进行检查(常规包名检查、权限检查),它关注的核心逻辑是为待启动的Activity找到宿主任务,Activity最终属于哪个任务中,有很多因素影响,譬如launchMode,launchFlags,当前任务的状态等,因此,要厘清楚该函数的各种分支判断,还需要结合实际Activity启动的动态场景,仅这么静态分析代码逻辑,是很难理解作者的编码意图的。建议读者设计一些Activity启动的场景,边断点调试,边理解代码逻辑。</p>
</blockquote>
<p><strong>分析完了ASS.startActivityUncheckedLocked()的函数逻辑,我们一起来回顾一下是如何走到这一步的:</strong></p>
<div align="center"><img src="/assets/images/activity/launchprocess/2-activity-launchprocess-part1.png" alt="Activity Launching Process" /></div>
<ul>
<li>
<p><strong>1 AMS.startHomeActivityLocked()</strong>:在系统进程执行systemReady()的最后,会调用该函数,这个函数的功能就是为了解析出HomeActivity的信息,为后面的启动工作做准备;在这之后,Activity启动的控制权交到ASS,它主要的职能是管理多个ActivityStack,所以扮演的是“承上启下”的角色,所谓“承上”,就是应对Activity启动的请求,完成一些Activity启动的检查、参数的设置以及栈调度的工作;所谓“启下”,就是指ASS只是为Activity启动准备必要的环境,譬如为目标Activity找到宿主任务。Activity启动时的调度还是交由后面接续完成。ActivityStackSupvisor作为ActivityStack的最高管理者,后面Activity的调度,还会求助于它;</p>
</li>
<li>
<p><strong>2 startHomeActivity()</strong>: 为了要显示Activity,就需要将其所在的ActivityStack挪到栈顶,即HomeStack的栈顶。需要注意的是,HomeStack早在系统进程启动时,就已经构建完毕,但HomeStack栈中还没有任何元素;</p>
</li>
<li>
<p><strong>3 startActivityLocked()</strong>: 启动Activity时,会有很多参数需要设置和调整,还会有一些常规检查,譬如调用者包名是否合法,一些基本的权限检查也是在这时候完成的。如果出现检查出错,则通过返回错误码中断启动过程。待启动的ActivityRecord就是在这一步被构建出来的;</p>
</li>
<li>
<p><strong>4 startActivityUncheckedLocked()</strong>: 检查完成之后,就要着手Activity的启动了,这时候需要对Activity的启动参数进行判定和调整,因为启动参数决定了目标Activity的存在于哪。如果能够在当前已有任务中找到目标Activity,那就看情况是否将其挪到前台,这时候就可以通过大于零的返回值完成启动过程了;如果当前未找到目标Activity,那就在宿主任务中启动一个新的Activity。</p>
</li>
</ul>
<p><b><font color="red">至此,HomeActivity启动的第二个大部分已经分析完毕,主要干了两件大事:其一是构建目标ActivityRecord,其二是寻找宿主TaskRecord。</font></b></p>
<hr />
<h1 id="5-asstartactivitylocked">5. AS.startActivityLocked()</h1>
<p>对于HomeActivity初次启动而言,之前没有Activity被创建,所以属于目标Activity还不存在的这一类场景,所以会进入该函数调用,意味着要在目标栈中启动一个新的Activity。</p>
<p>按照惯例,说明一下函数入参:</p>
<table>
<thead>
<tr>
<th>函数入参</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>r</td>
<td>待启动的ActivityRecord。</td>
</tr>
<tr>
<td>newTask</td>
<td>是否需要在新的任务中启动Activity。对于初次启动HomeActivity而言,该值为true。</td>
</tr>
<tr>
<td>doResume</td>
<td>是否需要立即显示Activity。有些需要Pending的Activity,该值为false;对于初次启动HomeActivity而言,该值为true。</td>
</tr>
<tr>
<td>keeyCurTransition</td>
<td>与Activity启动动画相关的参数,由上面的函数计算得到。</td>
</tr>
</tbody>
</table>
<p>该函数返回void,代码逻辑如下:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">startActivityLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">newTask</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">doResume</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">keepCurTransition</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="n">TaskRecord</span> <span class="n">rTask</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">taskId</span> <span class="o">=</span> <span class="n">rTask</span><span class="o">.</span><span class="na">taskId</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">mLaunchTaskBehind</span> <span class="o">&&</span> <span class="o">(</span><span class="n">taskForIdLocked</span><span class="o">(</span><span class="n">taskId</span><span class="o">)</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">newTask</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// 进入该条件,表示宿主栈ActivityStack中没有历史任务,或者强制要求在新的任务中启动Activity</span>
<span class="c1">// 既然是一个新任务,那么就需要需要将任务插入宿主栈顶</span>
<span class="n">insertTaskAtTop</span><span class="o">(</span><span class="n">rTask</span><span class="o">,</span> <span class="n">r</span><span class="o">);</span>
<span class="c1">// 此处调用的WindowManagerService的接口,留到后文分析</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">moveTaskToTop</span><span class="o">(</span><span class="n">taskId</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">TaskRecord</span> <span class="n">task</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">newTask</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 进入该条件,表示需要在一个已有的任务中启动Activity</span>
<span class="c1">// 先设置一个标志位,表示是否需要启动Activity,默认当然是要显示;</span>
<span class="c1">// 但是当目标任务之上有全屏的Activity时,该标志位被置为false。</span>
<span class="kt">boolean</span> <span class="n">startIt</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">taskNdx</span> <span class="o">=</span> <span class="n">mTaskHistory</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">taskNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">taskNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 从上往下遍历宿主栈中的所有任务,找到目标任务的位置</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">mTaskHistory</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">taskNdx</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">getTopActivity</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</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">task</span> <span class="o">==</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">startIt</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">task</span><span class="o">.</span><span class="na">addActivityToTop</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="n">r</span><span class="o">.</span><span class="na">putInHistory</span><span class="o">();</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">addAppToken</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">mActivities</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">r</span><span class="o">),</span> <span class="n">r</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">taskId</span><span class="o">,</span> <span class="n">mStackId</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">screenOrientation</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">fullscreen</span><span class="o">,</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">flags</span> <span class="o">&</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">FLAG_SHOW_FOR_ALL_USERS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">userId</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">configChanges</span><span class="o">,</span> <span class="n">task</span><span class="o">.</span><span class="na">voiceSession</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">mLaunchTaskBehind</span><span class="o">);</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">break</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">task</span><span class="o">.</span><span class="na">numFullscreen</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果目标任务之上的某个任务包含全屏显示的Activity,则不需要显示目标Activity</span>
<span class="n">startIt</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 以上完成的操作就是将待显示的任务放到了宿主栈的栈顶位置,</span>
<span class="c1">// 接下来,需要将待显示的ActivityRecord放到任务的栈顶</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">;</span>
<span class="n">task</span><span class="o">.</span><span class="na">addActivityToTop</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="n">task</span><span class="o">.</span><span class="na">setFrontOfTask</span><span class="o">();</span> <span class="c1">// 将一个新的ActivityRecord添加到任务栈顶后,需要重新调整FrontOfTask</span>
<span class="n">r</span><span class="o">.</span><span class="na">putInHistory</span><span class="o">();</span> <span class="c1">// 标记ActivityRecord已经放置到宿主栈中</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isHomeStack</span><span class="o">()</span> <span class="o">||</span> <span class="n">numActivities</span><span class="o">()</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 进入该分支,表示宿主栈中已经有Activity</span>
<span class="o">...</span> <span class="c1">// 此处省略与Activity启动动画相关的代码</span>
<span class="c1">// 将待启动的Activity绑定到窗口上</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">addAppToken</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">mActivities</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">r</span><span class="o">),</span>
<span class="n">r</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">taskId</span><span class="o">,</span> <span class="n">mStackId</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">screenOrientation</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">fullscreen</span><span class="o">,</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">flags</span> <span class="o">&</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">FLAG_SHOW_FOR_ALL_USERS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">userId</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">configChanges</span><span class="o">,</span> <span class="n">task</span><span class="o">.</span><span class="na">voiceSession</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">mLaunchTaskBehind</span><span class="o">);</span>
<span class="o">...</span> <span class="c1">// 此处省略与Activity启动动画相关的代码</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 进入该分支,表示当前的宿主栈中还没有任何Activity,则不需要设置任何Activity启动相关的动画</span>
<span class="c1">// 只是将待启动的Activity绑定到窗口上</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">addAppToken</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">mActivities</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">r</span><span class="o">),</span> <span class="n">r</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span>
<span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">taskId</span><span class="o">,</span> <span class="n">mStackId</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">screenOrientation</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">fullscreen</span><span class="o">,</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">flags</span> <span class="o">&</span> <span class="n">ActivityInfo</span><span class="o">.</span><span class="na">FLAG_SHOW_FOR_ALL_USERS</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">userId</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">configChanges</span><span class="o">,</span> <span class="n">task</span><span class="o">.</span><span class="na">voiceSession</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">mLaunchTaskBehind</span><span class="o">);</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="n">options</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 将Activity推入显示状态</span>
<span class="k">if</span> <span class="o">(</span><span class="n">doResume</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">resumeTopActivitiesLocked</span><span class="o">(</span><span class="k">this</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="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>该函数会经过以下3大块逻辑:</p>
<ul>
<li>
<p>将ActivityRecord放到宿主任务的栈顶位置。注意,这里有两个栈顶位置:一个是ActivityRecord在TaskRecord中的位置,另一个是TaskRecord在ActivityStack中的位置。前一个是该函数保证的,但后一个,还得根据实际的情况决定。如果是在一个新的任务中启动Activity,即newTask为true,那么,这个新的TaskRecord就会插入ActivityStack的栈顶,初次启动HomeActivity属于此类情况;</p>
</li>
<li>
<p>将待启动的ActivityRecord绑定到了一个窗口。与此同时,Activity启动的动画也在此设置。注意,本函数的分析中,省略了部分与WMS交互的窗口操作逻辑;</p>
</li>
<li>
<p>判断doResume的值,决定是否要将ActivityRecord迁移到显示状态(Resumed)。这个功能是交由resumeTopActivitiesLocked()实现的。</p>
</li>
</ul>
<h1 id="6-assresumetopactivitieslocked">6. ASS.resumeTopActivitiesLocked()</h1>
<p>初次看到这个函数名,读者心中可能会有疑问?默认都是显示一个Activity,该函数名中为什么会有Activities这种复数出现呢?难道有很多Activity都要被显示吗?</p>
<p>在<a href="2016-02-01-Activity-Maintenance.md">Android四大组件之Activity–管理方式</a>一文中我们指出,ASS的职能就是管理多个ActivityStack,然而,为什么会有多个ActivityStack的这种设计呢?其实,在低版本的Android中,只有一个ActivityStack,它对应到手机屏幕;到了高版本的Android,支持多屏显示了,譬如可以同时在手机和电视上显示不同的Activity。这时候就有了多个ActivityStack的概念,于是乎,多个ActivityStack的管理者ASS就应运而生了。
ASS中还有很多其他类似的函数,实现逻辑都比较简单,仅仅是完成一些多ActivityStack的管理功能,真正的复杂的调度逻辑还是在ActivityStack中</p>
<p>该函数的代码实现如下:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">boolean</span> <span class="nf">resumeTopActivitiesLocked</span><span class="o">(</span><span class="n">ActivityStack</span> <span class="n">targetStack</span><span class="o">,</span> <span class="n">ActivityRecord</span> <span class="n">target</span><span class="o">,</span>
<span class="n">Bundle</span> <span class="n">targetOptions</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">targetStack</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">targetStack</span> <span class="o">=</span> <span class="n">mFocusedStack</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="k">if</span> <span class="o">(</span><span class="n">isFrontStack</span><span class="o">(</span><span class="n">targetStack</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// 转交ActivityStack完成待启动Activity的显示</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">targetStack</span><span class="o">.</span><span class="na">resumeTopActivityLocked</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="c1">// 对所有显示设备上的ActivityStack都进行相同的操作</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">mActivityDisplays</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">displayNdx</span> <span class="o">>=</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="n">ArrayList</span><span class="o"><</span><span class="n">ActivityStack</span><span class="o">></span> <span class="n">stacks</span> <span class="o">=</span> <span class="n">mActivityDisplays</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">displayNdx</span><span class="o">).</span><span class="na">mStacks</span><span class="o">;</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">stackNdx</span> <span class="o">=</span> <span class="n">stacks</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">stackNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">stackNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">stacks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">stackNdx</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">stack</span> <span class="o">==</span> <span class="n">targetStack</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 上面的逻辑中,已经处理过了</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 转由其他显示设备的ActivityStack完成待启动Activity的显示</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isFrontStack</span><span class="o">(</span><span class="n">stack</span><span class="o">))</span> <span class="o">{</span>
<span class="n">stack</span><span class="o">.</span><span class="na">resumeTopActivityLocked</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">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>该函数的逻辑可以一句话说明白,就是将所有ActivityStack栈顶的ActivityRecord迁移到显示状态,都是通过调用AS.resumeTopActivityLocked()来完成实际的操作,细细品味,调用参数的传入略有区别:</p>
<ul>
<li>宿主ActivityStack,传入的参数是待启动的ActivityRecord;</li>
<li>其他ActivityStack,传入的参数的是null</li>
</ul>
<p>在接下来的分析中,我们会解释这种区别的含义是什么。</p>
<h1 id="7-asresumetopactivityinnerlocked">7. AS.resumeTopActivityInnerLocked()</h1>
<p>上一个函数中,调用的是AS.resumeTopActivityLocked(),其职能很简单,就是为了避免递归调用,本文省略之,直接进入AS.resumeTopActivityInnerLocked()进行分析。</p>
<table>
<thead>
<tr>
<th>函数入参</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>prev</td>
<td>当前正在显示的ActivityRecord</td>
</tr>
<tr>
<td>options</td>
<td> </td>
</tr>
</tbody>
</table>
<p>该函数返回true表示XXXX;返回false表示并没有将待显示的ActivityRecord推进到Resumed状态。</p>
<p>不知各位读者有没有发现,在上一个函数发起调用时,传入的参数名称是<strong>target</strong>,表示待启动的ActivityRecord;但这里摇身一变,参数名称却是<strong>prev</strong>,表示之前启动的ActivityRecord,即将要进入Pausing状态的那个Activity,到底意欲几何?</p>
<p>问题接踵而来,只有回到代码中,才能找到问题的答案:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 代码片段1</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">resumeTopActivityInnerLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">prev</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</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">mBooting</span> <span class="o">&&</span> <span class="o">!</span><span class="n">mService</span><span class="o">.</span><span class="na">mBooted</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 如果系统还未启动完毕,那AMS还不能正常工作,所以也不能显示Activity</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="c1">// 省略parentActivity的相关判定</span>
<span class="n">ActivityRecord</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">mActivityContainer</span><span class="o">.</span><span class="na">mParentActivity</span><span class="o">;</span>
<span class="c1">// 当前AS中可能存在一些正处于Intializing状态的ActivityRecord,</span>
<span class="c1">// 如果这些ActivityRecord不是位于栈顶,而且正在执行窗口启动动画,</span>
<span class="c1">// 那么,就需要取消这些Activity的启动动画。</span>
<span class="n">cancelInitializingActivities</span><span class="o">();</span>
<span class="c1">// 找到当前AS的栈顶</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">next</span> <span class="o">=</span> <span class="n">topRunningActivityLocked</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">userLeaving</span> <span class="o">=</span> <span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">mUserLeaving</span><span class="o">;</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">mUserLeaving</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">TaskRecord</span> <span class="n">prevTask</span> <span class="o">=</span> <span class="n">prev</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">prev</span><span class="o">.</span><span class="na">task</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">next</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 进入该条件分支表示AS中没有要显示的Activity</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">reason</span> <span class="o">=</span> <span class="s">"noMoreActivities"</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mFullscreen</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 当前AS不是全屏显示,则需要将焦点切换到下一个待显示的AS</span>
<span class="kd">final</span> <span class="n">ActivityStack</span> <span class="n">stack</span> <span class="o">=</span> <span class="n">getNextVisibleStackLocked</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">adjustFocusToNextVisibleStackLocked</span><span class="o">(</span><span class="n">stack</span><span class="o">,</span> <span class="n">reason</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">resumeTopActivitiesLocked</span><span class="o">(</span><span class="n">stack</span><span class="o">,</span> <span class="n">prev</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 默认情况下,AS都是占据全屏的,所以,当前AS如果没有要显示的Activity,则会要求显示桌面</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">returnTaskType</span> <span class="o">=</span> <span class="n">prevTask</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">prevTask</span><span class="o">.</span><span class="na">isOverHomeStack</span><span class="o">()</span> <span class="o">?</span>
<span class="n">HOME_ACTIVITY_TYPE</span> <span class="o">:</span> <span class="n">prevTask</span><span class="o">.</span><span class="na">getTaskToReturnTo</span><span class="o">();</span>
<span class="k">return</span> <span class="nf">isOnHomeDisplay</span><span class="o">()</span> <span class="o">&&</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">resumeHomeStackTask</span><span class="o">(</span><span class="n">returnTaskType</span><span class="o">,</span> <span class="n">prev</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">next</span><span class="o">.</span><span class="na">delayedResume</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">mResumedActivity</span> <span class="o">==</span> <span class="n">next</span> <span class="o">&&</span> <span class="n">next</span><span class="o">.</span><span class="na">state</span> <span class="o">==</span> <span class="n">ActivityState</span><span class="o">.</span><span class="na">RESUMED</span> <span class="o">&&</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">allResumedActivitiesComplete</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// 当前正在显示的Activity正好就是下一个待显示的Activity,</span>
<span class="c1">// 那么,就中断对待显示ActivityRecord的调度</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">executeAppTransition</span><span class="o">();</span>
<span class="n">mNoAnimActivities</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</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="kd">final</span> <span class="n">TaskRecord</span> <span class="n">nextTask</span> <span class="o">=</span> <span class="n">next</span><span class="o">.</span><span class="na">task</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">prevTask</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">prevTask</span><span class="o">.</span><span class="na">stack</span> <span class="o">==</span> <span class="k">this</span> <span class="o">&&</span>
<span class="n">prevTask</span><span class="o">.</span><span class="na">isOverHomeStack</span><span class="o">()</span> <span class="o">&&</span> <span class="n">prev</span><span class="o">.</span><span class="na">finishing</span> <span class="o">&&</span> <span class="n">prev</span><span class="o">.</span><span class="na">frontOfTask</span><span class="o">)</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">isSleepingOrShuttingDown</span><span class="o">()</span>
<span class="o">&&</span> <span class="n">mLastPausedActivity</span> <span class="o">==</span> <span class="n">next</span>
<span class="o">&&</span> <span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">allPausedActivitiesComplete</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// 系统进入休眠状态,当前AS的栈顶Activity已经处于Paused状态</span>
<span class="c1">// 那么,中断对待显示Activity的调度</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">executeAppTransition</span><span class="o">();</span>
<span class="n">mNoAnimActivities</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="n">ActivityOptions</span><span class="o">.</span><span class="na">abort</span><span class="o">(</span><span class="n">options</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">mService</span><span class="o">.</span><span class="na">mStartedUsers</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">userId</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">// 如果找不到启动Activity的User,则返回</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 在ASS中维护了很多数组,来统一管理ActivityRecord的状态,</span>
<span class="c1">// 譬如mStoppingActivities记录了当前所有处于Stopping状态的ActivityRecord。</span>
<span class="c1">// 在某些场景下,待显示ActivityRecord可能处于这些数组中,但需要从中剔除</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">mStoppingActivities</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">next</span><span class="o">);</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">mGoingToSleepActivities</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">next</span><span class="o">);</span>
<span class="n">next</span><span class="o">.</span><span class="na">sleeping</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">mWaitingVisibleActivities</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">next</span><span class="o">);</span>
<span class="c1">// 如果当前还有ActivityRecord不是处于PAUSED, STOPPED或STOPPING这三个状态之一,</span>
<span class="c1">// 那么,需要先等这些ActivityRecord进入停止状态</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">allPausedActivitiesComplete</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">// 未完接代码片段2</span></code></pre></figure>
<p>【代码片段1】主要是一些“路障”,即便将待显示ActivityRecord已经位于栈顶,但要真正将其显示出来,即迁移到Resumed状态,这一路有很多障碍,或者说还有很多前提条件需要满足,譬如,系统要休眠时,当前启动过程要中断;当前有Activity正处于Pausing状态时,也需要等其执行完毕。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 代码片段2</span>
<span class="c1">// LauncherSource表示当前是哪个用户在启动Activity</span>
<span class="c1">// 实则是获取一个WakeLock,保证在显示Activity的过程中,系统不会进行休眠状态</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">setLaunchSource</span><span class="o">(</span><span class="n">next</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="c1">// Activity的启动参数中包含FLAG_RESUME_WHILE_PAUSING</span>
<span class="c1">// 表示可以在当前显示的Activity执行Pausing时,同时进行Resume操作</span>
<span class="c1">// 变量dontWaitForPause的取意就是不需要等到Activity执行Pause完毕</span>
<span class="kt">boolean</span> <span class="n">dontWaitForPause</span> <span class="o">=</span> <span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">flags</span><span class="o">&</span><span class="n">ActivityInfo</span><span class="o">.</span><span class="na">FLAG_RESUME_WHILE_PAUSING</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>
<span class="c1">// 将后台AS中的Activity迁移到Pausing状态</span>
<span class="kt">boolean</span> <span class="n">pausing</span> <span class="o">=</span> <span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">pauseBackStacks</span><span class="o">(</span><span class="n">userLeaving</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">dontWaitForPause</span><span class="o">);</span>
<span class="c1">// 将当前AS中正在显示的Activity迁移到Pausing状态</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mResumedActivity</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">pausing</span> <span class="o">|=</span> <span class="n">startPausingLocked</span><span class="o">(</span><span class="n">userLeaving</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="n">dontWaitForPause</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pausing</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 当前有正在Pausing的Activity</span>
<span class="k">if</span> <span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">app</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">next</span><span class="o">.</span><span class="na">app</span><span class="o">.</span><span class="na">thread</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mService</span><span class="o">.</span><span class="na">updateLruProcessLocked</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">app</span><span class="o">,</span> <span class="kc">true</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">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 通过PackageManager修改待启动Package的状态</span>
<span class="n">AppGlobals</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">().</span><span class="na">setPackageStoppedState</span><span class="o">(</span>
<span class="n">next</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="n">next</span><span class="o">.</span><span class="na">userId</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">{...}</span>
<span class="kt">boolean</span> <span class="n">anim</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">...</span> <span class="c1">// 省略与Activity切换动画相关的代码</span>
<span class="n">ActivityStack</span> <span class="n">lastStack</span> <span class="o">=</span> <span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">getLastStack</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">app</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">next</span><span class="o">.</span><span class="na">app</span><span class="o">.</span><span class="na">thread</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 待启动Activity的宿主进程已经存在了</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">setAppVisibility</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">next</span><span class="o">.</span><span class="na">startLaunchTickingLocked</span><span class="o">();</span>
<span class="n">ActivityRecord</span> <span class="n">lastResumedActivity</span> <span class="o">=</span>
<span class="n">lastStack</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">lastStack</span><span class="o">.</span><span class="na">mResumedActivity</span><span class="o">;</span>
<span class="n">ActivityState</span> <span class="n">lastState</span> <span class="o">=</span> <span class="n">next</span><span class="o">.</span><span class="na">state</span><span class="o">;</span>
<span class="n">mService</span><span class="o">.</span><span class="na">updateCpuStats</span><span class="o">();</span>
<span class="c1">// **这里,将待显示的Activity已经处于Resumed状态**</span>
<span class="n">next</span><span class="o">.</span><span class="na">state</span> <span class="o">=</span> <span class="n">ActivityState</span><span class="o">.</span><span class="na">RESUMED</span><span class="o">;</span>
<span class="n">mResumedActivity</span> <span class="o">=</span> <span class="n">next</span><span class="o">;</span>
<span class="n">next</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">touchActiveTime</span><span class="o">();</span>
<span class="n">mRecentTasks</span><span class="o">.</span><span class="na">addLocked</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">task</span><span class="o">);</span>
<span class="n">mService</span><span class="o">.</span><span class="na">updateLruProcessLocked</span><span class="o">(</span><span class="n">next</span><span class="o">.</span><span class="na">app</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">updateLRUListLocked</span><span class="o">(</span><span class="n">next</span><span class="o">);</span>
<span class="n">mService</span><span class="o">.</span><span class="na">updateOomAdjLocked</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">mStackSupervisor</span><span class="o">.</span><span class="na">startSpecificActivityLocked</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="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>【代码片段2】 首先要将当前正在显示的Activity迁移到Pausing状态,然后要将待显示的Activity迁移到Resumed状态,然而,这时候待显示Activity的宿主进程可能还未启动,这就需要调用<strong>ASS.startSpecificActivityLocked()</strong>。</p>
<p><b><font color="red">至此,HomeActivity启动的第三大部分分析完毕,完成两件大事:其一是将ActivityRecord挪到栈顶位置;其二是尝试将栈顶的ActivityRecord迁移到显示状态(Resumed)</font></b></p>
<hr />
<h1 id="8-assstartspecificactivitylocked">8. ASS.startSpecificActivityLocked()</h1>
<p>第一次启动HomeActivity时,由于桌面进程还未启动,所以便进入了该函数。
多了一个<strong>Specific</strong>关键字,表明Activity的启动操作到这一步已经明确下来,XXX</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">startSpecificActivityLocked</span><span class="o">(</span><span class="n">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="n">ProcessRecord</span> <span class="n">app</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">getProcessRecordLocked</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="kc">true</span><span class="o">);</span>
<span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">.</span><span class="na">stack</span><span class="o">.</span><span class="na">setLaunchTime</span><span class="o">(</span><span class="n">r</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">&&</span> <span class="n">app</span><span class="o">.</span><span class="na">thread</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="k">if</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">flags</span><span class="o">&</span><span class="n">ActivityInfo</span><span class="o">.</span><span class="na">FLAG_MULTIPROCESS</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span>
<span class="o">||</span> <span class="o">!</span><span class="s">"android"</span><span class="o">.</span><span class="na">equals</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">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">app</span><span class="o">.</span><span class="na">addPackage</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">packageName</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">versionCode</span><span class="o">,</span> <span class="n">mService</span><span class="o">.</span><span class="na">mProcessStats</span><span class="o">);</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">app</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="n">RemoteException</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">// 启动一个新的应用进程</span>
<span class="n">mService</span><span class="o">.</span><span class="na">startProcessLocked</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="kc">true</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span>
<span class="s">"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="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="o">}</span></code></pre></figure>
<h1 id="9-amsstartprocesslockedstring-processname">9. AMS.startProcessLocked(String processName…)</h1>
<p>在AMS中有多个重载的<strong>startProcessLocked()</strong>函数,我们先来看其中一个,根据processName来启动进程,这时候可能processName对应的进程还没创建。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">ProcessRecord</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="n">String</span> <span class="n">processName</span><span class="o">,</span> <span class="n">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="n">String</span> <span class="n">hostingType</span><span class="o">,</span> <span class="n">ComponentName</span> <span class="n">hostingName</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">keepIfLarge</span><span class="o">,</span>
<span class="n">String</span> <span class="n">abiOverride</span><span class="o">,</span> <span class="n">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">String</span><span class="o">[]</span> <span class="n">entryPointArgs</span><span class="o">,</span> <span class="n">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="n">SystemClock</span><span class="o">.</span><span class="na">elapsedRealtime</span><span class="o">();</span>
<span class="n">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">FLAG_FROM_BACKGROUND</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">keepIfLarge</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">"startProcess: after getProcessRecord"</span><span class="o">);</span>
<span class="c1">// 启动参数中,带有FLAG_FROM_BACKGROUND标志,表示进程需要后台启动</span>
<span class="k">if</span> <span class="o">((</span><span class="n">intentFlags</span> <span class="o">&</span> <span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_FROM_BACKGROUND</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">if</span> <span class="o">(</span><span class="n">mBadProcesses</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">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="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 后台启动一个BadProcess,直接退出</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">else</span> <span class="o">{</span>
<span class="c1">// 前台启动,则需要将宿主进程从坏的进程中剔除</span>
<span class="n">mProcessCrashTimes</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">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="k">if</span> <span class="o">(</span><span class="n">mBadProcesses</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">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="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mBadProcesses</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">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="k">if</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="n">app</span><span class="o">.</span><span class="na">bad</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</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">app</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 当进程已经被分配了PID时</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">&&</span> <span class="n">app</span><span class="o">.</span><span class="na">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">knownToBeDead</span> <span class="o">||</span> <span class="n">app</span><span class="o">.</span><span class="na">thread</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 进程还处于启动的过程中</span>
<span class="n">app</span><span class="o">.</span><span class="na">addPackage</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">info</span><span class="o">.</span><span class="na">versionCode</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">"startProcess: done, added package to proc"</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="c1">// 进程已经挂掉</span>
<span class="n">killProcessGroup</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">uid</span><span class="o">,</span> <span class="n">app</span><span class="o">.</span><span class="na">pid</span><span class="o">);</span>
<span class="n">handleAppDiedLocked</span><span class="o">(</span><span class="n">app</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="n">String</span> <span class="n">hostingNameStr</span> <span class="o">=</span> <span class="n">hostingName</span> <span class="o">!=</span> <span class="kc">null</span>
<span class="o">?</span> <span class="n">hostingName</span><span class="o">.</span><span class="na">flattenToShortString</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>
<span class="c1">// 创建一个新的ProcessRecord</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">)</span> <span class="o">{</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">newProcessRecordLocked</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">processName</span><span class="o">,</span> <span class="n">isolated</span><span class="o">,</span> <span class="n">isolatedUid</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="c1">// 如果系统还未启动,则需要将待启动进程先保持住,等系统启动后,再来启动这些进程</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mProcessesReady</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">isAllowedWhileBooting</span><span class="o">(</span><span class="n">info</span><span class="o">)</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">allowWhileBooting</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mProcessesOnHold</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">app</span><span class="o">))</span> <span class="o">{</span>
<span class="n">mProcessesOnHold</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="o">}</span>
<span class="k">return</span> <span class="n">app</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 调用另外一个startProcessLocked()函数</span>
<span class="n">startProcessLocked</span><span class="o">(</span>
<span class="n">app</span><span class="o">,</span> <span class="n">hostingType</span><span class="o">,</span> <span class="n">hostingNameStr</span><span class="o">,</span> <span class="n">abiOverride</span><span class="o">,</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">entryPointArgs</span><span class="o">);</span>
<span class="k">return</span> <span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">pid</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</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></code></pre></figure>
<p>该函数的逻辑有两个关键点:</p>
<ul>
<li>
<p>对于启动后台进程的处理。进程有isolated的概念,意味着这是一个隔离的进程。对于隔离的进程而言,每次启动都是独立的,不能复用已有的进程信息。如果要启动一个非隔离的进程,那么就需要区分进程是在前台启动还是后台启动,这是用户体验相关的设计。</p>
<p>在AMS中维护了一个badProcesses的结构体,用于保存一些“坏进程”,什么才是“坏进程”呢?如果一个进程在一分钟内连续崩溃两次,那就变成了一个“坏进程”。对于后台启动的进程而言(即启动参数中带有FLAG_FROM_BACKGROUND标识),如果进程崩溃了,会造成用户使用的困惑,因为进程崩溃时,会弹出一个对话框,而后台启动的进程是没有任何操作界面的,这时候弹一个框,用户会觉得自己什么都没干,却弹出了一个对话框。所以,后台启动一个“坏进程”时,会直接退出。</p>
<p>当进程是在前台启动时,即便是一个“坏进程”,那也应该宽恕这个进程以前的不良记录,因为这通常是用户通过界面主动要唤起的进程。本着用户是上帝的原则,还是得让用户达到启动进程的目的,即便这个进程可能再次崩溃。</p>
</li>
<li>
<p>创建一个新的ProcessRecord。当进程还未启动时,变量app为null,这时候会调用AMS.newProcessRecord来创建一个新的ProcessRecord,然后再调用另外一个重载的AMS.startProcessLocked()函数,来创建一个进程。</p>
</li>
</ul>
<h1 id="10-amsnewprocessrecordlocked">10. AMS.newProcessRecordLocked()</h1>
<p>对于第一次启动HomeActivity而言,Home进程还不存在,所以会顺利进入到这个函数中。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">ProcessRecord</span> <span class="nf">newProcessRecordLocked</span><span class="o">(</span><span class="n">ApplicationInfo</span> <span class="n">info</span><span class="o">,</span> <span class="n">String</span> <span class="n">customProcess</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="o">{</span>
<span class="n">String</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">customProcess</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">customProcess</span> <span class="o">:</span> <span class="n">info</span><span class="o">.</span><span class="na">processName</span><span class="o">;</span>
<span class="c1">// 电量统计</span>
<span class="n">BatteryStatsImpl</span> <span class="n">stats</span> <span class="o">=</span> <span class="n">mBatteryStatsService</span><span class="o">.</span><span class="na">getActiveStatistics</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">uid</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="o">...</span> <span class="c1">// 省略与isolated相关调整uid的代码</span>
<span class="kd">final</span> <span class="n">ProcessRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProcessRecord</span><span class="o">(</span><span class="n">stats</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="n">proc</span><span class="o">,</span> <span class="n">uid</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mBooted</span> <span class="o">&&</span> <span class="o">!</span><span class="n">mBooting</span>
<span class="o">&&</span> <span class="n">userId</span> <span class="o">==</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span>
<span class="o">&&</span> <span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">flags</span> <span class="o">&</span> <span class="n">PERSISTENT_MASK</span><span class="o">)</span> <span class="o">==</span> <span class="n">PERSISTENT_MASK</span><span class="o">)</span> <span class="o">{</span>
<span class="n">r</span><span class="o">.</span><span class="na">persistent</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">addProcessNameLocked</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="k">return</span> <span class="n">r</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>函数逻辑简单明了,根据ApplicaitonInfo创建一个ProcessRecord,并将ProcessRecord加入AMS的管理范围,与此同时,与进程相关的电量统计也是在这一步被绑定上的。</p>
<h1 id="11-amsstartprocesslockedprocessrecord-app">11. AMS.startProcessLocked(ProcessRecord app…)</h1>
<p>进入该函数,意味着进程的ProcessRecord已经构建好了,但进程真正的运行环境还没有准备好啊,所以该函数的职能就是为了构建应用程序真正的进程环境。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">startProcessLocked</span><span class="o">(</span><span class="n">ProcessRecord</span> <span class="n">app</span><span class="o">,</span> <span class="n">String</span> <span class="n">hostingType</span><span class="o">,</span>
<span class="n">String</span> <span class="n">hostingNameStr</span><span class="o">,</span> <span class="n">String</span> <span class="n">abiOverride</span><span class="o">,</span> <span class="n">String</span> <span class="n">entryPoint</span><span class="o">,</span> <span class="n">String</span><span class="o">[]</span> <span class="n">entryPointArgs</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">pid</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">app</span><span class="o">.</span><span class="na">pid</span> <span class="o">!=</span> <span class="n">MY_PID</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 新启动进程了,清除超时消息设置</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">mPidsSelfLocked</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">pid</span><span class="o">);</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">removeMessages</span><span class="o">(</span><span class="n">PROC_START_TIMEOUT_MSG</span><span class="o">,</span> <span class="n">app</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">app</span><span class="o">.</span><span class="na">setPid</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 在HoldProcess中清除app</span>
<span class="n">mProcessesOnHold</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">app</span><span class="o">);</span>
<span class="n">updateCpuStats</span><span class="o">();</span>
<span class="o">...</span> <span class="c1">// 省略gid相关的设置,gid与进程的访问权限相关</span>
<span class="o">...</span> <span class="c1">// 省略debugFlags相关的处理,与进程的调试选项相关</span>
<span class="c1">// 创建一个进程</span>
<span class="n">Process</span><span class="o">.</span><span class="na">ProcessStartResult</span> <span class="n">startResult</span> <span class="o">=</span> <span class="n">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">debugFlags</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">app</span><span class="o">.</span><span class="na">info</span><span class="o">.</span><span class="na">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">entryPointArgs</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>略去了一大堆进程启动参数设置相关的代码后,终于可以创建一个进程了,<strong>Process.start()</strong>的具体实现我们不在此处展开,各位读者可以参考源码。我们知道应用进程都是从Zygote进程fork出来的,这正是<strong>Process.start()</strong>函数的职能,它会给二进制程序<strong>zygote</strong>准备一些参数,然后通过系统调用fork()出一个子进程,这就是要启动的应用进程。</p>
<p><strong>分析完AMS.startProcessLocked(ProcessRecord app…)函数,我们来总结一下是如何走到这一步的:</strong></p>
<div align="center"><img src="/assets/images/activity/launchprocess/2-activity-launchprocess-part2.png" alt="Activity Launching Process" /></div>
<p>HomeActivity经过了前面4个函数,得到了结论是:HomeActivity是一个新启动的Activity,当前栈中并不存在。接下来的逻辑,就是试图要显示HomeActivity:</p>
<ul>
<li>
<p><strong>5 AS.startActivityLocked()</strong>:将待显示的Activity挪到到栈顶位置,意味着待显示的ActivityRecord是TaskRecord的栈顶,并且TaskRecord是当前ActivityStack的栈顶。这里还会设置Activity的启动动画</p>
</li>
<li>
<p><strong>6 ASS.resumeTopActivitiesLocked()</strong>:ActivityRecord已经在栈顶后,就需要将其迁移到显示状态(Resumed),由于Android支持多屏幕,所有需要对多个AS的栈顶都进行这个操作,ASS作为多个AS的管理者,很自然的就承担的这个工作。</p>
</li>
<li>
<p><strong>7 AS.resumeTopActivityInnerLocked()</strong>:收到上级的指示后,AS开始将栈顶的ActivityRecord推进到显示状态。首先,需要将当前正在显示的Activity推进到Pausing状态。对于HomeActivity而言,它就是第一个要启动的Activity,所以当前并没有正在显示的。</p>
</li>
<li>
<p><strong>8 ASS.startSpecificActivityLocked()</strong>:启动Activity带上Specific关键字,表示已经所有的障碍已经排除,明确(Specific)要启动目标Activity了。然而,这时候可能宿主进程还没创建呢,HomeActivity就属于这种情况。</p>
</li>
<li>
<p><strong>9 AMS.startProcessLocked(String processName…)</strong>:该函数的主要目的是为了准备一个ProcessRecord对象,如果AMS中还没有待启动Activity所在宿主进程的ProcessRecord对象,则需要新建一个。此外,对于后台启动的进程,有一个BadProcesses相关的处理逻辑。</p>
</li>
<li>
<p><strong>10 AMS.newProcessRecordLocked()</strong>:新建一个ProcessRecord对象。在<a href="2016-02-01-Activity-Maintenance.md">Android四大组件之Activity–管理方式</a>一文中,介绍了ProcessRecord这个数据结构,它是用来维护进程的运行时的状态信息。AMS统一管理所有应用进程和系统进程的ProcessRecord。</p>
</li>
<li>
<p><strong>11 AMS.startProcessLocked(ProcessRecord app…)</strong>:目的是为了启动ProcessRecord所关联的进程,在准备进程启动的参数之后,就会通过Zygote孵化出一个子进程,这就是Activity的宿主应用进程。</p>
</li>
</ul>
<p><b><font color="red">至此,HomeActivity启动的第四大部分分析完毕,干了一件大事:创建待启动Activity的宿主应用进程。</font></b></p>
<blockquote>
<p>在介绍完HomeActivity启动路径上的11个函数之后,终于把一个应用进程给启动了。想必读者内心一定是百感交集,坦白说,从代码设计的角度,这些函数无论从实现上还是命名上,笔者觉得并不优美,太多的分支条件交织在一起,往往一个函数看下来就已经摸不着头脑了。在理解这一部分代码时,一定要把握好整体与局部的关系:整体路径是Activity启动过程,而局部有很多条件约束,一些约束条件是由Activity的管理方式导致的,一些约束条件是实际的用户场景和需求导致的,读者不要陷入这些局部中,一定要在脑海中保持一条清晰的Activity启动路径。</p>
<p>稍微舒缓一下,再给大家带来一个噩耗,创建一个应用进程,还仅仅只是Activity启动的半途,后面的路还长着了。以上的过程,基本都在系统进程中完成,接下来的过程需要系统进程和应用进程不断进行通信,才能把Activity给真正启动起来,在<a href="2016-10-23-Activity-LaunchProcess-Part2">Activity的启动过程(下)</a>一文中,我们再见。</p>
</blockquote>
<hr />
<h1 id="总结">总结</h1>
<p>本文分成四个大的部分介绍Activity的启动过程:</p>
<ol>
<li>
<p>构建个HomeIntent,并将HomeTask挪到HomeStack的栈顶;涉及到<strong>AMS.startHomeActivityLocked()、ASS.startHomeActivity、 ASS.moveHomeStackTaskToTop()、AS.moveHomeStackTaskToTop()</strong>这几个函数;关键点在于HomeTask的挪动过程。</p>
</li>
<li>
<p>构建ActivityRecord,并未其寻找宿主任务TaskRecord。涉及<strong>ASS.startActivityLocked()、ASS.startActivityUncheckedLocked()</strong>两个大函数;关键点在于ActivityRecord创建前的检查,以及创建后,根据启动参数寻找宿主任务的过程。</p>
</li>
<li>
<p>挪动ActivityRecord到宿主栈顶。涉及<strong>AS.startActivityLocked()、ASS.resumeTopActivitiesLocked()、AS.resumeTopActivityInnerLocked()</strong>这几个函数;当ActivityRecord位于栈顶时,会尝试将其迁移到显示状态。</p>
</li>
<li>
<p>创建ActivityRecord所在的应用进程。涉及<strong>ASS.startSpecificActivityLocked()、AMS.startProcessLocked()、AMS.newProcessRecordLocked()</strong>这几个函数。对于HomeActivity第一次启动而言,宿主进程还未创建,所以需要先创建一个可以执行的进程环境。</p>
</li>
</ol>
ActivityManagerService的启动过程
2016-07-15T00:00:00+00:00
https://duanqz.github.io/AMS-LaunchProcess
<h1 id="1-概览">1. 概览</h1>
<p>ActivitityManagerService(下文简称AMS)作为系统中最重要的服务,在开机时就会被启动,而且一直存在。
那么,Android是如何启动AMS,以及启动AMS时做了哪些初始化工作呢?</p>
<p>从进入Android系统进程(SystemServer)到用户所见的桌面(HomeActivity),整个过程中,AMS都扮演着主角。
AMS就像一个大管家,总管着四大组件、进程调度、各类统计等信息。在AMS启动时,就表明了自己的身份:引导服务(BootstrapService),意味着AMS是最重要的一类服务,大多数其他系统服务的启动都依赖于AMS。</p>
<p>本章分析的对象是AMS在系统进程中的启动过程,虽然仅仅是启动,涉及到的知识点已然非常多。</p>
<h1 id="2-在系统进程中启动ams">2. 在系统进程中启动AMS</h1>
<p>AMS对象随系统进程启动而构建,随着系统进程退出而消亡,可以说,AMS与系统进程共存亡。
本章分析的内容是系统进程启动时与AMS相关的一些初始化操作,先上一张总的启动时序图:</p>
<div align="center"><img src="/assets/images/systemservice/amslaunching/1-amslaunching-sequence-diagram.png" alt="Sequence Diagram" /></div>
<p>可以分为三个步骤:</p>
<ol>
<li>初始化系统进程的运行环境;</li>
<li>初始化AMS对象;</li>
<li>AMS对象启动的配套工作。</li>
</ol>
<h2 id="21-初始化系统进程的运行环境">2.1 初始化系统进程的运行环境</h2>
<p><a href="">SystemServer</a>是我们理解Android系统进程的入口,它的初始化是从Native层开始的:Zygote从Native层调用SystemServer的main()函数,便开始了SystemServer的初始化。初始化很重要的一个步骤就是创建系统进程的运行环境,即创建一个SystemContext,调用关系如下所示:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SystemServer.main() // Zygote会从Native层调用该方法,进入SystemServer的执行代码
└── SystemServer.run() // SystemServer一旦运行起来,就一直循环处理消息队列中的消息
└── SystemServer.createSystemContext() // 创建SystemContext
</code></pre></div></div>
<p>SystemContext到底是什么呢?说到底,它还是一个Context类型的对象,需要借助于ActivityThread才能获取到:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// SystemServer.createSystemContext()</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">createSystemContext</span><span class="o">()</span> <span class="o">{</span>
<span class="n">ActivityThread</span> <span class="n">activityThread</span> <span class="o">=</span> <span class="n">ActivityThread</span><span class="o">.</span><span class="na">systemMain</span><span class="o">();</span>
<span class="n">mSystemContext</span> <span class="o">=</span> <span class="n">activityThread</span><span class="o">.</span><span class="na">getSystemContext</span><span class="o">();</span>
<span class="n">mSystemContext</span><span class="o">.</span><span class="na">setTheme</span><span class="o">(</span><span class="n">android</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">style</span><span class="o">.</span><span class="na">Theme_DeviceDefault_Light_DarkActionBar</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p><strong>ActivityThread.systemMain()</strong>函数用于创建系统进程的主线程,方法主体很简单,创建了一个新的ActivityThread对象,并调用了<strong>attach()</strong>方法,输入参数是true,表示需要将其绑定到系统进程。</p>
<blockquote>
<p>应用进程创建ActivityThread对象是通过<strong>ActivityThread.main()</strong>函数完成的。由于系统进程的特殊性,专辟了一个<strong>systemMain()</strong>函数给系统进程。</p>
</blockquote>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="n">ActivityThread</span> <span class="nf">systemMain</span><span class="o">()</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略硬件渲染相关配置的代码</span>
<span class="n">ActivityThread</span> <span class="n">thread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">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">true</span><span class="o">);</span>
<span class="k">return</span> <span class="n">thread</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>接下来,我们来着重分析一下<strong>ActivityThread.attach()</strong>函数:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><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="o">{</span>
<span class="n">sCurrentActivityThread</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="k">if</span> <span class="o">(!</span><span class="n">system</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 绑定应用进程,本例仅讨论绑定系统进程,故省略之</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 设置进程名为system_process</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">"system_process"</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">myUserId</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="k">new</span> <span class="n">Instrumentation</span><span class="o">();</span>
<span class="n">ContextImpl</span> <span class="n">context</span> <span class="o">=</span> <span class="n">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">getSystemContext</span><span class="o">().</span><span class="na">mPackageInfo</span><span class="o">);</span>
<span class="n">mInitialApplication</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">mPackageInfo</span><span class="o">.</span><span class="na">makeApplication</span><span class="o">(</span><span class="kc">true</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">mInitialApplication</span><span class="o">.</span><span class="na">onCreate</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// 省略抛出异常的代码 }</span>
<span class="o">}</span>
<span class="c1">// 设置与输出日志相关的Dropbox</span>
<span class="n">DropBox</span><span class="o">.</span><span class="na">setReporter</span><span class="o">(</span><span class="k">new</span> <span class="n">DropBoxReporter</span><span class="o">());</span>
<span class="n">ViewRootImpl</span><span class="o">.</span><span class="na">addConfigCallback</span><span class="o">(</span> <span class="o">...</span> <span class="c1">// );</span>
<span class="o">}</span></code></pre></figure>
<p>在上面的函数实现中,会创建几个很重要的对象:</p>
<ul>
<li>
<p><strong>mInstrumentation</strong>: 工具类,主要用于测试,与AndroidManifest.xml中<instrumentation>对应;</p>
</li>
<li>
<p><strong>context</strong>: Application的运行环境,创建它的目的是为了创建下面的Application对象。创建这个context需要传入一个LoadedApk类型的对象,通过<strong>ActivityThread.getSystemContext()</strong>函数便可获取:</p>
</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="n">ContextImpl</span> <span class="nf">getSystemContext</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="k">if</span> <span class="o">(</span><span class="n">mSystemContext</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSystemContext</span> <span class="o">=</span> <span class="n">ContextImpl</span><span class="o">.</span><span class="na">createSystemContext</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">mSystemContext</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>这里是第一次调用<strong>getSystemContext()</strong>函数,所以<strong>mSystemContext</strong>为null,进而会通过<strong>ContextImpl.createSystemContext()</strong>函数创建一个新的对象:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// ContextImpl.createSystemContext()</span>
<span class="kd">static</span> <span class="n">ContextImpl</span> <span class="nf">createSystemContext</span><span class="o">(</span><span class="n">ActivityThread</span> <span class="n">mainThread</span><span class="o">)</span> <span class="o">{</span>
<span class="n">LoadedApk</span> <span class="n">packageInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="n">LoadedApk</span><span class="o">(</span><span class="n">mainThread</span><span class="o">);</span>
<span class="n">ContextImpl</span> <span class="n">context</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ContextImpl</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="n">mainThread</span><span class="o">,</span>
<span class="n">packageInfo</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">false</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">Display</span><span class="o">.</span><span class="na">INVALID_DISPLAY</span><span class="o">);</span>
<span class="n">context</span><span class="o">.</span><span class="na">mResources</span><span class="o">.</span><span class="na">updateConfiguration</span><span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">mResourcesManager</span><span class="o">.</span><span class="na">getConfiguration</span><span class="o">(),</span>
<span class="n">context</span><span class="o">.</span><span class="na">mResourcesManager</span><span class="o">.</span><span class="na">getDisplayMetricsLocked</span><span class="o">());</span>
<span class="k">return</span> <span class="n">context</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>上述函数首先会创建一个LoadedApk的对象,LoadedApk表示一个装载到内存的Apk,对于SystemContext,这个Apk就是<strong>framework-res.apk</strong>;
然后,创建一个ContextImpl的对象,进一步初始化资源相关的配置; 最终,返回的context就是SystemContext。</p>
<ul>
<li><strong>mInitialApplication</strong>: Application对象,由于是通过<strong>framework-res.apk</strong>这个APK的Context创建而来,所以这个Application对象就对应到了<strong>framework-res.apk</strong>,这是系统中第一个运行的APK,所以叫做InitialApplication,它是运行在系统进程中。</li>
</ul>
<p>兜兜转转弄了一圈,终于把SystemContext给创建好了,系统进程好好运行就好了,为什么要大费周张的搞一个运行环境Context出来呢?
操作系统运行基本单位是进程,我们写的任何Android代码最终都要落到一个实际的进程中去执行。然而,Android有意弱化了进程的概念,我们在写Android应用程序的时候,基础的概念都是运行环境(Context),而不是去考虑进程中有什么可以使用。
Android对待系统进程,也像对待普通的应用进程一样,都需要构建一个运行环境,就是Context。在构建AMS对象时,会将SystemContext传入,通过这个特殊的Context,AMS就能获取运行时所需要各种信息。</p>
<p>我们通过一张类图来总结一下,与系统进程运行环境初始化相关的各个类之间的关系:</p>
<div align="center"><img src="/assets/images/systemservice/amslaunching/2-amslaunching-activitythread-classes-diagram.png" alt="Classes Diagram" /></div>
<ol>
<li>
<p>Android要为应用进程创造一个运行环境,同样也需要为系统进程创造一个运行环境,在系统进程启动伊始,这个运行环境就需要创建完毕;</p>
</li>
<li>
<p>SystemServer会创建一个ActivityThread对象,代表系统进程的主线程,在ActivityThread对象构建的过程中,又会创建Instrumentation对象和framework-res.apk的LoadedApk对象,再通过LoadedApk创建Application对象;</p>
</li>
<li>
<p>在ActivityThread对象构建完毕后,SystemServer便可获取到系统进程的运行环境了,即SystemContext,这是ActivityThread通过ContextImpl创建而成的。</p>
</li>
</ol>
<h2 id="22-初始化activitymanagerservice">2.2 初始化ActivityManagerService</h2>
<h3 id="221-systemservice和systemservicemanager">2.2.1 SystemService和SystemServiceManager</h3>
<p>在Android起机的过程中,系统服务是相互影响的,所以有启动顺序的先后之分,譬如BatteryService,WindowManagerService就需要在ActivityManagerService创建之后才能创建。还有一些系统事件,譬如BOOT_COMPLETED广播,需要在系统完全启动之后才能发出。
Android为系统服务封装了SystemService类,设计了系统服务的生命周期:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">SystemService</span> <span class="o">{</span>
<span class="c1">// 阶段 1: 等待显示设备准备完毕</span>
<span class="c1">// 这个阶段,Installer, AMS, PowerManagerService, DisplayManagerService已经构建完毕</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_WAIT_FOR_DEFAULT_DISPLAY</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span>
<span class="c1">// 阶段 2: 锁屏服务已经准备完毕</span>
<span class="c1">// 这个阶段,大部分系统服务已经构建完毕。其他服务可以获取锁屏相关的数据了,譬如锁屏密码等</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_LOCK_SETTINGS_READY</span> <span class="o">=</span> <span class="mi">480</span><span class="o">;</span>
<span class="c1">// 阶段 3: 系统服务已经准备完毕</span>
<span class="c1">// 这个阶段,大部分系统服务已经构建完毕,PackageManagerService,PowerManagerService已经能够提供服务</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_SYSTEM_SERVICES_READY</span> <span class="o">=</span> <span class="mi">500</span><span class="o">;</span>
<span class="c1">// 阶段 4: ActivityManagerService已经能够提供服务了</span>
<span class="c1">// 这个阶段,获取ContentProvider、发送广播等AMS相关的功能已经可以用了</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_ACTIVITY_MANAGER_READY</span> <span class="o">=</span> <span class="mi">550</span><span class="o">;</span>
<span class="c1">// 阶段 5: 已经可以启动应用程序了</span>
<span class="c1">// 这个阶段,用户界面已经启动,数据连接、音频等服务都已可用</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_THIRD_PARTY_APPS_CAN_START</span> <span class="o">=</span> <span class="mi">600</span><span class="o">;</span>
<span class="c1">// 阶段 6: 系统起机完成</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">PHASE_BOOT_COMPLETED</span> <span class="o">=</span> <span class="mi">1000</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">void</span> <span class="nf">onStart</span><span class="o">();</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onBootPhase</span><span class="o">(</span><span class="kt">int</span> <span class="n">phase</span><span class="o">)</span> <span class="o">{}</span>
<span class="c1">// ... 省略其他函数</span>
<span class="o">}</span></code></pre></figure>
<p>部分系统服务的初始化依赖于当前系统已经起机到什么阶段,当系统服务启动时,onStart()函数会被调用; 系统启动到一定的阶段,onBootPhase()函数会被调用。所有系统服务的创建和生命周期的管理都是由<strong>SystemServiceManager</strong>这个类完成的,笔者着重分析该类的两个函数:</p>
<ul>
<li><strong>startService()</strong>: 启动系统服务</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 这是一个范型方法,SystemService的子类都是通过这种方式启动的。</span>
<span class="kd">public</span> <span class="o"><</span><span class="n">T</span> <span class="kd">extends</span> <span class="n">SystemService</span><span class="o">></span> <span class="n">T</span> <span class="nf">startService</span><span class="o">(</span><span class="n">Class</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="n">serviceClass</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">serviceClass</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
<span class="n">Slog</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Starting "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
<span class="c1">// 判断是否为SystemService的子类</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">SystemService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">serviceClass</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">"Failed to create "</span> <span class="o">+</span> <span class="n">name</span>
<span class="o">+</span> <span class="s">": service must extend "</span> <span class="o">+</span> <span class="n">SystemService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="o">}</span>
<span class="c1">// 通过反射构建一个新的SystemService对象</span>
<span class="kd">final</span> <span class="n">T</span> <span class="n">service</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">Constructor</span><span class="o"><</span><span class="n">T</span><span class="o">></span> <span class="n">constructor</span> <span class="o">=</span> <span class="n">serviceClass</span><span class="o">.</span><span class="na">getConstructor</span><span class="o">(</span><span class="n">Context</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">service</span> <span class="o">=</span> <span class="n">constructor</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="n">mContext</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InstantiationException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="o">...</span> <span class="c1">// 省略catch其他异常 }</span>
<span class="c1">// 将新创建的服务添加到全局列表</span>
<span class="n">mServices</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">service</span><span class="o">);</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 调用SystemService.onStart()函数</span>
<span class="n">service</span><span class="o">.</span><span class="na">onStart</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RuntimeException</span> <span class="n">ex</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">service</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>startBootPhase()</strong>: 进入当前所在的起动阶段,即上文SystemService中定义的6个阶段之一</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">startBootPhase</span><span class="o">(</span><span class="kd">final</span> <span class="kt">int</span> <span class="n">phase</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// SystemServiceManager中通过mCurrentPhase属性表示当前所在的阶段</span>
<span class="k">if</span> <span class="o">(</span><span class="n">phase</span> <span class="o"><=</span> <span class="n">mCurrentPhase</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">"Next phase must be larger than previous"</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mCurrentPhase</span> <span class="o">=</span> <span class="n">phase</span><span class="o">;</span>
<span class="c1">// 通知所有的SystemService当前所处的启动阶段,通过调用SystemService.onBootPhase()函数实现</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">serviceLen</span> <span class="o">=</span> <span class="n">mServices</span><span class="o">.</span><span class="na">size</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">0</span><span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">serviceLen</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="n">SystemService</span> <span class="n">service</span> <span class="o">=</span> <span class="n">mServices</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="k">try</span> <span class="o">{</span>
<span class="n">service</span><span class="o">.</span><span class="na">onBootPhase</span><span class="o">(</span><span class="n">mCurrentPhase</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Exception</span> <span class="n">ex</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></code></pre></figure>
<p>具体到本文中要分析的AMS,它并没有直接继承SystemService,而是通过内部类Lifecycle来继承实现的,
AMS.Lifecycle非常简单,就是调用了AMS的构造函数和start()函数,是一个间接初始化AMS的过程。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Lifecycle</span> <span class="kd">extends</span> <span class="n">SystemService</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">ActivityManagerService</span> <span class="n">mService</span><span class="o">;</span>
<span class="c1">// 构造函数会被SystemServiceManager反射调用</span>
<span class="kd">public</span> <span class="nf">Lifecycle</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="n">mService</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityManagerService</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 在Lifecycle对象构造完成后,这个函数会被回调</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onStart</span><span class="o">()</span> <span class="o">{</span>
<span class="n">mService</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">ActivityManagerService</span> <span class="nf">getService</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">mService</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>我们通过类图来总结一下SystemServiceManager, SystemService以及AMS三者之间的关系:</p>
<div align="center"><img src="/assets/images/systemservice/amslaunching/3-amslaunching-systemservice-classes-diagram.png" alt="System Service Classes Diagram" /></div>
<ol>
<li>
<p>SystemService是系统服务的抽象类,封装了onStart()和onBootPhase()等生命周期函数供SystemServiceManager回掉;</p>
</li>
<li>
<p>AMS并不是直接继承SystemService,而是通过内部类Lifecycle间接完成了系统服务启动的生命周期;</p>
</li>
<li>
<p>SystemServiceManager管理了多个SystemService。</p>
</li>
</ol>
<h3 id="222-systemserver和activitymanagerservice">2.2.2 SystemServer和ActivityManagerService</h3>
<p>SystemServer在完成一些简单的初始化后,就需要启动系统服务了,最重要的一系列服务是在<strong>SystemServer.startBootstrapServices()</strong>
这个函数中启动的,BootstrapServices的命名也很贴切,表示要启动一些”引导服务”,这些服务直接影响到其他服务的启动。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">startBootstrapServices</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 1. 启动Installer系统服务</span>
<span class="n">Installer</span> <span class="n">installer</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="n">Installer</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="c1">// 2. 启动AMS</span>
<span class="n">mActivityManagerService</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="n">ActivityManagerService</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="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="c1">// 3. 启动PowerManagerService</span>
<span class="n">mPowerManagerService</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="n">PowerManagerService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">initPowerManagement</span><span class="o">();</span>
<span class="c1">// 4. 启动LED和背光灯、显示、包管理这些重要的系统服务</span>
<span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startService</span><span class="o">(</span><span class="n">LightsService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">mDisplayManagerService</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="n">DisplayManagerService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">...</span>
<span class="n">mPackageManagerService</span> <span class="o">=</span> <span class="n">PackageManagerService</span><span class="o">.</span><span class="na">main</span><span class="o">(</span><span class="n">mSystemContext</span><span class="o">,</span> <span class="n">installer</span><span class="o">,</span>
<span class="n">mFactoryTestMode</span> <span class="o">!=</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_OFF</span><span class="o">,</span> <span class="n">mOnlyCore</span><span class="o">);</span>
<span class="n">mFirstBoot</span> <span class="o">=</span> <span class="n">mPackageManagerService</span><span class="o">.</span><span class="na">isFirstBoot</span><span class="o">();</span>
<span class="n">mPackageManager</span> <span class="o">=</span> <span class="n">mSystemContext</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">();</span>
<span class="c1">// 5. 设置当前进程为系统进程</span>
<span class="n">mActivityManagerService</span><span class="o">.</span><span class="na">setSystemProcess</span><span class="o">();</span>
<span class="c1">// 6. 启动Sensor相关的服务。这是一个Native方法,与硬件相关度较大。</span>
<span class="n">startSensorService</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>
<ol>
<li>
<p>启动AMS之前,需要先连接installd进程。在系统进程(system_process)中,对应的服务就Installer,
通过Installer这个服务,就能完成一些重要目录的创建,譬如/data/user。</p>
</li>
<li>
<p>启动AMS。由此可见AMS的重要性,Android第二个启动的系统服务就是AMS。
实际上,这里通过SystemServcieManager来启动的是AMS.Lifecycle,AMS的真正初始化工作是由AMS.Lifecyle间接完成的。</p>
</li>
<li>
<p>启动PowerManagerService,这也是非常重要的一个系统服务。AMS也需要使用PowerManagerService的服务,
譬如,在启动Activity时,要避免系统进入休眠状态,就需要获取WakeLock。</p>
</li>
<li>
<p>这一步启动Lights、 DisplayManager、 PackageManager这些系统服务。</p>
</li>
<li>
<p>调用<strong>AMS.setSystemProcess()</strong>将当前进程设置为系统进程。为什么在SystemServer中需要调用AMS的方法来设置当前进程的信息呢?
因为AMS的职责之一就是维护所有进程的状态,不管是应用进程还是系统进程,都是AMS的管辖范围。</p>
</li>
</ol>
<p>上述步骤中,与AMS直接相关的是<strong>第2步</strong>和<strong>第5步</strong>,我们再深入展开分析这两个步骤:</p>
<p><strong>在第2步中</strong>,AMS.Lifecycle最终还是会调用AMS的构造器来实例化一个AMS对象:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="nf">ActivityManagerService</span><span class="o">(</span><span class="n">Context</span> <span class="n">systemContext</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mContext</span> <span class="o">=</span> <span class="n">systemContext</span><span class="o">;</span>
<span class="c1">// 是否为测试模式,0表示关闭</span>
<span class="n">mFactoryTest</span> <span class="o">=</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">getMode</span><span class="o">();</span>
<span class="c1">// 表示主线程的变量。在AMS对象构建之前,系统进程的主线程已经构建好了。</span>
<span class="n">mSystemThread</span> <span class="o">=</span> <span class="n">ActivityThread</span><span class="o">.</span><span class="na">currentActivityThread</span><span class="o">();</span>
<span class="c1">// 创建了绑定了消息队列的线程并运行,这个线程就是AMS线程,要处理大量的消息</span>
<span class="n">mHandlerThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ServiceThread</span><span class="o">(</span><span class="n">TAG</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="kc">false</span> <span class="cm">/*allowIo*/</span><span class="o">);</span>
<span class="n">mHandlerThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="n">mHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">MainHandler</span><span class="o">(</span><span class="n">mHandlerThread</span><span class="o">.</span><span class="na">getLooper</span><span class="o">());</span>
<span class="c1">// 创建UI绑定到全局UI线程的Handler,ANR的对话框显示的消息就会抛到全局UI线程上面</span>
<span class="n">mUiHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="n">UiHandler</span><span class="o">();</span>
<span class="c1">// 创建广播消息队列。有前/后台之分,为了区分不同的广播消息超时时间。</span>
<span class="n">mFgBroadcastQueue</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BroadcastQueue</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">mHandler</span><span class="o">,</span>
<span class="s">"foreground"</span><span class="o">,</span> <span class="n">BROADCAST_FG_TIMEOUT</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
<span class="n">mBgBroadcastQueue</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BroadcastQueue</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">mHandler</span><span class="o">,</span>
<span class="s">"background"</span><span class="o">,</span> <span class="n">BROADCAST_BG_TIMEOUT</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">mBroadcastQueues</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="n">mFgBroadcastQueue</span><span class="o">;</span>
<span class="n">mBroadcastQueues</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="n">mBgBroadcastQueue</span><span class="o">;</span>
<span class="c1">// AMS所管理的Service和Provider</span>
<span class="n">mServices</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActiveServices</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">mProviderMap</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProviderMap</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="c1">// 创建 /data/system 目录,诸如包管理packages.xml, packages.list等文件都存放于此目录</span>
<span class="n">File</span> <span class="n">dataDir</span> <span class="o">=</span> <span class="n">Environment</span><span class="o">.</span><span class="na">getDataDirectory</span><span class="o">();</span>
<span class="n">File</span> <span class="n">systemDir</span> <span class="o">=</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">dataDir</span><span class="o">,</span> <span class="s">"system"</span><span class="o">);</span>
<span class="n">systemDir</span><span class="o">.</span><span class="na">mkdirs</span><span class="o">();</span>
<span class="c1">// 创建电量统计服务</span>
<span class="n">mBatteryStatsService</span> <span class="o">=</span> <span class="k">new</span> <span class="n">BatteryStatsService</span><span class="o">(</span><span class="n">systemDir</span><span class="o">,</span> <span class="n">mHandler</span><span class="o">);</span>
<span class="o">...</span> <span class="c1">// 省略部分电量统计服务的初始化代码</span>
<span class="c1">// 创建CPU/内存等信息统计服务,内部实现就是读取 /proc/stat</span>
<span class="n">mProcessStats</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProcessStatsService</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">systemDir</span><span class="o">,</span> <span class="s">"procstats"</span><span class="o">));</span>
<span class="n">mAppOpsService</span> <span class="o">=</span> <span class="k">new</span> <span class="n">AppOpsService</span><span class="o">(</span><span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">systemDir</span><span class="o">,</span> <span class="s">"appops.xml"</span><span class="o">),</span> <span class="n">mHandler</span><span class="o">);</span>
<span class="n">mGrantFile</span> <span class="o">=</span> <span class="k">new</span> <span class="n">AtomicFile</span><span class="o">(</span><span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="n">systemDir</span><span class="o">,</span> <span class="s">"urigrants.xml"</span><span class="o">));</span>
<span class="c1">// 在多用户场景下。USER_OWNER(0)是启动时唯一的用户</span>
<span class="n">mStartedUsers</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">,</span> <span class="k">new</span> <span class="n">UserState</span><span class="o">(</span><span class="n">UserHandle</span><span class="o">.</span><span class="na">OWNER</span><span class="o">,</span> <span class="kc">true</span><span class="o">));</span>
<span class="n">mUserLru</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">);</span>
<span class="n">updateStartedUserArrayLocked</span><span class="o">();</span>
<span class="c1">// 获取OpenGL版本号</span>
<span class="n">GL_ES_VERSION</span> <span class="o">=</span> <span class="n">SystemProperties</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"ro.opengles.version"</span><span class="o">,</span>
<span class="n">ConfigurationInfo</span><span class="o">.</span><span class="na">GL_ES_VERSION_UNDEFINED</span><span class="o">);</span>
<span class="c1">// 配置区域、语言、字体、屏幕方向等,启动Activity时,需要用到这个配置</span>
<span class="n">mConfiguration</span><span class="o">.</span><span class="na">setToDefaults</span><span class="o">();</span>
<span class="o">...</span> <span class="c1">// 省略部分Configuration的初始化</span>
<span class="n">mProcessCpuTracker</span><span class="o">.</span><span class="na">init</span><span class="o">();</span>
<span class="c1">// AndroidManifest.xml中compatible-screens相关的解析工具</span>
<span class="n">mCompatModePackages</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CompatModePackages</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">systemDir</span><span class="o">,</span> <span class="n">mHandler</span><span class="o">);</span>
<span class="c1">// Intent防火墙</span>
<span class="n">mIntentFirewall</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IntentFirewall</span><span class="o">(</span><span class="k">new</span> <span class="n">IntentFirewallInterface</span><span class="o">(),</span> <span class="n">mHandler</span><span class="o">);</span>
<span class="c1">// 多任务列表。Task就是任务栈,最近使用的任务都会出现在列表中</span>
<span class="n">mRecentTasks</span> <span class="o">=</span> <span class="k">new</span> <span class="n">RecentTasks</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="c1">// 多个ActivityStack的管理者</span>
<span class="n">mStackSupervisor</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityStackSupervisor</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">mRecentTasks</span><span class="o">);</span>
<span class="c1">// 将任务写入文件的工具类</span>
<span class="n">mTaskPersister</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TaskPersister</span><span class="o">(</span><span class="n">systemDir</span><span class="o">,</span> <span class="n">mStackSupervisor</span><span class="o">,</span> <span class="n">mRecentTasks</span><span class="o">);</span>
<span class="c1">// 创建定期更新CPU统计信息的线程</span>
<span class="n">mProcessCpuThread</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="o">(</span><span class="s">"CpuTracker"</span><span class="o">)</span> <span class="o">{...</span><span class="c1">//省略具体实现}</span>
<span class="c1">// 将自身添加到WatchDog中,以便监测Service的运行状态,譬如是否发生死锁、消息队列是否阻塞</span>
<span class="n">Watchdog</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">addMonitor</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">Watchdog</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">addThread</span><span class="o">(</span><span class="n">mHandler</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>再来看一下AMS对象初始化后,紧接着调用的<strong>AMS.start()</strong>函数:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><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="n">Process</span><span class="o">.</span><span class="na">removeAllProcessGroups</span><span class="o">();</span>
<span class="c1">// 启动CPU使用统计线程</span>
<span class="n">mProcessCpuThread</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="c1">// 设置电量使用统计服务和权限管理服务的一些参数,并将其添加到ServiceManager</span>
<span class="n">mBatteryStatsService</span><span class="o">.</span><span class="na">publish</span><span class="o">(</span><span class="n">mContext</span><span class="o">);</span>
<span class="n">mAppOpsService</span><span class="o">.</span><span class="na">publish</span><span class="o">(</span><span class="n">mContext</span><span class="o">);</span>
<span class="c1">// LocalServices类似于ServiceManager的功能,主要用于系统进程内部访问的一些服务</span>
<span class="n">LocalServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="n">ActivityManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">new</span> <span class="n">LocalService</span><span class="o">());</span>
<span class="o">}</span></code></pre></figure>
<p>经过第2步,AMS对象就已经构建完毕。构建时,要初始化的内容非常多,大致可以分成两类:</p>
<ul>
<li><strong>监测统计</strong>: Watchdog,CPU、内存、电量的使用统计</li>
<li><strong>组件管理</strong>: broadcastQueues, services, providers, statckSupervisor,recentTasks, Android四大组件的相关信息都由AMS统一维护</li>
</ul>
<p><strong>在第5步中</strong>, 通过AMS对象调用了<strong>setSystemProcess()</strong>函数,目的是为了将当前进程(system_process)设置为系统进程:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">setSystemProcess</span><span class="o">()</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 1. 注册系统服务</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="n">Context</span><span class="o">.</span><span class="na">ACTIVITY_SERVICE</span><span class="o">,</span> <span class="k">this</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="n">ProcessStats</span><span class="o">.</span><span class="na">SERVICE_NAME</span><span class="o">,</span> <span class="n">mProcessStats</span><span class="o">);</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"meminfo"</span><span class="o">,</span> <span class="k">new</span> <span class="n">MemBinder</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"gfxinfo"</span><span class="o">,</span> <span class="k">new</span> <span class="n">GraphicsBinder</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"dbinfo"</span><span class="o">,</span> <span class="k">new</span> <span class="n">DbBinder</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">MONITOR_CPU_USAGE</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"cpuinfo"</span><span class="o">,</span> <span class="k">new</span> <span class="n">CpuBinder</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
<span class="o">}</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"permission"</span><span class="o">,</span> <span class="k">new</span> <span class="n">PermissionController</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
<span class="n">ServiceManager</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="s">"processinfo"</span><span class="o">,</span> <span class="k">new</span> <span class="n">ProcessInfoService</span><span class="o">(</span><span class="k">this</span><span class="o">));</span>
<span class="c1">// 2. 装载应用信息,将ApplicationInfo和ClassLoader设置到LoadedApk中</span>
<span class="n">ApplicationInfo</span> <span class="n">info</span> <span class="o">=</span> <span class="n">mContext</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">().</span><span class="na">getApplicationInfo</span><span class="o">(</span>
<span class="s">"android"</span><span class="o">,</span> <span class="n">STOCK_PM_FLAGS</span><span class="o">);</span>
<span class="n">mSystemThread</span><span class="o">.</span><span class="na">installSystemApplicationInfo</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">getClass</span><span class="o">().</span><span class="na">getClassLoader</span><span class="o">());</span>
<span class="c1">// 3. 初始化进程的ProcessRecord</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="n">ProcessRecord</span> <span class="n">app</span> <span class="o">=</span> <span class="n">newProcessRecordLocked</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">info</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
<span class="n">app</span><span class="o">.</span><span class="na">persistent</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="c1">// 常驻内存</span>
<span class="n">app</span><span class="o">.</span><span class="na">pid</span> <span class="o">=</span> <span class="n">MY_PID</span><span class="o">;</span> <span class="c1">// 当前的PID</span>
<span class="n">app</span><span class="o">.</span><span class="na">maxAdj</span> <span class="o">=</span> <span class="n">ProcessList</span><span class="o">.</span><span class="na">SYSTEM_ADJ</span><span class="o">;</span> <span class="c1">// 在内存不足时,会根据oom_adj值来杀进程</span>
<span class="n">app</span><span class="o">.</span><span class="na">makeActive</span><span class="o">(</span><span class="n">mSystemThread</span><span class="o">.</span><span class="na">getApplicationThread</span><span class="o">(),</span> <span class="n">mProcessStats</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">mPidsSelfLocked</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">app</span><span class="o">.</span><span class="na">pid</span><span class="o">,</span> <span class="n">app</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">updateLruProcessLocked</span><span class="o">(</span><span class="n">app</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// 更新最近使用进程列表</span>
<span class="n">updateOomAdjLocked</span><span class="o">();</span> <span class="c1">// 更新OOM ADJ</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageManager</span><span class="o">.</span><span class="na">NameNotFoundException</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 find android system package"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<ol>
<li>
<p>通过ServiceManager向系统注册一些重要的服务。诸如meminfo、gfxinfo、dbinfo等信息都是由系统进程维护的,
可以通过<code class="highlighter-rouge">adb shell dumpsys</code>命令输出。</p>
</li>
<li>
<p>ApplicationInfo包含了一个应用程序的信息,这些信息从AndroidManifest.xml的<application>标签中解析出来,譬如进程名、版本号、使用的主题等,那么,通过<strong>android</strong>这个包名,获取的ApplicationInfo自然就是Android系统这个应用程序的信息。</p>
<blockquote>
<p>每一个普通的应用程序都会有一个AndroidManifest.xml文件,这个应用程序运行的环境,就是我们所说的”应用进程”;
有一个特殊的应用程序,具备更多的特权,这个应用程序的运行环境就是”系统进程”。
<strong>android</strong>就是这个特殊应用程序的包名,其所对应的<application>标签定义在<a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/res/AndroidManifest.xml">frameworks/base/core/res/AndroidManifest.xml</a>中,最终会编译在<strong>framework-res.apk</strong>中。</p>
</blockquote>
<p>不论是应用进程还是系统进程,都有主线程,ActivityThread就是Android定义的主线程类。
AMS中的<strong>mSystemThread</strong>对象就是ActivityThread的实例,表示这是系统进程的主线程。
通过<strong>mSystemThread.installSystemApplicationInfo()</strong>这个函数调用,ApplicationInfo和ClassLoader就被设置到了
LoadedApk中,ApplicationInfo与LoadedApk的关系我们后文再描述。</p>
</li>
<li>
<p>ProcessRecord这个类用于描述一个运行的进程,AMS管理着所有ProcessRecord的状态,包括系统进程的ProcessRecord。
系统进程的ProcessRecord几个重要的属性值:</p>
<ul>
<li><strong>persistent=true</strong>: 系统进程的ProcessRecord是常驻内存的</li>
<li><strong>maxAdj=SYSTEM_ADJ(-16)</strong>: 在内存不足时,这个值越小,存活的几率就越大。SYSTEM_ADJ已经是倒数第二小了,可见系统进程在内存不足时被杀的可能性极小。</li>
<li><strong>active</strong>: 在上文中,我们介绍过ProcessRecord有Active和InActive两种状态,所谓”激活”,就是将应用程序的信息(IApplicationThread)绑定到进程,这样就能够通过ProcessRecord<strong>间接</strong>完成对进程的调度。</li>
</ul>
<p>AMS通过<strong>mPidSelfLocked</strong>这个映射表来记录所有的ProcessRecord,(键 => 值)关系是(PID => ProcessRecord)。
在创建一个ProcessRecord之后,就需要集中对进程的信息进行调整了,AMS中管理进程的函数就两类:<strong>updateLruProcessLocked()</strong>用于更新最近使用进程列表,<strong>updateOomAdjLocked()</strong>用户更新进程的OOM ADJ。具体的调用场景和实现,留到后文分析。</p>
</li>
</ol>
<h2 id="23-ams启动的配套工作">2.3 AMS启动的配套工作</h2>
<p>在引导服务(BootstrapServices)启动完毕后,SystemServer就开始启动核心服务(CoreServices),包括电池服务(BatteryService),用户行为统计服务(UsageStatsService)等; 最后,就是启动其他服务了,非常之多,不再此列举。部分服务在启动时,仍需要与AMS关联,譬如: AMS需要与WindowManagerService关联。AMS在这个过程之中有两个关键的步骤:</p>
<ol>
<li><strong>AMS.installSystemProviders()</strong>: 表示要装载SettingsProvider, 很多系统服务都需要从这个数据库中读取配置信息;</li>
<li><strong>AMS.systemReady()</strong>: 表示当前SystemServer已经启动完毕, AMS仍需要做一些准备就绪工作。</li>
</ol>
<p>这两个步骤牵扯到的逻辑非常之庞大,我们现在深入分析之。</p>
<h3 id="231-installsystemproviders">2.3.1 installSystemProviders</h3>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">installSystemProviders</span><span class="o">()</span> <span class="o">{</span>
<span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</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="c1">// 获取系统进程的ProcessRecord, 之前setSystemProcess()时,</span>
<span class="c1">// 会基于framework-res.apk的AndroidManifest.xml新建一个ProcessRecord,</span>
<span class="c1">// 此处,获取的ProcessRecord就是它</span>
<span class="n">ProcessRecord</span> <span class="n">app</span> <span class="o">=</span> <span class="n">mProcessNames</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"system"</span><span class="o">,</span> <span class="n">Process</span><span class="o">.</span><span class="na">SYSTEM_UID</span><span class="o">);</span>
<span class="c1">// 生成ProviderInfo的列表</span>
<span class="n">providers</span> <span class="o">=</span> <span class="n">generateApplicationProvidersLocked</span><span class="o">(</span><span class="n">app</span><span class="o">);</span>
<span class="c1">// 剔除一些非系统程序的Provider</span>
<span class="k">if</span> <span class="o">(</span><span class="n">providers</span> <span class="o">!=</span> <span class="kc">null</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">i</span><span class="o">=</span><span class="n">providers</span><span class="o">.</span><span class="na">size</span><span class="o">()-</span><span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">>=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">--)</span> <span class="o">{</span>
<span class="n">ProviderInfo</span> <span class="n">pi</span> <span class="o">=</span> <span class="o">(</span><span class="n">ProviderInfo</span><span class="o">)</span><span class="n">providers</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="k">if</span> <span class="o">((</span><span class="n">pi</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">flags</span><span class="o">&</span><span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">providers</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 将Provider安装到系统进程中</span>
<span class="k">if</span> <span class="o">(</span><span class="n">providers</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mSystemThread</span><span class="o">.</span><span class="na">installSystemProviders</span><span class="o">(</span><span class="n">providers</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mCoreSettingsObserver</span> <span class="o">=</span> <span class="k">new</span> <span class="n">CoreSettingsObserver</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>以上有两个关键的函数,我们再进一步展开分析:</p>
<font size="3"><b>关键函数: generateApplicationProvidersLocked()</b></font>
<p>该函数基于AndroidManifest.xml文件的定义, 生成一个应用程序的Provider信息, 以方便AMS对Provider进行管理, 函数的逻辑如下所示:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">final</span> <span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="nf">generateApplicationProvidersLocked</span><span class="o">(</span><span class="n">ProcessRecord</span> <span class="n">app</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 1. 获取指定processName和uid中的Provider信息</span>
<span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">ParceledListSlice</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">slice</span> <span class="o">=</span> <span class="n">AppGlobals</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">().</span>
<span class="n">queryContentProviders</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">app</span><span class="o">.</span><span class="na">uid</span><span class="o">,</span>
<span class="n">STOCK_PM_FLAGS</span> <span class="o">|</span> <span class="n">PackageManager</span><span class="o">.</span><span class="na">GET_URI_PERMISSION_PATTERNS</span><span class="o">);</span>
<span class="n">providers</span> <span class="o">=</span> <span class="n">slice</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">slice</span><span class="o">.</span><span class="na">getList</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
<span class="c1">// 2. 将Provider和ProcessRecord绑定</span>
<span class="kt">int</span> <span class="n">userId</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="k">if</span> <span class="o">(</span><span class="n">providers</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 确保ProcessRecord中的Provider映射表的容量</span>
<span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="n">providers</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="n">app</span><span class="o">.</span><span class="na">pubProviders</span><span class="o">.</span><span class="na">ensureCapacity</span><span class="o">(</span><span class="n">N</span> <span class="o">+</span> <span class="n">app</span><span class="o">.</span><span class="na">pubProviders</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="c1">// 遍历ProviderInfo列表</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">N</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">ProviderInfo</span> <span class="n">cpi</span> <span class="o">=</span> <span class="o">(</span><span class="n">ProviderInfo</span><span class="o">)</span><span class="n">providers</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="kt">boolean</span> <span class="n">singleton</span> <span class="o">=</span> <span class="n">isSingleton</span><span class="o">(</span><span class="n">cpi</span><span class="o">.</span><span class="na">processName</span><span class="o">,</span> <span class="n">cpi</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">,</span>
<span class="n">cpi</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">cpi</span><span class="o">.</span><span class="na">flags</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">singleton</span> <span class="o">&&</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">getUserId</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="o">!=</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">)</span> <span class="o">{</span>
<span class="n">providers</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="n">N</span><span class="o">--;</span>
<span class="n">i</span><span class="o">--;</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 如果mProviderMap中不存在ContentProviderRecord对象,则新建一个</span>
<span class="n">ComponentName</span> <span class="n">comp</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ComponentName</span><span class="o">(</span><span class="n">cpi</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">cpi</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="n">ContentProviderRecord</span> <span class="n">cpr</span> <span class="o">=</span> <span class="n">mProviderMap</span><span class="o">.</span><span class="na">getProviderByClass</span><span class="o">(</span><span class="n">comp</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">cpr</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">cpr</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ContentProviderRecord</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">cpi</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">comp</span><span class="o">,</span> <span class="n">singleton</span><span class="o">);</span>
<span class="n">mProviderMap</span><span class="o">.</span><span class="na">putProviderByClass</span><span class="o">(</span><span class="n">comp</span><span class="o">,</span> <span class="n">cpr</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">app</span><span class="o">.</span><span class="na">pubProviders</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">cpi</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">cpr</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">cpi</span><span class="o">.</span><span class="na">multiprocess</span> <span class="o">||</span> <span class="o">!</span><span class="s">"android"</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">cpi</span><span class="o">.</span><span class="na">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">app</span><span class="o">.</span><span class="na">addPackage</span><span class="o">(</span><span class="n">cpi</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">cpi</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">.</span><span class="na">versionCode</span><span class="o">,</span> <span class="n">mProcessStats</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 对APK进行dex优化</span>
<span class="n">ensurePackageDexOpt</span><span class="o">(</span><span class="n">cpi</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="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">providers</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li>
<p><strong>第1步</strong>, 指定processName为”system”, uid为”Process.SYSTEM_UID(1000)”, 通过PackageManager,可以获取到运行在系统进程中的所有Provider的信息。这里获取的结果是一个元素为ProviderInfo类型的列表。一般而言,以下Provider会出现在返回结果中:</p>
<ul>
<li>framework-res.apk中的Provider, 定义在frameworks/base/core/res/AndroidManifest.xml</li>
<li>SettingsProvider.apk中的Provider, 定义在frameworks/base/packages/SettingsProvider/AndroidManifest.xml</li>
</ul>
</li>
</ul>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c"><!-- frameworks/base/core/res/AndroidManifest.xml片段 --></span>
<span class="nt"><provider</span> <span class="na">android:name=</span><span class="s">"com.android.server.am.DumpHeapProvider"</span>
<span class="na">android:authorities=</span><span class="s">"com.android.server.heapdump"</span>
<span class="na">android:grantUriPermissions=</span><span class="s">"true"</span>
<span class="na">android:multiprocess=</span><span class="s">"false"</span>
<span class="na">android:singleUser=</span><span class="s">"true"</span> <span class="nt">/></span></code></pre></figure>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="c"><!-- frameworks/base/packages/SettingsProvider/AndroidManifest.xml --></span>
<span class="nt"><manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">package=</span><span class="s">"com.android.providers.settings"</span>
<span class="na">coreApp=</span><span class="s">"true"</span>
<span class="na">android:sharedUserId=</span><span class="s">"android.uid.system"</span><span class="nt">></span> 表示UID为SYSTEM_UID
<span class="nt"><application</span> <span class="na">android:allowClearUserData=</span><span class="s">"false"</span>
<span class="na">android:label=</span><span class="s">"@string/app_label"</span>
<span class="na">android:process=</span><span class="s">"system"</span> <span class="err">表示运行在系统进程之中</span>
<span class="na">android:backupAgent=</span><span class="s">"SettingsBackupAgent"</span>
<span class="na">android:killAfterRestore=</span><span class="s">"false"</span>
<span class="na">android:icon=</span><span class="s">"@mipmap/ic_launcher_settings"</span><span class="nt">></span>
<span class="nt"><provider</span> <span class="na">android:name=</span><span class="s">"SettingsProvider"</span> <span class="na">android:authorities=</span><span class="s">"settings"</span>
<span class="na">android:multiprocess=</span><span class="s">"false"</span>
<span class="na">android:exported=</span><span class="s">"true"</span>
<span class="na">android:singleUser=</span><span class="s">"true"</span>
<span class="na">android:initOrder=</span><span class="s">"100"</span> <span class="nt">/></span>
<span class="nt"></application></span>
<span class="nt"></manifest></span></code></pre></figure>
<ul>
<li><strong>第2步</strong>, 通过PackageManager获取到的ProviderInfo只是一个静态的信息, 还需要绑定到具体的ProcessRecord。
要理解这个绑定关系,需要先了解AMS对Provider的管理方式:</li>
</ul>
<div align="center"><img src="/assets/images/systemservice/amslaunching/4-amslaunching-provider-classes-diagram.png" alt="Provider Classes Diagram" /></div>
<ul>
<li>
<p>AMS中使用ContentProviderRecord来管理一个ContentProvider。ProviderInfo, ApplicationInfo, 运行ContentProvider的ProcessRecord等信息都保存在ContentProviderRecord中。</p>
</li>
<li>
<p>AMS维护了一个ProviderMap, 支持从Authority或CompnentName到ContentProviderRecord的映射; AMS也维护了各种各样的ProcessRecord, 譬如: 前台进程, 内存常驻进程, 最近使用的进程等。 当一个ContentProvider绑定到具体的进程时, 就会添加到ProcessRecord中维护的mPubProviders的映射表中。所以, ProcessRecord.mPubProviders就表示进程所拥有的ContentProvider。</p>
</li>
</ul>
<p>现在,我们再来看这块函数的逻辑:</p>
<ul>
<li>
<p>首先, 需要确保ProcessRecord能够容纳一定数量的Provider, 前面通过PackageManager找到的ProviderInfo可能会关联到ProcessRecord中,所以, 在mPubProvider上已有容量的基础上, 再扩容的大小为找到的ProviderInfo的数量。</p>
</li>
<li>
<p>然后, 对找到的ProviderInfo列表进行遍历, 如有需要, 则新建一个ContentProviderRecord对象, 将其添加到mProviderMap中以方便AMS管理;同时, 也需要将其添加到PrcoessRecord.mPubProviders中。</p>
</li>
<li>
<p>最后, 由于可能新增其他APK中的ProviderInfo, 所以需要确保对APK进行dex优化。</p>
</li>
</ul>
<font size="3"><b>关键函数: ActivityThread.installSystemProviders()</b></font>
<p>将一个ProviderInfo绑定到ProcessRecord后, AMS中就有了Provider的信息了, 但这时Provider还不能工作, 因为真正的ContentProvider还未创建, 该函数的就是将上面找到的ProviderInfo装载到系统进程之中, 函数的逻辑如下:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">installSystemProviders</span><span class="o">(</span><span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">providers</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 在前文创建ActivityThread并attch的时候,就见过mInitialApplication</span>
<span class="c1">// 它是什么呢? 请读者自行回顾前文。</span>
<span class="n">installContentProviders</span><span class="o">(</span><span class="n">mInitialApplication</span><span class="o">,</span> <span class="n">providers</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">installContentProviders</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span> <span class="n">List</span><span class="o"><</span><span class="n">ProviderInfo</span><span class="o">></span> <span class="n">providers</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 待填充的ContentProviderHolder列表</span>
<span class="kd">final</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span><span class="o">></span> <span class="n">results</span> <span class="o">=</span>
<span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span><span class="o">>();</span>
<span class="c1">// 对ProviderInfo列表进行遍历, 调用的installProvider()函数逻辑下文展开分析</span>
<span class="k">for</span> <span class="o">(</span><span class="n">ProviderInfo</span> <span class="n">cpi</span> <span class="o">:</span> <span class="n">providers</span><span class="o">)</span> <span class="o">{</span>
<span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span> <span class="n">cph</span> <span class="o">=</span> <span class="n">installProvider</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">cpi</span><span class="o">,</span>
<span class="kc">false</span> <span class="cm">/*noisy*/</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/*noReleaseNeeded*/</span><span class="o">,</span> <span class="kc">true</span> <span class="cm">/*stable*/</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">cph</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">cph</span><span class="o">.</span><span class="na">noReleaseNeeded</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">results</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">cph</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// 向AMS注册ContentProvider</span>
<span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</span><span class="o">().</span><span class="na">publishContentProviders</span><span class="o">(</span>
<span class="n">getApplicationThread</span><span class="o">(),</span> <span class="n">results</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">RemoteException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>所有ContentProvider的创建都需要经过<strong>installContentProvider()</strong>函数, 它接收两个参数, 一个是进程的运行环境Context, 一个是
ProviderInfo列表; 对系统进程而言, 运行环境就是mInitialApplication。该函数中使用了ContentProviderHodler这个类,它实现了Parcelable接口, 通常表示这类数据结构需要跨进程传递, 应用进程中生成的ContentProvider需要向系统进程注册后才能使用, 所以, 需要将ContentProvider的信息从应用进程传递到系统进程, 这就用到了ContentProviderHolder进行数据封装。</p>
<p>基于ProviderInfo生成的ContentProviderHolder的函数实现是<strong>installProvider()</strong>:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span> <span class="nf">installProvider</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">,</span>
<span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span> <span class="n">holder</span><span class="o">,</span> <span class="n">ProviderInfo</span> <span class="n">info</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">noisy</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">noReleaseNeeded</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">stable</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 本例中传入的context为mInitialApplication, 表示系统进程的运行环境;</span>
<span class="c1">// holder为null, 表示需要创建新的Hodler; info为系统进程中的ProviderInfo</span>
<span class="c1">// noReleaseNeeded表示</span>
<span class="n">ContentProvider</span> <span class="n">localProvider</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">IContentProvider</span> <span class="n">provider</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">holder</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">holder</span><span class="o">.</span><span class="na">provider</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Context</span> <span class="n">c</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">ApplicationInfo</span> <span class="n">ai</span> <span class="o">=</span> <span class="n">info</span><span class="o">.</span><span class="na">applicationInfo</span><span class="o">;</span>
<span class="c1">// 1. 找到ProviderInfo对应的Context</span>
<span class="k">if</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="na">equals</span><span class="o">(</span><span class="n">ai</span><span class="o">.</span><span class="na">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">context</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">mInitialApplication</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span>
<span class="n">mInitialApplication</span><span class="o">.</span><span class="na">getPackageName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">ai</span><span class="o">.</span><span class="na">packageName</span><span class="o">))</span> <span class="o">{</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">mInitialApplication</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="n">c</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">createPackageContext</span><span class="o">(</span><span class="n">ai</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">Context</span><span class="o">.</span><span class="na">CONTEXT_INCLUDE_CODE</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">PackageManager</span><span class="o">.</span><span class="na">NameNotFoundException</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">if</span> <span class="o">(</span><span class="n">c</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ... 省略日志输出</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 2. 根据包名,反射创建新的ContentProvider对象</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">c</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">();</span>
<span class="n">localProvider</span> <span class="o">=</span> <span class="o">(</span><span class="n">ContentProvider</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">info</span><span class="o">.</span><span class="na">name</span><span class="o">).</span><span class="na">newInstance</span><span class="o">();</span>
<span class="n">provider</span> <span class="o">=</span> <span class="n">localProvider</span><span class="o">.</span><span class="na">getIContentProvider</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">provider</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">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">localProvider</span><span class="o">.</span><span class="na">attachInfo</span><span class="o">(</span><span class="n">c</span><span class="o">,</span> <span class="n">info</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">Exception</span> <span class="n">e</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">else</span> <span class="o">{</span>
<span class="n">provider</span> <span class="o">=</span> <span class="n">holder</span><span class="o">.</span><span class="na">provider</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 3. 设置ConentProviderHolder</span>
<span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span> <span class="n">retHolder</span><span class="o">;</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">mProviderMap</span><span class="o">)</span> <span class="o">{</span>
<span class="n">IBinder</span> <span class="n">jBinder</span> <span class="o">=</span> <span class="n">provider</span><span class="o">.</span><span class="na">asBinder</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">localProvider</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ComponentName</span> <span class="n">cname</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ComponentName</span><span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">packageName</span><span class="o">,</span> <span class="n">info</span><span class="o">.</span><span class="na">name</span><span class="o">);</span>
<span class="n">ProviderClientRecord</span> <span class="n">pr</span> <span class="o">=</span> <span class="n">mLocalProvidersByName</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">cname</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pr</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">provider</span> <span class="o">=</span> <span class="n">pr</span><span class="o">.</span><span class="na">mProvider</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 创建一个新的ContentClientProvider</span>
<span class="n">holder</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IActivityManager</span><span class="o">.</span><span class="na">ContentProviderHolder</span><span class="o">(</span><span class="n">info</span><span class="o">);</span>
<span class="n">holder</span><span class="o">.</span><span class="na">provider</span> <span class="o">=</span> <span class="n">provider</span><span class="o">;</span>
<span class="n">holder</span><span class="o">.</span><span class="na">noReleaseNeeded</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">pr</span> <span class="o">=</span> <span class="n">installProviderAuthoritiesLocked</span><span class="o">(</span><span class="n">provider</span><span class="o">,</span> <span class="n">localProvider</span><span class="o">,</span> <span class="n">holder</span><span class="o">);</span>
<span class="n">mLocalProviders</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">jBinder</span><span class="o">,</span> <span class="n">pr</span><span class="o">);</span>
<span class="n">mLocalProvidersByName</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">cname</span><span class="o">,</span> <span class="n">pr</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">retHolder</span> <span class="o">=</span> <span class="n">pr</span><span class="o">.</span><span class="na">mHolder</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">ProviderRefCount</span> <span class="n">prc</span> <span class="o">=</span> <span class="n">mProviderRefCountMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">jBinder</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">prc</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">noReleaseNeeded</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">ProviderClientRecord</span> <span class="n">client</span> <span class="o">=</span> <span class="n">installProviderAuthoritiesLocked</span><span class="o">(</span>
<span class="n">provider</span><span class="o">,</span> <span class="n">localProvider</span><span class="o">,</span> <span class="n">holder</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">noReleaseNeeded</span><span class="o">)</span> <span class="o">{</span>
<span class="n">prc</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ProviderRefCount</span><span class="o">(</span><span class="n">holder</span><span class="o">,</span> <span class="n">client</span><span class="o">,</span> <span class="mi">1000</span><span class="o">,</span> <span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">prc</span> <span class="o">=</span> <span class="n">stable</span>
<span class="o">?</span> <span class="k">new</span> <span class="n">ProviderRefCount</span><span class="o">(</span><span class="n">holder</span><span class="o">,</span> <span class="n">client</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span>
<span class="o">:</span> <span class="k">new</span> <span class="n">ProviderRefCount</span><span class="o">(</span><span class="n">holder</span><span class="o">,</span> <span class="n">client</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mProviderRefCountMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">jBinder</span><span class="o">,</span> <span class="n">prc</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">retHolder</span> <span class="o">=</span> <span class="n">prc</span><span class="o">.</span><span class="na">holder</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">retHolder</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<ol>
<li>
<p>找到ProviderInfo对应的Context:</p>
<ul>
<li>对于framework-res.apk中定义的com.android.server.am.DumpHeapProvider而言, 重新设置的Context就是mInitialApplication</li>
<li>对于SettingProvider.apk中定义的SettingsProvider而言, 它的包名为com.android.providers.settings, 不等于mInitialApplication的包名android, 所以, 会通过Context.createPackageContext()函数创建一个新的Context</li>
</ul>
</li>
<li>
<p>反射创建ContentProvider对象, 之所以前面要找到ProviderInfo的Context, 就是因为只有与ContentProvider对应的Context才能从APK中加载Java字节码进行反射。在ContentProvider创建好后, 会调用ContentProvider.attach()函数, 其内部进行一些初始化操作后, 会调用ContentProvider.onCreate()函数, 这样以来, ContentProvider就进入其生命周期了。</p>
</li>
<li>
<p>设置CotentProviderHolder。要理解这一段逻辑, 需要先理解ContentProvider的管理机制. 在<a href="">Android四大组件之ContentProvider</a>一文中有详细的介绍, 在此, 我们只需要了解这里会找到ContentProviderHolder, 拿到这个数据结构后, 就能向AMS申报Provider可以使用了。</p>
</li>
</ol>
<p><strong>至此, installSystemProviders()函数的逻辑已经分析完毕,该函数的功能就是装载运行在系统进程中的Provider,诸如SettingsProvider这一类需要被很多其他系统服务用到的Provider,都将在这一步被装载。</strong></p>
<h3 id="232-systemready">2.3.2 systemReady</h3>
<p>该函数的逻辑比较庞大,我们按序分成几个片段分析。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段1: 主要处理PRE_BOOT_COMPLETED广播</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="n">Runnable</span> <span class="n">goingCallback</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="c1">// 1. 判断系统进程是否已经准备完毕,如果已经准备完毕,则调用goingCallback后返回</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mSystemReady</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">goingCallback</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">goingCallback</span><span class="o">.</span><span class="na">run</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">mLocalDeviceIdleController</span>
<span class="o">=</span> <span class="n">LocalServices</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="n">DeviceIdleController</span><span class="o">.</span><span class="na">LocalService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="c1">// Make sure we have the current profile info, since it is needed for</span>
<span class="c1">// security checks.</span>
<span class="n">updateCurrentProfileIdsLocked</span><span class="o">();</span>
<span class="c1">// 2. 恢复最近任务列表</span>
<span class="n">mRecentTasks</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="n">mRecentTasks</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">mTaskPersister</span><span class="o">.</span><span class="na">restoreTasksLocked</span><span class="o">());</span>
<span class="n">mRecentTasks</span><span class="o">.</span><span class="na">cleanupLocked</span><span class="o">(</span><span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_ALL</span><span class="o">);</span>
<span class="n">mTaskPersister</span><span class="o">.</span><span class="na">startPersisting</span><span class="o">();</span>
<span class="c1">// 3. 处理PRE_BOOT_COMPLETED广播</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mDidUpdate</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mWaitingUpdate</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">ComponentName</span><span class="o">></span> <span class="n">doneReceivers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">ComponentName</span><span class="o">>();</span>
<span class="c1">// 派发PRE_BOOT_COMPLETED广播, 函数参数Runnable是一个回调,当最后一个广播接收者处理完该广播后,</span>
<span class="c1">// 则进入Runnable执行。</span>
<span class="n">mWaitingUpdate</span> <span class="o">=</span> <span class="n">deliverPreBootCompleted</span><span class="o">(</span><span class="k">new</span> <span class="n">Runnable</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="kd">synchronized</span> <span class="o">(</span><span class="n">ActivityManagerService</span><span class="o">.</span><span class="na">this</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mDidUpdate</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">showBootMessage</span><span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getText</span><span class="o">(</span>
<span class="n">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">android_upgrading_complete</span><span class="o">),</span>
<span class="kc">false</span><span class="o">);</span>
<span class="n">writeLastDonePreBootReceivers</span><span class="o">(</span><span class="n">doneReceivers</span><span class="o">);</span>
<span class="n">systemReady</span><span class="o">(</span><span class="n">goingCallback</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span> <span class="n">doneReceivers</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">USER_OWNER</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mWaitingUpdate</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">mDidUpdate</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">mAppOpsService</span><span class="o">.</span><span class="na">systemReady</span><span class="o">();</span>
<span class="n">mSystemReady</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">// END of synchronized</span>
<span class="c1">// 未完接片段2</span></code></pre></figure>
<ol>
<li>
<p>mSystemReady表示系统进程是否准备完毕, 由于systemReady()函数会被多次调用到,而且多线程并行,所以一旦mSystemReady为true,表示不再需要执行下面的逻辑了,直接回调函数入参goingCallback,这个回调函数我们放到后面分析;</p>
</li>
<li>
<p>进入这一步,表示mSystemReady为false,第一次进入systemReady()函数时,就是这种场景。这里会恢复最近任务列表;</p>
</li>
<li>
<p>涉及到PRE_BOOT_COMPLETED广播的处理。这个广播在BOOT_COMPLETED广播之前发送,而且只发给系统应用。系统应用收到该广播后,也应该标注已经处理过该广播,下次不用再派发过来。设计PRE_BOOT_COMPLETED广播的目的,是为了应对系统升级的场景:当从旧的版本升级时,系统应用可能有一些清除数据的需要,系统升级后的第一次起机时,就会向接收者派发这个广播。</p>
</li>
</ol>
<p>PRE_BOOT_COMPLETED的派发实现在deliverPreBootCompleted()函数中,本文不展开分析。需要知道这里有两个控制变量:</p>
<ul>
<li>mDidUpdate: 默认为false; 如果为true,表示已PRE_BOOT_COMPLETED已经处理完毕,确切的说是已经检查完毕,因为在派发该广播之前,要检查是否已经向接收者派发过一次该广播了; 之所以该变量取名为update,是因为该广播的设计与系统升级后的操作有关;</li>
<li>mWaitingUpdate: 默认为false; 如果有PRE_BOOT_COMPLETED的接收者,而且之前没有处理过该广播,则这个变量会被true,直到广播处理完成后才被重新置成false。</li>
</ul>
<p>经过这3步mSystemReady就被设置为true了,再次调用该systemReady()函数,就会进入第1步的逻辑。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段2: 杀掉在AMS准备就绪之前就已经启动的进程</span>
<span class="n">ArrayList</span><span class="o"><</span><span class="n">ProcessRecord</span><span class="o">></span> <span class="n">procsToKill</span> <span class="o">=</span> <span class="kc">null</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="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="n">mPidsSelfLocked</span><span class="o">.</span><span class="na">size</span><span class="o">()-</span><span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">>=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">--)</span> <span class="o">{</span>
<span class="n">ProcessRecord</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">mPidsSelfLocked</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isAllowedWhileBooting</span><span class="o">(</span><span class="n">proc</span><span class="o">.</span><span class="na">info</span><span class="o">)){</span>
<span class="k">if</span> <span class="o">(</span><span class="n">procsToKill</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">procsToKill</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">ProcessRecord</span><span class="o">>();</span>
<span class="o">}</span>
<span class="n">procsToKill</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">proc</span><span class="o">);</span>
<span class="o">}</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="k">if</span> <span class="o">(</span><span class="n">procsToKill</span> <span class="o">!=</span> <span class="kc">null</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">i</span><span class="o">=</span><span class="n">procsToKill</span><span class="o">.</span><span class="na">size</span><span class="o">()-</span><span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">>=</span><span class="mi">0</span><span class="o">;</span> <span class="n">i</span><span class="o">--)</span> <span class="o">{</span>
<span class="n">ProcessRecord</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">procsToKill</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="n">removeProcessLocked</span><span class="o">(</span><span class="n">proc</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="s">"system update done"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 表示AMS已经准备完毕,可以启动其他进程了</span>
<span class="n">mProcessesReady</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">EventLog</span><span class="o">.</span><span class="na">writeEvent</span><span class="o">(</span><span class="n">EventLogTags</span><span class="o">.</span><span class="na">BOOT_PROGRESS_AMS_READY</span><span class="o">,</span>
<span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">());</span>
<span class="c1">// 未完接片段3</span></code></pre></figure>
<p>在AMS完全准备就绪之前,就可能有一些进程已经启动,在这里需要进行一个检查,如果非peristent的进程先于AMS启动,那么就需要杀掉这些进程。
注意,这里所指的进程是AMS管理的进程(系统进程和应用进程),Native进程并不在AMS的管辖范围。</p>
<p>在该代码片段的最后,输出了一行Event Log:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>boot_process_ams_ready: xxx
</code></pre></div></div>
<p>进行日志分析时,可以根据这行日志信息判定AMS已经准备就绪,其他应用进程可以启动了。因为只有当AMS就绪后,才能开始管理应用进程。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段3: 读取SettingProvider、urigrants.xml等配置信息</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="k">if</span> <span class="o">(</span><span class="n">mFactoryTest</span> <span class="o">==</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_LOW_LEVEL</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略测试相关的代码</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// 从SettingsProvider中读取一些设置项</span>
<span class="n">retrieveSettings</span><span class="o">();</span>
<span class="c1">// 加载与多任务显示相关的资源</span>
<span class="n">loadResourcesOnSystemReady</span><span class="o">();</span>
<span class="c1">// 从/data/system/urigrants.xml文件中读取URI相关的权限信息</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="n">readGrantedUriPermissionsLocked</span><span class="o">();</span>
<span class="o">}</span>
<span class="c1">// 关键调用</span>
<span class="k">if</span> <span class="o">(</span><span class="n">goingCallback</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">goingCallback</span><span class="o">.</span><span class="na">run</span><span class="o">();</span>
<span class="c1">// 未完接片段4</span></code></pre></figure>
<p>在AMS完全准备完毕后,就可以从SettingsProvider中读取一些配置信息了,以上代码片段中,最重要的就是调用传入的goingCallback.run()函数:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><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="n">Slog</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"Making services ready"</span><span class="o">);</span>
<span class="c1">// 通知所有的系统服务进入PHASE_ACTIVITY_MANAGER_READY阶段</span>
<span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startBootPhase</span><span class="o">(</span>
<span class="n">SystemService</span><span class="o">.</span><span class="na">PHASE_ACTIVITY_MANAGER_READY</span><span class="o">);</span>
<span class="c1">// 设置NativeCrash的监听器</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="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="c1">// 启动SystemUi</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">startSystemUi</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="c1">// NetScorce系统服务进入systemReady状态</span>
<span class="k">try</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">networkScoreF</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="n">networkScoreF</span><span class="o">.</span><span class="na">systemReady</span><span class="o">();</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">Throwable</span> <span class="n">e</span><span class="o">)</span> <span class="o">{...}</span>
<span class="o">...</span> <span class="c1">// 省略其他系统服务进入systemReady状态</span>
<span class="o">}</span></code></pre></figure>
<p>这个函数以回调的形式,在AMS准备就绪后被调用。AMS准备就绪是个关键节点,在此之后,很多其他服务就可以开始进入准备就绪的状态了,其实就是调用这些系统服务的systemReady()函数。</p>
<p>以上代码片段,还有一个关键事件就是启动SystemUi,其实就是启动com.androi.systemui这个包(对应的APK是SystemUI.apk)中的一个服务。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">static</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">startSystemUi</span><span class="o">(</span><span class="n">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Intent</span> <span class="n">intent</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Intent</span><span class="o">();</span>
<span class="n">intent</span><span class="o">.</span><span class="na">setComponent</span><span class="o">(</span><span class="k">new</span> <span class="n">ComponentName</span><span class="o">(</span><span class="s">"com.android.systemui"</span><span class="o">,</span>
<span class="s">"com.android.systemui.SystemUIService"</span><span class="o">));</span>
<span class="n">context</span><span class="o">.</span><span class="na">startServiceAsUser</span><span class="o">(</span><span class="n">intent</span><span class="o">,</span> <span class="n">UserHandle</span><span class="o">.</span><span class="na">OWNER</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>回调完goingCallback.run()函数,AMS.systemReady()并没有就此结束,它还没有完成自己的使命。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="c1">// 片段4: 通知用户以进入运行状态并启动桌面</span>
<span class="c1">// 通知电量统计服务:当前用户已经启动运行了</span>
<span class="n">mBatteryStatsService</span><span class="o">.</span><span class="na">noteEvent</span><span class="o">(</span><span class="n">BatteryStats</span><span class="o">.</span><span class="na">HistoryItem</span><span class="o">.</span><span class="na">EVENT_USER_RUNNING_START</span><span class="o">,</span>
<span class="n">Integer</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">mCurrentUserId</span><span class="o">),</span> <span class="n">mCurrentUserId</span><span class="o">);</span>
<span class="n">mBatteryStatsService</span><span class="o">.</span><span class="na">noteEvent</span><span class="o">(</span><span class="n">BatteryStats</span><span class="o">.</span><span class="na">HistoryItem</span><span class="o">.</span><span class="na">EVENT_USER_FOREGROUND_START</span><span class="o">,</span>
<span class="n">Integer</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">mCurrentUserId</span><span class="o">),</span> <span class="n">mCurrentUserId</span><span class="o">);</span>
<span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startUser</span><span class="o">(</span><span class="n">mCurrentUserId</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="k">if</span> <span class="o">(</span><span class="n">mFactoryTest</span> <span class="o">!=</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_LOW_LEVEL</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span> <span class="c1">// 省略测试相关的代码</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="n">mBooting</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="c1">// 关键调用1: 启动桌面</span>
<span class="n">startHomeActivityLocked</span><span class="o">(</span><span class="n">mCurrentUserId</span><span class="o">,</span> <span class="s">"systemReady"</span><span class="o">);</span>
<span class="o">..</span> <span class="c1">// 省略与多用户相关的广播发送</span>
<span class="c1">// 关键调用2: 将桌面Activity迁移到Resumed的状态</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">resumeTopActivitiesLocked</span><span class="o">();</span>
<span class="c1">// 发送用户切换广播</span>
<span class="n">sendUserSwitchBroadcastsLocked</span><span class="o">(-</span><span class="mi">1</span><span class="o">,</span> <span class="n">mCurrentUserId</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// END of systemRead()</span></code></pre></figure>
<p>以上代码片段是systemReady()函数的最后部分,一部分工作是发送通知:“当前用户已经进入使用状态了”; 另一部分工作就是启动桌面,有两处关键调用: <strong>startHomeActivityLocked()</strong>和<strong>mStackSupervisor.resumeTopActivitiesLocked()</strong>,在<a href="">Activity启动过程</a>一文中,我们再详细分析涉及Activity启动的一系列函数。</p>
<p><strong>至此,systemReady()函数的执行逻辑已经分析完了,一共经历了4个步骤</strong></p>
<ul>
<li>片段1: 主要处理PRE_BOOT_COMPLETED广播;</li>
<li>片段2: 杀掉在AMS准备就绪之前就已经启动的进程。因为这些进程要被AMS管理起来,需要在AMS准备就绪之后才启动;</li>
<li>片段3: 主要任务是通知其他系统服务进入就绪状态。在AMS就绪完毕后,从SettingsProvider和本地文件中(urigrants.xml)读取一些配置信息。通知其他系统服务进入就绪状态,启动SystemUi;</li>
<li>片段4: 主要任务是启动桌面。</li>
</ul>
<p>systemReady()函数调用完成之后,桌面就可见了,用户就真正见到了Android系统的可操作界面。想必,各位读者心中还有一些困惑,不是开机会发送ACTION_BOOT_COMPLETED广播吗,桌面都已经启动了,怎么一直都没见到这个广播在哪发送?下面就给大家解惑。</p>
<h3 id="233-发送action_boot_completed广播">2.3.3 发送ACTION_BOOT_COMPLETED广播</h3>
<p>在桌面启动后,桌面进程主线程的消息队列进入空闲状态,此时会发起跨进程调用AMS.activityIdle(),紧接着会引发下面的调用关系:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AMS.activityIdle()
└── ActivityStackSupervisor.activityIdleInternalLocked()
└── ActivityStackSupervisor.checkFinishBootingLocked()
└── AMS.postFinishBooting() // 向主线程抛出FINISH_BOOTING_MSG消息
└── AMS.MainHandler.handleMessage(FINISH_BOOTING_MSG)
└── AMS.finishBooting()
</code></pre></div></div>
<p>我们来看这一轮调用的落脚点AMS.finishBooting()函数:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">finishBooting</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// 如果开机动画没有显示完,则直接退出,等开机动画显示完后再调用</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="k">if</span> <span class="o">(!</span><span class="n">mBootAnimationComplete</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCallFinishBooting</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">mCallFinishBooting</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// ABI检查。ABI是Application Binary Interface的简称,与CPU的指令集相关。</span>
<span class="n">ArraySet</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">completedIsas</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArraySet</span><span class="o"><</span><span class="n">String</span><span class="o">>();</span>
<span class="k">for</span> <span class="o">(</span><span class="n">String</span> <span class="n">abi</span> <span class="o">:</span> <span class="n">Build</span><span class="o">.</span><span class="na">SUPPORTED_ABIS</span><span class="o">)</span> <span class="o">{</span>
<span class="o">..</span> <span class="c1">// 省略ABI判断代码。如果ABI不支持,则退出,表示启动失败。</span>
<span class="o">}</span>
<span class="c1">// 注册ACTION_QUERY_PACKAGE_RESTART广播</span>
<span class="n">IntentFilter</span> <span class="n">pkgFilter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IntentFilter</span><span class="o">();</span>
<span class="n">pkgFilter</span><span class="o">.</span><span class="na">addAction</span><span class="o">(</span><span class="n">Intent</span><span class="o">.</span><span class="na">ACTION_QUERY_PACKAGE_RESTART</span><span class="o">);</span>
<span class="n">pkgFilter</span><span class="o">.</span><span class="na">addDataScheme</span><span class="o">(</span><span class="s">"package"</span><span class="o">);</span>
<span class="n">mContext</span><span class="o">.</span><span class="na">registerReceiver</span><span class="o">(</span><span class="k">new</span> <span class="n">BroadcastReceiver</span><span class="o">()</span> <span class="o">{...</span><span class="c1">//省略广播实现代码}, pkgFilter);</span>
<span class="c1">// 注册ACTION_DELETE_DUMPHEAP广播</span>
<span class="n">IntentFilter</span> <span class="n">dumpheapFilter</span> <span class="o">=</span> <span class="k">new</span> <span class="n">IntentFilter</span><span class="o">();</span>
<span class="n">dumpheapFilter</span><span class="o">.</span><span class="na">addAction</span><span class="o">(</span><span class="n">DumpHeapActivity</span><span class="o">.</span><span class="na">ACTION_DELETE_DUMPHEAP</span><span class="o">);</span>
<span class="n">mContext</span><span class="o">.</span><span class="na">registerReceiver</span><span class="o">(</span><span class="k">new</span> <span class="n">BroadcastReceiver</span><span class="o">()</span> <span class="o">{...},</span> <span class="n">dumpheapFilter</span><span class="o">);</span>
<span class="c1">// 通知系统启动已经进入最后阶段:PHASE_BOOT_COMPLETED</span>
<span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startBootPhase</span><span class="o">(</span><span class="n">SystemService</span><span class="o">.</span><span class="na">PHASE_BOOT_COMPLETED</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="c1">// 启动之前被“抑制启动”的进程,这些进程在AMS准备就绪之前,就已经准备要启动了,</span>
<span class="c1">// 但又必须在AMS就绪之后启动,所以,就先放在mProcessesOnHold这个数组中</span>
<span class="c1">// 现在,是时候真正启动这些进程了。</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">NP</span> <span class="o">=</span> <span class="n">mProcessesOnHold</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">NP</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ArrayList</span><span class="o"><</span><span class="n">ProcessRecord</span><span class="o">></span> <span class="n">procs</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">ProcessRecord</span><span class="o">>(</span><span class="n">mProcessesOnHold</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">ip</span><span class="o">=</span><span class="mi">0</span><span class="o">;</span> <span class="n">ip</span><span class="o"><</span><span class="n">NP</span><span class="o">;</span> <span class="n">ip</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">startProcessLocked</span><span class="o">(</span><span class="n">procs</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">ip</span><span class="o">),</span> <span class="s">"on-hold"</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">if</span> <span class="o">(</span><span class="n">mFactoryTest</span> <span class="o">!=</span> <span class="n">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_LOW_LEVEL</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 抛出一个消息,用于检查是否有App滥用了WakeLock,即长时间不释放WakeLock</span>
<span class="n">Message</span> <span class="n">nmsg</span> <span class="o">=</span> <span class="n">mHandler</span><span class="o">.</span><span class="na">obtainMessage</span><span class="o">(</span><span class="n">CHECK_EXCESSIVE_WAKE_LOCKS_MSG</span><span class="o">);</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">sendMessageDelayed</span><span class="o">(</span><span class="n">nmsg</span><span class="o">,</span> <span class="n">POWER_CHECK_DELAY</span><span class="o">);</span>
<span class="c1">// 写入一个系统属性,sys.boot_completed=1 表示系统启动完毕</span>
<span class="n">SystemProperties</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="s">"sys.boot_completed"</span><span class="o">,</span> <span class="s">"1"</span><span class="o">);</span>
<span class="c1">// 发送ACTION_BOOT_COMPLETED广播</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">mStartedUsers</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">UserState</span> <span class="n">uss</span> <span class="o">=</span> <span class="n">mStartedUsers</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">uss</span><span class="o">.</span><span class="na">mState</span> <span class="o">==</span> <span class="n">UserState</span><span class="o">.</span><span class="na">STATE_BOOTING</span><span class="o">)</span> <span class="o">{</span>
<span class="n">uss</span><span class="o">.</span><span class="na">mState</span> <span class="o">=</span> <span class="n">UserState</span><span class="o">.</span><span class="na">STATE_RUNNING</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">userId</span> <span class="o">=</span> <span class="n">mStartedUsers</span><span class="o">.</span><span class="na">keyAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="n">Intent</span> <span class="n">intent</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Intent</span><span class="o">(</span><span class="n">Intent</span><span class="o">.</span><span class="na">ACTION_BOOT_COMPLETED</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="n">intent</span><span class="o">.</span><span class="na">putExtra</span><span class="o">(</span><span class="n">Intent</span><span class="o">.</span><span class="na">EXTRA_USER_HANDLE</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
<span class="n">intent</span><span class="o">.</span><span class="na">addFlags</span><span class="o">(</span><span class="n">Intent</span><span class="o">.</span><span class="na">FLAG_RECEIVER_NO_ABORT</span><span class="o">);</span>
<span class="n">broadcastIntentLocked</span><span class="o">(...);</span>
<span class="o">}</span>
<span class="c1">// 调度性能统计</span>
<span class="n">scheduleStartProfilesLocked</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>以上函数的逻辑不复杂,主要经过以下流程:进行ABI检查,注册一些系统广播,启动之前被抑制启动的进程,发送ACTION_BOOT_COMPLETED广播,调度性能统计功能。</p>
<h1 id="3-总结">3 总结</h1>
<p>本章节分析了系统进程启动过程中与AMS相关的逻辑,总体而言,可以分为三步:</p>
<ol>
<li>
<p><strong>初始化系统进程的运行环境</strong>。所谓运行环境,就是指的Context,Context蕴含了代码执行过程中所需要的信息,包括进程信息、包信息、资源信息等等。Android有意弱化进程的概念,强化Context的概念,在Android编程时,一定避免不了与Context打交道。对于系统进程而言,Context有一定的特殊性,所以单独造了一个SystemContext。</p>
</li>
<li>
<p><strong>初始化AMS对象</strong>。AMS对象在系统进程构建,作为最重要的系统服务,AMS初始化要做的事情非常多。由于各种系统服务耦合在一块,相互影响,Android设计了“系统进程启动阶段”这个概念,就像一个简单的状态机,只有进入的某个阶段,才能做某些操作。譬如,只有进入PHASE_ACTIVITY_MANAGER_READY,AMS才能正常工作,这时才可以进行派发广播、管理进程等操作。</p>
<p>因为系统进程也在AMS的管辖范围之内,所以,AMS对象构建后有一个重要的任务,就是设置系统进程的一些属性。这时,会将第一个启动的应用frameworks-res.apk的信息装载到系统进程中,创建一个系统进程的ProcessRecord对象以便AMS管理。</p>
</li>
<li>
<p><strong>AMS初始化的配套工作</strong>。这里所谓配套工作是指,系统要完全运行起来,还需要经由AMS进行一系列的运作:系统设置SettingsProvider会经由AMS装载到系统进程中;其他系统服务在AMS准备就绪后,也会进入就绪状态,表示可以正常工作;桌面会经由AMS启动,最终ACTION_BOOT_COMPLETED广播发出。</p>
</li>
</ol>
一种Android多分支的自动合并方案
2016-06-12T00:00:00+00:00
https://duanqz.github.io/A-Way-to-Maintain-Multi-Branches
<h1 id="1-背景">1. 背景</h1>
<p>对于Android系统级开发人员而言,维护多个分支的代码是常态,尤其是当新的Android版本发布时,设备厂商更是苦不堪言,即疲于最新Android版本的适配,又苦于大量旧机型的维护。
为了尽可能的减少维护的工作量,设备厂商有很多手段来避免新增分支,譬如:</p>
<ul>
<li>
<p><strong>编译时过滤</strong>。通过编译开关来兼容代码差异,以便于一个分支下,通过不同的开关配置,编译出不同的版本。静态编译开关以<strong>MTK的方案</strong>为代表,HTC、SONY等大厂商都有采用这种方式。</p>
</li>
<li>
<p><strong>运行时反射</strong>。多份功能类似的代码都经过编译,但运行时,根据配置信息,选择加载的类。运行时反射以<strong>CM的方案</strong>为代表,尤其是RIL层的反射框架极为精彩。</p>
</li>
<li>
<p><strong>基于SDK开发</strong>。很多应用层的开发都转向了基于SDK或基于厂商自己的中间件。即便框架层有差异,需要新增分支,但应用层仍然可以做到不开设新分支。</p>
</li>
</ul>
<p>以上手段能够减少分支的膨胀,但并不能完全避免新增分支。设备厂商通常都不是完全基于AOSP进行开发,而是基于不同芯片的平台方案,然而不同的芯片厂商都会提供自己的平台方案,譬如MTK, QCOM, SAMSUNG等,
芯片平台方案的差异,导致设备厂商不得不额外新增分支,如此一来,多分支管理的噩梦依旧挥之不去。</p>
<p>其实,与大部分设备厂商一样,Google也面临着<strong>AOSP(Android Open Source Project)</strong>多分支的管理问题,在Kitkat发布之前,Lollipop就已经启动开发了[1],
同时,Android还引入了很多开源项目和第三方社区贡献的代码,这些都需要开设分支。</p>
<p>本文提出了一种多分支的自动合并方案,将一个分支的代码提交自动合并到其他分支,能够有效地缓解多分支维护的压力。</p>
<h1 id="2-分支合并方案">2. 分支合并方案</h1>
<p>同时维护多个分支,意味着在一个分支上的代码变更,也可能适应于其他分支,这种情况下,开发人员可以在其他分支上提交相同的代码变更。
然而,随着分支数量的膨胀,重复提交不仅繁琐而且容易遗漏。因此,有必要引入<strong>自动提交</strong>的机制:当一个分支上有变更时,自动将这个变更记录提交到其他分支。
这样,代码变更就像是从一个分支上<strong>流</strong>向了其他分支,<strong>流</strong>的起点称为<strong>上游分支(upstream)</strong>,<strong>流</strong>的终点称为<strong>下游分支(downstream)</strong>,代码的自动提交机制称为<strong>代码流</strong>。</p>
<h2 id="21-代码合并方式比较">2.1 代码合并方式比较</h2>
<p>Android采用git进行代码版本管理,将一个分支的代码合并到另一个分支,git有三种方式:<code class="highlighter-rouge">rebase</code>、<code class="highlighter-rouge">cherry-pick</code>和<code class="highlighter-rouge">merge</code>。
这三种方式的原理并不相同,出于分支不断演进变化的考虑,代码流采用<code class="highlighter-rouge">merge</code>的方式,</p>
<h3 id="cherry-pick">cherry-pick</h3>
<p><strong>在downstream上,使用cherry-pick从upstream选择所需要的提交</strong></p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> git cherry-pick E,F</code></pre></figure>
<div align="center"><img src="/assets/images/automerger/1-automerger-cherrypick.png" alt="cherrypick" /></div>
<p>上游分支的提交 <em>E</em> 和 <em>F</em> ,会依次重新提交到下游分支,如果产生冲突,则<strong>cherry-pick</strong>失败,需要解决冲突后重新提交,直到产生新的提交记录 <em>E’</em> 和 <em>F’</em> 。
即使提交的代码改动一模一样,提交记录的SHA1(<strong>Commit ID</strong>)却已经发生了变化。</p>
<h3 id="rebase">rebase</h3>
<p><strong>在downstream上,使用rebase,将downstream变基到upstream</strong></p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> git rebase upstream</code></pre></figure>
<div align="center"><img src="/assets/images/automerger/2-automerger-rebase.png" alt="rebase" /></div>
<p>在downstream上使用rebase,表示要改变当前的基节点的位置,通过<strong>rebase</strong>到upstream,就意味着将基节点切换到upstream的最新提交 <em>F</em>。
本来downstream和upstream公共的父节点是 <em>B</em> , 使用完<strong>rebase</strong>后,则会将 <em>C</em> 和 <em>D</em> 两个提交记录挑出来,重新提交到 <em>F</em> 之后,
这同样会生成两个新的提交记录 <em>C’</em> 和 <em>D’</em> , Commit ID与之前 <em>C</em> 和 <em>D</em> 的是不同的。</p>
<h3 id="merge">merge</h3>
<p><strong>在downstream上,使用merge,将upstream和提交合并到downstream</strong></p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> git merge upstream</code></pre></figure>
<div align="center"><img src="/assets/images/automerger/3-automerger-merge.png" alt="merge" /></div>
<p>upstream和downstream两个分支<strong>merge</strong>,会出现一个新的提交 <em>M1</em> , 它的父提交有两个,分别是 <em>D</em> 和 <em>F</em>。
如果产生冲突,则会一次性提示所有代码改动产生的冲突,这与<strong>rebase</strong>不一样,有一个很形象的比喻来形容<strong>merge</strong>和<strong>rebase</strong>进行代码合并的区别:</p>
<blockquote>
<p>将一堆玩具整理到一个箱子中,<strong>rebase</strong>是一件一件挪,如果箱子满了(产生冲突),则需要整理一下箱子,腾出空间,再接着挪;
而<strong>merge</strong>是一股脑将玩具扔到箱子中,箱子满了,再一起整理。</p>
</blockquote>
<p>不同于cherry-pick和rebase,采用merge时,upstream的<strong>Commit ID</strong>并没有发生变化,而是在downstream生成了一个新的提交记录,这一点非常重要。
再进一步考虑,基于downstream的提交记录 <em>D</em> 又拉了新的分支downstream2 ,并增加了新的提交 <em>H</em> , 仍然采用<strong>merge</strong>将upstream合并到downstream2:</p>
<div align="center"><img src="/assets/images/automerger/4-automerger-merge-evolve.png" alt="merge-evolve" /></div>
<p>这时产生了一个新的合并提交 <em>M2</em> , 它的父提交是 <em>F</em> 和 <em>H</em> 。downstream和downstreanm2的公共父提交 <em>F</em>。
upstream合并到downstream2,相当于<strong>(B, F, H)</strong>的三路合并,在此之前,将upstream合并到downstream,相当于<strong>(B, F, D)</strong>的三路合并。
<strong>E, F</strong>与<strong>C, D</strong>合并可能会产生冲突,一旦冲突解决,则冲突解决的方法就被git记录下来了,再将<strong>E, F</strong>与<strong>C, D, H</strong>合并时,会利用之前解决的冲突,这样冲突数量会减少很多。
具体可以参见<strong>git-rerere - Reuse recorded resolution of conflicted merges</strong>机制[2]。</p>
<p>随着upstream的不断演进,提交记录也会不断地<strong>merge</strong>到downstream和downstream2, 所有的下游分支的公共父提交始终都跟踪到上游分支的最新提交记录。
这有以下好处:</p>
<ul>
<li>
<p><strong>旧Android版本可以快速迁移到新版本</strong>。假设upstream是设备厂商基于Lollipop构建的分支,downstream是基于Kitkat构建的某一个分支,
一次merge就快速将Lollipop上的改动合并到Kitkat,实现旧版本的升级。如果downstream2是基于Kitkat的另一个分支,那么,downstream2升级的成本会更低,
因为downstream升级时,解决的冲突能够被downstream2所用。</p>
</li>
<li>
<p><strong>快速实现代码流变更</strong>。downstream2本来是作为upstream的下游分支,它可以快速的切换为downstream的下游分支。只需要执行一次<code class="highlighter-rouge">git merge downstream downstream2</code>即可将代码流建立起来,从而实现代码流的分级,
这能有效的兼容不同芯片平台的差异。假设upstream是设备厂商基于AOSP构建的分支,downstream和downstream2是基于MTK平台构建的分支。如果部分代码改动只适用于MTK平台,那么,就可以将代码流调整为
downstream流向downstream2,只让一些公共的代码改动从upstream流向downstream。</p>
</li>
<li>
<p><strong>避免无效的Gerrit Review</strong>。设备厂商一般都会引入Gerrit代码审查,<strong>Commit ID</strong>保持不变,并不会增加新的Review任务,这在实现代码自动流时,能够减少人工的参与。</p>
</li>
</ul>
<h2 id="22-忽略不需要的变更">2.2 忽略不需要的变更</h2>
<p><strong>cherry-pick</strong>能够做到对每个提交记录的精准选择,但<strong>merge</strong>做不到,每一次<strong>merge</strong>都会合并上游分支的所有代码,然而实际的情况是,部分代码变更是不需要合并的,
所以,<strong>merge</strong>时还需要采取一定的策略来应对不需要合并的代码提交。</p>
<p>通过<code class="highlighter-rouge">git merge -s ours</code>这种方式能够将上游分支的提交记录合并到下游分支,仍然会在下游分支生成一个合并的提交记录,但实际上没有发生任何代码变更,
在下一次merge时,就只会合并从上一次merge以来的提交记录。这样一来,不仅略过了不需要的提交,而且还保持了分支合并的延续性。</p>
<p>要识别出哪些提交记录是略过的,就需要对这些提交记录进行标记,一个简单有效的方案是:通过提交描述(<strong>Commit Comment</strong>)中的关键字来标识该提交是否需要自动合并到其他分支。
譬如,可以将”DO NOT MERGE”作为关键字加到提交描述中,在自动合并代码时,检查一下待合并的提交记录,如果存在该关键字,就单独将这个提交挑出来,采用<code class="highlighter-rouge">-s ours</code>的方式进行<strong>merge</strong>。
假设上游分支有 <strong>A、B、C、D</strong> 四个提交,其中 <strong>C</strong> 不需要合并的,那么,在合并这些提交时,需要做三次<strong>merge</strong>:</p>
<ul>
<li>第一次,git merge B,这会将合并 <strong>A、B</strong> 两个提交,代码会发生变更</li>
<li>第二次,git merge -s ours C,这会合并 <strong>C</strong> 一个提交,但代码没有变更</li>
<li>第三次,git merge D,这会和并 <strong>D</strong> 一个提交,代码会发生变更</li>
</ul>
<h1 id="3-自动化合并的实现">3. 自动化合并的实现</h1>
<p>采用<strong>merge</strong>进行分支合并相比其他方式更加适合代码流,本方案基于此方式实现了一套自动化工具<strong>AutoMerger</strong>:</p>
<div align="center"><img src="/assets/images/automerger/5-automerger-mechanism.png" alt="automerger mechanism" /></div>
<p>对于上游分支的每一次提交,都会自动触发AutoMerger的执行,正常提交会采用<code class="highlighter-rouge">merge</code>的方式合并到下游分支;
如果某个提交注明不需要合并,则通过关键字判断(DO NOT MERGER),采用<code class="highlighter-rouge">merge -s ours</code>的方式合并到下游分支。
如果在合并过程中发生了冲突,则需要通知提交人解决冲突。</p>
<p>要实现整套方案的<strong>自动化</strong>,要考虑的问题还很多:</p>
<ol>
<li>执行<strong>merge</strong>操作的时机;</li>
<li>执行<strong>merge</strong>操作的冲突解决机制;</li>
<li><strong>AutoMerger</strong>的部署和配置。</li>
</ol>
<h2 id="31-执行合并的时机">3.1 执行合并的时机</h2>
<p>当上游分支有代码提交时,都需要通知<strong>AutoMerger</strong>。git提供一些被回调的钩子[3],其实是一个约定命名的脚本,
服务端的钩子有三个:</p>
<ul>
<li><strong>pre-receive</strong>: 在将提交合入代码服务器之前,会调用该脚本一次。可以利用该脚本对提交内容做一些检查,譬如提交描述是否满足规范等。</li>
<li><strong>update</strong>:与pre-receive有点类似,但对于每个分支,都会调用该脚本。譬如有两个分支都收到了代码提交,那这个脚本就会被调用两次。</li>
<li><strong>post-receive</strong>: 在提交合入代码服务器之后,会调用该脚本。</li>
</ul>
<p>在代码服务器上实现<strong>post-receive</strong>这个脚本,就能监测到某个分支是否有代码提交操作,如果有,则通知<strong>AutoMerger</strong>,要将这个提交合并到下游分支了。</p>
<p>在使用Gerrit进行代码审查的环境下,也可以利用Gerrit提供了钩子[4],其原理和git钩子大同小异。在服务器上实现Gerrit的<strong>change-merged</strong>脚本,也能达到与git的<strong>post-receive</strong>同样的效果。</p>
<h2 id="32-解决冲突的机制">3.2 解决冲突的机制</h2>
<p>解决冲突,是AutoMerger的一项要求,否则,代码流就一直阻塞在冲突的地方。
因为<strong>merge</strong>会追溯两条分支的最近公共父节点,如果<strong>merge</strong>产生了冲突,那么本次就合并失败,两条分支的最近公共父节点没有发生变化,
下一次<strong>merge</strong>还是会追溯到相同的最近公共父节点,只要冲突不解决,那两条分支就一直无法合并。</p>
<p>解决冲突需要人工参与,需要遵循一定的原则,保证后续的<strong>merge</strong>操作能够正常执行。每次解决冲突,都要求采用同样的方式:</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> repo sync <span class="nb">.</span>
<span class="gp">$</span> git checkout downstream
<span class="gp">$</span> git merge upstream
<span class="gp">$</span> <span class="o">[</span>本地解决冲突]
<span class="gp">$</span> repo upload .</code></pre></figure>
<p>因为AutoMerger采用<strong>merge</strong>进行上、下游分支合并,所以,解决冲突时,也需要在上、下游分支之间执行一次<code class="highlighter-rouge">git merge</code>命令,
完全模拟AutoMerger的分支合并过程,人工解决冲突后,再将代码提交到下游分支。</p>
<p>git有自动解决冲突的机制,能够记录一些历史的冲突解决办法,下次遇到同样的冲突时,自动就将冲突解决了。这套机制减少解决冲突时的人工参与。</p>
<h2 id="33-执行环境的部署">3.3 执行环境的部署</h2>
<p>通常,部署Android开发环境都需要代码服务器来保存源码,开发人员都会在本地进行代码变更,并定期保持与代码服务器的同步。
AutoMerger可以部署在独立服务器、开发人员本地或代码服务器,都不会影响到其功能,下图示意将AutoMerger部署在独立服务器的场景:</p>
<div align="center"><img src="/assets/images/automerger/6-automerger-deploy.png" alt="automerger mechanism" /></div>
<p>在一个服务器上部署AutoMerger,该服务器需要具备以下能力:</p>
<ol>
<li>接收从代码服务器发出的代码合并通知;</li>
<li>与代码服务器进行代码同步;</li>
<li>通知冲突责任人解决冲突。</li>
</ol>
<p>AutoMerger收到代码服务器的代码提交通知后,便从代码服务器拉取最新的代码,将提交从上游分支合入下游分支;
如果没有产生冲突,则将下游分支的代码提交到代码服务器; 否则,通知提交人产生的冲突;
冲突责任人在收到通知后,遵循约定的方式解决冲突,再次向代码服务器提交代码。</p>
<h1 id="4-总结">4. 总结</h1>
<p>本文对比了<strong>cherry-pick</strong>、<strong>rebase</strong>和<strong>merge</strong>这三种分支合并方式的区别,
基于<strong>merge</strong>的方式设计了一套多分支合并的方案,称为<strong>代码流</strong>。
通过部署AutoMerger这个工具,能够选择性地将上游分支的代码提交自动合并到下游分支。
分支合并过程中间产生的冲突,需要人工参与解决,AutoMerger能够及时地发现并通知冲突责任人。</p>
<p>AutoMerger已经经过了多个Android开发团队的验证,能够有效的降低多分支维护的成本,提高设备厂商升级Android版本的效率。</p>
<hr />
<h1 id="参考文献">参考文献</h1>
<ol>
<li>AOSP的代码线: <a href="http://source.android.com/source/code-lines.html">http://source.android.com/source/code-lines.html</a></li>
<li>git-rerere自动解决冲突机制:<a href="https://git-scm.com/blog/2010/03/08/rerere.html">https://git-scm.com/blog/2010/03/08/rerere.html</a></li>
<li>git钩子: <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks</a></li>
<li>gerrit钩子: <a href="https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.3/config-hooks.html">https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.3/config-hooks.html</a></li>
</ol>
Android Local Manifests机制的使用实践
2016-04-15T00:00:00+00:00
https://duanqz.github.io/Android-Local-Manifests-Practice
<p>本文介绍Android代码管理中的一种本地化定制机制:<strong>Local Manifest</strong>。Android使用<code class="highlighter-rouge">repo</code>来管来管理庞大的源码,<code class="highlighter-rouge">repo</code>所管辖的源码都记录在一个清单文件<strong>manifest.xml</strong>,<strong>Local Manifest</strong>是对<strong>manifest.xml</strong>的一种扩展。</p>
<h1 id="1-android源码管理的清单">1. Android源码管理的清单</h1>
<p>为了便于管理多个git库,Android提供了一套Python脚本,称为<code class="highlighter-rouge">repo</code>[1],它是全局管理Android源码的利器,Android系统开发的第一步就是获取源码,这时,就需要用到<code class="highlighter-rouge">repo</code>命令了:</p>
<ul>
<li>
<p><strong>repo init</strong>,用于初始化repo环境,一个XML格式的<strong>manifest.xml</strong>文件会生成在本地新建的<strong>.repo/</strong>中,<strong>manifest.xml</strong>定义了本地代码的目录结构,以及从远程下载的代码路径。</p>
</li>
<li>
<p><strong>repo sync</strong>,用于下载Android源码,这需要解析<strong>manifest.xml</strong>文件,按照指示下构建本地源码目录。</p>
</li>
</ul>
<p><code class="highlighter-rouge">repo</code>基于<strong>manifest.xml</strong>完成对Android源码的管理,<strong>manifest.xml</strong>是描述源码结构的清单,其实,它只是一个到<strong>.repo/manifests/default.xml</strong>的文件链接,真正的清单文件是通过<strong>manifests</strong>这个git库托管起来的,打开AOSP(Android Open Source Project)的<a href="https://android.googlesource.com/platform/manifest"><strong>manifests</strong>库</a>,其中只包含一个<strong>default.xml</strong>文件,
这就是最基本的清单构成。<strong>manifests</strong>库会有很多分支,从最早的android-1.6_r1到目前最新的android-6.0.1_r9,<strong>manifest</strong>库其实是Android版本演进的一个标志,当<strong>manifest</strong>库有新的android-7.0.0_r1分支拉出时,就意味着Android N的发布。
Android不断在变化,不同版本所包含的库的清单是不一样,所以不同分支下的<strong>default.xml</strong>文件内容也是不同的。</p>
<p>在进行Android系统开发时,通常需要对清单文件进行定制,譬如以下场景:</p>
<ul>
<li>
<p>设备厂商都会构建自己的<strong>manifest</strong>库,通常是基于AOSP的<strong>default.xml</strong>进行定制,譬如:去掉AOSP的一些git库、增加一些自有的git库。</p>
</li>
<li>
<p>CyanogenMod[2]适配了数百款机型,官方提供的<strong>default.xml</strong>并没有囊括所有机型的代码清单,否则会导致下载太多不需要的代码。
基于CyanogenMod进行开发时,只是按需下载待适配机型的代码,譬如:要适配<strong>HTC One</strong>这款机型,就需要向清单文件中额外添加这款机型<a href="https://github.com/cyanogenmod/android_device_htc_m7">Device库</a>和<a href="https://github.com/cyanogenmod/android_kernel_htc_msm8960">kernel库</a></p>
</li>
</ul>
<p>要实现定制清单的目的,可以直接对<strong>default.xml</strong>文件内容进行修改,然而这种方式在一些场景下存在弊端:</p>
<ul>
<li>
<p>部分对<strong>default.xml</strong>的修改,不需要上传到代码服务器; 而定期同步最新的<strong>default.xml</strong>,就容易与本地的修改产生冲突;</p>
</li>
<li>
<p>设备厂商大都是在多分支的环境下开发,对不同<strong>default.xml</strong>修改的内容都往往是相同的,导致需要在多分支上重复提交相同的修改。</p>
</li>
</ul>
<p><code class="highlighter-rouge">repo</code>还支持另外一种定制方式:<strong>Local Manifests</strong>,在<code class="highlighter-rouge">repo sync</code>之前,会将<strong>.repo/manifests/default.xml</strong>和<strong>.repo/local_manifests/</strong>目录下存在清单文件进行合并,再根据融合的清单文件进行代码同步。
这样一来,只需要将清单文件的修改项放到<strong>.repo/local_manifests/</strong>目录下,
就能够在不修改<strong>default.xml</strong>的前提下,完成对清单的文件的定制。</p>
<h1 id="2-local-manifests机制">2. Local Manifests机制</h1>
<p><code class="highlighter-rouge">repo</code>命令的执行依赖于解析清单文件的结果,解析时,就约定了在<strong>manifests/default.xml</strong>的基础上,融合<strong>local_manifest.xml</strong>文件和<strong>local_manifests/</strong>目录下的文件,生成一个的数据结构<strong>manifest_xml</strong>。<strong>Local Manifests</strong>机制的原理图如下所示:</p>
<div align="center"><img src="/assets/images/localmanifests/1-local-manifests-mechanism.png" alt="mechanisim" /></div>
<p>清单文件的解析由<em><a href="https://gerrit.googlesource.com/git-repo/+/master/manifest_xml.py">manifest_xml.py</a></em>这个脚本负责;解析结果输出给其他命令,譬如<code class="highlighter-rouge">repo sync</code>。这里有一些隐含的规则:</p>
<ul>
<li>
<p>先解析<strong>local_manifest.xml</strong>,再解析<strong>local_manifests/</strong>目录下的清单文件;</p>
</li>
<li>
<p><strong>local_manifests</strong>目录下的清单文件是没有命名限制的,但会按照字母序被解析,即字母序靠后的文件内容会覆盖之前的;</p>
</li>
<li>
<p>所有清单文件的内容必须遵循<code class="highlighter-rouge">repo</code>定义的格式[3]才能被正确解析。</p>
</li>
</ul>
<p>笔者实现了Local Manifests机制的一个使用示例:<a href="https://github.com/LocalManifestsDemo">https://github.com/LocalManifestsDemo</a>,这是一个包含多个git库的项目。
该项目中,默认的<strong><a href="https://github.com/LocalManifestsDemo/manifests/blob/master/default.xml">default.xml</a></strong>文件内容如下:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><manifest></span>
<span class="nt"><remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="nt">/></span>
<span class="nt"><default</span> <span class="na">revision=</span><span class="s">"refs/heads/master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"A"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-A"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"B"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-B"</span> <span class="nt">/></span>
<span class="nt"></manifest></span></code></pre></figure>
<p>在<strong>default.xml</strong>中,配置了两个项目: <strong>A</strong> 和 <strong>B</strong>,每个项目对应到一个git库。
当执行完<code class="highlighter-rouge">repo sync</code>之后,本地的代码目录结构如下:</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="go">LocalManifestsDemo
├── A (master)
└── B (master)</span></code></pre></figure>
<p>利用<strong>Local Manifests</strong>机制,新增<a href="https://github.com/LocalManifestsDemo/local_manifests/blob/master/default_local.xml">local_manifests/default_local.xml</a>文件:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><manifest></span>
<span class="nt"><remove-project</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-A"</span> <span class="nt">/></span>
<span class="nt"><remove-project</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-B"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"B"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-B"</span> <span class="na">revision=</span><span class="s">"stable"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"C"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-C"</span> <span class="nt">/></span>
<span class="nt"></manifest></span></code></pre></figure>
<p>在<strong>local_manifests</strong>目录下的<strong>default_local.xml</strong>文件中,定义了:</p>
<ul>
<li>删除项目 <strong>A</strong>,通过<remove-project>标签可以删除项目</li>
<li>将项目 <strong>B</strong> 指定为stable分支,通过先删除后新增的方式间接完成对 <strong>B</strong> 的修改</li>
<li>新增项目 <strong>C</strong></li>
</ul>
<p>最终,融合的清单文件内容如下所示:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><manifest></span>
<span class="nt"><remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="nt">/></span>
<span class="nt"><default</span> <span class="na">revision=</span><span class="s">"refs/heads/master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"B"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-B"</span> <span class="na">revision=</span><span class="s">"stable"</span> <span class="nt">/></span>
<span class="nt"><project</span> <span class="na">path=</span><span class="s">"C"</span> <span class="na">name=</span><span class="s">"LocalManifestsDemo/project-C"</span> <span class="nt">/></span>
<span class="nt"></manifest></span></code></pre></figure>
<p>执行完<code class="highlighter-rouge">repo sync</code>后,本地的代码目录结构如下所示:</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="go">LocalManifestsDemo
├── B (stable)
└── C (master)</span></code></pre></figure>
<p>可以看到, 项目 <strong>A</strong> 的代码目录被删除了,项目 <strong>B</strong> 被切换到了stable分支,新增了一个项目 <strong>C</strong>。</p>
<h1 id="3-local-manifests应用">3. Local Manifests应用</h1>
<p>只要准备好本地的<strong>local_manifests/</strong>目录,<strong>Local Manifests</strong>机制就生效了。因此,设计一套自动生成<strong>local_manifests/</strong>目录的方案,就能完成清单文件定制的需求,本文给出的方案图如下所示:</p>
<div align="center"><img src="/assets/images/localmanifests/2-local-manifests-usage.png" alt="usage" /></div>
<ul>
<li>在服务器上配置清单文件的定制信息;</li>
<li>在本地植入生成<strong>local_manifests/</strong>目录的客户端。</li>
</ul>
<p>服务端和客户端的具体实现由业务场景决定,譬如:服务端可以部署很多git库,客户端可以输入git库的名称,向服务端发起查询请求,获取服务端返回的git库的信息,然后生成本地的<strong>local_manifests/</strong>目录,客户端和服务端通信可以基于HTTP实现。</p>
<h2 id="31-同一android分支编译不同版本">3.1 同一Android分支编译不同版本</h2>
<p>一个Android分支对应到一份清单文件,一份清单文件定义了Android源码中包含了git库(由<project>标签定义)和git库所在的分支(由<project>标签的revision属性定义),所以,一个Android分支所包含的git库是不变的。
然而,为了编译出不同版本的固件,需要在这个分支上增加或删除一些git库,一种方案是增加新的分支:</p>
<div align="center"><img src="/assets/images/localmanifests/3-local-manifests-scene1-plus-branches.png" /></div>
<p>对于设备厂商而言,增加分支会导致提高维护成本,所以,避免增加分支的方案也有很多,譬如编译时开关、运行时反射等,<strong>Local Manifests</strong>也是一种有效的方案。以CyanogenMod为例,在同一分支上编译出数百款机型,机型不同自然就会有很多差异的地方,譬如kernel、配置项、固件中的APK,所以CyanogenMod把不同机型的差异项抽离出来,放到了<strong>local_manifests</strong>中:</p>
<div align="center"><img src="/assets/images/localmanifests/4-local-manifests-scene1-plus-manifests.png" /></div>
<p>每一个机型都有自己的<strong>local_manifests</strong>,它们对默认的清单文件进行了定制,这样一来,只需要在编译时准备不同的<strong>local_manifests</strong>,就能在同一Android分支编译出不同固件。实际上,不同的固件还是对应到不同的清单文件,所以,本质上与增加一份<strong>local_manifests</strong>与增加一个分支并没有不同,都是对已有清单文件的定制,只不过通过<strong>local_manifests</strong>来集中维护有差异的git库,比维护一个分支的成本要低。</p>
<p>CyanogenMod所有机型的device库和vendor库都放到了<a href="https://github.com/">https://github.com/</a>上,
在执行lunch命令时,植入了生成<strong>local_manifests</strong>的客户端:</p>
<ul>
<li>
<p>根据lunch命令获取机型名称,通过github的Search API[4],从服务器上查询机型的依赖库的信息;</p>
</li>
<li>
<p>将获取到的依赖库信息重新组织成XML格式,添加到<strong>local_manfiests/</strong>目录下的清单文件中。</p>
</li>
</ul>
<h2 id="32-多个android分支编译不同版本">3.2 多个Android分支编译不同版本</h2>
<p>设备厂商会基于同一Android版本构建很多分支,以适应不同芯片平台、不同发布版本的开发需要; 多个分支,就对应到多份清单文件。
编译不同分支时,就需要指定到不同的清单文件下载代码。通过<code class="highlighter-rouge">repo init</code>命令的 <em>-b</em> 参数指定不同的分支,其实就是指定不同的清单文件。
每个分支都可能出定制版本,譬如针对运营商的定制版(移动/联通/电信版)、针对海外市场的定制版,一种解决方案是增加分支:</p>
<div align="center"><img src="/assets/images/localmanifests/5-local-manifests-scene2-plus-branches.png" /></div>
<p>设备厂商一般不会采用以上方案,每一类定制版都增加一个分支,会导致分支成倍的扩张,这会变得不可维护。
通过编译开关等手段,还是可以在已有分支上编出不同的定制版,但在同一份代码中兼容太多的差异,也会提升代码的维护成本。
将差异的git库放到<strong>local_manifests</strong>中,实现已有分支的定制需求,是一种有效的方案:</p>
<div align="center"><img src="/assets/images/localmanifests/6-local-manifests-scene2-plus-manifests.png" /></div>
<p>通过<strong>local_manifests</strong>,每个分支就能编译出定制化的固件,<strong>local_manifests</strong>就像一个切面,在已有的不同分支中,都植入了相同的差异化内容。</p>
<p>以面向海外市场的定制版为例,需要在已有分支上都增加GMS(Google Mobile Services),这时,可以将GMS组织成独立的git库,部署在代码服务器上,
在编译海外定制版时,植入<strong>local_manifests</strong>,在清单文件中添加GMS这个git库的信息。这种方式能够以较低的成本实现对已有多个分支的定制。</p>
<hr />
<h1 id="参考文献">参考文献</h1>
<ol>
<li>repo介绍: <a href="https://duanqz.github.io/2015-06-25-Intro-to-Repo">https://duanqz.github.io/2015-06-25-Intro-to-Repo</a></li>
<li>CyanogenMod介绍:<a href="https://wiki.cyanogenmod.org/w/About">https://wiki.cyanogenmod.org/w/About</a></li>
<li>清单文件的格式: <a href="https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.txt">https://gerrit.googlesource.com/git-repo/+/master/docs/manifest-format.txt</a></li>
<li>CyanogenMod的Search API文档:<a href="https://developer.github.com/v3/search/">https://developer.github.com/v3/search/</a></li>
</ol>
Android四大组件之Activity--管理方式
2016-02-01T00:00:00+00:00
https://duanqz.github.io/Activity-Maintenance
<h1 id="1-概览">1. 概览</h1>
<p>Activity的管理有<strong>静态</strong>和<strong>动态</strong>两层涵义:</p>
<ul>
<li>
<p><strong>静态</strong>是指Activity的代码组织结构,即Application中声明的Activity的集合,这些Activity被组织在一个APK中,有特定的包名。
在编写应用程序时,Activity对应到用户界面,它定义了用户界面的布局、交互行为、启动方式等,最重要的,是Activity的生命周期函数。
在应用进程看来,只需要按照Android定义的规范,实现生命周期函数的具体逻辑即可,所有的用户界面都遵循同一个规范。
编写完一个应用程序的所有用户界面,就算是完成了Activity的静态管理。</p>
</li>
<li>
<p><strong>动态</strong>是指Activity的运行调度方式,即Activity生命周期的执行过程中,内部数据结构的变化,Android对所有Activity进行统一管理。
在一个应用程序安装时,系统会解析出APK中所有Activity的信息,当显示APK中的用户界面时,就需要调度Activity的生命周期函数了。
系统进程(system_process)中维护了所有Activity的状态,管理中枢就是ActivityManagerService,Android为此做了精密的设计,采用
<strong>栈</strong> 作为基本的数据结构。</p>
</li>
</ul>
<p>本文分析的Activity管理方式属于<strong>动态</strong>这个层面,也就是系统进程中针对Activity的调度机制。</p>
<h1 id="2-activity管理的基础">2. Activity管理的基础</h1>
<p>Activity的管理离不开基础的数据结构以及它们之间的相互关联,
所以,笔者会从基础的数据结构出发,分析类的属性和行为,并结合一些场景进行源码分析; 进一步,会分析各个类之间关联关系的构建过程。
这样一来,整个Activity管理运转的模型就清楚了,这个模型承载的很多业务,本文不会具体展开,
在<a href="2016-07-29-Activity-LaunchProcess-Part1">Android四大组件之Activity–启动过程</a>一文中,笔者会再详细介绍一种典型的业务。</p>
<h2 id="21-数据结构">2.1 数据结构</h2>
<p>Activity管理相关的数据结构包括:</p>
<ul>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ActivityRecord.java">ActivityRecord</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/TaskRecord.java">TaskRecord</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ActivityStack.java">ActivityStack</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ActivityStackSupervisor.java">ActivityDisplay</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ActivityStackSupervisor.java">ActivityStackSupervisor</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ProcessRecord.java">ProcessRecord</a></li>
</ul>
<p>这些数据结构都是Java类,它们都属于系统进程的范畴,即对象的构建和销毁都在系统进程中完成,笔者将从类的属性和行为这两个角度来分析类的职能。
Android有一些约定俗成的函数命名方式,与Activity管理相关很多函数都会带有<strong>Locked</strong>后缀,表示这些函数需要进行多线程同步操作(synchronized),它们会读/写一些多线程共享的数据,读者在分析代码的时候可以适当关注。</p>
<p>先上一张数据结构的概览图:</p>
<div align="center"><img src="/assets/images/activity/maintenace/1-activity-maintenace-structure.png" alt="Maintenace Guideline" /></div>
<p>图中的方框可以理解为一个中包含关系:譬如一个TaskRecord中包含多个ActivityRecord; 图中的连接线可以理解为等价关系,譬如同一个ActivityRecord会被TaskRecord和ProcessRecord引用,两者是从不同维度来管理ActivityRecord。</p>
<ul>
<li><strong>ActivityRecord</strong>是Activity管理的最小单位,它对应着一个用户界面;</li>
<li><strong>TaskRecord</strong>也是一个栈式管理结构,每一个<strong>TaskRecord</strong>都可能存在一个或多个<strong>ActivityRecord</strong>,栈顶的<strong>ActivityRecord</strong>表示当前可见的界面;</li>
<li><strong>ActivityStack</strong>是一个栈式管理结构,每一个<strong>ActivityStack</strong>都可能存在一个或多个<strong>TaskRecord</strong>,栈顶的<strong>TaskRecord</strong>表示当前可见的任务;</li>
<li><strong>ActivityStackSupervisor</strong>管理着多个<strong>ActivityStack</strong>,但当前只会有一个获取焦点(Focused)的<strong>ActivityStack</strong>;</li>
<li><strong>ProcessRecord</strong>记录着属于一个进程的所有<strong>ActivityRecord</strong>,运行在不同<strong>TaskRecord</strong>中的<strong>ActivityRecord</strong>可能是属于同一个
<strong>ProcessRecord</strong>。</li>
</ul>
<h3 id="activityrecord">ActivityRecord</h3>
<p>ActivityRecord是AMS调度Activity的基本单位,它需要记录AndroidManifest.xml中所定义Activity的静态特征,同时,
也需要记录Activity在被调度时的状态变化,因此ActivityRecord这个类的属性比较多。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ActivityInfo</strong></td>
<td>从<activity>标签中解析出来的信息,包含launchMode,permission,taskAffinity等</td>
</tr>
<tr>
<td><strong>mActivityType</strong></td>
<td>Activity的类型有三种:APPLICATION_ACTIVITY_TYPE(应用)、HOME_ACTIVITY_TYPE(桌面)、RECENTS_ACTIVITY_TYPE(最近使用)</td>
</tr>
<tr>
<td><strong>appToken</strong></td>
<td>当前ActivityRecord的标识</td>
</tr>
<tr>
<td><strong>packageName</strong></td>
<td>当前所属的包名,这是由<activity>静态定义的</td>
</tr>
<tr>
<td><strong>processName</strong></td>
<td>当前所属的进程名,大部分情况都是由<activity>静态定义的,但也有例外</td>
</tr>
<tr>
<td><strong>taskAffinity</strong></td>
<td>相同taskAffinity的Activity会被分配到同一个任务栈中</td>
</tr>
<tr>
<td><strong>intent</strong></td>
<td>启动当前Activity的Intent</td>
</tr>
<tr>
<td><strong>launchedFromUid</strong></td>
<td>启动当前Activity的UID,即发起者的UID</td>
</tr>
<tr>
<td><strong>launchedFromPackage</strong></td>
<td>启动当前Activity的包名,即发起者的包名</td>
</tr>
<tr>
<td><strong>resultTo</strong></td>
<td>在当前ActivityRecord看来,resultTo表示上一个启动它的ActivityRecord,当需要启动另一个ActivityRecord,会把自己作为resultTo,传递给下一个ActivityRecord</td>
</tr>
<tr>
<td><strong>state</strong></td>
<td>ActivityRecord所处的状态,初始值是ActivityState.INITIALIZING</td>
</tr>
<tr>
<td><strong>app</strong></td>
<td>ActivityRecord的宿主进程</td>
</tr>
<tr>
<td><strong>task</strong></td>
<td>ActivityRecord的宿主任务</td>
</tr>
<tr>
<td><strong>inHistory</strong></td>
<td>标识当前的ActivityRecord是否已经置入任务栈中</td>
</tr>
<tr>
<td><strong>frontOfTask</strong></td>
<td>标识当前的ActivityRecord是否处于任务栈的根部,即是否为进入任务栈的第一个ActivityRecord</td>
</tr>
<tr>
<td><strong>newIntents</strong></td>
<td>Intent数组,用于暂存还没有调度到应用进程Activity的Intent</td>
</tr>
</tbody>
</table>
<p>由于ActivityRecord是一个最基本的数据结构,所以其行为相对较少,大都是一些用于判定和更新当前ActivityRecord状态的函数:</p>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>putInHistory(), takeFromHistory(), isInHistory()</strong></td>
<td>基于inHistory属性,来判定和更新ActivityRecord是否在任务栈的状态值</td>
</tr>
<tr>
<td><strong>isHomeActivity(), isRecentsActivity(), isApplicationActivity()</strong></td>
<td>基于mActivityType属性,判定Activity的类型</td>
</tr>
<tr>
<td><strong>setTask()</strong></td>
<td>设置ActivityRecord的宿主任务</td>
</tr>
<tr>
<td><strong>deliverNewIntentLocked()</strong></td>
<td>向当前ActivityRecord继续派发Intent。在一些场景下,位于任务栈顶的ActivityRecord会继续接受新的Intent(譬如以singleTop方式启动的同一个Activity),这时候,会触发调度<strong>Activity.onNewIntent()</strong>函数</td>
</tr>
<tr>
<td><strong>addNewIntentLocked()</strong></td>
<td>如果Intent没有派发到应用进程,则通过该函数往<strong>newIntents</strong>数组中添加一个元素。</td>
</tr>
</tbody>
</table>
<p>要理解ActivityRecord这个数据结构,可以从其构造函数出发,理解其属性在什么场景下会发生变化。
每次需要启动一个新的Activity时,都会构建一个ActivityRecord对象,这在<strong>ActivityStackSupervisor.startActivityLocked()</strong>函数中完成,构造一个ActivityRecord要传入的参数是相当多的:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">ActivityRecord</span><span class="o">(</span><span class="n">ActivityManagerService</span> <span class="n">_service</span><span class="o">,</span> <span class="n">ProcessRecord</span> <span class="n">_caller</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">_launchedFromUid</span><span class="o">,</span> <span class="n">String</span> <span class="n">_launchedFromPackage</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">_intent</span><span class="o">,</span> <span class="n">String</span> <span class="n">_resolvedType</span><span class="o">,</span>
<span class="n">ActivityInfo</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">Configuration</span> <span class="n">_configuration</span><span class="o">,</span>
<span class="n">ActivityRecord</span> <span class="n">_resultTo</span><span class="o">,</span> <span class="n">String</span> <span class="n">_resultWho</span><span class="o">,</span> <span class="kt">int</span> <span class="n">_reqCode</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">_componentSpecified</span><span class="o">,</span> <span class="n">ActivityStackSupervisor</span> <span class="n">supervisor</span><span class="o">,</span>
<span class="n">ActivityContainer</span> <span class="n">container</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="n">service</span> <span class="o">=</span> <span class="n">_service</span><span class="o">;</span> <span class="c1">// AMS对象</span>
<span class="n">appToken</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Token</span><span class="o">(</span><span class="k">this</span><span class="o">);</span> <span class="c1">//appToken可以进行跨进程传递,标识一个AR对象</span>
<span class="n">info</span> <span class="o">=</span> <span class="n">aInfo</span><span class="o">;</span> <span class="c1">//从AndroidManifest.xml中解析得到的Activity信息</span>
<span class="n">launchedFromUid</span> <span class="o">=</span> <span class="n">_launchedFromUid</span><span class="o">;</span> <span class="c1">//譬如从浏览器启动当前AR,那么该属性记录的就是浏览器的UID</span>
<span class="n">launchedFromPackage</span> <span class="o">=</span> <span class="n">_launchedFromPackage</span><span class="o">;</span>
<span class="n">userId</span> <span class="o">=</span> <span class="n">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="n">intent</span> <span class="o">=</span> <span class="n">_intent</span><span class="o">;</span> <span class="c1">//启动当前AR的Intent</span>
<span class="n">shortComponentName</span> <span class="o">=</span> <span class="n">_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">resolvedType</span> <span class="o">=</span> <span class="n">_resolvedType</span><span class="o">;</span>
<span class="n">componentSpecified</span> <span class="o">=</span> <span class="n">_componentSpecified</span><span class="o">;</span>
<span class="n">configuration</span> <span class="o">=</span> <span class="n">_configuration</span><span class="o">;</span>
<span class="n">resultTo</span> <span class="o">=</span> <span class="n">_resultTo</span><span class="o">;</span> <span class="c1">//记录上一个AR对象</span>
<span class="n">resultWho</span> <span class="o">=</span> <span class="n">_resultWho</span><span class="o">;</span> <span class="c1">//reslutTo的字符串标识</span>
<span class="n">requestCode</span> <span class="o">=</span> <span class="n">_reqCode</span><span class="o">;</span> <span class="c1">//上一个AR对象设定的Request Code</span>
<span class="n">state</span> <span class="o">=</span> <span class="n">ActivityState</span><span class="o">.</span><span class="na">INITIALIZING</span><span class="o">;</span> <span class="c1">//AR的状态,Activity调度时发生改变</span>
<span class="n">frontOfTask</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">//是否处于Task的根部,调整任务栈中AR顺序时,可能发生改变</span>
<span class="n">launchFailed</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">stopped</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">//pause操作完成状态位</span>
<span class="n">delayedResume</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">finishing</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">//stoped到finished之间的过渡状态位</span>
<span class="n">configDestroy</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">keysPaused</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">//如果置为true,则当前AR不再接受用户输入</span>
<span class="n">inHistory</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="c1">//将AR压入任务栈后,该状态位被置为true</span>
<span class="n">visible</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">waitingVisible</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">nowVisible</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">idle</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">hasBeenLaunched</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">mStackSupervisor</span> <span class="o">=</span> <span class="n">supervisor</span><span class="o">;</span>
<span class="n">mInitialActivityContainer</span> <span class="o">=</span> <span class="n">container</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">options</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">pendingOptions</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityOptions</span><span class="o">(</span><span class="n">options</span><span class="o">);</span>
<span class="n">mLaunchTaskBehind</span> <span class="o">=</span> <span class="n">pendingOptions</span><span class="o">.</span><span class="na">getLaunchTaskBehind</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">haveState</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">aInfo</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">//根据aInfo给realActivity, taskAffinity, processName等属性赋值</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">//没有aInfo的情况下,赋予默认值</span>
<span class="n">realActivity</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">taskAffinity</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">stateNotNeeded</span> <span class="o">=</span> <span class="kc">false</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="n">processName</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">packageName</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="n">fullscreen</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">noDisplay</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">mActivityType</span> <span class="o">=</span> <span class="n">APPLICATION_ACTIVITY_TYPE</span><span class="o">;</span>
<span class="n">immersive</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<h3 id="taskrecord">TaskRecord</h3>
<p>TaskRecord的职责是管理多个ActivityRecord,本文所述的任务、任务栈指的就是TaskRecord。
启动Activity时,需要找到Activity的宿主任务,如果不存在,则需要新建一个,也就是说所有的ActivityRecord都必须有宿主。
TaskRecord与ActivityRecord是一对多的关系,TaskRecord的属性中包含了ActivityRecord的数组;
同时,TaskRecord还需要维护任务栈本身的状态。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>taskid</strong></td>
<td>TaskRecord的唯一标识</td>
</tr>
<tr>
<td><strong>taskType</strong></td>
<td>任务栈的类型,等同于ActivityRecord的类型,是由任务栈的第一个ActivityRecord决定的</td>
</tr>
<tr>
<td><strong>intent</strong></td>
<td>在当前任务栈中启动的第一个Activity的Intent将会被记录下来,后续如果有相同的Intent时,会与已有任务栈的Intent进行匹配,如果匹配上了,就不需要再新建一个TaskRecord了</td>
</tr>
<tr>
<td><strong>realActivity, origActivity</strong></td>
<td>启动任务栈的Activity,这两个属性是用包名(CompentName)表示的,real和orig是为了区分Activity有无别名(alias)的情况,如果AndroidManifest.xml中定义的Activity是一个alias,则此处real表示Activity的别名,orig表示真实的Activity</td>
</tr>
<tr>
<td><strong>affinity</strong></td>
<td>TaskRecord把Activity的affinity记录下来,后续启动Activity时,会从已有的任务栈中匹配affinity,如果匹配上了,则不需要新建TaskRecord</td>
</tr>
<tr>
<td><strong>rootAffinity</strong></td>
<td>记录任务栈中最底部Activity的affinity,一经设定后就不再改变</td>
</tr>
<tr>
<td><strong>mActivities</strong></td>
<td>这是TaskRecord最重要的一个属性,TaskRecord是一个栈结构,栈的元素是ActivityRecord,其内部实现是一个数组mActivities</td>
</tr>
<tr>
<td><strong>stack</strong></td>
<td>当前TaskRecord所在的ActivityStack</td>
</tr>
</tbody>
</table>
<p>TaskRecord的行为侧重在TaskRecord本身的管理:增/删/改/查任务栈中的元素。</p>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>getRootActivity(), getTopActivity()</strong></td>
<td>任务栈有<strong>根部(Root)</strong>和<strong>顶部(Top)</strong>,可以通过这两个函数分别获取到根部和顶部的ActivityRecord。获取的过程就是对TaskRecord.mActivities进行遍历,如果ActivityRecord的状态不是finishing,就认为是有效的ActivityRecord</td>
</tr>
<tr>
<td><strong>topRunningActivityLocked()</strong></td>
<td>虽然也是从顶至底对任务栈进行遍历获取顶部的ActivityRecord,但这个函数同<strong>getTopActivity()</strong>有区别:输入参数<strong>notTop</strong>,表示在遍历的过程中需要排除<strong>notTop</strong>这个ActivityRecord;</td>
</tr>
<tr>
<td><strong>addActivityToTop(), addActivityAtBottom()</strong></td>
<td>将ActivityRecord添加到任务栈的顶部或底部</td>
</tr>
<tr>
<td><strong>moveActivityToFrontLocked()</strong></td>
<td>该函数将一个ActivityRecord移至TaskRecord的顶部,实现方法就是先删除已有的,再在栈顶添加一个新的</td>
</tr>
<tr>
<td><strong>setFrontOfTask()</strong></td>
<td>ActivityRecord有一个属性是frontOfTask,表示ActivityRecord是否为TaskRecord的根Activity。该函数设置TaskRecord中所有ActivityRecord的frontOfTask属性,从栈底往上开始遍历,第一个不处于finishing状态的ActivityRecord的frontOfTask属性置成true,其他都为false</td>
</tr>
<tr>
<td><strong>performClearTaskLocked()</strong></td>
<td>清除TaskRecord中的ActivityRecord。当启动Activity时,用了<strong>Intent.FLAG_ACTIVITY_CLEAR_TOP</strong>参数,那么在宿主任务中,待启动ActivityRecord之上的其他ActivityRecord都会被清除</td>
</tr>
</tbody>
</table>
<p>仅仅把类的属性和行为罗列出来,当然不足以理解TaskRecord的工作原理。
接下来,将深入部分函数的代码,分析TaskRecord在一些场景下的具体执行逻辑。</p>
<p><strong><code class="highlighter-rouge">场景 1</code></strong> 启动一个Activity时,通常需要将ActivityRecord压入任务栈顶,<strong>addActivityToTop()</strong>就是为此功能设计:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">addActivityToTop</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="n">addActivityAtIndex</span><span class="o">(</span><span class="n">mActivities</span><span class="o">.</span><span class="na">size</span><span class="o">(),</span> <span class="n">r</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>将ActivityRecord压入栈顶,其实就是在mActivities数组末尾添加一个元素,所以,实际压入栈顶的操作是由<strong>addActivityAtIndex()</strong>完成:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">addActivityAtIndex</span><span class="o">(</span><span class="kt">int</span> <span class="n">index</span><span class="o">,</span> <span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 1. 移除已有的ActivityRecord对象</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">mActivities</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">r</span><span class="o">)</span> <span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">fullscreen</span><span class="o">)</span> <span class="o">{</span>
<span class="n">numFullscreen</span><span class="o">++;</span>
<span class="o">}</span>
<span class="c1">// 2. 根据任务栈是否为空,设置状态</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mActivities</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span>
<span class="n">taskType</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">mActivityType</span><span class="o">;</span>
<span class="n">isPersistable</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">isPersistable</span><span class="o">();</span>
<span class="n">mCallingUid</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromUid</span><span class="o">;</span>
<span class="n">mCallingPackage</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromPackage</span><span class="o">;</span>
<span class="n">maxRecents</span> <span class="o">=</span> <span class="n">Math</span><span class="o">.</span><span class="na">min</span><span class="o">(</span><span class="n">Math</span><span class="o">.</span><span class="na">max</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">maxRecents</span><span class="o">,</span> <span class="mi">1</span><span class="o">),</span>
<span class="n">ActivityManager</span><span class="o">.</span><span class="na">getMaxAppRecentsLimitStatic</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">mActivityType</span> <span class="o">=</span> <span class="n">taskType</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 3. 在指定的位置添加ActivityRecord</span>
<span class="n">mActivities</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">index</span><span class="o">,</span> <span class="n">r</span><span class="o">);</span>
<span class="c1">// 4. 更新任务栈关联的Intent</span>
<span class="n">updateEffectiveIntent</span><span class="o">();</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>该函数会经过以下处理过程:</p>
<ol>
<li>
<p>移除任务栈中已有的ActivityRecord对象,即任务栈中不会出现两个同样的ActivityRecord对象。此处需要注意,两次启动同一个Activity,是会产生两个不同的ActivityRecord对象的;</p>
</li>
<li>
<p>如果任务栈为空,则设置任务栈的初始状态,否则,设置ActivityRecord的类型为任务栈的类型。由此可见,同一个任务栈中,所有ActivityRecord的类型都是一样的,而且是由任务栈的第一个ActivityRecord的类型决定的;</p>
</li>
<li>
<p>此处的位置就是任务栈顶,也就是mActivities属性的末尾;</p>
</li>
<li>
<p>任务栈中元素发生了变化,所以需要更新任务栈关联的Intent,这是通过调用<strong>updateEffectiveIntent()</strong>实现的,函数的具体逻辑,在<strong><code class="highlighter-rouge">场景 3</code></strong>中再行分析。</p>
</li>
</ol>
<p><strong><code class="highlighter-rouge">场景 2</code></strong> 当待显示的Activity压入任务栈后,就需要设置栈顶ActivityRecord的状态,这时候,会调用<strong>topRunningActivityLocked()</strong>函数来获取栈顶的元素,为了更好的分析<strong>topRunningActivityLocked()</strong>的使用场景,笔者把另一个与其功能相似的函数<strong>getTopActivity()</strong>也列出来:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">ActivityRecord</span> <span class="nf">getTopActivity</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">i</span> <span class="o">=</span> <span class="n">mActivities</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">>=</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="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">mActivities</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="k">if</span> <span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">finishing</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">return</span> <span class="n">r</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="n">ActivityRecord</span> <span class="nf">topRunningActivityLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">notTop</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">activityNdx</span> <span class="o">=</span> <span class="n">mActivities</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">activityNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">activityNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">mActivities</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">activityNdx</span><span class="o">);</span>
<span class="c1">// 除了要求ActivityRecord不是finishing状态以外,还要求不是当前给定输入的ActivityRecord</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">r</span><span class="o">.</span><span class="na">finishing</span> <span class="o">&&</span> <span class="n">r</span> <span class="o">!=</span> <span class="n">notTop</span> <span class="o">&&</span> <span class="n">stack</span><span class="o">.</span><span class="na">okToShowLocked</span><span class="o">(</span><span class="n">r</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">r</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>两者是从顶到底对任务栈进行遍历,但实现逻辑不同,<strong>topRunningActivityLocked()</strong>接受一个输入参数<strong>notTop</strong>,在寻找时,要求排除<strong>notTop</strong>指定的ActivityRecord,通常,传入的<strong>notTop</strong>都是null,隐含的意思就是栈顶的ActivityRecord还没有被销毁。从函数命名topRunning,也可以看出其与getTop的区别:getTop不管栈顶的死活,拿到就好; topRunning要求拿到的一定是活的栈顶。</p>
<p>另外,<strong>topRunningActivityLocked()</strong>还有一个限制条件: ActivityRecord是可以被显示的(okToShow),这是通过<strong>ActivityStack.okToShowLocked()</strong>来判定的,主要是为了应多多用户或锁屏显示的Activity,一般情况下,函数返回值都为true。</p>
<p><strong><code class="highlighter-rouge">场景 3</code></strong> 假定在启动一个Activity时,设置了Intent的FLAG_ACTIVITY_REORDER_TO_FRONT,表示要将Activity重排序到任务栈顶。
如果目标的Activity在任务栈中已经启动过,则需要将其挪至栈顶。譬如目标任务栈从底到顶是 <strong><code class="highlighter-rouge">A - B - C</code></strong>,
然后,又以FLAG_ACTIVITY_REORDER_TO_FRONT启动了 <strong>A</strong>,那最终任务栈会变化为 <strong><code class="highlighter-rouge">B - C - A</code></strong> 。
这就会调用到<strong>moveActivityToFrontLocked()</strong>函数:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">moveActivityToFrontLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">newTop</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mActivities</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">newTop</span><span class="o">);</span>
<span class="n">mActivities</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">newTop</span><span class="o">);</span>
<span class="n">updateEffectiveIntent</span><span class="o">();</span>
<span class="n">setFrontOfTask</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>
<p>该函数需要调整任务栈中ActivityRecord的顺序,延用上述例子, <strong>A</strong> 将作为参数newTop。
首先,会将 <strong>A</strong> 从任务栈中移除; 然后,再将 <strong>A</strong> 添加到任务栈顶;
接着,会调用<strong>updateEffectiveIntent()</strong>函数来更新任务栈关联的Intent:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">updateEffectiveIntent</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">effectiveRootIndex</span> <span class="o">=</span> <span class="n">findEffectiveRootIndex</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">mActivities</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">effectiveRootIndex</span><span class="o">);</span>
<span class="n">setIntent</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>该函数会找到一个有效的Root Index,即任务栈底部的索引,根据这个索引值取出对应的ActivityRecord。
延续上述例子,<strong>B</strong> 会被调整为任务栈的根部ActivityRecord,通过调用<strong>setIntent()</strong>来设置当前任务栈关联的Intent为启动 <strong>B</strong> 的Intent,然而,这里可不止改变TaskRecord.intent这一个属性这个简单,与Taskrecord的发起者相关的属性值都要更改,
譬如mCallingUid,mCallingPackage都得更改为 <strong>B</strong> 的发起者:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">setIntent</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setIntent</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="n">r</span><span class="o">.</span><span class="na">info</span><span class="o">);</span>
<span class="n">mCallingUid</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromUid</span><span class="o">;</span>
<span class="n">mCallingPackage</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">launchedFromPackage</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<p>这里还有一个重载的setIntent()函数,不再展开分析了,只需要知道诸如affinity, realActivity等属性都会被重置即可。</p>
<p>更新完TaskRecord的Intent,再回到<strong>moveActivityToFrontLocked()</strong>函数中,需要继续更新任务栈的Front。
之前任务栈的Front是 <strong>A</strong>,在发生变化后, Front需要更新为 <strong>B</strong>,然而,TaskRecord并没有一个属性用来记录当前的Front,
它是根据任务栈中每一个ActivityRecord的frontOfTask属性来判定的:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">setFrontOfTask</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">foundFront</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">numActivities</span> <span class="o">=</span> <span class="n">mActivities</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">activityNdx</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">activityNdx</span> <span class="o"><</span> <span class="n">numActivities</span><span class="o">;</span> <span class="o">++</span><span class="n">activityNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 从栈底往上遍历</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">mActivities</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">activityNdx</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">foundFront</span> <span class="o">||</span> <span class="n">r</span><span class="o">.</span><span class="na">finishing</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 其他ActivityRecord的这个属性都置为false</span>
<span class="n">r</span><span class="o">.</span><span class="na">frontOfTask</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// 不为finishing状态,表示已经找到了front的ActivityRecord</span>
<span class="n">r</span><span class="o">.</span><span class="na">frontOfTask</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="n">foundFront</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">foundFront</span> <span class="o">&&</span> <span class="n">numActivities</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mActivities</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">frontOfTask</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>该函数从底到顶对任务栈进行遍历,找到的第一个未结束(finishing = faulse)的ActivityRecord,
将其frontOfTask属性设置成true;其他所有ActivtyRecord的frontOfTask属性设置为false。</p>
<h3 id="activitystack">ActivityStack</h3>
<p>ActivityStack的职责是管理多个任务栈(TaskRecord),它是一个栈式结构,栈中的元素是TaskRecord。
每个Activity在特定的时刻都会有一个状态,譬如显示、销毁等,
在应用进程看来,这些状态的变化就是在执行Activity的生命周期函数;
在系统进程看来,这些状态的变化都需要经过ActivityStack来驱动。
Activity的状态是通过ActivityState这个枚举类来定义的:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">enum</span> <span class="n">ActivityState</span> <span class="o">{</span>
<span class="n">INITIALIZING</span><span class="o">,</span>
<span class="n">RESUMED</span><span class="o">,</span>
<span class="n">PAUSING</span><span class="o">,</span>
<span class="n">PAUSED</span><span class="o">,</span>
<span class="n">STOPPING</span><span class="o">,</span>
<span class="n">STOPPED</span><span class="o">,</span>
<span class="n">FINISHING</span><span class="o">,</span>
<span class="n">DESTROYING</span><span class="o">,</span>
<span class="n">DESTROYED</span>
<span class="o">}</span></code></pre></figure>
<p>从INITIALIZING到DESTROYED,所定义状态值示意了Activity生命周期的走向。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>stackId</strong></td>
<td>每一个ActivityStack都有一个编号,从0开始递增。编号为0,表示桌面(Launcher)所在的ActivityStack,叫做Home Stack</td>
</tr>
<tr>
<td><strong>mTaskHistory</strong></td>
<td>TaskRecord数组,ActivityStack栈就是通过这个数组实现的</td>
</tr>
<tr>
<td><strong>mPausingActivity</strong></td>
<td>在发生Activity切换时,正处于Pausing状态的Activity</td>
</tr>
<tr>
<td><strong>mResumedActivity</strong></td>
<td>当前处于Resumed状态的ActivityRecord</td>
</tr>
<tr>
<td><strong>mStacks</strong></td>
<td>ActivityStack会绑定到一个显示设备上,譬如手机屏幕、投影仪等,在AMS中,通过ActivityDisplay这个类来抽象表示一个显示设备,ActivityDisplay.mStacks表示当前已经绑定到显示设备的所有ActivityStack。当执行一次绑定操作时,就会将ActivityStack.mStacks这个属性赋值成ActivityDisplay.mStacks,否则,ActivityStack.mStacks就为null。简而言之,当mStacks不为null时,表示当前ActivityStack已经绑定到了一个显示设备</td>
</tr>
</tbody>
</table>
<p>Activity状态的变迁,不仅仅是给ActivityRecord.state赋一个状态值那么简单,ActivityStack要对栈进行调整:之前的Activity要销毁或者挪到后台,待显示的Activity要挪到栈顶,这一调整,涉及到的工作就多了。</p>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>findTaskLocked()</strong></td>
<td>该函数的功能是找到目标ActivityRecord(target)所在的任务栈(TaskRecord),如果找到,则返回栈顶的ActivityRecord,否则,返回null</td>
</tr>
<tr>
<td><strong>findActivityLocked()</strong></td>
<td>根据Intent和ActivityInfo这两个参数可以获取一个Activity的包名,该函数会从栈顶至栈底遍历ActivityStack中的所有Activity,如果包名匹配成功,就返回</td>
</tr>
<tr>
<td><strong>moveToFront)</strong></td>
<td>该函数用于将当前的ActivityStack挪到前台,执行时,调用ActivityStack中的其他一些判定函数</td>
</tr>
<tr>
<td><strong>isAttached()</strong></td>
<td>用于判定当前ActivityStack是否已经绑定到显示设备</td>
</tr>
<tr>
<td><strong>isOnHomeDisplay()</strong></td>
<td>用于判定当前是否为默认的显示设备(Display.DEFAULT_DISPLAY),通常,默认的显示设备就是手机屏幕</td>
</tr>
<tr>
<td><strong>isHomeStack()</strong></td>
<td>用于判定当前ActivityStack是否为Home Stack,即判定当前显示的是否为桌面(Launcher)</td>
</tr>
<tr>
<td><strong>moveTaskToFrontLocked()</strong></td>
<td>该函数用于将指定的任务栈挪到当前ActivityStack的最前面。在Activity状态变化时,需要对已有的ActivityStack中的任务栈进行调整,待显示Activity的宿主任务需要挪到前台</td>
</tr>
<tr>
<td><strong>insertTaskAtTop()</strong></td>
<td>将任务插入ActivityStack栈顶</td>
</tr>
</tbody>
</table>
<p>ActivityStack还有很多与<strong>迁移Activity状态</strong>相关的行为: <strong>startActivityLocked()</strong>, <strong>resumeTopActivityLocked()</strong>,
<strong>completeResumeLocked()</strong>, <strong>startPausingLocked()</strong>, <strong>completePauseLocked()</strong>, <strong>stopActivityLocked()</strong>,
<strong>activityPausedLocked()</strong>, <strong>finishActivityLocked()</strong>, <strong>activityDestroyedLocked()</strong>, 它们与Activity的生命周期调度息息相关,在<a href="2016-07-29-Activity-LaunchProcess-Part1">Android四大组件之Activity–启动过程</a>一文中,会再详细分析这几个函数的实现逻辑,本文还是通过一个简单的场景来分析ActivityStack的行为。</p>
<p><strong><code class="highlighter-rouge">场景 1</code></strong> 以singleTask的方式启动一个处于后台的Activity,那么,就需要将Activity挪到前台。怎么挪呢?</p>
<p><strong>第一步,findTaskLocked()</strong>: 找到Activity所在TaskRecord;</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">ActivityRecord</span> <span class="nf">findTaskLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">target</span><span class="o">)</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">taskNdx</span> <span class="o">=</span> <span class="n">mTaskHistory</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">taskNdx</span> <span class="o">>=</span> <span class="mi">0</span><span class="o">;</span> <span class="o">--</span><span class="n">taskNdx</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">TaskRecord</span> <span class="n">task</span> <span class="o">=</span> <span class="n">mTaskHistory</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">taskNdx</span><span class="o">);</span>
<span class="o">...</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">getTopActivity</span><span class="o">();</span>
<span class="o">...</span>
<span class="kd">final</span> <span class="n">Intent</span> <span class="n">taskIntent</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">intent</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">Intent</span> <span class="n">affinityIntent</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">affinityIntent</span><span class="o">;</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isDocument</span> <span class="o">&&</span> <span class="o">!</span><span class="n">taskIsDocument</span> <span class="o">&&</span> <span class="n">task</span><span class="o">.</span><span class="na">rootAffinity</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">task</span><span class="o">.</span><span class="na">rootAffinity</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">target</span><span class="o">.</span><span class="na">taskAffinity</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">r</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">taskIntent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">taskIntent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span>
<span class="n">taskIntent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">().</span><span class="na">compareTo</span><span class="o">(</span><span class="n">cls</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span>
<span class="n">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">documentData</span><span class="o">,</span> <span class="n">taskDocumentData</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</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="k">if</span> <span class="o">(</span><span class="n">affinityIntent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">affinityIntent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span>
<span class="n">affinityIntent</span><span class="o">.</span><span class="na">getComponent</span><span class="o">().</span><span class="na">compareTo</span><span class="o">(</span><span class="n">cls</span><span class="o">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span>
<span class="n">Objects</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">documentData</span><span class="o">,</span> <span class="n">taskDocumentData</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">r</span>
<span class="o">}</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></code></pre></figure>
<p>该函数的功能是找到target ActivityRecord所在的Task,如果找到,则返回Task栈顶的ActivityRecord,否则,返回null。
主体逻辑是对ActivityStack中的所有Task进行遍历,以下几种情况表示找到了ActivityRecord的宿主task:</p>
<ul>
<li>Affinity相同。rootAffinity表示第一次启动该task时affinity值,如果一个ActivityRecord的taskAffinity属性与其相等,
那么这个task自然是ActivityRecord的宿主;</li>
<li>Intent的包名相同。</li>
<li>Affinity Intent的包名相同。</li>
</ul>
<p><strong>第二步,moveToFront()</strong>: 将TaskRecord所在的ActivityStack挪到前台;</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">moveToFront</span><span class="o">(</span><span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isAttached</span><span class="o">())</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isOnHomeDisplay</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">moveHomeStack</span><span class="o">(</span><span class="n">isHomeStack</span><span class="o">(),</span> <span class="n">reason</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">mStacks</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">mStacks</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="kd">final</span> <span class="n">TaskRecord</span> <span class="n">task</span> <span class="o">=</span> <span class="n">topTask</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">task</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">moveTaskToTop</span><span class="o">(</span><span class="n">task</span><span class="o">.</span><span class="na">taskId</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>首先,会有一些判定:</p>
<ul>
<li>isAttached(): 只有在当前ActivityStack绑定到显示设备的情况下,才需要挪到;</li>
<li>isOnHomeDisplay(): 如果当前是默认的显示设备,则对HomeStack(桌面)进行挪动,
这涉及到对多个ActivityStack的操作,所以需要通过ActivityStackSupervisor完成;</li>
<li>isHomeStack(): 如果当前是HomeStack,则将其挪到前后; 否则,将HomeStack挪到后台</li>
</ul>
<p>然后,就是对mStacks这个属性进行操作:在mStacks数组中,删除已有的ActivityStack对象,并添加一个新的,这样做其实达到了一个目的,前台的ActivityStacks处于mStacks末尾。</p>
<p>最后,调用WMS.moveTaskToTop()通知窗口的进行变化调整。</p>
<p><strong>第三步, moveTaskToFrontLocked()</strong>: 将TaskRecord挪到ActivityStack的栈顶;</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">moveTaskToFrontLocked</span><span class="o">(</span><span class="n">TaskRecord</span> <span class="n">tr</span><span class="o">,</span> <span class="n">ActivityRecord</span> <span class="n">source</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</span><span class="o">,</span>
<span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">numTasks</span> <span class="o">=</span> <span class="n">mTaskHistory</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="n">mTaskHistory</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="n">tr</span><span class="o">);</span>
<span class="c1">// 判定ActivityStack是否需要挪动任务栈</span>
<span class="k">if</span> <span class="o">(</span><span class="n">numTasks</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">index</span> <span class="o"><</span> <span class="mi">0</span><span class="o">)</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">// 调整ActivityStack</span>
<span class="n">insertTaskAtTop</span><span class="o">(</span><span class="n">tr</span><span class="o">);</span>
<span class="n">moveToFront</span><span class="o">(</span><span class="n">reason</span><span class="o">);</span>
<span class="o">...</span>
<span class="c1">// 将栈顶的Activity置为Resumed状态</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">resumeTopActivitiesLocked</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>
<p>首先,会经过判定:如果当前的ActivityStack为空,或者不存在要挪动的任务,则不需要对当前ActivityStack进行调整;</p>
<p>然后,确定目标任务在当前ActivityStack中,则对ActivityStack进行调整,将目标任务插入ActivityStack栈顶。</p>
<ul>
<li><strong>insertTaskAtTop()</strong>,会先将已有的目标任务删除,再重新添加到栈顶位置;</li>
<li><strong>moveToFront()</strong>,在第二步中执行过一次,因为在某些场景下,只会调用<strong>moveToFront()</strong>,不会调用<strong>moveTaskToFrontLocked()</strong>;
一旦要将任务挪到ActivityStack栈顶,意味着ActivityStack也一定要挪到前台;</li>
</ul>
<p>最后,将任务栈顶的Activity置为Resumed状态,这里是通过ActivityStackSupervisor完成的。因为可能同时存在多个显示设备,所以需要对多个ActivityStack进行操作。</p>
<blockquote>
<p>Activity管理中有两个栈顶:一是ActivityStack的栈顶,它对应到要显示的TaskRecord; 二是TaskRecord的栈顶,它对应到要显示的Activity。简单来说,当前显示的Activity一定是位于其所属的TaskRecord的栈顶,TaskRecord也一定位于其所属的ActivityStack的栈顶。</p>
</blockquote>
<h3 id="activitydisplay">ActivityDisplay</h3>
<p>Android支持多屏显示,在不同的显示设备上可以有不同的ActivityStack。</p>
<p>笔者一直在重述:所有的ActivityStack都是通过ActivityStackSupervisor管理起来的。
在ActivityStackSupervisor内部,设计了ActivityDisplay这个内部类,它对应到一个显示设备,默认的显示设备是手机屏幕。
ActivityStackSupervisor间接通过ActivityDisplay来维护多个ActivityStack的状态。
ActivityStack有一个属性是mStacks,当mStacks不为空时,表示ActivityStack已经绑定到了显示设备,
其实ActivityStack.mStacks只是一个副本,真正的对象在ActivityDisplay中。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>mDisplayId</strong></td>
<td>显示设备的唯一标识</td>
</tr>
<tr>
<td><strong>mDisplay</strong></td>
<td>获取显示设备信息的工具类,</td>
</tr>
<tr>
<td><strong>mDisplayInfo</strong></td>
<td>显示设备信息的数据结构,包括类型、大小、分辨率等</td>
</tr>
<tr>
<td><strong>mStacks</strong></td>
<td>绑定到显示设备上的ActivityStack</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>attachActivities()</strong></td>
<td>将一个ActivityStack绑定到显示设备</td>
</tr>
<tr>
<td><strong>setVisibleBehindActivity()</strong></td>
<td>设置后台显示的Activity</td>
</tr>
<tr>
<td><strong>moveHomeStack()</strong></td>
<td>移动HomeStack</td>
</tr>
</tbody>
</table>
<h3 id="activitycontainer">ActivityContainer</h3>
<p>在ActivityStackSupervisor中,还设计了名为ActivityContainer的内部类。
该类是对ActivityStack的封装,相当于开了一个后门,可以通过<code class="highlighter-rouge">adb shell am</code>命令对ActivityStack进行读写操作,方便开发和调试。</p>
<h3 id="activitystacksupervisor">ActivityStackSupervisor</h3>
<p>ActivityStackSupervisor的职责是管理多个ActivityStack。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>mHomeStack</strong></td>
<td>主屏(桌面)所在ActivityStack</td>
</tr>
<tr>
<td><strong>mFocusedStack</strong></td>
<td>表示焦点ActivityStack,它能够获取用户输入</td>
</tr>
<tr>
<td><strong>mLastFocusedStack</strong></td>
<td>上一个焦点ActivityStack</td>
</tr>
<tr>
<td><strong>mActivityDisplays</strong></td>
<td>表示当前的显示设备,ActivityDisplay中绑定了若干ActivityStack。通过该属性就能间接获取所有ActivityStack的信息</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>setFocusedStack()</strong></td>
<td>设置当前的焦点ActivityStack</td>
</tr>
<tr>
<td><strong>adjustStackFocus()</strong></td>
<td> </td>
</tr>
<tr>
<td><strong>startHomeActivity()</strong></td>
<td>启动桌面</td>
</tr>
</tbody>
</table>
<p>ActivityStackSupervisor有很多与ActivityStack功能类似的行为,不过都是针对多个ActivityStack进行操作。
譬如<strong>findTaskLocked()</strong>, <strong>findActivityLocked()</strong>, <strong>topRunningActivityLocked()</strong>, <strong>ensureActivitiesVisibleLocked()</strong>等,</p>
<p><strong><code class="highlighter-rouge">场景 1</code></strong> 在启动一个新的Activity时,需要设置当前的焦点,通过<strong>AMS.setFocusedActivityLocked()</strong>函数,就能设置一个
ActivityRecord为当前的焦点Activity:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">void</span> <span class="nf">setFocusedActivityLocked</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mFocusedActivity</span> <span class="o">!=</span> <span class="n">r</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mFocusedActivity</span> <span class="o">=</span> <span class="n">r</span><span class="o">;</span>
<span class="o">...</span>
<span class="n">mStackSupervisor</span><span class="o">.</span><span class="na">setFocusedStack</span><span class="o">(</span><span class="n">r</span><span class="o">,</span> <span class="n">reason</span> <span class="o">+</span> <span class="s">" setFocusedActivity"</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="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mWindowManager</span><span class="o">.</span><span class="na">setFocusedApp</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">appToken</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">applyUpdateLockStateLocked</span><span class="o">(</span><span class="n">r</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>该函数的逻辑很简单,如果当前的焦点(mFocusedActivity)不是待显示的(r),则需要更新焦点; 然后,就发起了其他函数调用。
这里,需要通过ActivityStackSupervisor完成对ActivityStack的管理,通过调用<strong>setFocusedStack()</strong>来设置当前的焦点Stack,
焦点Stack就是焦点Activity所属的ActivityStack。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">setFocusedStack</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">r</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</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="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">TaskRecord</span> <span class="n">task</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="na">task</span><span class="o">;</span>
<span class="c1">// 判断输入的ActivityRecord是否为HomeActivity</span>
<span class="kt">boolean</span> <span class="n">isHomeActivity</span> <span class="o">=</span> <span class="o">!</span><span class="n">r</span><span class="o">.</span><span class="na">isApplicationActivity</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isHomeActivity</span> <span class="o">&&</span> <span class="n">task</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">isHomeActivity</span> <span class="o">=</span> <span class="o">!</span><span class="n">task</span><span class="o">.</span><span class="na">isApplicationTask</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">isHomeActivity</span> <span class="o">&&</span> <span class="n">task</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="n">ActivityRecord</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">task</span><span class="o">.</span><span class="na">stack</span><span class="o">.</span><span class="na">mActivityContainer</span><span class="o">.</span><span class="na">mParentActivity</span><span class="o">;</span>
<span class="n">isHomeActivity</span> <span class="o">=</span> <span class="n">parent</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&&</span> <span class="n">parent</span><span class="o">.</span><span class="na">isHomeActivity</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">moveHomeStack</span><span class="o">(</span><span class="n">isHomeActivity</span><span class="o">,</span> <span class="n">reason</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>只有前台的ActivityStack才能获取焦点,所以,该函数的功能就是要将待显示的Activity所在的ActivityStack挪到前台。
很重要的一个处理逻辑就是判定待显示的ActivityRecord的类型是否为HomeActivity,判定细节此处不表。结果是:
如果待显示的ActivityRecord类型为HomeActivity,则需要将HomeStack挪到前台; 否则,意味着要将HomeStack挪到后台。
挪动HomeStack,是通过<strong>moveHomeStack()</strong>这个函数实现的:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">moveHomeStack</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">toFront</span><span class="o">,</span> <span class="n">String</span> <span class="n">reason</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 1. 获取当前的Top Stack</span>
<span class="n">ArrayList</span><span class="o"><</span><span class="n">ActivityStack</span><span class="o">></span> <span class="n">stacks</span> <span class="o">=</span> <span class="n">mHomeStack</span><span class="o">.</span><span class="na">mStacks</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">topNdx</span> <span class="o">=</span> <span class="n">stacks</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">if</span> <span class="o">(</span><span class="n">topNdx</span> <span class="o"><=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">ActivityStack</span> <span class="n">topStack</span> <span class="o">=</span> <span class="n">stacks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">topNdx</span><span class="o">);</span>
<span class="c1">// 2. 判定HomeStack是否需要挪动</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">homeInFront</span> <span class="o">=</span> <span class="n">topStack</span> <span class="o">==</span> <span class="n">mHomeStack</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">homeInFront</span> <span class="o">!=</span> <span class="n">toFront</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mLastFocusedStack</span> <span class="o">=</span> <span class="n">topStack</span><span class="o">;</span>
<span class="n">stacks</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="n">mHomeStack</span><span class="o">);</span>
<span class="n">stacks</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">toFront</span> <span class="o">?</span> <span class="n">topNdx</span> <span class="o">:</span> <span class="mi">0</span><span class="o">,</span> <span class="n">mHomeStack</span><span class="o">);</span>
<span class="n">mFocusedStack</span> <span class="o">=</span> <span class="n">stacks</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">topNdx</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 3. 判定当前AMS是否完成启动</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mService</span><span class="o">.</span><span class="na">mBooting</span> <span class="o">||</span> <span class="o">!</span><span class="n">mService</span><span class="o">.</span><span class="na">mBooted</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="n">topRunningActivityLocked</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="kc">null</span> <span class="o">&&</span> <span class="n">r</span><span class="o">.</span><span class="na">idle</span><span class="o">)</span> <span class="o">{</span>
<span class="n">checkFinishBootingLocked</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<ol>
<li>
<p>获取当前的Top Stack,其实就是获取mStacks这个数组最后的元素。mStacks这个属性在ActivityStack和ActivityDisplay中都见过,它们是同一个东西,ActivityStackSupervisor要管理的就是这个东西;</p>
</li>
<li>判定当前HomeStack是否需要挪动。有四种情况:
<ul>
<li>homeInFront = true, toFront = false: 表示HomeStack在前台,要将其挪到后台,则需要将HomeStack挪到mStacks的0号位置;</li>
<li>homeInFront = true, toFront = true: 表示HomeStack在前台,要将其挪到前台,则不需要对mStacks进行调整;</li>
<li>homeInFront = false, toFront = true: 表示HomeStack在后台,要将其挪到前台,则需要将HomeStack挪到mStacks的末尾;</li>
<li>homeInFront = false, toFront = false: 表示HomeStack在后台,要将其挪到后台,则不需要对mStacks进行调整。</li>
</ul>
</li>
<li>判断当前AMS是否完成启动。如果当前是刚开机,AMS都还未启动完成,需要显示的Activity还处于idle状态,则需要发起一次是否启动完成的检查</li>
</ol>
<h3 id="processrecord">ProcessRecord</h3>
<p>AMS采用ProcessRecord这个数据结构来维护进程运行时的状态信息,当创建系统进程(system_process)或应用进程的时候,就会通过AMS初始化一个ProcessRecord。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>BatteryStats</strong></td>
<td>电量统计的接口</td>
</tr>
<tr>
<td><strong>ApplicationInfo</strong></td>
<td>系统进程的ApplicationInfo是从android包中解析出来的数据; 应用程序的ApplicationInfo是从AndroidManifest.xml中解析出来的数据</td>
</tr>
<tr>
<td><strong>Process Name</strong></td>
<td>进程名称</td>
</tr>
<tr>
<td><strong>UID</strong></td>
<td>进程的UID。系统进程的UID是1000(Process.SYSTEM_UID); 应用进程的UID是从10000(Process.FIRST_APPLICATION_UID)开始分配的</td>
</tr>
<tr>
<td><strong>maxAdj, curAdj, setAdj</strong></td>
<td>各种不同的OOM Adjustment值</td>
</tr>
<tr>
<td><strong>lastPss, lastPssTime</strong></td>
<td>物理内存(PSS)相关,进程中有对象创建或销毁时,PSS相关的属性会被更新。</td>
</tr>
<tr>
<td><strong>activities, services, receivers</strong></td>
<td>进程中的Android组件,随着进程的运行,这些信息都可能需要更新。譬如Activity的启动时,ProcessRecord.activies会增加一个实例; 销毁时,对将对应的实例从activities删除</td>
</tr>
<tr>
<td><strong>pkgList</strong></td>
<td>进程中运行的包</td>
</tr>
<tr>
<td><strong>thread</strong></td>
<td>该属性是IApplicationThread类型的对象</td>
</tr>
</tbody>
</table>
<p>ProcessRecord有“<strong>激活(Active)</strong>”和“<strong>非激活(Inactive)</strong>”两种状态,只有将ProcessRecord绑定到一个实际进程的时候,才是激活状态。
绑定成功后,<strong>thread</strong>属性就被赋值,表示ProcessRecord已经激活。
激活后,AMS就可以通过这个接口完成对应用进程的管理,譬如启动Activity、派发广播等。
将ProcessRecord绑定到应用进程的过程在<a href="">Android四大组件之Activity–应用进程与系统进程的通信</a>一文中有详细的分析。</p>
<table>
<thead>
<tr>
<th>行为</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>makeActive()</strong></td>
<td>将ProcessRecord置成激活状态</td>
</tr>
<tr>
<td><strong>makeInactive()</strong></td>
<td>将ProcessRecord置成非激活状态</td>
</tr>
<tr>
<td><strong>addPackage()</strong></td>
<td>向ProcessRecord添加包</td>
</tr>
</tbody>
</table>
<h2 id="22-关联关系">2.2 关联关系</h2>
<div align="center"><img src="/assets/images/activity/maintenace/2-activity-maintenace-relationship.png" alt="Maintenace Guideline" /></div>
<ul>
<li><strong>AMS运行在SystemServer进程中。</strong>SystemServer进程启动时,会通过SystemServer.startBootstrapServices()来创建一个AMS的对象;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">startBootstrapServices</span><span class="o">()</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">mActivityManagerService</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="n">ActivityManagerService</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="o">...</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>AMS通过ActivityStackSupervisor来管理Activity。</strong>AMS对象只会存在一个,在初始化的时候,会创建一个唯一的ActivityStackSupervisor对象;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="nf">ActivityManagerService</span><span class="o">(</span><span class="n">Context</span> <span class="n">systemContext</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">mStackSupervisor</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityStackSupervisor</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>ActivityStackSupervisor中维护了显示设备的信息。</strong>当有新的显示设备添加时,会创建一个新的ActivityDisplay对象;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">handleDisplayAddedLocked</span><span class="o">(</span><span class="kt">int</span> <span class="n">displayId</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">newDisplay</span> <span class="o">=</span> <span class="n">mActivityDisplays</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">displayId</span><span class="o">)</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">newDisplay</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ActivityDisplay</span> <span class="n">activityDisplay</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityDisplay</span><span class="o">(</span><span class="n">displayId</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>ActivityStack与显示设备的绑定。</strong>当需要创建一个ActivityStack时,需要将其绑定到具体的显示设备。
ActivityStackSupervisor通过ActivityContainer这个内部类对ActivityStack进行了一层封装,
所以,会首先创建一个ActivityContainer对象;然后,通过ActivityContainer.attachToDisplayLocked()函数进行具体的绑定操作;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">int</span> <span class="nf">createStackOnDisplay</span><span class="o">(</span><span class="kt">int</span> <span class="n">stackId</span><span class="o">,</span> <span class="kt">int</span> <span class="n">displayId</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">ActivityContainer</span> <span class="n">activityContainer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityContainer</span><span class="o">(</span><span class="n">stackId</span><span class="o">);</span>
<span class="n">mActivityContainers</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">stackId</span><span class="o">,</span> <span class="n">activityContainer</span><span class="o">);</span>
<span class="n">activityContainer</span><span class="o">.</span><span class="na">attachToDisplayLocked</span><span class="o">(</span><span class="n">activityDisplay</span><span class="o">);</span>
<span class="k">return</span> <span class="n">stackId</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>AMS维护了所有进程的信息ProcessRecord。</strong>当需要创建一个新的进程时,
会通过AMS.newProcessRecordLocked()函数来创建一个ProcessRecord对象,
ProcessRecord对象都保存在AMS.mPidsSelfLocked这个属性中;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="n">ProcessRecord</span> <span class="nf">newProcessRecordLocked</span><span class="o">(</span><span class="n">ApplicationInfo</span> <span class="n">info</span><span class="o">,</span> <span class="n">String</span> <span class="n">customProcess</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="o">{</span>
<span class="o">...</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ProcessRecord</span><span class="o">(</span><span class="n">stats</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="n">proc</span><span class="o">,</span> <span class="n">uid</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>通过ActivityStackSupervisor来创建ActivityRecord。</strong>当SystemServer进程收到来自应用进程的启动Activity请求时,
会通过ActivityStackSupervisor来创建一个ActivityRecord对象;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">final</span> <span class="kt">int</span> <span class="nf">startActivityLocked</span><span class="o">(</span><span class="n">IApplicationThread</span> <span class="n">caller</span><span class="o">,</span> <span class="o">...)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">ActivityRecord</span> <span class="n">r</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ActivityRecord</span><span class="o">(</span><span class="n">mService</span><span class="o">,</span> <span class="n">callerApp</span><span class="o">,</span> <span class="n">callingUid</span><span class="o">,</span> <span class="n">callingPackage</span><span class="o">,</span>
<span class="n">intent</span><span class="o">,</span> <span class="n">resolvedType</span><span class="o">,</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">mService</span><span class="o">.</span><span class="na">mConfiguration</span><span class="o">,</span> <span class="n">resultRecord</span><span class="o">,</span> <span class="n">resultWho</span><span class="o">,</span>
<span class="n">requestCode</span><span class="o">,</span> <span class="n">componentSpecified</span><span class="o">,</span> <span class="k">this</span><span class="o">,</span> <span class="n">container</span><span class="o">,</span> <span class="n">options</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>在ActivityStack上创建TaskRecord。</strong>当需要创建新的任务栈时,就会通过ActivityStack对象来创建一个TaskRecord对象,
这样就建立了ActivityStack和TaskRecord的关联;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">TaskRecord</span> <span class="nf">createTaskRecord</span><span class="o">(</span><span class="kt">int</span> <span class="n">taskId</span><span class="o">,</span> <span class="n">ActivityInfo</span> <span class="n">info</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">intent</span><span class="o">,</span>
<span class="n">IVoiceInteractionSession</span> <span class="n">voiceSession</span><span class="o">,</span> <span class="n">IVoiceInteractor</span> <span class="n">voiceInteractor</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">toTop</span><span class="o">)</span> <span class="o">{</span>
<span class="n">TaskRecord</span> <span class="n">task</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TaskRecord</span><span class="o">(</span><span class="n">mService</span><span class="o">,</span> <span class="n">taskId</span><span class="o">,</span> <span class="n">info</span><span class="o">,</span> <span class="n">intent</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">addTask</span><span class="o">(</span><span class="n">task</span><span class="o">,</span> <span class="n">toTop</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span>
<span class="k">return</span> <span class="n">task</span><span class="o">;</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>ActivityRecord的宿主TaskRecord。</strong>每一个ActivityRecord都需要找到自己的宿主TaskRecord,通过ActivityRecord.setTask()函数
就能建立ActivityRecord和TaskRecord的关联;</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">setTask</span><span class="o">(</span><span class="n">TaskRecord</span> <span class="n">newTask</span><span class="o">,</span> <span class="n">TaskRecord</span> <span class="n">taskToAffiliateWith</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">task</span> <span class="o">=</span> <span class="n">newTask</span><span class="o">;</span>
<span class="n">setTaskToAffiliateWith</span><span class="o">(</span><span class="n">taskToAffiliateWith</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li><strong>进程中运行的Activity信息。</strong>Activity在应用进程中运行,AMS中记录了进程中所有运行的Activity的信息,在ActivityRecord创建后,
会通过ProcessRecord.addPackage()函数,在ProcessRecord中登记ActivityRecord的信息</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">startSpecificActivityLocked</span><span class="o">(</span><span class="n">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="n">ProcessRecord</span> <span class="n">app</span> <span class="o">=</span> <span class="n">mService</span><span class="o">.</span><span class="na">getProcessRecordLocked</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="kc">true</span><span class="o">);</span>
<span class="o">...</span>
<span class="n">app</span><span class="o">.</span><span class="na">addPackage</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">packageName</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">versionCode</span><span class="o">,</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<h1 id="3-activity管理的延伸">3. Activity管理的延伸</h1>
<p>在分析完Activity管理的基础数据结构及关联关系后,想必各位读者已经感受到了Activity管理的复杂性。
如此庞大而精密的数据结构设计,是在什么背景下产生的呢?从已有的Activity设计中,
能否窥探出以后Android在Activity相关特性的发展方向呢?譬如多屏幕、多窗口的Activity显示。</p>
<p>笔者一直认为,研究Android源码不仅仅是理解Android的内部运行机制,更重要的是体会出其背后的设计思想,
总结出一套解决同类问题的方法论,然后再到具体的软件开发中进行实践,哪怕不在Android平台下开发,
提炼出来的方法论仍然是受用的。</p>
<h2 id="31-设计思想">3.1 设计思想</h2>
<ul>
<li>TaskRecord是一个栈,ActivityStack也是一个栈,Android这么设计有什么好处吗?</li>
</ul>
<hr />
<p><strong>参考资料</strong></p>
<p>1.</p>
Android四大组件之Activity--应用进程与系统进程的通信
2016-01-29T00:00:00+00:00
https://duanqz.github.io/Activity-IPC
<p>Android中有一个系统进程(system_process,在Lollipop之前,叫system_server),运行着系统的重要服务(AMS, PMS, WMS),
针对Activity而言,系统进程需要不断地调度Activity执行,管理Activity的状态;
每一个APK都需要运行在一个应用进程中,有自己独立的内存空间,
针对Activity而言,应用进程需要执行Activity生命周期函数(onCreate, onStart, …onDestroy)的具体逻辑。</p>
<p>应用进程需要频繁与系统进程通信,譬如Activity生命周期的各个方法都是需要经过系统进程调度的,只是在应用进程进行回调,这就需要从系统到应用的跨进程调用; 应用进程有需要将当前Activity的状态告诉系统进程,以便系统将Activity驱动到下一个状态,这就需要从应用到系统的跨进程调用。</p>
<p>应用进程与系统进程相互通信的手段,就是老生长谈的Binder机制。
本文要分析的自然不是Binder机制的内在原理,而是应用进程与系统进程建立在Binder之上通信的业务逻辑,Android为此设计了两个Binder接口:</p>
<ul>
<li><strong><a href="">IApplicationThread</a></strong>: 作为系统进程请求应用进程的接口;</li>
<li><strong><a href="">IActivityManager</a></strong>: 作为应用进程请求系统进程的接口。</li>
</ul>
<div align="center"><img src="/assets/images/activity/ipc/1-activity-ipc-overview.png" alt="IPC Overview" /></div>
<h1 id="1-通信接口的实现">1. 通信接口的实现</h1>
<p>先上一张类图,描述了两个Binder接口实现相关的类:</p>
<div align="center"><img src="/assets/images/activity/ipc/2-activity-ipc-classes.png" alt="Class Diagram" /></div>
<p>在接口实现手段上,应用进程与系统进程是一致的,毕竟逃不出Binder的标准实现。</p>
<p><strong>在系统进程一侧</strong>:</p>
<ul>
<li>最上层的有<strong>IActivityManager</strong>的接口定义,图中只列出了少数成员函数作为示意;</li>
<li>往下一层,有两个接口的实现,其中一个<strong>ActivityManagerNative</strong>作为服务端的“桩(Stub)”,其主要职责就是对远程传递过来的数据进行”反序列化(unparcel)”; 另一个<strong>ActivityManagerProxy</strong>作为服务的“代理(Proxy)”,运行在客户端,其主要职责就是将数据进行“序列化(parcel)”,再传递给远程的“桩(Stub)”;</li>
<li>再下一层,就是接口的具体业务实现<strong>AMS</strong>了,对“桩(Stub)”做了继承扩展,逻辑庞大到令人害怕。</li>
</ul>
<p><strong>在应用进程一侧</strong>:</p>
<ul>
<li>最上层的有<strong>IApplicationThread</strong>的接口定义,</li>
<li>往下一层,同样有“代理(Proxy)”和“桩(Stub)”,连类名都如出一辙;</li>
<li>再下一层,具体业务的实现类是<strong>ApplicationThread</strong>是ActivityThread的一个内部类,ApplicationThread负责响应系统进程发起的请求,这些请求大部分都是需要调度在应用进程的主线程执行,而ActivityThread是应用进程的主线程,通过Handle往主线程抛消息,ApplicationThread就轻松将具体执行任务的工作转交给了主线程。</li>
</ul>
<blockquote>
<p>“代理(Proxy)”和“桩(Stub)”是Binder接口实现时成对出现的概念。可以对应到一个生活中的例子:银行的取款机就像是银行的业务代理(Proxy),客户只需要通过取款机,就能方便地完成存、取款等业务。对于客户来说,银行实际是什么样子,钱存在哪,都不重要了,只要有取款机在就够了。对于取款机这个代理而言,它需要连接银行的服务器(Stub),完成数据传递。</p>
</blockquote>
<p>上面的类图中,用两种不同的颜色对类进行了区分。除了Binder的标准实现,还示意了几个不同的类依赖关系:</p>
<ul>
<li>ProcessRecord类和ActivityRecord类的对象是运行在系统进程之中,它们都由AMS管理;</li>
<li>ProcessRecord依赖于客户端进程中的ApplicationThread对象,而客户端进程中的Activity依赖于ActivityRecord。</li>
</ul>
<p>这种依赖关系的构建,是在启动一个全新的Activity时,利用<strong>IActivityManager</strong>和<strong>IApplicationThread</strong>完成了数据绑定操作。
绑定成功以后,相当于应用进程和系统进程相互打了个招呼,知道了彼此的存在,后续就可以进行更加深入友好的交流。</p>
<p>下面,我们就来分析一下启动一个Activity时数据绑定的过程。</p>
<h1 id="2-启动一个activity的通信过程">2. 启动一个Activity的通信过程</h1>
<p>当启动一个全新的Activity时,Activity的宿主进程可能还未启动,这时候就需要先启动宿主进程,在<a href="">Android四大组件之Activity–管理方式</a>一文中,我们介绍过:</p>
<ol>
<li>AMS通过ProcessRecord来维护进程运行时的状态信息,需要将应用进程绑定到ProcessRecord才能开始一个Application的构建;</li>
<li>AMS通过ActivityRecord来维护Activity运行时的状态信息,需要将Activity绑定到AMS中的ActivityRecord能开始Activity的生命周期。</li>
</ol>
<p>这两个绑定操作是应用进程与系统进程相互通信的开始。</p>
<h2 id="21-application与processrecord的绑定">2.1. Application与ProcessRecord的绑定</h2>
<p><strong>从应用进程到系统进程</strong></p>
<p>在ActivityThread创建的时候,会将自己的ApplicationThread绑定到AMS中,调用关系如下所示:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ActivityThread.main()
└── ActivityThread.attach()
└── IActivityManager.attachApplication(mAppThread)
└── Binder.transact()
</code></pre></div></div>
<p>应用进程作为客户端,通过<strong>IAcitivtyManager</strong>接口发起了跨进程调用,
跨进程传递的参数<strong>mAppThread</strong>就是IApplicationThread的实例,
执行流程从应用进程进入到系统进程:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ActivityManagerService.onTransact()
└── ActivityManagerService.attachApplication(IApplicationThread thread)
</code></pre></div></div>
<p>AMS作为IActivityManager接口的服务端实现,会响应客户端的请求,最终<strong>AMS.attachApplication()</strong>函数会被执行,
该函数接收跨进程传递过来的<strong>IApplicationThread</strong>实例,将其绑定到系统进程。
具体的绑定操作细节此处不表,我们只需要知道AMS中维护了所有进程运行时的信息(ProcessRecord),一旦发生了应用进程的绑定请求,
ProcessRecord.thread就被赋值成应用进程的<strong>IApplicationThread</strong>实例,这样一来,在AMS中就能通过该实例发起向应用进程的调用。</p>
<p><strong>从系统进程到应用进程</strong></p>
<p>在<strong>AMS.attachApplication()</strong>的过程中,会有一些信息要传递给应用进程,以便应用进程的初始化,系统进程会发起如下函数调用:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ActivityManagerService.attachApplication()
└── ActivityManagerService.attachApplicationLocked()
└── IApplicationThread.bindApplication(processName, appInfo ...)
└── Binder.transact()
</code></pre></div></div>
<p>此时,AMS会反转角色,即系统进程作为客户端,通过<strong>IApplicationThread</strong>接口向应用进程发起调用。
AMS中维护了ProcessRecord这个数据结构,包含了进程运行时的信息,譬如应用进程的名称processName、解析AndroidManifest.xml得到的数据结构ApplicationInfo等,其中,要传递给应用进程的数据都是Parcelable类型的实例。应用进程响应请求的调用关系如下所示:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ApplicationThread.onTransact()
└── ApplicationThread.bindApplication()
└── ActivityThread.H.handleMessage(BIND_APPLICATION)
└── ActivityThread.handleBindApplication()
└── Application.onCreate()
</code></pre></div></div>
<p><strong>ApplicationThread</strong>作为<strong>IApplicationThread</strong>接口的服务端实现,运行在应用进程中,
然后<strong>ApplicationThread.bindApplication()</strong>会被执行,完成一些简单的数据封装(AppBindData)后,通过Handler抛出<strong>BIND_APPLICATION</strong>消息。这一抛,就抛到了主线程上,<strong>ActivityThread.handleBindApplication()</strong>会被执行,接着就到了各位观众较为熟悉的<strong>Application.onCreate()</strong>函数。历经应用进程和系统进程之间的一个来回,总算是创建了一个应用程序。</p>
<h2 id="22-acitivity与activityrecord的绑定">2.2. Acitivity与ActivityRecord的绑定</h2>
<p><strong>ActivityRecord的Token</strong></p>
<p>在Activity类中有一个IBinder类型的属性:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private IBinder mToken;
</code></pre></div></div>
<p>IBinder类型表示这个属性是一个远程对象的引用,取了一个恰如其分的变量名:mToken。
为什么叫Token呢?这个名字源自于<strong>IApplicationToken.aidl</strong>这个接口,
最终ActivityRecord中的一个内部类Token实现了这个接口:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">static</span> <span class="kd">class</span> <span class="nc">Token</span> <span class="kd">extends</span> <span class="n">IApplicationToken</span><span class="o">.</span><span class="na">Stub</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">WeakReference</span><span class="o"><</span><span class="n">ActivityRecord</span><span class="o">></span> <span class="n">weakActivity</span><span class="o">;</span>
<span class="n">Token</span><span class="o">(</span><span class="n">ActivityRecord</span> <span class="n">activity</span><span class="o">)</span> <span class="o">{</span>
<span class="n">weakActivity</span> <span class="o">=</span> <span class="k">new</span> <span class="n">WeakReference</span><span class="o"><</span><span class="n">ActivityRecord</span><span class="o">>(</span><span class="n">activity</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>Token持有了一个ActivityRecord实例的弱引用。在创建一个ActivityRecord的时候,就会创建了一个Token类型的对象:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">ActivityRecord</span><span class="o">(</span><span class="n">ActivityManagerService</span> <span class="n">_service</span><span class="o">,</span> <span class="n">ProcessRecord</span> <span class="n">_caller</span><span class="o">,</span>
<span class="kt">int</span> <span class="n">_launchedFromUid</span><span class="o">,</span> <span class="n">String</span> <span class="n">_launchedFromPackage</span><span class="o">,</span> <span class="n">Intent</span> <span class="n">_intent</span><span class="o">,</span> <span class="n">String</span> <span class="n">_resolvedType</span><span class="o">,</span>
<span class="n">ActivityInfo</span> <span class="n">aInfo</span><span class="o">,</span> <span class="n">Configuration</span> <span class="n">_configuration</span><span class="o">,</span>
<span class="n">ActivityRecord</span> <span class="n">_resultTo</span><span class="o">,</span> <span class="n">String</span> <span class="n">_resultWho</span><span class="o">,</span> <span class="kt">int</span> <span class="n">_reqCode</span><span class="o">,</span>
<span class="kt">boolean</span> <span class="n">_componentSpecified</span><span class="o">,</span> <span class="n">ActivityStackSupervisor</span> <span class="n">supervisor</span><span class="o">,</span>
<span class="n">ActivityContainer</span> <span class="n">container</span><span class="o">,</span> <span class="n">Bundle</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="n">service</span> <span class="o">=</span> <span class="n">_service</span>
<span class="n">appToken</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Token</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>构造一个ActivityRecord时,会将自己(this)传给Token,变量<strong>ActivityRecord.appToken</strong>存的就是最终创建出来的Token。</p>
<p><strong>将Token传递给Activity</strong></p>
<p>在启动一个新的Activity时,AMS会将ActivityRecord的Token传递给应用进程,调用关系如下所示:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ActivityStackSupervisor.realStartActivityLocked(ActivityRecord, ...)
└── IApplicationThread.scheduleLaunchActivity(...token, ...)
// 将ActivityRecord的Token跨进程传递给应用进程
└── Binder.transact()
</code></pre></div></div>
<p><strong>ActivityStackSupervisor.realStartActivityLocked()</strong>表示要启动一个Activity实例,ActivityRecord作为参数。从ActivityRecord中提取出Token对象,作为跨进程调用的参数,通过<strong>IApplicationThread.scheduleLaunchActivity()</strong>传递到应用进程。</p>
<p>应用进程这一侧,会收到启动Activity的跨进程调用,触发以下一系列的函数调用:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ApplicationThread.onTransact()
└── ApplicationThread.scheduleLaunchActivity(...token, ...)
// token将被封装进ActivityClientRecord这个数据结构中
└── ActivityThread.H.handleMessage()
└── ActivityThread.handleLaunchActivity(LAUNCH_ACTIVITY)
└── ActivityThread.performLaunchActivity(ActivityClientRecord, ...)
// 从ActivityRecord取出token
└── Activity.attch(...token, ...)
</code></pre></div></div>
<p>标准的Binder服务端处理流程,收到AMS传递过来的Token对象,进行一下数据封装(ActivityClientRecord),然后通过Handler抛出一个<strong>LAUNCH_ACTIVITY</strong>消息。这个消息显然也是抛到了应用进程的主线程去执行,所以<strong>ActivityThread.performLaunchActivity()</strong>函数会在主线程上执行,该函数从封装的数据结构ActivityClientRecord中取出Token对象,调用<strong>Activity.attach()</strong>函数,将其绑定到Activity上,如此一来,就建立应用进程的Activity与系统进程中ActivityRecord的关联。</p>
<p>系统进程维护的是ActivityRecord,应用进程维护的是Activity,两者之间的映射关系就是利用Token来维系的。
应用进程的Activity在创建的时候,就被赋予了一个Token,拿着这个Token就能完成后续与系统进程的通信。
在发生Activity切换时,应用进程会将上一个Activity的Token(<strong>AMS.startActivity()</strong>的输入参数<strong>resultTo</strong>)传递给系统进程,系统进程会根据这个Token找到ActivityRecord,对其完成调度后,再通知应用进程:Activity状态发生了变化。</p>
<blockquote>
<p>Token就像一个令牌,揣着这个令牌,就是游走江湖的身份象征。
Activity出生的时候,管理者(AMS)就会登记Activity的真实身份(ActivityRecord),并颁发一个令牌(Token)给Activity。
以后这个Activity要有什么行为,都要交出令牌,让管理者核实一下真实身份。</p>
</blockquote>
Android四大组件之Activity--启动模式
2016-01-21T00:00:00+00:00
https://duanqz.github.io/Activity-LaunchMode
<p>Activity的启动模式一共有四种:Standard、SingleTop、SingleTask和SingleInstance。
之所以要设计出不同的启动模式,是因为要应对不同的用户交互场景,以便达到更好的体验。</p>
<p>要决定一个Activity的启动模式,只需要指定Activity的launchMode即可,有两种方式:</p>
<ol>
<li>
<p>在<strong>AndroidManifest.xml</strong>的<Activity>标签中设置<code>android:launchMode</code>属性,
在解析<strong>AndroidManifest.xml</strong>的时候,Activity相关的信息都会被<strong>ActivityInfo</strong>这个类管理起来,
在ActivityInfo中,launchMode会有以下四个取值,也就对应到了Activity的四种启动方式:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public static final int LAUNCH_MULTIPLE = 0; // 默认值,对应到Standard模式
public static final int LAUNCH_SINGLE_TOP = 1;
public static final int LAUNCH_SINGLE_TASK = 2;
public static final int LAUNCH_SINGLE_INSTANCE = 3;
</code></pre></div> </div>
<blockquote>
<p>ActivityInfo类的具体结构可以参见<a href="/2016-02-01-Activity-Maintenance">Android四大组件之Activity–管理方式</a>一文。</p>
</blockquote>
</li>
<li>
<p>通过<strong>Intent Flags</strong>设置Activity的启动方式:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Intent.FLAG_ACTIVITY_SINGLE_TOP // 以singleTop模式启动
Intent.FLAG_ACTIVITY_NEW_TASK // 以singleTask模式启动
</code></pre></div> </div>
<blockquote>
<p>需要说明的是,standard和singleInstance并没有对应的<strong>Intent Flags</strong>,可以从<a href="2016-07-29-Activity-LaunchProcess-Part1.md">Android四大组件之Activity–启动过程</a>一文
中了解Android内部对不同<strong>Intent Flags</strong>的处理。</p>
</blockquote>
</li>
</ol>
<p>作为一个Android开发人员,这四种Activity启动模式或许耳熟能详,但其使用场景,未必大家都有所了解。
下面我们就来介绍一下这四种启动模式对Activity的影响以及常见的使用场景。</p>
<h1 id="1-standardmultiple">1. standard(MULTIPLE)</h1>
<p><strong>MULTIPLE</strong>意指Activity可以有多个实例,不同任务中可以有不同的Activity实例,同一个任务中也可以有多个Activity实例。
默认情况下,Activity都会按照此种模式启动。</p>
<p>Lollipop(Android 5.0)前后对这种启动模式的处理方式是不同的:</p>
<ul>
<li>
<p><strong>在Lollipop之前</strong>,每次以MULTIPLE启动的Activity都会被压入当前任务的顶部,启动 <strong>N</strong> 次,在当前任务就会出现 <strong>N</strong> 个Activity的实例,每次Back键就会销毁一个,直到按了 <strong>N</strong> 次Back键。</p>
</li>
<li>
<p><strong>从Lollipop开始</strong>,如果要以MULTIPLE启动Activity都是来自同一应用,那么还是会像之前一样,压入当前任务的顶部; 如果是来自不同应用,那么将会创建一个新的任务,然后将Activity的实例压入新的任务中。Lollipop做的优化主要是针对多任务的显示。</p>
</li>
</ul>
<p>我们来具体分析一下standard模式,假设有来自两个不同应用程序(APK)的Activity:</p>
<div align="center"><img src="/assets/images/activity/launchmode/1-activity-launchmode-applications.png" alt="activities in different applications" /></div>
<p>Activity A和Activity B属于application 1, Activity X和Activity Y属于application 2。</p>
<div align="center"><img src="/assets/images/activity/launchmode/2-activity-launchmode-standard-pre-lollipop.png" alt="standard pre-lollipop" /></div>
<ol>
<li>首先,从Activity A启动了同一个应用中的Activity B,这时,会将Activity B压入任务Task 1的栈顶,Activity B变成了当前可见的实例;</li>
<li>然后,从Activity B启动了另外一个应用Application 2中的Activity Y,
这时,还是会将Activity Y压入Task 1的栈顶,Activity Y变成当前可见状态;</li>
<li>最后,当Back键响应时,会销毁当前可见的Activity Y,将之前的Activity B变成可见状态。</li>
</ol>
<p>如果在上述第2步的时候,又启动了Activity B,那么又会新建一个Activity B的实例,压入Task 1栈顶。
具体图例不展示了,各位看官可以通过<code class="highlighter-rouge">adb shell dumpsys activity</code>来查看这种场景下任务的情况:</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> adb shell dumpsys activity activities
<span class="c">...
</span><span class="gp">* TaskRecord{2fe897e2 #</span>3184 <span class="nv">A</span><span class="o">=</span>org.xo.launchmode.application1 <span class="nv">U</span><span class="o">=</span>0 <span class="nv">sz</span><span class="o">=</span>3<span class="o">}</span>
<span class="go"> affinity=org.xo.launchmode.application1
</span><span class="c"> ...
</span><span class="go"> Activities=[ActivityRecord{5ebd996 u0 org.xo.launchmode.application1/.ActivityA t3184}, ActivityRecord{267cd2bf u0 org.xo.launchmode.application1/.ActivityB t3184}]
</span><span class="gp"> * Hist #</span>2: ActivityRecord<span class="o">{</span>1d910399 u0 org.xo.launchmode.application1/.ActivityB t3184<span class="o">}</span>
<span class="c"> ...
</span><span class="gp"> * Hist #</span>1: ActivityRecord<span class="o">{</span>1e7fe4c8 u0 org.xo.launchmode.application1/.ActivityB t3184<span class="o">}</span>
<span class="c"> ...
</span><span class="gp"> * Hist #</span>0: ActivityRecord<span class="o">{</span>5ebd996 u0 org.xo.launchmode.application1/.ActivityA t3184<span class="o">}</span>
<span class="c">...</span></code></pre></figure>
<p>TaskRecord中有3个Activity的实例, 从顶到底排列,Hist #2和#1都是Activity B的实例,#0是底部的Activity A的实例。</p>
<p>这个过程是Lollipop之前的做法,只要不断地以standard模式启动Activity,就会将新的Activity实例压入当前的任务顶。
Lollipop开始有了一些优化,虽然从用户体验来看Activity的启动,和Lollipop之前并没有区别,
但从多任务窗口来查看当前的任务时,可以看到两个不同的任务,这更加符合用户的思维习惯。
Lollipop以standard模式启动Activity如下图所示:</p>
<div align="center"><img src="/assets/images/activity/launchmode/3-activity-launchmode-standard-lollipop.png" alt="standard pre-lollipop" /></div>
<ol>
<li>首先,还是一样的,从Activity A启动同一应用中的Activity B;</li>
<li>然后,<strong>区别来了</strong>,从Activity B启动了另外一个应用Application 2中的Activity Y,
这时,会新建一个任务Task 2,并将Activity Y压入 Task 2的栈顶,Task 1将会被挪到后台;</li>
<li>最后,当Back键响应时,会销毁Task 2中的Activity Y。Task 2变成了空的任务,
Task 1会被重新挪到前台,Activity Y的上一个Activity B变成了可见状态。</li>
</ol>
<p>以MULTIPLE模式启动Activity是最常见的用户场景,所以又名standard,在一个应用中,绝大多数Activity都是以这种模式启动的。</p>
<h1 id="2-singletop">2. singleTop</h1>
<p>singleTop意指仅有一个栈顶的Activity实例,如果当前任务的顶部就是待启动的Activity实例,那么并不会再创建一个新的Activity实例,而是仅仅调用已有实例的<strong>onNewIntent()</strong>方法,所以对于要以singleTop启动的Activity,需要处理<strong>onCreate()</strong>和<strong>onNewIntent()</strong>这两种情况下的启动参数。</p>
<p>以singleTop模式启动Activity的过程如下图所示:</p>
<div align="center"><img src="/assets/images/activity/launchmode/4-activity-launchmode-singletop.png" alt="singleTop" /></div>
<ol>
<li>首先,启动了Application 1中的Activity A,位于任务Task 1的顶部,Activity A是当前可见的实例;</li>
<li>然后,从Activity B又启动了相同了Activity B,<strong>区别于standard模式的地方来了</strong>
这时,并不会创建一个Activity B的新实例,而是调用已有Activity B的<strong>onNewIntent()</strong>方法;</li>
<li>最后,当Back键响应时,会销毁Activity B,重新将Activity A置为可见状态。</li>
</ol>
<p>singleTop和standard的区别在于,如果栈顶已经存在待启动Activity的实例,singleTop会利用已有的实例,而standard仍然会新建一个。
然而,singleTop只对当前应用启动有效,对于跨应用启动的情况,singleTop与standard模式没有区别。</p>
<p>什么是跨应用启动呢?假设当前有一个任务的顶部Activity设置了singleTop模式,如果有一个来自其他应用的Intent要启动这个Activity,就是跨应用启动。在这种情况下,Intent并不会去寻找已有任务的Activity,而是直接创建一个新的Activity实例:在lollipop之前,将Activity实例置于发起者的任务顶,在Lollipop之后,将Activity实例置于新任务的根部。singleTop和standard都是这么做的:</p>
<div align="center"><img src="/assets/images/activity/launchmode/5-activity-launchmode-singletop-cross-application.png" alt="singleTop Cross-Application" /></div>
<p>从Application 1中的Activity B以singleTop或standard模式启动Application 2中的Activity Y,行为都是一样的:新建Activity Y的实例,压入任务Task 2的栈顶。当Back键响应时,会销毁Activity Y的实例,将Task 1重新挪到前台,Activity Y之前的Activity B也会被重新置为可见状态。</p>
<p>singleTop也是很常见的启动模式,当我们只需要更新一个Activity的显示数据时,就会用到这种模式,譬如:
新建短信界面和短信会话界面都是com.android.mms.ui.ComposeMessageActivity,
当在这个界面编辑短信时,收到对方的一条新短信,这时,会要求启动短信会话界面,然而,此时并不需要新建一个ComposeMessageActivity的实例,
只需要刷新一下数据就可以了; 否则,会导致一种很困惑的用户操作行为:按一下Back,仍然停留在新建短信界面。</p>
<h1 id="3-singletask">3. singleTask</h1>
<p>singleTask意指一个Activity实例仅有一个宿主任务,这里有两种可能的情况:</p>
<ol>
<li>待启动的Activity实例存在;</li>
<li>待启动的Activity实例不存在。</li>
</ol>
<p>我们先来讨论第1种情况。如果某个任务中存在Activity实例,则仅仅是调用已有实例的<strong>onNewIntent()</strong>方法,在这之前,如果已有Activity实例不是位于任务顶,那么在它之上的其他Activity都会被销毁,确保目标Activity位于栈顶。</p>
<p>与singleTop相同的是,singleTask也会调用已有实例的<strong>onNewIntent()</strong>方法,不同的是:singleTop可能会存在多个Activity实例,而singleTask只会存在一个Activity实例,即无论来自何方的Intent,要以singleTask模式来启动Activity,最终都只会落到一个相同的任务中,而且这个任务中Activity实例有且仅有一个。</p>
<div align="center"><img src="/assets/images/activity/launchmode/6-activity-launchmode-singletask.png" alt="singleTask" /></div>
<ul>
<li>首先,从Activity A中以singleTask的方式启动Activity X。由于Activity X中已经存在于任务Task 2中,所以并不会新建一个Activity X的实例;</li>
<li>然后,Task 2会被挪至前台,由于Activity X并非Task 2的栈顶,所以需要清楚在Activity X之上的所有Activity,这样一来,Ativity Y就会被清除,走完它的生命周期;</li>
<li>最后,当Back键响应时,Activitiy X会被销毁,Task 1又会被重新挪到前台。</li>
</ul>
<p>再来看第2种情况:待启动Activity的实例不存在,这时候肯定会新建一个Activity的实例,但是否会新建一个任务呢?这又得分情况讨论,关键在于
taskAffinity这个属性。新建的Activity实例需要寻找一个宿主任务,当某个任务的affinity属性(<strong>TaskRecord.affinity</strong>)与Activity实例的taskAffinity属性(<strong>ActivityRecord.taskAffinity</strong>)相同时,则认为找到了宿主任务。所以,是否新建一个任务,还取决于taskAffinity属性。</p>
<p>如果没有为<Activity>标签设置<code>android:taskAffinity</code>属性,则会继承自<Application>标签;
如果<Application>标签也没有设置,则taskAffinity就是包名。
在这种情况下,从Application 1中的Activity A以singleTask模式启动Activity B时,并不会新建任务,而是将Activity B压入已有任务的顶部,就像standard模式一样。除非将<ActivityB>设置为一个新的值:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><activity</span>
<span class="na">android:name=</span><span class="s">".ActivityB"</span>
<span class="na">android:taskAffinity=</span><span class="s">""</span> <span class="nt">/></span></code></pre></figure>
<p>这时候,Activity B就会在一个新的任务中启动,并且新任务的affinity属性为空。图例就不给出了,可以通过
<code class="highlighter-rouge">adb shell dumpsys activity</code>命令查看Activity的情况:</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> adb shell dumpsys activity activities
<span class="gp">* TaskRecord{1d7975ca #</span>3187 <span class="nv">I</span><span class="o">=</span>org.xo.launchmode.application1/.ActivityB <span class="nv">U</span><span class="o">=</span>0 <span class="nv">sz</span><span class="o">=</span>1<span class="o">}</span>
<span class="c"> ...
</span><span class="go"> Activities=[ActivityRecord{1f51ad3 u0 org.xo.launchmode.application1/.ActivityB t3187}]
</span><span class="gp"> * Hist #</span>0: ActivityRecord<span class="o">{</span>1f51ad3 u0 org.xo.launchmode.application1/.ActivityB t3187<span class="o">}</span>
<span class="c">...
</span><span class="gp">* TaskRecord{2fe897e2 #</span>3184 <span class="nv">A</span><span class="o">=</span>org.xo.launchmode.application1 <span class="nv">U</span><span class="o">=</span>0 <span class="nv">sz</span><span class="o">=</span>1<span class="o">}</span>
<span class="go"> affinity=org.xo.launchmode.application1
</span><span class="c"> ...
</span><span class="go"> Activities=[ActivityRecord{5ebd996 u0 org.xo.launchmode.application1/.ActivityA t3184}]
</span><span class="gp"> * Hist #</span>0: ActivityRecord<span class="o">{</span>5ebd996 u0 org.xo.launchmode.application1/.ActivityA t3184<span class="o">}</span>
<span class="c">...</span></code></pre></figure>
<p>使用singleTask的场景并不多,当希望有一个仅有Activity实例为全局服务,而且这个Activity上又需要覆盖其他Activity时,就适合使用singleTask模式。譬如:设置的主界面com.android.settings.Settings,无论从哪个应用唤起Settings,都只会有一个Settings的实例存在,一些SubSettings界面也可以压到Settings的界面之上,当这个任务从后台重新挪到前台时,仍保持任务中的顺序相应Back键。</p>
<h1 id="4-singleinstance">4. singleInstance</h1>
<p>singleInstance意指有且仅有一个Activity实例,这一点与singleTask相同,但不同的是,singleTask的任务中可以有其他Activity实例,而singleInstance的任务中有且仅有一个singleInstance的Activity实例。</p>
<p>singleInstance的使用场景很少,但却非常显眼。譬如:来电界面com.android.incallui.InCallActivity,
这个界面可以被来其他地方的Intent唤起,而且这个界面之上不允许覆盖其他界面,每次启动时,都是一个唯一任务唯一实例的存在。</p>
<p>Activity启动模式是应用程序交互设计的基础,standard和singleTop模式相对用得较多; singleTask的行为显得有点诡异,因为这种模式会在用户不可知的情况下销毁Activity(宿主任务中,目标Activity实例之上的Activity都将被销毁); singleInstance不用则矣,用则就是一个特殊的存在。
这里,简单总结一下四种启动模式的异同:</p>
<ul>
<li>standard和singleTop的行为很相近,但遇到相同的栈顶Activity实例,standard会再次新建一个,而singleTop不会;</li>
<li>singleTop和singleTask在找到目标的Activity实例时,会调用其<strong>onNewIntent()</strong>方法; singleTop可以存在多个相同的Activity实例,而singleTask仅存在一个;</li>
<li>singleTask和singleInstance都只会存在一个相同的Activity实例,singleTask任务中可以有其他不同的Activity实例,而singleInstance栈中仅有一个Activity实例。</li>
</ul>
<hr />
<p><strong>参考资料</strong></p>
<ol>
<li><a href="http://developer.android.com/intl/zh-cn/guide/components/tasks-and-back-stack.html">http://developer.android.com/intl/zh-cn/guide/components/tasks-and-back-stack.html</a></li>
<li><a href="http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en">http://inthecheesefactory.com/blog/understand-android-activity-launchmode/en</a></li>
<li><a href="http://blog.csdn.net/luoshengyang/article/details/6714543">http://blog.csdn.net/luoshengyang/article/details/6714543</a></li>
</ol>
Android四大组件之Activity
2016-01-19T00:00:00+00:00
https://duanqz.github.io/Activity-Guideline
<p>在Android四大组件中,Activity是最重要也是最复杂的,为了厘清Activity的内在运行机制,
笔者从以下几个方面来分析Activity:</p>
<ol>
<li>
<p><strong><a href="2016-01-21-Activity-LaunchMode">Activity的四种启动模式</a></strong></p>
<p>作为一个Android开发人员,这四种Activity启动模式或许耳熟能详,但其使用场景,未必大家都有所了解,
之所以要设计出不同的启动模式,是因为要应对不同的用户交互场景,以便达到更好的体验。
这一节介绍四种启动模式对Activity的影响以及常见的使用场景。</p>
<ul>
<li>standard(MULTIPLE): 默认的Activity启动模式</li>
<li>singleTop</li>
<li>singleTask</li>
<li>singleInstance</li>
</ul>
</li>
<li>
<p><strong><a href="2016-07-15-AMS-LaunchProcess">ActivityManagerService的启动过程</a></strong></p>
<p>ActivityManagerService是Android四大组件的管理中心,是Android中非常非常重要的系统服务。
本文介绍ActivityManagerService的启动过程。</p>
<ul>
<li>SystemServer</li>
<li>SystemService</li>
<li>ServiceManager</li>
<li>ActivityManagerService</li>
</ul>
</li>
<li>
<p><strong><a href="2016-02-01-Activity-Maintenance">Activity的管理方式</a></strong></p>
<p>Activity管理最重要的数据结构是栈,Android设计了多层栈结构来维护Activity。
这一节介绍Activity相关的各种数据结构。</p>
<ul>
<li>Activity</li>
<li>ActivityInfo</li>
<li>ActivityRecord</li>
<li>TaskRecord</li>
<li>ActivityStack</li>
</ul>
</li>
<li>
<p><strong><a href="2016-01-29-Activity-IPC">应用进程与系统进程的通信</a></strong></p>
<p>应用进程需要频繁与系统进程通信,譬如Activity生命周期的各个方法都是需要经过系统进程调度的,这就需要从系统到应用的跨进程调用;
应用进程有需要将当前Activity的状态告诉系统进程,以便系统将Activity驱动到下一个状态,这就需要从应用到系统的跨进程调用。
这一节介绍应用进程与系统进程的通信接口实现以及跨进程数据绑定过程。</p>
<ul>
<li>IActivityManager</li>
<li>IApplicationThread</li>
<li>IApplicationToken</li>
<li>ActivityThread</li>
</ul>
</li>
<li>
<p><strong><a href="2016-07-29-Activity-LaunchProcess-Part1">Activity的启动过程(上)</a></strong></p>
<p>Activity的启动过程涉及到的逻辑非常庞大,贯穿了整个Activity的管理结构,与进程启动也息息相关。
这一节将以HomeActivity的启动时序为主线,详解在系统进程中经历所经历的Activity启动过程。</p>
<ul>
<li>ActivityStack</li>
<li>ActivityStackSupervisor</li>
<li>ActivityManagerService</li>
</ul>
</li>
<li>
<p><strong><a href="2016-10-23-Activity-LaunchProcess-Part2">Activity的启动过程(下)</a></strong></p>
<p>Activity的启动过程经过了系统进程的层层磨难,又会进入到一个系统进程与应用进程相互通信的阶段,两个进程合力完成Activity的启动过程。
这一节将仍然以启动时序为主线,不过会涉及到两个进程之间的通信。</p>
<ul>
<li>IApplicationThread</li>
<li>ActivityThread</li>
<li>ActivityManagerService</li>
</ul>
</li>
</ol>
GMS(Google Mobile Services)介绍
2016-01-06T00:00:00+00:00
https://duanqz.github.io/Intro-Google-Mobile-Service
<p><code class="highlighter-rouge">GMS</code>是Google针对移动终端提供的一系列服务,主要是面向于Android设备,不同于AOSP(Android Open Source Project),GMS需要Google的授权才能使用。本文从以下问题来探讨GMS:</p>
<ul>
<li>如何获取GMS授权?</li>
<li>为什么国内手机没有预装GMS?</li>
<li>如何预装GMS?</li>
</ul>
<h1 id="如何获取gms授权">如何获取GMS授权</h1>
<p>众所周知,Android由于其开源的特性导致版本众多,各厂商定制的版本甚至比Google自身的版本还要受欢迎,当年Samsung的S3大卖,足够秒杀Google的Nexus系列。虽然Google将Android开源,但并不意味着要将Android的主动权拱手想让。所以,从2013年开始,Google就针对不断分裂的Android做了一系列悄无声息的动作。</p>
<p>2013年9月,Google将一些服务从Android开发者官网上独立出来,称之为<strong>GMS</strong>,并声称这部分服务不再属于AOSP。换言之,这部分服务是不开源的。随后,Android对OEM厂商设置门槛,要求对使用<strong>GMS</strong>的设备进行授权和认证。彼时坊间流言四起,一则说Google要对GMS进行授权收费,一则说Google从此要将Android闭源,以收回对Android的控制权。然而,随后发生的事情并非流言所说,Google即没有对GMS进行授权收费,也没有将Android闭源。Google创造<strong>GMS</strong>这么一个概念,得从开放手机联盟说起。</p>
<p>Google于2007年联合业内知名的设备厂商、芯片厂商、移动运营商等成立了开放手机联盟(Open Handset Alliance, OHA),联盟的成员从最初的34家变成了现在的84家,最新的成员列表可查看<a href="http://www.openhandsetalliance.com/oha_members.html">http://www.openhandsetalliance.com/oha_members.html</a>。OHA的宗旨是打造更好的手机,第一个联合项目当然就是Android。加入OHA,意味这加入了Android的生态圈,一方面各地区的运营商更青睐推广OHA成员的手机,另一方面,加盟OHA的设备厂商也能更快的拿到最新的Android版本,先于OHA以外成员适配Android,占得市场先机。作为OHA的成员,需要遵循<strong>Compatible Android</strong>的约束,如何做到Android的兼容性,具体可以查阅<a href="https://source.android.com/compatibility">https://source.android.com/compatibility</a>,简单来说有三条:1. 使用AOSP进行适配; 2. 遵循Android兼容性定义; 3. 通过CTS(Compatibilty Test Suite)测试。满足这三条,就可以向Google申请GMS授权,Google会审查设备厂商的资质,通过Google的认证后,就能在出厂的手机中预装GMS了。</p>
<p>在GMS的影响下,出现了三种级别的设备:</p>
<ol>
<li>不预装GMS,这类设备通常对AOSP进行了深度定制,譬如国内的大部分手机;</li>
<li>预装部分GMS,这类设备不能使用Google的商标,譬如Samsung的手机;</li>
<li>预装全部GMS,这类设备可以使用Google的商标,譬如Nexus系列的手机。</li>
</ol>
<p>GMS包虽然由Google官方提供,但也流出了一些民间制作的版本,譬如<a href="https://s.basketbuild.com/gapps">https://s.basketbuild.com/gapps</a>就提供了众多Android版本的GMS包。
随着Android版本的升级,不同的GMS包的内容是有差异的,但组成结构是一样的,都是一个刷机包(以<strong>Lollipop 5.0</strong>的一个GMS包为例):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GMS-Lollipop-5.0/
├── META-INF # 包含签名信息和刷机脚本
└── system # 安装到system分区的文件
├── app # 安装到 system/app 目录
│ ├── Hangouts
│ ├── Gmail2
│ ├── GoogleCalendarSyncAdapter
│ ├── GoogleContactsSyncAdapter
| ...
├── framework # 安装到 system/framework 目录
├── priv-app
│ ├── GooglePartnerSetup
│ ├── GoogleServicesFramework
| ...
├── lib # 安装到 system/lib 目录
</code></pre></div></div>
<p>诸如Chrome,Gmail,Play,Search等应用一般都是包含在GMS包中的,只需要刷入GMS包即可。
热心的网友们也为一些不懂刷机的用户提供了一键安装GMS包的工具。</p>
<h1 id="国内的智能手机为什么没有预装gms">国内的智能手机为什么没有预装GMS</h1>
<p>只要满足Android的兼容性要求,就能申请GMS的授权,Google设定了GMS授权的门槛,目前全球范围内也只有少数几个设备厂商具备GMS授权。
国内市场的手机没有预装GMS除了资质审查不过关的原因以外,还有以下两个原因:</p>
<ul>
<li>Google于2010年退出中国,因此国内没有Google提供的服务,自然也就没有GMS;</li>
<li>国内市场上出货的手机,一般都对AOSP进行了深度定制,接入了本土的服务,因此没有GMS并不影响整机的体验。</li>
</ul>
<p>当国内一些设备厂商想要出货到海外时,没有GMS的劣势很快就显现出来了,大多数海外用户对GMS还是情有独钟的,一些深受国内用户喜欢的服务推广到海外后,并不受欢迎。当然,也有一些厂商在没有授权的情况下预装GMS,规模比较小时,Google通常都是放任不管,但当规模大到一定程度,就会受到Google的制裁。</p>
<p>Google通过OHA构建了Android生态圈,与Apple iOS和Windows Phone形成三足鼎立的局面。GMS的授权使用体现出Google作为一个互联网公司,宗旨还是在推行软件服务而不是硬件设备,同时也体现出了Google对Android的控制力。无论Android碎片化有多么严重,只有在Android生态圈下才能良性生存,要进入Android生态圈,就要满足Android的兼容性要求。对于设备厂商而言,进入Android生态圈,获取全球用户,最轻量的做法就是接入GMS。Google给设备厂商搭建了一条无形的路,按着这条路走,就能与Android共享繁荣。</p>
<p>当然,国内也有背离Android生态圈的存在,典型不过阿里的<code class="highlighter-rouge">YunOS</code>了,野心勃勃地想要构建同Android平行的一个生态圈。<strong>YunOS</strong>最初的推广手段比较简单粗暴,直接给设备厂商补贴,即设备厂商预装一台<strong>YunOS</strong>,就补贴一定的金额。阿里的这种勇气和决断是值得赞赏的,在硬件的盈利能力日趋下降的情况下,部分小的设备厂商也都挺乐意接受这种合作的。但对于一些瞄准海外的大的设备厂商而言,加入<code class="highlighter-rouge">YunOS</code>就意味着背离Android生态圈,自然不利于其海外的推广。有一些设备厂商提出了双品牌战略,即国内和国外使用不同的品牌,针对国内市场,对Android进行深度定制; 针对国外市场,积极的融入Android生态圈。</p>
<h1 id="如何预装gms">如何预装GMS</h1>
<p>通过Recovery将GMS包刷入手机这种方式属于后装,即已经有了Android刷机包后,再继续刷入GMS包。
下面我们讲述一下如何在AOSP源码环境下,编译出包含GMS的Android刷机包。</p>
<blockquote>
<p><strong>注意</strong>:在没有经过Google授权的情况下预装GMS等同于盗版行为,应当坚决抵制。
本例展示的GMS预装进作为学习用途,任何一个开发者都应该遵循”不作恶”的契约精神,这才是良性的发展之道。</p>
</blockquote>
<p>预装就是要将GMS包中的文件直接编译到Android的刷机包中,AOSP的编译系统能够轻松满足预装文件的需求。</p>
<p>对于APK而言,只需要利用编译系统提供的<strong>$(BUILD_PREBUILT)</strong>脚本,以GMSCore.apk预装为例,
新建一个Android.mk文件,添加如下内容:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include $(CLEAR_VARS) # 清除所有临时变量
LOCAL_MODULE := GmsCore # 定义当前模块的名称
LOCAL_SRC_FILES := priv-app/$(LOCAL_MODULE)/$(LOCAL_MODULE).apk #当前预装APK的路径
LOCAL_MODULE_CLASS := APPS # 模块的类别
LOCAL_CERTIFICATE := platform # APK签名
LOCAL_PRIVILEGED_MODULE := true # 表示当前APK到编译到priv-app目录
include $(BUILD_PREBUILT) # 预装编译
</code></pre></div></div>
<p>对于其他APK而言,只需要在Android.mk中添加类似的代码块即可,如此以来,这些APK就能编译产出到AOSP的<strong>out/target/product/$TARGET_DEVICE</strong>目录。为了将APK打包进最终的刷机包,还需要在对应<strong>$TARGET_DEVICE</strong>的device.mk中添加:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PRODUCT_PACKAGES += GmsCore
</code></pre></div></div>
静态代码检查工具--Klocwork
2015-12-05T00:00:00+00:00
https://duanqz.github.io/Code-Static-Analysis
<h1 id="1-前言">1. 前言</h1>
<blockquote>
<p>全球前5位的智能手机公司就有4个在使用Klocwork</p>
<p>全球前7位的芯片制造商就有5个在使用Klocwork</p>
<p>全球前10位的通讯设备制造商就有8个在使用Klocwork</p>
<p>美国前7位的航空与国防企业就有5个在使用Klocwork</p>
<p>全球前10位的医疗设备企业就有7个在使用Klocwork</p>
</blockquote>
<p>初识Klockwork,我看到了这么一些宣传语,于是就认定了Klocwork是行业内首屈一指的静态代码检查工具。</p>
<h1 id="2-功能">2. 功能</h1>
<h2 id="21-缺陷检测">2.1 缺陷检测</h2>
<p>Klocwork的自动化缺陷检测功能易于使用,定制灵活,具有图形化的构建和报表管理图形界面,拥有业界领先的消息过滤器,极具柔性的配置,和强大的学习调整知识库。</p>
<p>C/C++缺陷类型样例</p>
<ul>
<li>空指针释放</li>
<li>数组越界</li>
<li>内存管理问题(如:内存泄漏)</li>
<li>未初始化数据使用</li>
<li>编码风格问题(如:在条件中赋值)</li>
</ul>
<p>Java 缺陷类型样例</p>
<ul>
<li>效率错误(如:空的 finalize 方法)</li>
<li>可靠性问题(如资源泄漏)</li>
<li>可维护性问题(如:空的 catch 从句)</li>
</ul>
<h2 id="22-安全漏洞检测">2.2 安全漏洞检测</h2>
<p>Klocwork 安全漏洞分析是基于市场领先的缺陷检测能力,从原来的缺陷检测分离出来单独的安全漏洞检测和安全代码的缺陷检测。</p>
<p>C/C++安全漏洞分类</p>
<ul>
<li>访问控制缺陷</li>
<li>缓冲区溢出</li>
<li>DNS欺骗</li>
<li>注入缺陷</li>
<li>不安全的存储</li>
<li>未经验证的用户输入</li>
<li>忽略返回值</li>
</ul>
<p>Java 安全漏洞分类</p>
<ul>
<li>拒绝服务</li>
<li>注入缺陷(如:SQL 注入、进程注入等)</li>
<li>未经验证的输入</li>
<li>移动代码安全</li>
<li>有漏洞的会话管理</li>
<li>跨站点脚本</li>
<li>出错处理不当</li>
<li>有漏洞的访问控制</li>
</ul>
<h2 id="23-软件架构分析">2.3 软件架构分析</h2>
<p>Klocwork Architect为软件项目的架构提供一个生动的可视化的架构图,帮助项目经理、软件架构师查看、分析和优化复杂代码库的软件架构。</p>
<ul>
<li>
<p>代码结构展示。系统视图显示已有应用系统的物理结构,应用系统中的依赖关系连同应用系统和外部环境间的依赖关系也显示出来。</p>
</li>
<li>
<p>功能强大的代码审查。使用自动的流程图功能,从源代码分析得出程序的流程图,理解文件的处理流程,来帮助进行更加高效的编码。</p>
</li>
<li>
<p>优化头文件结构。在大型 C/C++ 软件中,一个普遍的可维护性问题就是过于复杂的头文件结构,比如包含了许多不必要的头文件。
k8 能够对您的系统执行自动分析,然后给出经过优化后,对潜在的系统文件大小、构建时间减少的量值,并给出优化您头文件的某种推荐方案的路径图。</p>
</li>
<li>
<p>发现并修复设计异常。软件架构师利用这种分析快速发现复杂的架构问题(如循环依赖)、模型改进等方面的内容然后为开发人员产生一个可操作的变更列表。</p>
</li>
</ul>
<h2 id="24-软件度量分析">2.4 软件度量分析</h2>
<p>Klocwork发布100多种关于文件、类、和函数/方法的代码度量,包括了从McCabe复杂度、Halstead程序度量、代码行数、继承数、循环数等各种基本度量。</p>
<ul>
<li>
<p>过程度量。过程度量是经典的项目管理层次的度量,如:测试用例执行完成的数量或者已经实现的需求的百分比。</p>
</li>
<li>
<p>资源度量。另外一种常用的、然而更加宏观层次的度量集合,通常用于访问人力资源方面,如:劳动生产率或者分配的项目 A 与项目 Z 的人时对比。</p>
</li>
</ul>
<p>Klocwork能够提供包括这些度量在内的最佳解决方案,自动产生直接从源代码中产生的客观的、可操作的产品度量。
Klocwork面向结果的产品度量,让项目ledaer和软件QA人员,随时查看软件的缺陷情况,控制软件的产品质量,并做出及时响应。</p>
StrictMode机制以及使用场景
2015-11-04T00:00:00+00:00
https://duanqz.github.io/StrictMode-Analysis
<h1 id="1-概览">1. 概览</h1>
<p><code class="highlighter-rouge">StrictMode</code>,严苛模式,是Android提供的一种运行时检测机制,用于检测代码运行时的一些不规范的操作,最常见的场景是用于发现主线程的IO操作。</p>
<p><code class="highlighter-rouge">StrictMode</code>包含两个维度的概念:</p>
<ul>
<li>
<p><strong>Policy(策略)</strong>: 是指StrictMode对一些违规操作的发现策略,分为两类:一类是针对一个具体的线程(ThreadPolicy),另一类是针对虚拟机的所有对象(VMPolicy)。</p>
</li>
<li>
<p><strong>Penalty(惩罚)</strong>:是指StrictMode发现违规操作后进行惩罚的方式,譬如绘制红框、打印日志、显示对话框、杀掉进程等。</p>
</li>
</ul>
<p>Android在很多关键的代码路径上都植入了StrictMode, 譬如磁盘读写、网络访问、系统进程启动等。StrictMode会根据设置的策略进行检查,如果某个进程在代码运行时出现了违规操作,那么就会受到”惩罚”。</p>
<p>应用程序可以利用StrictMode尽可能的发现一些编码的疏漏,
Android在 <a href="https://android.googlesource.com/platform/packages/experimental/+/master/StrictModeTest/">packages/experimental/StrictModeTest</a> 这个APK中提供了常见违规操作的样例,
谨作为大家的反面教材。</p>
<p>本文深入分析StrictMode背后的实现原理以及使用场景。</p>
<h1 id="2-strictmode机制">2. StrictMode机制</h1>
<p>StrictMode的实现涉及到以下源码:</p>
<ul>
<li><a href="https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/BlockGuard.java">libcore/dalvik/src/main/java/dalvik/system/BlockGuard.java</a></li>
<li><a href="https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/CloseGuard.java">libcore/dalvik/src/main/java/dalvik/system/CloseGuard.java</a></li>
<li><a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/StrictMode.java">frameworks/base/core/java/android/os/StrictMode.java</a></li>
</ul>
<p>总体而言,StrictMode机制所涉及到的代码量并不大,但Android中植入StrictMode的地方都是一些重要的关口,StrictMode所体现的面向接口编程的思想以及设计模式的应用,值得我们好好学习。
下面,我们就深入源码,分析一下StrictMode机制的内部实现。</p>
<h2 id="21-blockguard和closeguard">2.1 BlockGuard和CloseGuard</h2>
<p>StrictMode针对单个线程和虚拟机的所有对象都定义了检查策略,用来发现一些违规操作,譬如:主线程中的磁盘读/写、网络访问、未关闭cursor,这些操作都能够被StrictMode检查出来。
怎么做到的呢?在做这些操作时,植入StrictMode的检查代码就可以了。有一部分植入代码是建立在BlockGuard和CloseGuard之上的,可以说,StrictMode是建立在BlockGuard和CloseGuard之上的机制。</p>
<p><strong>Guard</strong>有“守卫”的意思,Block是阻塞的意思,在进行一些耗时操作时,譬如磁盘读写、网络操作,有一个守卫在监测着,它就是BlockGuard,如果这些耗时的操作导致主线程阻塞,BlockGuard就会发出通知;
Close对应到可打开的文件,在文件被打开后,也有一个守卫在监测着,它就是CloseGuard,如果没有关闭文件,则CloseGuard就会发出通知。</p>
<p>Android在很多代码中植入了BlockGuard,以BlockGuardOs为例,这个类代理大部分POSIX系统调用接口,所谓代理,从代码角度,就是在一个类外层再做一层封装。
BlockGuardOs代理了Os类,并植入了BlockGuard,譬如<strong>BlockGuardOs.read()</strong>这个系统调用:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">int</span> <span class="nf">read</span><span class="o">(</span><span class="n">FileDescriptor</span> <span class="n">fd</span><span class="o">,</span> <span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span><span class="o">,</span> <span class="kt">int</span> <span class="n">byteOffset</span><span class="o">,</span> <span class="kt">int</span> <span class="n">byteCount</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ErrnoException</span><span class="o">,</span> <span class="n">InterruptedIOException</span> <span class="o">{</span>
<span class="n">BlockGuard</span><span class="o">.</span><span class="na">getThreadPolicy</span><span class="o">().</span><span class="na">onReadFromDisk</span><span class="o">();</span>
<span class="k">return</span> <span class="n">os</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">fd</span><span class="o">,</span> <span class="n">bytes</span><span class="o">,</span> <span class="n">byteOffset</span><span class="o">,</span> <span class="n">byteCount</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>经过BlockGuard的一层封装,在每次进行read()系统调用时,都会通过BlockGuard通知发生了读磁盘的操作:<strong>BlockGuard.getThreadPolicy().onReadFromDisk()</strong></p>
<p>这里用到了BlockGuard的getThreadPolicy()方法,其实BlockGuard内部有一个Policy,定义了可能导致阻塞的方法:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Policy</span> <span class="o">{</span>
<span class="kt">void</span> <span class="nf">onWriteToDisk</span><span class="o">();</span>
<span class="kt">void</span> <span class="nf">onReadFromDisk</span><span class="o">();</span>
<span class="kt">void</span> <span class="nf">onNetwork</span><span class="o">();</span>
<span class="kt">int</span> <span class="nf">getPolicyMask</span><span class="o">();</span>
<span class="o">}</span></code></pre></figure>
<p>这个Policy只是一个接口定义,专门暴露给外部的 ,StrictMode就实现了BlockGuard.Policy:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">AndroidBlockGuardPolicy</span> <span class="kd">implements</span> <span class="n">BlockGuard</span><span class="o">.</span><span class="na">Policy</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onWriteToDisk</span><span class="o">()</span> <span class="o">{...}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onReadFromDisk</span><span class="o">()</span> <span class="o">{...}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onNetwork</span><span class="o">()</span> <span class="o">{...}</span>
<span class="kt">void</span> <span class="nf">onCustomSlowCall</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{...}</span>
<span class="kt">int</span> <span class="nf">getPolicyMask</span><span class="o">()</span> <span class="o">{...}</span>
<span class="o">}</span></code></pre></figure>
<p>StrictMode不仅针对BlockGuard.Policy实现了自身的处理逻辑,还扩展了一个方法onCustomSlowCall(),通过<strong>BlockGuard.setThreadPolicy()</strong>就能够将AndroidBlockGuardPolicy植入到BlockGuard中。</p>
<p>再来看CloseGuard,与BlockGuard一样,Android在很多代码中也植入了CloseGuard,以FileInputStream为例:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">FileInputStream</span> <span class="kd">extends</span> <span class="n">InputStream</span> <span class="o">{</span>
<span class="c1">// 1. 新建CloseGuard全局变量</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">CloseGuard</span> <span class="n">guard</span> <span class="o">=</span> <span class="n">CloseGuard</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
<span class="kd">public</span> <span class="nf">FileInputStream</span><span class="o">(</span><span class="n">File</span> <span class="n">file</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">FileNotFoundException</span> <span class="o">{</span>
<span class="o">...</span>
<span class="c1">// 2. 设置CloseGuard标志</span>
<span class="n">guard</span><span class="o">.</span><span class="na">open</span><span class="o">(</span><span class="s">"close"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">close</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="c1">// 3. 清除CloseGuard标志</span>
<span class="n">guard</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">finalize</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="c1">// 4. 判断Close标志是否被清除</span>
<span class="k">if</span> <span class="o">(</span><span class="n">guard</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">guard</span><span class="o">.</span><span class="na">warnIfOpen</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>CloseGuard的植入逻辑很清晰,一共分为4部分:</p>
<ol>
<li>新建一个CloseGuard全局变量</li>
<li>在对象初始化时,设置一个标志,表示需要调用close()方法关闭该对象</li>
<li>在关闭方法中,调用<strong>CloseGuard.close()</strong>方法,清除标志</li>
<li>在对象销毁时,调用<strong>CloaseGuard.warnIfOpen()</strong>方法,判断标志是否被清除:</li>
</ol>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">warnIfOpen</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">allocationSite</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">ENABLED</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">String</span> <span class="n">message</span> <span class="o">=</span>
<span class="o">(</span><span class="s">"A resource was acquired at attached stack trace but never released. "</span>
<span class="o">+</span> <span class="s">"See java.io.Closeable for information on avoiding resource leaks."</span><span class="o">);</span>
<span class="n">REPORTER</span><span class="o">.</span><span class="na">report</span><span class="o">(</span><span class="n">message</span><span class="o">,</span> <span class="n">allocationSite</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>从<strong>CloseGuard.warnIfOpen()</strong>方法中,可以看到,设置的标志就是<strong>allocationSite</strong>变量,如果该变量已经置空了,表示已经被清除过了; 否则,就会通过REPORTER报告违规操作。</p>
<p>REPORTER是CloseGuard暴露一个接口,StrictMode就实现了这个接口:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">AndroidCloseGuardReporter</span> <span class="kd">implements</span> <span class="n">CloseGuard</span><span class="o">.</span><span class="na">Reporter</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">report</span> <span class="o">(</span><span class="n">String</span> <span class="n">message</span><span class="o">,</span> <span class="n">Throwable</span> <span class="n">allocationSite</span><span class="o">)</span> <span class="o">{</span>
<span class="n">onVmPolicyViolation</span><span class="o">(</span><span class="n">message</span><span class="o">,</span> <span class="n">allocationSite</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>当StrictMode启用时,REPORTER就被设置成了AndroidCloseGuardReporter对象,如此一来,StrictMode就能够收集到CloseGuard报告的未关闭文件。</p>
<p><strong>至此,我们揭开了StrictMode的面纱:Android通过BlockGuard和CloseGuard在一些执行路径中埋入了一些切点,譬如磁盘读写时BlockGuard会收到通知,对象销毁时CloseGuard就会收到通知。</strong>
<strong>BlockGuard和CloseGuard都设计了一套接口:BlockGuard.Policy和CloseGuard.Reporter,其实就是切点的不同分类,StrictMode正是利用这两个接口所定义的一些切点,切入了自已的处理逻辑。</strong></p>
<blockquote>
<p><strong>题外话:</strong> 得益于面向接口的设计,我们可以另起炉灶,完全独立于StrictMode再实现其他BlockGuard.Policy和CloseGuard.Reporter的处理逻辑;
也可以对BlockGuard.Policy和CloseGuard.Reporter进行扩展,StrictMode只需要实现新的处理逻辑即可,这都不会影响已有的架构。
接口定义和接口实现分离,两者可以独立变化,适应新的需求,这是桥接模式(Bridge Pattern)的精髓,它降低了Guard和StrictMode两者之间的耦合度。<br />
从BlockGuardOs的设计中,我们也看到了代理模式(Proxy Pattern),BlockGuardOs对被代理的Os类进行了简单控制,植入了BlockGuard的逻辑,作为一个中间者,处于调用者和被调用实体中间,能够降低两者的耦合度。</p>
</blockquote>
<h2 id="22-strictmode-policy">2.2 StrictMode Policy</h2>
<p>StrictMode利用了BlockGuard和CloseGuard,不仅实现了两者定义的一些策略(Policy),还进行了扩展。
这些策略,在StrictMode看来,就是一些违规操作,下面我们深入介绍StrictMode定义的每一项违规操作。</p>
<h3 id="221-threadpolicy">2.2.1 ThreadPolicy</h3>
<p>ThreadPolicy细分为以下几种:</p>
<ul>
<li><strong>Disk Write</strong>:实现了BlockGuard的策略,写磁盘操作</li>
<li><strong>Disk Read</strong>:实现了BlockGuard的策略,读磁盘操作</li>
<li><strong>Network Access</strong>:实现了BlockGuard的策略,网络访问操作</li>
<li><strong>Custom Slow Code</strong>:StrictMode扩展的策略,目前只有Webview中植入了这项检查</li>
</ul>
<p>前三项的植入都是通过BlockGuard完成的,StrictMode只是实现了处理逻辑;最后一项是StrictMode扩展的,
如果一个方法执行的时间较长,可以调用<strong>StrictMode.noteSlowCall()</strong>方法来发出通知。
当这些操作发生后,最终都会调用<strong>StrictMode.handleViolation()</strong>方法进行处理,后文再展开讨论这个方法。</p>
<p>StrictMode通过标志位来区别以上几项,为此还特意封装了一个内部类<strong>StrictMode.ThreadPlicy</strong>,目的是为了方便标志位的设定。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">ThreadPolicy</span> <span class="o">{</span>
<span class="c1">// ThreadPolicy标志位</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">mask</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">ThreadPolicy</span><span class="o">(</span><span class="kt">int</span> <span class="n">mask</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">mask</span> <span class="o">=</span> <span class="n">mask</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 利用Builder完成标志位的初始化</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">mMask</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">detectDiskReads</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nf">enable</span><span class="o">(</span><span class="n">DETECT_DISK_READ</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>ThreadPolicy的初始化采用了<strong>构建者模式(Builder Pattern)</strong>,这样一来,调用者在使用起来就会更加自然一点,不用记住各个标志位的意义。
为了完成标志位的设定,StrictMode提供setThreadPolicy()方法,接收ThreadPolicy类型的对象作为参数,该方法的实现就是直接调用setThreadPolicyMask():</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">setThreadPolicy</span><span class="o">(</span><span class="kd">final</span> <span class="n">ThreadPolicy</span> <span class="n">policy</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setThreadPolicyMask</span><span class="o">(</span><span class="n">policy</span><span class="o">.</span><span class="na">mask</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">pivate</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">setThreadPolicyMask</span><span class="o">(</span><span class="kd">final</span> <span class="kt">int</span> <span class="n">policyMask</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setBlockGuardPolicy</span><span class="o">(</span><span class="n">policyMask</span><span class="o">);</span>
<span class="n">Binder</span><span class="o">.</span><span class="na">setThreadStrictModePolicy</span><span class="o">(</span><span class="n">policyMask</span><span class="o">);</span>
<span class="o">}</span></code></pre></figure>
<p>这里完成了两个层面的ThreadPolicy设定:</p>
<ul>
<li>
<p><strong>Java层</strong>,通过<strong>StrictMode.setBlockGuardPolicy()</strong>完成,最终会调用<strong>BlockGuard.setThreadPolicy()</strong>方法,
将AndroidBlockGuardPolicy对象设定为BlockGuard的Policy;</p>
</li>
<li>
<p><strong>Native层</strong>,通过<strong>Binder.setThreadStrictModePolicy()</strong>完成,看到这里,想必各位读者心中有了疑问,为什么还会有Native层的ThreadPolicy设置?
其实,看到Binder,就很容易联想到这是用作跨进程调用的,当<strong>进程A</strong>发起跨进程调用进入到<strong>进程B</strong>后,那<strong>进程B</strong>中的违规操作怎么判定呢?当然也需要一个ThreadPolicy,
Binder.setThreadStrictModePolicy()就是用来设置其他进程的ThreadPolicy。<strong>进程B</strong>中的违规异常也会通过Binder再传回<strong>进程A</strong>中,如此一来,
一个方法执行路径上的所有违规操作都会被StrictMode发现。</p>
</li>
</ul>
<h3 id="222-vmpolicy">2.2.2 VMPolicy</h3>
<p>ThreadPolicy主要用于发现一些容易导致主线程阻塞的操作,所以它针对的对象是单个线程; 而VMPolicy主要用于发现Java层的内存泄漏,所以它针对的是虚拟机的所有对象。
VMPolicy细分为以下几种:</p>
<ul>
<li><strong>Cursor Leak</strong>: 如果注册SQlite Cursor后没有调用close(),则发生了泄漏。</li>
<li><strong>Closable Leak</strong>: 这一项是CloseGuard的实现。如果存在未关闭的对象,则发生了泄漏。</li>
<li><strong>Activity Leak</strong>: 如果Activity在销毁后,其对象引用还被持有,则发生了泄漏。</li>
<li><strong>Instance Leak</strong>: StrictMode允许设置一个类的对象数量上限,在系统闲时,Strict会统计虚拟机中实际的对象数量,如果超出设定的上限,则判定为对象泄漏。</li>
<li><strong>Registion Leak</strong>: 如果注册IntentReceiver后没有调用unregisterReceiver(),则发生了泄漏</li>
<li><strong>File URI Exposure</strong>:这一项是安全性检查。通过file://的方式共享文件时,存在安全隐患。Android建议通过content://的方式共享文件。</li>
</ul>
<p>如同ThreadPolicy一样,VMPolicy也采用了<strong>构建者模式(Builder Pattern)</strong>进行初始化,在<strong>Closable Leak</strong>这一项的使用上,与BlockGuard有异曲同工之妙,
但除了<strong>Closable Leak</strong>是利用CloseGuard以外,其他违规项都是StrictMode自身的逻辑,需要在一些关键路径上植入StrictMode的代码,我们举出两例:</p>
<p><strong>例1:Cursor Leak</strong></p>
<p>以下是SQLite Cursor植入了StrictMode机制的代码片段:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="nf">SQLiteCursor</span><span class="o">(</span><span class="n">SQLiteCursorDriver</span> <span class="n">driver</span><span class="o">,</span> <span class="n">String</span> <span class="n">editTable</span><span class="o">,</span> <span class="n">SQLiteQuery</span> <span class="n">query</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">StrictMode</span><span class="o">.</span><span class="na">vmSqliteObjectLeaksEnabled</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mStackTrace</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DatabaseObjectNotClosedException</span><span class="o">().</span><span class="na">fillInStackTrace</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">mStackTrace</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="kd">protected</span> <span class="kt">void</span> <span class="nf">finalize</span><span class="o">()</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="c1">// if the cursor hasn't been closed yet, close it first</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mWindow</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">mStackTrace</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">sql</span> <span class="o">=</span> <span class="n">mQuery</span><span class="o">.</span><span class="na">getSql</span><span class="o">();</span>
<span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">sql</span><span class="o">.</span><span class="na">length</span><span class="o">();</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">onSqliteObjectLeaked</span><span class="o">(</span>
<span class="s">"Finalizing a Cursor that has not been deactivated or closed. "</span> <span class="o">+</span>
<span class="s">"database = "</span> <span class="o">+</span> <span class="n">mQuery</span><span class="o">.</span><span class="na">getDatabase</span><span class="o">().</span><span class="na">getLabel</span><span class="o">()</span> <span class="o">+</span>
<span class="s">", table = "</span> <span class="o">+</span> <span class="n">mEditTable</span> <span class="o">+</span>
<span class="s">", query = "</span> <span class="o">+</span> <span class="n">sql</span><span class="o">.</span><span class="na">substring</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="o">(</span><span class="n">len</span> <span class="o">></span> <span class="mi">1000</span><span class="o">)</span> <span class="o">?</span> <span class="mi">1000</span> <span class="o">:</span> <span class="n">len</span><span class="o">),</span>
<span class="n">mStackTrace</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">finalize</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>在SQLiteCursor对象初始化时,设置一个变量mStackTrace,如果开启了<strong>DETECT_VM_CURSOR_LEAKS</strong>,则将其置为非空。
在SQLiteCursor对象销毁时,会对Cursor是否关闭进行判断(如果CursorWindow非空,则说明没有显示关闭Cursor)。此时,如果mStackTrace变量非空,则向StrictMode报告。</p>
<p><strong>例2:Activity Leak</strong></p>
<p>再来一例Activity植入StrictMode的逻辑:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ActivityThread.performLaunchActivity()
└── StrictMode.incrementExpectedActivityCount()
ActivityThread.performDestroyActivity()
└── StrictMode.decrementExpectedActivityCount()
</code></pre></div></div>
<p>StrictMode对象中维护了Activity的计数器,统计着Activity对象的数量。在Activity对象新建和销毁的时候,会分别调用increment和decrement,对计数进行增减调整。
每一次有Activity对象销毁,都会调用<strong>VMDebug.countInstancesOfClass()</strong>,计算虚拟机中实际的Activity对象数量,如果实际Activity对象的数量超出了StrictMode的统计值,
则说明Activity对象虽然销毁了,但其对象引用还在,这就存在泄漏。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">decrementExpectedActivityCount</span><span class="o">(</span><span class="n">Class</span> <span class="n">klass</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="kt">long</span> <span class="n">instances</span> <span class="o">=</span> <span class="n">VMDebug</span><span class="o">.</span><span class="na">countInstancesOfClass</span><span class="o">(</span><span class="n">klass</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">instances</span> <span class="o">></span> <span class="n">limit</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Throwable</span> <span class="n">tr</span> <span class="o">=</span> <span class="k">new</span> <span class="n">InstanceCountViolation</span><span class="o">(</span><span class="n">klass</span><span class="o">,</span> <span class="n">instances</span><span class="o">,</span> <span class="n">limit</span><span class="o">);</span>
<span class="n">onVmPolicyViolation</span><span class="o">(</span><span class="n">tr</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(),</span> <span class="n">tr</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>从上述两例中,我们看到,虽然检测的形式各有不同,但本质都是在被检测的对象初始化时(constructor)设置一个标志,在对象销毁时(finalize)再对这个标志进行判断。其他的检测项的实现方式也都大同小异。</p>
<h2 id="23-strictmode-penalty">2.3 StrictMode Penalty</h2>
<p>当StrictMode发现有违规操作后,提供一些惩罚的方式,使用者可以自行组合。</p>
<ul>
<li><strong>penaltyDialog</strong>: 弹出对话框</li>
<li><strong>penaltyDeath</strong>: 杀掉进程</li>
<li><strong>penaltyDeathOnNetwork</strong></li>
<li><strong>penaltyFlashScreen</strong>: 在屏幕的最外围绘制一个红框</li>
<li><strong>penaltyLog</strong>:打印StrictMode日志</li>
<li><strong>penaltyDropBox</strong>:将日志保存到Dropbox中</li>
</ul>
<p>StrictMode内部是通过标志位来记录惩罚操作的类型的,并提供了上述的方法来设置不同的标志位。
StrictMode检测到违规操作后,最终都会调用<strong>StrictMode.handleViolation()</strong>方法,该方法中就会根据设置的标志位进行惩罚:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kt">void</span> <span class="nf">handleViolation</span><span class="o">(</span><span class="kd">final</span> <span class="n">ViolationInfo</span> <span class="n">info</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">justDropBox</span> <span class="o">=</span> <span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">policy</span> <span class="o">&</span> <span class="n">THREAD_PENALTY_MASK</span><span class="o">)</span> <span class="o">==</span> <span class="n">PENALTY_DROPBOX</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">justDropBox</span><span class="o">)</span> <span class="o">{</span>
<span class="n">dropboxViolationAsync</span><span class="o">(</span><span class="n">violationMaskSubset</span><span class="o">,</span> <span class="n">info</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="n">ActivityManagerNative</span><span class="o">.</span><span class="na">getDefault</span><span class="o">().</span><span class="na">handleApplicationStrictModeViolation</span><span class="o">(</span>
<span class="n">RuntimeInit</span><span class="o">.</span><span class="na">getApplicationObject</span><span class="o">(),</span>
<span class="n">violationMaskSubset</span><span class="o">,</span>
<span class="n">info</span><span class="o">);</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">((</span><span class="n">info</span><span class="o">.</span><span class="na">policy</span> <span class="o">&</span> <span class="n">PENALTY_DEATH</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">executeDeathPenalty</span><span class="o">(</span><span class="n">info</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>方法的实现逻辑一目了然,最终通过Binder发起跨进程调用,走到<strong>ActivityManagerService.handleApplicationStrictModeViolation()</strong>中</p>
<h1 id="3-strictmode使用">3. StrictMode使用</h1>
<p>StrictMode机制只是用于发现一些违规操作,这些违规操作一般都是我们编码的疏漏,在运行时会被StrictMode暴露出来,但StrictMode并非真正意思上的“动态代码检查”。
各位读者有必要知道StrictMode的使用范围:</p>
<ul>
<li>StrictMode只是用在开发调试阶段,在正式发布时,应该关掉StrictMode
<ul>
<li>AOSP的源码中,USER版并没有打开StrictMode</li>
<li>由于Android还会对StrictMode的检查策略进行调整,所以Google Play建议上架的APK都关闭StrictMode;
从另一个角度,Google认为所有StrictMode的错误,在正式发布前,都应该解决。</li>
</ul>
</li>
<li>StrictMode并不能发现Native层的违规操作,仅仅是用在Java层</li>
</ul>
<p>StrictMode的使用场景可以分为三类,使用方式也都比较固定,可见StrictMode的对外接口还是封装得比较优美的。
下面,我们逐个介绍一下StrictMode的使用场景。</p>
<h2 id="31-普通应用开启strictmode">3.1 普通应用开启StrictMode</h2>
<p>对于应用程序而言,Android提供了一个最佳使用实践:尽可能早的在<strong>android.app.Application</strong>或<strong>android.app.Activity</strong>的生命周期使能StrictMode,
onCreate()方法就是一个最佳的时机,越早开启就能在更多的代码执行路径上发现违规操作。</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">DEVELOPER_MODE</span><span class="o">)</span> <span class="o">{</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">setThreadPolicy</span><span class="o">(</span><span class="k">new</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">ThreadPolicy</span><span class="o">.</span><span class="na">Builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">detectDiskReads</span><span class="o">()</span>
<span class="o">.</span><span class="na">detectDiskWrites</span><span class="o">()</span>
<span class="o">.</span><span class="na">detectNetwork</span><span class="o">()</span> <span class="c1">// or .detectAll() for all detectable problems</span>
<span class="o">.</span><span class="na">penaltyLog</span><span class="o">()</span>
<span class="o">.</span><span class="na">build</span><span class="o">());</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">setVmPolicy</span><span class="o">(</span><span class="k">new</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">VmPolicy</span><span class="o">.</span><span class="na">Builder</span><span class="o">()</span>
<span class="o">.</span><span class="na">detectLeakedSqlLiteObjects</span><span class="o">()</span>
<span class="o">.</span><span class="na">detectLeakedClosableObjects</span><span class="o">()</span>
<span class="o">.</span><span class="na">penaltyLog</span><span class="o">()</span>
<span class="o">.</span><span class="na">penaltyDeath</span><span class="o">()</span>
<span class="o">.</span><span class="na">build</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="o">}</span></code></pre></figure>
<p>以上StrictMode的使能代码限定在<strong>DEVELOPER_MODE</strong>:</p>
<ul>
<li>设定了Disk Read, Disk Write, Network Access三项ThreadPolicy,惩罚是打印日志;</li>
<li>设定了Cursor Leak, Closable Leak两项VMPolicy,惩罚是打印日志和杀掉进程。</li>
</ul>
<p>当出现一些ThreadPolicy相关违规操作时,Android也提供了很多标准的解决方案,譬如<strong>Handler, AsyncTask, IntentService</strong>,能够将耗时的操作从主线程中分离出来。</p>
<h2 id="32-系统应用开启strictmode">3.2 系统应用开启StrictMode</h2>
<p>对于Android系统应用和系统进程(system_server)而言,其实默认就会开启StrictMode。
StrictMode提供了<strong>conditionallyEnableDebugLogging()</strong>方法:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">conditionallyEnableDebugLogging</span><span class="o">()</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">doFlashes</span> <span class="o">=</span> <span class="n">SystemProperties</span><span class="o">.</span><span class="na">getBoolean</span><span class="o">(</span><span class="n">VISUAL_PROPERTY</span><span class="o">,</span> <span class="kc">false</span><span class="o">)</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">amTheSystemServerProcess</span><span class="o">();</span>
<span class="kd">final</span> <span class="kt">boolean</span> <span class="n">suppress</span> <span class="o">=</span> <span class="n">SystemProperties</span><span class="o">.</span><span class="na">getBoolean</span><span class="o">(</span><span class="n">DISABLE_PROPERTY</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">doFlashes</span> <span class="o">&&</span> <span class="o">(</span><span class="n">IS_USER_BUILD</span> <span class="o">||</span> <span class="n">suppress</span><span class="o">))</span> <span class="o">{</span>
<span class="n">setCloseGuardEnabled</span><span class="o">(</span><span class="kc">false</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">int</span> <span class="n">threadPolicyMask</span> <span class="o">=</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">DETECT_DISK_WRITE</span> <span class="o">|</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">DETECT_DISK_READ</span> <span class="o">|</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">DETECT_NETWORK</span><span class="o">;</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">IS_USER_BUILD</span><span class="o">)</span> <span class="o">{</span>
<span class="n">threadPolicyMask</span> <span class="o">|=</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">PENALTY_DROPBOX</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">setThreadPolicyMask</span><span class="o">(</span><span class="n">threadPolicyMask</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">IS_USER_BUILD</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setCloseGuardEnabled</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">VmPolicy</span><span class="o">.</span><span class="na">Builder</span> <span class="n">policyBuilder</span> <span class="o">=</span> <span class="k">new</span> <span class="n">VmPolicy</span><span class="o">.</span><span class="na">Builder</span><span class="o">().</span><span class="na">detectAll</span><span class="o">().</span><span class="na">penaltyDropBox</span><span class="o">();</span>
<span class="o">...</span>
<span class="n">setVmPolicy</span><span class="o">(</span><span class="n">policyBuilder</span><span class="o">.</span><span class="na">build</span><span class="o">());</span>
<span class="n">setCloseGuardEnabled</span><span class="o">(</span><span class="n">vmClosableObjectLeaksEnabled</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></code></pre></figure>
<p>该方法的目的就是要设置ThreadPolicy和VMPolicy,不过会有一些条件判断,具体的逻辑不表。我们来看一下调用这个方法的地方:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SystemServer.run()
ServiceThread.run()
ActivityThread.handleBindApplication()
└── StrictMode.conditionallyEnableDebugLogging()
</code></pre></div></div>
<p>这表示在system_server进程、一些全局的消息线程(IoThread, UiThread, FgThread, DisplayThread)、应用进程这些东西启动的时候开启StrictMode。
在<strong>ActivityThread.handleBindApplication()</strong>中有这么一段限制:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">handleBindApplication</span><span class="o">(</span><span class="n">AppBindData</span> <span class="n">data</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">data</span><span class="o">.</span><span class="na">appInfo</span><span class="o">.</span><span class="na">flags</span> <span class="o">&</span>
<span class="o">(</span><span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_SYSTEM</span> <span class="o">|</span>
<span class="n">ApplicationInfo</span><span class="o">.</span><span class="na">FLAG_UPDATED_SYSTEM_APP</span><span class="o">))</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">conditionallyEnableDebugLogging</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>表示只为系统应用(<strong>FLAG_SYSTEM, FLAG_UPDATED_SYSTEM_APP</strong>)开启了StrictMode,其他应用还是需要自行开启。</p>
<h2 id="33-临时关闭strictmode">3.3 临时关闭StrictMode</h2>
<p>对于某些操作而言,我们明确知道是StrictMode定义的违规操作,但实际上对性能并没有什么影响,那么,在执行这类操作的时候,可以临时关闭StrictMode。
譬如针对一些主线程快速写磁盘的操作:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">StrictMode</span><span class="o">.</span><span class="na">ThreadPolicy</span> <span class="n">old</span> <span class="o">=</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">getThreadPolicy</span><span class="o">();</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">setThreadPolicy</span><span class="o">(</span><span class="k">new</span> <span class="n">StrictMode</span><span class="o">.</span><span class="na">ThreadPolicy</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="n">old</span><span class="o">)</span>
<span class="o">.</span><span class="na">permitDiskWrites</span><span class="o">()</span>
<span class="o">.</span><span class="na">build</span><span class="o">());</span>
<span class="c1">// 进行磁盘写操作...</span>
<span class="n">StrictMode</span><span class="o">.</span><span class="na">setThreadPolicy</span><span class="o">(</span><span class="n">old</span><span class="o">);</span></code></pre></figure>
<p>首先,将旧的ThreadPolicy缓存一把; 然后,设置新的ThreadPolicy,并允许写磁盘操作; 最后,在进行完正常的写磁盘操作后,还原旧的ThreadPolicy。
这样就临时性的避开了StrictMode对写磁盘操作的检查。</p>
Watchdog机制以及问题分析
2015-10-12T00:00:00+00:00
https://duanqz.github.io/Watchdog-Analysis
<h1 id="1-概览">1. 概览</h1>
<p><code class="highlighter-rouge">Watchdog</code>的中文的“看门狗”,有保护的意思。最早引入Watchdog是在单片机系统中,由于单片机的工作环境容易受到外界磁场的干扰,导致程序“跑飞”,造成整个系统无法正常工作,因此,引入了一个“看门狗”,对单片机的运行状态进行实时监测,针对运行故障做一些保护处理,譬如让系统重启。这种Watchdog属于硬件层面,必须有硬件电路的支持。</p>
<p>Linux也引入了Watchdog,在Linux内核下,当Watchdog启动后,便设定了一个定时器,如果在超时时间内没有对/dev/Watchdog进行写操作,则会导致系统重启。通过定时器实现的Watchdog属于软件层面。</p>
<p>Android设计了一个软件层面Watchdog,用于保护一些重要的系统服务,当出现故障时,通常会让Android系统重启。由于这种机制的存在,就经常会出现一些system_server进程被Watchdog杀掉而发生手机重启的问题。</p>
<p>本文期望回答以下问题:</p>
<blockquote>
<ol>
<li>Watchdog是怎么工作的?这涉及到Watchdog的工作机制。<br /></li>
<li>遇到Watchdog的问题该怎么办?这涉及到分析Watchdog问题的惯用方法。</li>
</ol>
</blockquote>
<h1 id="2-watchdog机制">2. Watchdog机制</h1>
<p>我们以<a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/Watchdog.java">frameworks/base/services/core/java/com/android/server/Watchdog.java</a>为蓝本,分析Watchdog的实现逻辑。为了描述方便,ActivityManagerService, PackageManagerService, WindowManagerService会分别简称为AMS, PKMS, WMS。</p>
<h2 id="21-watchdog的初始化">2.1 Watchdog的初始化</h2>
<p>Android的Watchdog是一个单例线程,在System Server时就会初始化Watchdog。Watchdog在初始化时,会构建很多<strong>HandlerChecker</strong>,大致可以分为两类:</p>
<ul>
<li>
<p><strong>Monitor Checker</strong>,用于检查是Monitor对象可能发生的死锁, AMS, PKMS, WMS等核心的系统服务都是Monitor对象。</p>
</li>
<li>
<p><strong>Looper Checker</strong>,用于检查线程的消息队列是否长时间处于工作状态。Watchdog自身的消息队列,Ui, Io, Display这些全局的消息队列都是被检查的对象。此外,一些重要的线程的消息队列,也会加入到<strong>Looper Checker</strong>中,譬如AMS, PKMS,这些是在对应的对象初始化时加入的。</p>
</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">private</span> <span class="nf">Watchdog</span><span class="o">()</span> <span class="o">{</span>
<span class="o">....</span>
<span class="n">mMonitorChecker</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="n">FgThread</span><span class="o">.</span><span class="na">getHandler</span><span class="o">(),</span>
<span class="s">"foreground thread"</span><span class="o">,</span> <span class="n">DEFAULT_TIMEOUT</span><span class="o">);</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">mMonitorChecker</span><span class="o">);</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="k">new</span> <span class="n">Handler</span><span class="o">(</span><span class="n">Looper</span><span class="o">.</span><span class="na">getMainLooper</span><span class="o">()),</span>
<span class="s">"main thread"</span><span class="o">,</span> <span class="n">DEFAULT_TIMEOUT</span><span class="o">));</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="n">UiThread</span><span class="o">.</span><span class="na">getHandler</span><span class="o">(),</span>
<span class="s">"ui thread"</span><span class="o">,</span> <span class="n">DEFAULT_TIMEOUT</span><span class="o">));</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="n">IoThread</span><span class="o">.</span><span class="na">getHandler</span><span class="o">(),</span>
<span class="s">"i/o thread"</span><span class="o">,</span> <span class="n">DEFAULT_TIMEOUT</span><span class="o">));</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="n">DisplayThread</span><span class="o">.</span><span class="na">getHandler</span><span class="o">(),</span>
<span class="s">"display thread"</span><span class="o">,</span> <span class="n">DEFAULT_TIMEOUT</span><span class="o">));</span>
<span class="o">...</span>
<span class="o">}</span></code></pre></figure>
<p>两类<strong>HandlerChecker</strong>的侧重点不同,<strong>Monitor Checker</strong>预警我们不能长时间持有核心系统服务的对象锁,否则会阻塞很多函数的运行;
<strong>Looper Checker</strong>预警我们不能长时间的霸占消息队列,否则其他消息将得不到处理。这两类都会导致系统卡住(System Not Responding)。</p>
<h2 id="22-添加watchdog监测对象">2.2 添加Watchdog监测对象</h2>
<p>Watchdog初始化以后,就可以作为system_server进程中的一个单独的线程运行了。但这个时候,还不能触发Watchdog的运行,因为AMS, PKMS等系统服务还没有加入到Watchdog的监测集。
所谓监测集,就是需要Watchdog关注的对象,Android中有成千上万的消息队列在同时运行,然而,Watchdog毕竟是系统层面的东西,它只会关注一些核心的系统服务。</p>
<p>Watchdog提供两个方法,分别用于添加<strong>Monitor Checker</strong>对象和<strong>Looper Checker</strong>对象:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">addMonitor</span><span class="o">(</span><span class="n">Monitor</span> <span class="n">monitor</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// 将monitor对象添加到Monitor Checker中,</span>
<span class="c1">// 在Watchdog初始化时,可以看到Monitor Checker本身也是一个HandlerChecker对象</span>
<span class="n">mMonitors</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">monitor</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addThread</span><span class="o">(</span><span class="n">Handler</span> <span class="n">thread</span><span class="o">,</span> <span class="kt">long</span> <span class="n">timeoutMillis</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="k">if</span> <span class="o">(</span><span class="n">isAlive</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">"Threads can't be added once the Watchdog is running"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">thread</span><span class="o">.</span><span class="na">getLooper</span><span class="o">().</span><span class="na">getThread</span><span class="o">().</span><span class="na">getName</span><span class="o">();</span>
<span class="c1">// 为Handler构建一个HandlerChecker对象,其实就是**Looper Checker**</span>
<span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">HandlerChecker</span><span class="o">(</span><span class="n">thread</span><span class="o">,</span> <span class="n">name</span><span class="o">,</span> <span class="n">timeoutMillis</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>被Watchdog监测的对象,都需要将自己添加到Watchdog的监测集中。以下是AMS的类定义和构造器的代码片段:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">ActivityManagerService</span> <span class="kd">extends</span> <span class="n">ActivityManagerNative</span>
<span class="kd">implements</span> <span class="n">Watchdog</span><span class="o">.</span><span class="na">Monitor</span><span class="o">,</span> <span class="n">BatteryStatsImpl</span><span class="o">.</span><span class="na">BatteryCallback</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nf">ActivityManagerService</span><span class="o">(</span><span class="n">Context</span> <span class="n">systemContext</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">Watchdog</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">addMonitor</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="n">Watchdog</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">addThread</span><span class="o">(</span><span class="n">mHandler</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">monitor</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="o">}</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<p>AMS实现了Watchdog.Monitor接口,这个接口只有一个方法,就是monitor(),它的作用后文会再解释。这里可以看到在AMS的构造器中,将自己添加到<strong>Monitor Checker</strong>对象中,然后将自己的handler添加到<strong>Looper Checker</strong>对象中。
其他重要的系统服务添加到Watchdog的代码逻辑都与AMS差不多。</p>
<p>整个Android系统中,被monitor的对象并不多,十个手指头就能数出来Watchdog.Monitor的实现类的个数。</p>
<h2 id="23-watchdog的监测机制">2.3 Watchdog的监测机制</h2>
<p>Watchdog本身是一个线程,它的run()方法实现如下:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Override</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="kt">boolean</span> <span class="n">waitedHalf</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="kc">true</span><span class="o">)</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="o">...</span>
<span class="c1">// 1. 调度所有的HandlerChecker</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">0</span><span class="o">;</span> <span class="n">i</span><span class="o"><</span><span class="n">mHandlerCheckers</span><span class="o">.</span><span class="na">size</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">HandlerChecker</span> <span class="n">hc</span> <span class="o">=</span> <span class="n">mHandlerCheckers</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="n">hc</span><span class="o">.</span><span class="na">scheduleCheckLocked</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 2. 开始定期检查</span>
<span class="kt">long</span> <span class="n">start</span> <span class="o">=</span> <span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">();</span>
<span class="k">while</span> <span class="o">(</span><span class="n">timeout</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">wait</span><span class="o">(</span><span class="n">timeout</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Log</span><span class="o">.</span><span class="na">wtf</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="n">timeout</span> <span class="o">=</span> <span class="n">CHECK_INTERVAL</span> <span class="o">-</span> <span class="o">(</span><span class="n">SystemClock</span><span class="o">.</span><span class="na">uptimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="n">start</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// 3. 检查HandlerChecker的完成状态</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="n">waitState</span> <span class="o">=</span> <span class="n">evaluateCheckerCompletionLocked</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">waitState</span> <span class="o">==</span> <span class="n">COMPLETED</span><span class="o">)</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">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">waitState</span> <span class="o">==</span> <span class="n">WAITING</span><span class="o">)</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">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">waitState</span> <span class="o">==</span> <span class="n">WAITED_HALF</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="k">continue</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// 4. 存在超时的HandlerChecker</span>
<span class="n">blockedCheckers</span> <span class="o">=</span> <span class="n">getBlockedCheckersLocked</span><span class="o">();</span>
<span class="n">subject</span> <span class="o">=</span> <span class="n">describeCheckersLocked</span><span class="o">(</span><span class="n">blockedCheckers</span><span class="o">);</span>
<span class="n">allowRestart</span> <span class="o">=</span> <span class="n">mAllowRestart</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 5. 保存日志,判断是否需要杀掉系统进程</span>
<span class="n">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="n">TAG</span><span class="o">,</span> <span class="s">"*** GOODBYE!"</span><span class="o">);</span>
<span class="n">Process</span><span class="o">.</span><span class="na">killProcess</span><span class="o">(</span><span class="n">Process</span><span class="o">.</span><span class="na">myPid</span><span class="o">());</span>
<span class="n">System</span><span class="o">.</span><span class="na">exit</span><span class="o">(</span><span class="mi">10</span><span class="o">);</span>
<span class="o">}</span> <span class="c1">// end of while (true)</span>
<span class="o">}</span></code></pre></figure>
<p>以上代码片段主要的运行逻辑如下:</p>
<ol>
<li>Watchdog运行后,便开始无限循环,依次调用每一个HandlerChecker的scheduleCheckLocked()方法</li>
<li>调度完HandlerChecker之后,便开始定期检查是否超时,每一次检查的间隔时间由<strong>CHECK_INTERVAL</strong>常量设定,为30秒</li>
<li>每一次检查都会调用evaluateCheckerCompletionLocked()方法来评估一下HandlerChecker的完成状态:
<ul>
<li>COMPLETED表示已经完成</li>
<li>WAITING和WAITED_HALF表示还在等待,但未超时</li>
<li>OVERDUE表示已经超时。默认情况下,timeout是1分钟,但监测对象可以通过传参自行设定,譬如PKMS的<strong>Handler Checker</strong>的超时是10分钟</li>
</ul>
</li>
<li>如果超时时间到了,还有HandlerChecker处于未完成的状态(OVERDUE),则通过getBlockedCheckersLocked()方法,获取阻塞的HandlerChecker,生成一些描述信息</li>
<li>保存日志,包括一些运行时的堆栈信息,这些日志是我们解决Watchdog问题的重要依据。如果判断需要杀掉system_server进程,则给当前进程(system_server)发送signal 9</li>
</ol>
<p>只要Watchdog没有发现超时的任务,HandlerChecker就会被不停的调度,那HandlerChecker具体做一些什么检查呢? 直接上代码:</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">HandlerChecker</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">scheduleCheckLocked</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Looper Checker中是不包含monitor对象的,判断消息队列是否处于空闲</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mMonitors</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="n">mHandler</span><span class="o">.</span><span class="na">getLooper</span><span class="o">().</span><span class="na">isIdling</span><span class="o">())</span> <span class="o">{</span>
<span class="n">mCompleted</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="c1">// 将Monitor Checker的对象置于消息队列之前,优先运行</span>
<span class="n">mHandler</span><span class="o">.</span><span class="na">postAtFrontOfQueue</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</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="c1">// 依次调用Monitor对象的monitor()方法</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">0</span> <span class="o">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">size</span> <span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="kd">synchronized</span> <span class="o">(</span><span class="n">Watchdog</span><span class="o">.</span><span class="na">this</span><span class="o">)</span> <span class="o">{</span>
<span class="n">mCurrentMonitor</span> <span class="o">=</span> <span class="n">mMonitors</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="o">}</span>
<span class="n">mCurrentMonitor</span><span class="o">.</span><span class="na">monitor</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="o">}</span></code></pre></figure>
<ul>
<li>
<p>对于<strong>Looper Checker</strong>而言,会判断线程的消息队列是否处于空闲状态。
如果被监测的消息队列一直闲不下来,则说明可能已经阻塞等待了很长时间</p>
</li>
<li>
<p>对于<strong>Monitor Checker</strong>而言,会调用实现类的monitor方法,譬如上文中提到的AMS.monitor()方法,
方法实现一般很简单,就是获取当前类的对象锁,如果当前对象锁已经被持有,则monitor()会一直处于wait状态,直到超时,这种情况下,很可能是线程发生了死锁</p>
</li>
</ul>
<p><strong>至此,我们已经分析了Watchdog的工作机制,回答了我们提出的第一个问题:</strong></p>
<p><strong>Watchdog定时检查一些重要的系统服务,举报长时间阻塞的事件,甚至杀掉system_server进程,让Android系统重启。</strong></p>
<h1 id="3-问题分析方法">3. 问题分析方法</h1>
<h2 id="31-日志获取">3.1 日志获取</h2>
<p>Andriod的日志门类繁多,而且,为了调试的需要,设备厂商和应用开发者都会在AOSP的基础上增加很多日志。
面对如此庞大复杂的日志系统,通常只有对应领域的专家才能看懂其透露的细节信息,就像去医院就诊,医生一看检查报告就知道患者身体出了什么问题,而外行对这些诊断信息往往是束手无策的。</p>
<p>解决Watchdog相关的问题,对日志的要求比较高,有些问题与当时的系统环境相关,仅仅凭借单一的日志并不能定位问题。
以下罗列出获取Android日志的一些重要手段,部分场景下,Watchdog相关的问题甚至需要以下所有的日志:</p>
<ul>
<li>
<p><strong>logcat</strong> 通过<code class="highlighter-rouge">adb logcat</code>命令输出Android的一些当前运行日志,可以通过logcat的 <strong>-b</strong> 参数指定要输出的日志缓冲区,缓冲区对应着logcat的一种日志类型。
高版本的logcat可以使用 <strong>-b all</strong> 获取到所有缓冲区的日志</p>
<ul>
<li><em>event</em> 通过android.util.EventLog工具类打印的日志,一些重要的系统事件会使用此类日志</li>
<li><em>main</em> 通过android.util.Log工具类打印的日志,应用程序,尤其是基于SDK的应用程序,会使用此类日志</li>
<li><em>system</em> 通过android.util.Slog工具类打印的日志,系统相关的日志一般都是使用此类日志,譬如SystemServer</li>
<li><em>radio</em> 通过android.util.Rlog工具类打印的日志,通信模块相关的日志一般都是使用此类日志,譬如RIL</li>
</ul>
</li>
<li>
<p><strong>dumpsys</strong> 通过<code class="highlighter-rouge">adb dumpsys</code>命令输出一些重要的系统服务信息,譬如内存、电源、磁盘等,
工作原理可以查阅<a href="/2015-07-19-Intro-to-dumpsys">dumpsys介绍</a>一文</p>
</li>
<li>
<p><strong>traces</strong> 该文件记录了一个时间段的函数调用栈信息,通常在应用发生ANR(Application Not Responding)时,会触发打印各进程的函数调用栈。
站在Linux的角度,其实就是向进程发送SIGNAL_QUIT(3)请求,譬如,我们可以通过<code class="highlighter-rouge">adb shell kill -3 <pid></code>命令,打印指定进程<pid>的的trace。
SIGNAL_QUIT(3)表面意思有一点误导,它其实并不会导致进程退出。输出一般在 */data/anr/traces.txt* 文件中,当然,这是可以灵活配置的,
Android提供的系统属性dalvik.vm.stack-trace-file可以用来配置生成traces文件的位置。</pid></p>
</li>
<li>
<p><strong>binder</strong> 通过Binder跨进程调用的日志,可以通过<code class="highlighter-rouge">adb shell cat</code>命令从 <em>/proc/binder</em> 下取出对应的日志</p>
<ul>
<li>failed_transaction_log</li>
<li>transaction_log</li>
<li>transactions</li>
<li>stats</li>
</ul>
</li>
<li>
<p><strong>dropbox</strong> 为了记录历史的logcat日志,Android引入了Dropbox,将历史日志持久化到磁盘中(<strong>/data/system/dropbox</strong>)。
logcat的缓冲区大小毕竟是有限的,所以需要循环利用,这样历史的日志信息就会被冲掉。在一些自动化测试的场景下,譬如Monkey需要长时间的运行,
就需要把历史的日志全都保存下来。</p>
</li>
<li>
<p><strong>tombstone</strong> tombstone错误一般由Dalvik错误、native层的代码问题导致的。当系统发生tombstone时,内核会上报一个严重的警告信号,
上层收到后,把当前的调用栈信息持久化到磁盘中(<strong>/data/tombstone</strong>)</p>
</li>
<li>
<p><strong>bugreport</strong> 通过<code class="highlighter-rouge">adb bugreport</code>命令输出,日志内容多到爆,logcat, traces, dmesg, dumpsys, binder的日志都包含在其中。
由于输出bugreport的时间很长,当系统发生错误时,我们再执行bugreport往往就来不及了(此时,系统可能都已经重启了),所以,要动用bugreport就需要结合一些其他机制,
譬如在杀掉system_server进程之前,先让bugreport运行完。</p>
</li>
</ul>
<h2 id="32-问题定位">3.2 问题定位</h2>
<p>Watchdog出现的日志很明显,logcat中的event, system中都会有体现,要定位问题,可以从检索日志中的watchdog关键字开始。</p>
<p>发生Watchdog检测超时这么重要的系统事件,Android会打印一个EventLog:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>watchdog: Blocked in handler XXX # 表示HandlerChecker超时了
watchdog: Blocked in monitor XXX # 表示MonitorChecker超时了
</code></pre></div></div>
<p>Watchdog是运行在system_server进程中,会打印一些System类型的日志。在手机处于非调试状态时,伴随Watchdog出现的往往是system_server进程被杀,从而系统重启。
当Watchdog要主动杀掉system_server进程时,以下关键字就会出现在SystemLog中:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: XXX
Watchdog: XXX
Watchdog: "*** GOODBYE!
</code></pre></div></div>
<p>当我们在日志中检索到上述两类关键信息时,说明“Watchdog显灵”了,从另一个角度来理解,就是“System Not Responding”了。
接下来,我们需要进一步定位在watchdog出现之前,system_server进程在干什么,处于一个什么状态。
这与排除”Application Not Responding“问题差不多,我们需要进程的traces信息、当前系统的CPU运行信息、IO信息。</p>
<p>找到Watchddog出现之前的traces.txt文件,这个时间差最好不要太大,因为Watchdog默认的超时时间是1分钟,太久以前的traces并不能说明问题。
诱导Watchdong出现的直接原因其实就是system_server中某个线程被阻塞了,这个信息在event和system的log中清晰可见。
我们以一个systemLog为例:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.wm.WindowManagerService on foreground thread (android.fg)
</code></pre></div></div>
<p>Watchdog告诉我们<strong>Monitor Checker</strong>超时了,具体在哪呢? 名为<strong>android.fg</strong>的线程在WindowManagerService的monitor()方法被阻塞了。这里隐含了两层意思:</p>
<ol>
<li>
<p>WindowManagerService实现了Watchdog.Monitor这个接口,并将自己作为<strong>Monitor Checker</strong>的对象加入到了Watchdog的监测集中</p>
</li>
<li>
<p>monitor()方法是运行在<strong>android.fg</strong>线程中的。Android将<strong>android.fg</strong>设计为一个全局共享的线程,意味着它的消息队列可以被其他线程共享,
Watchdog的<strong>Monitor Checker</strong>就是使用的<strong>android.fg</strong>线程的消息队列。因此,出现<strong>Monitor Checker</strong>的超时,肯定是<strong>android.fg</strong>线程阻塞在monitor()方法上。</p>
</li>
</ol>
<p>我们打开system_server进程的traces,检索 <strong>android.fg</strong> 可以快速定位到该线程的函数调用栈:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"android.fg" prio=5 tid=25 Blocked
| group="main" sCount=1 dsCount=0 obj=0x12eef900 self=0x7f7a8b1000
| sysTid=973 nice=0 cgrp=default sched=0/0 handle=0x7f644e9000
| state=S schedstat=( 3181688530 2206454929 8991 ) utm=251 stm=67 core=1 HZ=100
| stack=0x7f643e7000-0x7f643e9000 stackSize=1036KB
| held mutexes=
at com.android.server.wm.WindowManagerService.monitor(WindowManagerService.java:13125)
- waiting to lock <0x126dccb8> (a java.util.HashMap) held by thread 91
at com.android.server.Watchdog$HandlerChecker.run(Watchdog.java:204)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
</code></pre></div></div>
<p><strong>android.fg</strong>线程调用栈告诉我们几个关键的信息:</p>
<ul>
<li>这个线程当前的状态是<strong>Blocked</strong>,阻塞</li>
<li>由Watchdog发起调用monitor(),这是一个Watchdog检查,阻塞已经超时</li>
<li><strong>waiting to lock <0x126dccb8></strong>: 阻塞的原因是monitor()方法中在等锁<0x126dccb8></li>
<li><strong>held by thread 91</strong>: 这个锁被编号为91的线程持有,需要进一步观察91号线程的状态。</li>
</ul>
<blockquote>
<p>题外话:每一个进程都会对自己所辖的线程编号,从1开始。1号线程通常就是我们所说的主线程。
线程在Linux系统中还有一个全局的编号,由sysTid表示。我们在logcat等日志中看到的一般是线程的全局编号。
譬如,本例中android.fg线程在system_server进程中的编号是25,系统全局编号是973。</p>
</blockquote>
<p>可以在traces.txt文件中检索 <em>tid=91</em> 来快速找到91号线程的函数调用栈信息:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"Binder_C" prio=5 tid=91 Native
| group="main" sCount=1 dsCount=0 obj=0x12e540a0 self=0x7f63289000
| sysTid=1736 nice=0 cgrp=default sched=0/0 handle=0x7f6127c000
| state=S schedstat=( 96931835222 49673449591 260122 ) utm=7046 stm=2647 core=2 HZ=100
| stack=0x7f5ffbc000-0x7f5ffbe000 stackSize=1008KB
| held mutexes=
at libcore.io.Posix.writeBytes(Native method)
at libcore.io.Posix.write(Posix.java:258)
at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313)
at libcore.io.IoBridge.write(IoBridge.java:537)
at java.io.FileOutputStream.write(FileOutputStream.java:186)
at com.android.internal.util.FastPrintWriter.flushBytesLocked(FastPrintWriter.java:334)
at com.android.internal.util.FastPrintWriter.flushLocked(FastPrintWriter.java:355)
at com.android.internal.util.FastPrintWriter.appendLocked(FastPrintWriter.java:303)
at com.android.internal.util.FastPrintWriter.print(FastPrintWriter.java:466)
- locked <@addr=0x134c4910> (a com.android.internal.util.FastPrintWriter$DummyWriter)
at com.android.server.wm.WindowState.dump(WindowState.java:1510)
at com.android.server.wm.WindowManagerService.dumpWindowsNoHeaderLocked(WindowManagerService.java:12279)
at com.android.server.wm.WindowManagerService.dumpWindowsLocked(WindowManagerService.java:12266)
at com.android.server.wm.WindowManagerService.dump(WindowManagerService.java:12654)
- locked <0x126dccb8> (a java.util.HashMap)
at android.os.Binder.dump(Binder.java:324)
at android.os.Binder.onTransact(Binder.java:290)
</code></pre></div></div>
<p>91号线程的名字是<strong>Binder_C</strong>,它的函数调用栈告诉我们几个关键信息:</p>
<ul>
<li>Native,表示线程处于运行状态(RUNNING),并且正在执行JNI方法</li>
<li>在WindowManagerService.dump()方法申请了锁<0x126dccb8>,这个锁正是<strong>android.fg</strong>线程所等待的</li>
<li>FileOutputStream.write()表示<strong>Binder_C</strong>线程在执行IO写操作,正式因为这个写操作一直在阻塞,导致线程持有的锁不能释放</li>
</ul>
<blockquote>
<p>题外话:关于Binder线程。当Android进程启动时,就会创建一个线程池,专门处理Binder事务。线程池中会根据当前的binder线程计数器的值来构造新创建的binder线程,
线程名”Binder_%X”,X是十六进制。当然,线程池的线程数也有上限,默认情况下为16,所以,可以看到 Binder_1 ~ Binder_F 这样的线程命名。</p>
</blockquote>
<p>聪明的你看到这或许已经能够想到解决办法了,在这个IO写操作上加一个超时机制,并且这个超时小于Watchdog的超时,不就可以让线程释放它所占有的锁了吗?
是的,这确实可以作为一个临时解决方案(Workaround),或者说一个保护机制。但我们可以再往深处想一想,这个IO写操作为什么会阻塞:</p>
<ul>
<li>是不是IO缓冲区满了,导致写阻塞呢?</li>
<li>是不是写操作有什么锁,导致这个write方法在等锁呢?</li>
<li>是不是当前系统的IO负载过于高,导致写操作效率很低呢?</li>
</ul>
<p>这都需要我们再进一步从日志中去找原因。如果已有的日志不全,找不到论据,我们还需要设计场景来验证假设,解决问题的难度陡然上升。</p>
<h2 id="33-场景还原">3.3 场景还原</h2>
<p>我们经历了两个关键步骤:</p>
<ol>
<li>通过event或system类型的日志,发现了Watchdog杀掉system_server导致系统重启</li>
<li>通过traces日志,发了导致Watchdog出现的具体线程操作</li>
</ol>
<p>这两个过程基本就涵盖了Watchdog的运行机制了,但这并没有解决问题啊。我们需要找到线程阻塞的原因是什么,然而,线程阻塞的原因就千奇百怪了。
如果有问题出现的现场,并且问题可以重现,那么我们可以通过调试的手段来分析问题产生的原因。
如果问题只是偶然出现,甚至只有一堆日志,我们就需要从日志中来还原问题出现的场景,这一步才是真正考验大家Android/Linux功底的地方。</p>
<p>继续以上述问题为例,我们来进一步还原问题出现的场景,从Java层的函数调用栈来看:</p>
<ul>
<li>首先,跨进程发起了Binder.dump()方法的调用:at android.os.Binder.dump(Binder.java:324)</li>
<li>然后,进入了WMS的dump():at com.android.server.wm.WindowManagerService.dump(WindowManagerService.java:12654)</li>
<li>接着,发生了写文件操作:at java.io.FileOutputStream.write(FileOutputStream.java:186)</li>
<li>最后,调用了JNI方法:at libcore.io.Posix.writeBytes(Native method)</li>
</ul>
<p><strong>Binder_C</strong>线程要出现这种函数调用栈,我们可以初步确定是Android接受了如下命令
(dumpsys原理请查阅<a href="/2015-07-19-Intro-to-dumpsys">dumpsys介绍</a>一文):</p>
<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="gp">$</span> adb shell dumpsys window</code></pre></figure>
<p>当通过命令行运行以上命令时,客户端(PC)的adb server会向服务端(手机)的adbd发送指令,
adbd进程会fork出一个叫做dumpsys的子进程,dumpsys进程再利用Binder机制和system_server通信
(adb的实现原理可以查阅<a href="/2015-05-21-Intro-adb">adb介绍</a>一文)。</p>
<p>仅凭这个还是分析不出问题所在,我们需要启用内核的日志了。当调用JNI方法libcore.io.Posix.writeBytes()时,会触发系统调用,
Linux会从用户态切换到内核态,内核的函数调用栈也可以从traces中找到:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kernel: __switch_to+0x74/0x8c
kernel: pipe_wait+0x60/0x9c
kernel: pipe_write+0x278/0x5cc
kernel: do_sync_write+0x90/0xcc
kernel: vfs_write+0xa4/0x194
kernel: SyS_write+0x40/0x8c
kernel: cpu_switch_to+0x48/0x4c
</code></pre></div></div>
<p>在Java层,明确指明要写文件(FileOutputStream),正常情况下,系统调用write()就完事了,但Kernel却打开了一个管道,最终阻塞在了pipe_wait()方法。
什么场景下会打开一个管道,而且管道会阻塞呢?一系列的猜想和验证过程接踵而至。</p>
<p>这里有必要先补充一些基础知识了:</p>
<ul>
<li>
<p><strong><a href="http://www.cnblogs.com/biyeymyhjob/archive/2012/11/03/2751593.html">Linux进程间通信之管道(pipe)</a></strong></p>
<p>Linux的管道实现借助了文件系统的file结构和VFS(Virtual File System),通过将两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面时,
实际上就建立了一个管道。</p>
<p>这就解释了为什么发起系统调用write的时候,打开了一个管道。因为dumpsys和system_server进程,将自己的file结构指向了同一个VFS索引节点。</p>
</li>
<li>
<p><strong><a href="http://blog.csdn.net/sj13051180/article/details/47865803">管道挂起的案例</a></strong></p>
<p>管道是一个生产者-消费者模型,当缓冲区满时,则生产者不能往管道中再写数据了,需等到消费者读数据。如果消费者来不及处理缓冲区的数据,或者锁定缓冲区,则生产者就挂起了。</p>
<p>结合到例子中的场景,system_server进程无法往管道中写数据,很可能是dumpsys进程一直忙碌来不及处理新的数据。</p>
</li>
</ul>
<p>接下来,需要再从日志中寻找dumpsys进程的运行状态了:</p>
<ul>
<li>是不是dumpsys进程的负载太高?</li>
<li>是不是dumpsys进程死掉了,导致一直没有处理缓冲区数据?</li>
<li>是不是dumpsys进程有死锁?</li>
</ul>
<p>接下来的分析过程已经偏离Watchdog机制越来越远了,我们点到为止。</p>
<p>小伙伴们可以看到,场景还原涉及到的知识点非常之宽泛,而且有一定的深度。在没有现场的情况下,伴随一系列的假设和验证过程,充满了不确定性和发现问题的喜悦。
正所谓,同问题做斗争,其乐无穷!</p>
<p><strong>至此,我们分析Watchdog问题的惯用方法,回答前面提出来的第二个问题:</strong></p>
<p><strong>通过event或system类型的logcat日志,检索Watchdog出现的关键信息;通过traces,分析出导致Watchdog检查超时的直接原因;通过其他日志,还原出问题出现的场景。</strong></p>
<h1 id="4-实例分析">4. 实例分析</h1>
<p>在上面介绍Watchdog问题分析方法的时候,我们其实已经举了一个例子。通常,比较容易定位导致Watchdog出现的直接原因(Direct Cause),但很难找到更深层次的原因(Root Cause)。
这个小节,我们再介绍一个实例,来分析Watchdog出现的另一种场景。诚然,仅凭几个例子,远不够涵盖Watchdog的所有问题,我们的章法还是按照一定的方法论来深究问题。</p>
<p>回顾一下解决问题三部曲:</p>
<ol>
<li>
<p>日志获取。日志种类繁多,分析Watchdog问题,宁滥毋缺</p>
</li>
<li>
<p>问题定位。从logcat中锁定watchdog的出现,从traces锁定直接原因</p>
</li>
<li>
<p>场景还原。结合各类日志,不断假设验证</p>
</li>
</ol>
<p><strong>以CPU占用过高的场景为例:<a href="">下载该问题的全部日志</a></strong></p>
<p><strong>从sys_log中,检索到了Watchdog的出现关键信息</strong></p>
<blockquote>
<p>TIPS: 在sys_log中搜索关键字”WATCHDOG KILLING SYSTEM PROCESS”</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10-14 17:10:51.548 892 1403 W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in handler on ActivityManager (ActivityManager)
</code></pre></div></div>
<p>这是一个Watchdog的<strong>Looper Checker</strong>超时,由于ActivityManager这个线程一直处于忙碌状态,导致Watchdog检查超时。
Watchdog出现的时间是<strong>10-14 17:10:51.548</strong>左右,需要从traces.txt中找到这个时间段的system_server进程的函数调用栈信息, system_server的进程号是892。</p>
<p><strong>从traces.txt中找到对应的函数调用栈</strong></p>
<p>traces.txt包含很多进程在不同时间段的函数调用栈信息,为了检索的方便,首先可以将traces.txt分块。
笔者写了一个<a href="https://github.com/duanqz">工具</a>,可以从traces.txt文件中分割出指定进程号的函数调用栈信息。</p>
<blockquote>
<p>TIPS: 在system_server的traces中(通过工具分割出的system_server_892_2015-10-14-17:09:06文件)搜索关键字”ActivityManager”</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"ActivityManager" prio=5 tid=17 TimedWaiting
| group="main" sCount=1 dsCount=0 obj=0x12c0e6d0 self=0x7f84caf000
| sysTid=938 nice=-2 cgrp=default sched=0/0 handle=0x7f7d887000
| state=S schedstat=( 107864628645 628257779012 60356 ) utm=7799 stm=2987 core=2 HZ=100
| stack=0x7f6e68f000-0x7f6e691000 stackSize=1036KB
| held mutexes=
at java.lang.Object.wait!(Native method)
- waiting on <0x264ff09d> (a com.android.server.am.ActivityManagerService$5)
at java.lang.Object.wait(Object.java:422)
at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:5395)
at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:5282)
at com.android.server.am.ActivityManagerService$AnrActivityManagerService.dumpStackTraces(ActivityManagerService.java:22676)
at com.mediatek.anrmanager.ANRManager$AnrDumpMgr.dumpAnrDebugInfoLocked(SourceFile:1023)
at com.mediatek.anrmanager.ANRManager$AnrDumpMgr.dumpAnrDebugInfo(SourceFile:881)
at com.android.server.am.ActivityManagerService.appNotResponding(ActivityManagerService.java:6122)
- locked <0x21c77912> (a com.mediatek.anrmanager.ANRManager$AnrDumpRecord)
at com.android.server.am.BroadcastQueue$AppNotResponding.run(BroadcastQueue.java:228)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:192)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
</code></pre></div></div>
<p>ActivityManager线程实际上运行着AMS的消息队列,这个函数调用栈的关键信息:</p>
<ul>
<li>线程状态为TimedWaiting, 这表示当前线程阻塞在一个超时的wait()方法</li>
<li>正在处理广播消息超时发生的ANR(Application Not Responding),需要将当前的函数调用栈打印出来</li>
<li>最终在<0x264ff09d>等待,可以从<a href="https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/am/ActivityManagerService.java#4830">AMS的源码</a>
中找到这一处锁的源码,因为dumpStackTraces()会写文件,所以AMS设计了一个200毫秒的超时锁。</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="n">observer</span><span class="o">.</span><span class="na">wait</span><span class="o">(</span><span class="mi">200</span><span class="o">);</span> <span class="c1">// Wait for write-close, give up after 200msec</span></code></pre></figure>
<p><strong>还原问题的场景</strong></p>
<p>从ActivityManager这个线程的调用栈,我们就会有一些疑惑:</p>
<ul>
<li>是哪个应用发生了ANR?为什么会发生ANR?</li>
<li>超时锁只用200毫秒就释放了,为什么会导致Watchdog检查超时?(AMS的Looper默认超时是1分钟)</li>
</ul>
<p>带着这些疑惑,我们再回到日志中:</p>
<p>从sys_log中,可以检索到Watchdog出现的时间点(<strong>17:10:51.548</strong>)之前,com.android.systemui发生了ANR,从而引发AMS打印函数调用栈:</p>
<blockquote>
<p>TIPS: 在sys_log中检索”ANR in”关键字或在event_log中检索”anr”关键字</p>
</blockquote>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10-14 17:10:04.215 892 938 E ANRManager: ANR in com.android.systemui, time=27097912
10-14 17:10:04.215 892 938 E ANRManager: Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK flg=0x50000114 (has extras) }
10-14 17:10:04.215 892 938 E ANRManager: Load: 89.22 / 288.15 / 201.91
10-14 17:10:04.215 892 938 E ANRManager: Android time :[2015-10-14 17:10:04.14] [27280.396]
10-14 17:10:04.215 892 938 E ANRManager: CPU usage from 17016ms to 0ms ago:
10-14 17:10:04.215 892 938 E ANRManager: 358% 23682/float_bessel: 358% user + 0% kernel
10-14 17:10:04.215 892 938 E ANRManager: 57% 23604/debuggerd64: 3.8% user + 53% kernel / faults: 11369 minor
10-14 17:10:04.215 892 938 E ANRManager: 2% 892/system_server: 0.9% user + 1% kernel / faults: 136 minor
</code></pre></div></div>
<p>从这个日志信息中,我们两个疑惑就释然了:</p>
<p>发生ANR之前的CPU负载远高于正常情况好几倍(Load: 89.22 / 288.15 / 201.91),在这种CPU负载下,com.android.systemui进程发生处理广播消息超时(Reason: Broadcast of Intent)再正常不过了。
在这之前CPU都被<strong>float_bessel</strong>这个进程给占了,这货仅凭一己之力就耗了358%的CPU资源。</p>
<p>observer.wait(200)在调用后,便进入排队等待唤醒状态(Waiting),在等待200毫秒后,便重新开始申请CPU资源,而此时,CPU资源一直被<strong>float_bessel</strong>占着没有释放,所以该线程一直在等CPU资源。
等了1分钟后,Watchdog跳出来说“不行,你已经等了1分钟了,handler处理其他消息了”。</p>
<p>在多核情况下,CPU的使用率统计会累加多个核的使用率,所以会出现超过100%的情况。那么<strong>float_bessel</strong>究竟是什么呢?它是一个Linux的测试样本,贝塞尔函数的计算,耗的就是CPU。</p>
<p>这样,该问题的场景我们就还原出来了:在压力测试的环境下,CPU被<strong>float_bessel</strong>运算占用,导致com.android.systemui进程发生ANR,从而引发AMS打印trace;
但由于AMS一直等不到CPU资源,Watchdog检测超时,杀掉system_server进程,系统重启。</p>
<p>对于压力测试而言,我们一般会设定一个通过标准,在某些压力情况下,出现一些错误是允许的。对于Android实际用户的使用场景而言,本例中的压力通常是不存在的,所以在实际项目中,这种类型的Watchdog问题,我们一般不解决。</p>
<h1 id="5-总结">5. 总结</h1>
<p>Android中Watchdog用来看护system_server进程,system_server进程运行着系统最终要的服务,譬如AMS、PKMS、WMS等,
当这些服务不能正常运转时,Watchdog可能会杀掉system_server,让系统重启。</p>
<p>Watchdog的实现利用了锁和消息队列机制。当system_server发生死锁或消息队列一直处于忙碌状态时,则认为系统已经没有响应了(System Not Responding)。</p>
<p>在分析Watchdog问题的时候,首先要有详尽的日志,其次要能定位出导致Watchdog超时的直接原因,最重要的是能还原出问题发生的场景。</p>