<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>连接号de博客</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2026-09-20T15:23:29.422Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>TXM</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>FastAPI 快速入门</title>
    <link href="http://example.com/fastapi/"/>
    <id>http://example.com/fastapi/</id>
    <published>2026-09-17T07:38:02.000Z</published>
    <updated>2026-09-20T15:23:29.422Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这篇文章整理了笔者学习 FastAPI 的过程，并将其转化为一套可以按步骤操作和验证的实践教程。目标是完成两个动作：创建路由，以及正确处理请求。重点保留了学习中最容易混淆的几组边界：<code>400</code> 和 <code>422</code>、请求校验和响应校验、FastAPI 应用和 Uvicorn 服务器。</p><span id="more"></span><h2 id="1-先跑通一个最小服务"><a href="#1-先跑通一个最小服务" class="headerlink" title="1. 先跑通一个最小服务"></a>1. 先跑通一个最小服务</h2><p>本文示例在以下环境中验证：FastAPI 0.116.1、Pydantic 2.8.2、Starlette 0.47.2、Uvicorn 0.35.0。版本不同可能影响个别错误信息，但不改变本文的基本处理链。</p><p>先创建虚拟环境并安装 FastAPI：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">python -m venv .venv</span><br><span class="line"><span class="built_in">source</span> .venv/bin/activate       <span class="comment"># macOS / Linux</span></span><br><span class="line"><span class="comment"># Windows PowerShell 使用：.venv\Scripts\Activate.ps1</span></span><br><span class="line">pip install <span class="string">"fastapi[standard]"</span></span><br></pre></td></tr></tbody></table></figure><p><code>main.py</code> 中写一个最小的 <code>/hello</code> 路由：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/hello"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>(<span class="params">name: <span class="built_in">str</span> | <span class="literal">None</span> = <span class="literal">None</span></span>):</span><br><span class="line">    <span class="keyword">if</span> name <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">return</span> {<span class="string">"message"</span>: <span class="string">"hello world"</span>}</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"message"</span>: <span class="string">f"hello <span class="subst">{name}</span>"</span>}</span><br></pre></td></tr></tbody></table></figure><p><code>name: str | None = None</code> 表示：<code>name</code> 可以是字符串，也可以是 <code>None</code>；如果请求没有提供 <code>name</code>，就使用默认值 <code>None</code>。因此 <code>GET /hello?name=Lin</code> 时，<code>name</code> 是字符串 <code>"Lin"</code>，而直接访问 <code>GET /hello</code> 时，<code>name</code> 是 <code>None</code>。由于它有默认值，FastAPI 会把 <code>name</code> 识别为可选的查询参数。</p><p>从 <code>main.py</code> 所在目录启动：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uvicorn main:app --reload</span><br></pre></td></tr></tbody></table></figure><p>这里的 <code>main:app</code> 不是一个 URL：冒号左边是 Python 模块路径，右边是模块中的应用对象名。<code>app = FastAPI()</code> 创建的是应用对象，它保存路由并负责请求处理；Uvicorn 是服务器进程，负责监听端口、接收连接并调用这个应用。</p><p>访问下面两个地址，可以看到同一个路由如何读取查询参数：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">GET /hello</span><br><span class="line">-&gt; {"message": "hello world"}</span><br><span class="line"></span><br><span class="line">GET /hello?name=Lin</span><br><span class="line">-&gt; {"message": "hello Lin"}</span><br></pre></td></tr></tbody></table></figure><p>FastAPI 应用和 Uvicorn 之间通过 ASGI（Asynchronous Server Gateway Interface）协作。ASGI 是应用与服务器之间的接口规范，不是业务路由本身。传统的 WSGI 主要面向同步调用；ASGI 为异步调用、长连接等场景提供了更合适的接口。对入门代码而言，只要先记住这三层即可：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">浏览器/客户端 -&gt; Uvicorn（监听端口） -&gt; ASGI 应用（FastAPI 路由和业务逻辑）</span><br></pre></td></tr></tbody></table></figure><p>更多 ASGI 介绍，可参考<a href="https://blog.csdn.net/jsjsjs1789/article/details/136458203">这篇文章&gt;&gt;</a></p><h2 id="2-路由是-HTTP-方法和路径的组合"><a href="#2-路由是-HTTP-方法和路径的组合" class="headerlink" title="2. 路由是 HTTP 方法和路径的组合"></a>2. 路由是 HTTP 方法和路径的组合</h2><p>同一个路径可以注册不同的 HTTP 方法。FastAPI 选择处理函数时同时看方法和路径：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/items"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">list_items</span>():</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"method"</span>: <span class="string">"GET"</span>}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">"/items"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_item</span>():</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"method"</span>: <span class="string">"POST"</span>}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/items/{item_id}"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_item</span>(<span class="params">item_id: <span class="built_in">int</span></span>):</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"item_id"</span>: item_id}</span><br></pre></td></tr></tbody></table></figure><p>对应关系是：</p><table><thead><tr><th>请求</th><th>结果</th></tr></thead><tbody><tr><td><code>GET /items</code></td><td>执行 <code>list_items</code></td></tr><tr><td><code>POST /items</code></td><td>执行 <code>create_item</code></td></tr><tr><td><code>GET /items/42</code></td><td>执行 <code>get_item</code>，<code>item_id</code> 为 <code>42</code></td></tr><tr><td><code>GET /unknown</code></td><td><code>404 Not Found</code>，没有匹配的路径</td></tr><tr><td><code>POST /items/42</code></td><td>如果只注册了 GET，对应路径存在但方法不允许，返回 <code>405 Method Not Allowed</code></td></tr></tbody></table><p><code>404</code> 和 <code>405</code> 的差别：前者是路径没有匹配到，后者是路径匹配到了但 HTTP 方法不在注册列表中。</p><h2 id="3-路径参数、查询参数和校验时机"><a href="#3-路径参数、查询参数和校验时机" class="headerlink" title="3. 路径参数、查询参数和校验时机"></a>3. 路径参数、查询参数和校验时机</h2><p>路径占位符必须和函数参数同名。没有出现在路径中的简单类型参数，FastAPI 默认把它解释为查询参数：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/books/{book_id}"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_book</span>(<span class="params">book_id: <span class="built_in">int</span>, detail: <span class="built_in">bool</span> = <span class="literal">False</span>, sort: <span class="built_in">str</span> = <span class="string">"title"</span></span>):</span><br><span class="line">    <span class="keyword">return</span> {</span><br><span class="line">        <span class="string">"book_id"</span>: book_id,</span><br><span class="line">        <span class="string">"detail"</span>: detail,</span><br><span class="line">        <span class="string">"sort"</span>: sort,</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>这里 <code>book_id</code> 是路径参数，<code>detail</code> 和 <code>sort</code> 是查询参数：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">GET /books/42</span><br><span class="line">-&gt; {"book_id": 42, "detail": false, "sort": "title"}</span><br><span class="line"></span><br><span class="line">GET /books/42?detail=true&amp;sort=最新</span><br><span class="line">-&gt; {"book_id": 42, "detail": true, "sort": "最新"}</span><br></pre></td></tr></tbody></table></figure><p><code>detail</code> 和 <code>sort</code> 有默认值，所以可以省略；如果写成 <code>q: str</code> 而不提供默认值，<code>GET /search</code> 会在进入函数前返回 <code>422</code>：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/search"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">search</span>(<span class="params">q: <span class="built_in">str</span>, limit: <span class="built_in">int</span> = <span class="number">10</span></span>):</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"q"</span>: q, <span class="string">"limit"</span>: limit}</span><br></pre></td></tr></tbody></table></figure><p><code>GET /search?q=fastapi&amp;limit=abc</code> 同样返回 <code>422</code>，因为 <code>limit</code> 声明为 <code>int</code>，字符串 <code>abc</code> 无法转换为整数。</p><p>关键执行顺序是：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">路由匹配 -&gt; 参数转换和校验 -&gt; 函数体</span><br></pre></td></tr></tbody></table></figure><p>因此：</p><ul><li><code>GET /books/abc</code> 能匹配路径，但 <code>abc</code> 不能转换成 <code>int</code>，返回 <code>422</code>，函数体不会执行。</li><li><code>GET /books/42</code> 转换成功，函数体执行，通常返回 <code>200</code>。</li></ul><p>这里的 <code>422</code> 不是“资源不存在”。<code>/users/abc</code> 和 <code>/users/999</code> 应该分开判断：前者是参数校验失败，后者在 <code>999</code> 是合法整数的前提下，进入函数查询数据库，查不到用户时才是 <code>404</code>。</p><h2 id="4-POST-请求体由-Pydantic-模型声明"><a href="#4-POST-请求体由-Pydantic-模型声明" class="headerlink" title="4. POST 请求体由 Pydantic 模型声明"></a>4. POST 请求体由 Pydantic 模型声明</h2><p>POST 请求的 JSON body 不会因为使用了 <code>POST</code> 就自动变成某种结构。需要用 Pydantic 的 <code>BaseModel</code> 明确声明字段，再把模型类型写到路由函数参数上：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">PersonIn</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    name: <span class="built_in">str</span></span><br><span class="line">    age: <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">"/people"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_person</span>(<span class="params">person: PersonIn</span>):</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"name"</span>: person.name, <span class="string">"age"</span>: person.age}</span><br></pre></td></tr></tbody></table></figure><p><code>person: PersonIn</code> 同时完成两件事：告诉 FastAPI 从请求体读取 JSON，并让 Pydantic 按 <code>PersonIn</code> 校验和转换字段。下面的请求会得到 <code>200</code>，函数收到的 <code>person.age</code> 是整数 <code>20</code>：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span><span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"Lin"</span><span class="punctuation">,</span> <span class="attr">"age"</span><span class="punctuation">:</span> <span class="string">"20"</span><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>下面的请求得到 <code>422</code>，函数体不会执行：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span><span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"Lin"</span><span class="punctuation">,</span> <span class="attr">"age"</span><span class="punctuation">:</span> <span class="string">"unknown"</span><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p>同理，缺少 <code>age</code> 也会得到 <code>422</code>。Pydantic 默认会忽略模型没有声明的额外字段，因此 <code>city</code> 不会自动出现在返回值中：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span><span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"Lin"</span><span class="punctuation">,</span> <span class="attr">"age"</span><span class="punctuation">:</span> <span class="number">20</span><span class="punctuation">,</span> <span class="attr">"city"</span><span class="punctuation">:</span> <span class="string">"Shanghai"</span><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><h3 id="类型转换不是无条件的"><a href="#类型转换不是无条件的" class="headerlink" title="类型转换不是无条件的"></a>类型转换不是无条件的</h3><p>在当前 Pydantic v2 环境中，<code>int</code> 字段可以把可解析的数字字符串转换成整数，但 <code>str</code> 字段不会把整数 <code>123</code> 自动转换成字符串。也就是说，下面两种行为并不对称：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">age: int，输入 "20"       -&gt; 通过，得到 20</span><br><span class="line">name: str，输入 123        -&gt; 默认校验失败</span><br></pre></td></tr></tbody></table></figure><p>这种不对称是有意的：目标是让运行时数据更接近接口声明，避免把本来应该是字符串的字段悄悄接收成整数。</p><blockquote><p>PS：不要把一个字段的转换经验推广到所有字段，最终规则要看目标类型和 Pydantic 版本。</p></blockquote><h2 id="5-请求校验、业务异常和依赖"><a href="#5-请求校验、业务异常和依赖" class="headerlink" title="5. 请求校验、业务异常和依赖"></a>5. 请求校验、业务异常和依赖</h2><p>FastAPI 的请求处理可以画成一条更完整的链：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">客户端请求</span><br><span class="line">  -&gt; 路由匹配</span><br><span class="line">  -&gt; 提取并校验请求数据，同时解析 Depends 依赖</span><br><span class="line">  -&gt; 路由函数体</span><br><span class="line">  -&gt; response_model 响应校验和过滤</span><br><span class="line">  -&gt; 最终 HTTP 响应</span><br></pre></td></tr></tbody></table></figure><blockquote><p>先记住这条处理链即可，下一章节会详细介绍 <code>response_model</code> 如何校验和过滤响应。</p></blockquote><p>需要注意，“校验请求数据”和“解析依赖”不是两条严格串行、互不交错的流水线。FastAPI 会在调用路由函数前汇总参数并求解依赖；某个端点参数校验失败时，路由函数不会执行，但不依赖这个错误参数的依赖函数可能已经执行。</p><p><code>Depends</code> 用来声明接口依赖的处理步骤。下面的依赖函数要求一个 <code>token</code> 查询参数：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> Depends, FastAPI, HTTPException</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">require_admin</span>(<span class="params">token: <span class="built_in">str</span></span>):</span><br><span class="line">    <span class="keyword">if</span> token != <span class="string">"admin"</span>:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">403</span>, detail=<span class="string">"Forbidden"</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/admin/items/{item_id}"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">read_item</span>(<span class="params"></span></span><br><span class="line"><span class="params">    item_id: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">    is_admin: <span class="built_in">bool</span> = Depends(<span class="params">require_admin</span>),</span></span><br><span class="line"><span class="params"></span>):</span><br><span class="line">    <span class="keyword">if</span> item_id == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">404</span>, detail=<span class="string">"Not found"</span>)</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"item_id"</span>: item_id}</span><br></pre></td></tr></tbody></table></figure><p>这段代码的执行范围如下：</p><table><thead><tr><th>请求</th><th align="right">状态码</th><th>执行情况</th></tr></thead><tbody><tr><td><code>/admin/items/abc?token=admin</code></td><td align="right"><code>422</code></td><td><code>require_admin</code> 可能已执行；<code>item_id</code> 校验失败，路由函数不执行</td></tr><tr><td><code>/admin/items/1</code></td><td align="right"><code>422</code></td><td><code>token</code> 是依赖的必填参数，依赖函数还没执行</td></tr><tr><td><code>/admin/items/1?token=wrong</code></td><td align="right"><code>403</code></td><td>依赖函数执行并主动抛出异常，路由函数不执行</td></tr><tr><td><code>/admin/items/0?token=admin</code></td><td align="right"><code>404</code></td><td>依赖通过，路由函数执行后主动抛出异常</td></tr><tr><td><code>/admin/items/1?token=admin</code></td><td align="right"><code>200</code></td><td>依赖通过，路由函数正常返回</td></tr></tbody></table><p>如果认证信息应该放在请求头，不要继续使用普通的 <code>str</code> 参数；应显式使用 <code>Header</code>，否则 FastAPI 会把它当作查询参数处理。</p><p>例如，把依赖函数改成下面这样，<code>token</code> 就会从名为 <code>token</code> 的请求头中读取：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> Header, HTTPException</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">require_admin</span>(<span class="params">token: <span class="built_in">str</span> = Header(<span class="params">...</span>)</span>):</span><br><span class="line">    <span class="keyword">if</span> token != <span class="string">"admin"</span>:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">403</span>, detail=<span class="string">"Forbidden"</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></tbody></table></figure><p>请求时把 token 放在请求头，而不是 URL 的查询字符串中：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -H <span class="string">"token: admin"</span> <span class="string">"http://127.0.0.1:8000/admin/items/1"</span></span><br></pre></td></tr></tbody></table></figure><p><code>Header(...)</code> 中的 <code>...</code> 表示这个请求头是必填的；如果完全不发送 <code>token</code>，FastAPI 会在依赖函数执行前返回 <code>422</code>。</p><h2 id="6-response-model-约束最终响应"><a href="#6-response-model-约束最终响应" class="headerlink" title="6. response_model 约束最终响应"></a>6. <code>response_model</code> 约束最终响应</h2><p>请求模型约束“函数能接收什么”，<code>response_model</code> 约束“接口对外返回什么”：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserOut</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    name: <span class="built_in">str</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">"/users"</span>, status_code=<span class="number">201</span>, response_model=UserOut</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_user</span>():</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"name"</span>: <span class="string">"Lin"</span>, <span class="string">"password"</span>: <span class="string">"secret"</span>}</span><br></pre></td></tr></tbody></table></figure><p>最终响应是：</p><figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span><span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"Lin"</span><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure><p><code>password</code> 虽然出现在函数返回字典中，但不在 <code>UserOut</code> 中，所以被响应模型过滤。<code>status_code=201</code> 则把这次成功响应标记为资源创建成功。</p><p>响应校验发生在函数返回之后。如果声明：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ArticleOut</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    title: <span class="built_in">str</span></span><br><span class="line">    pages: <span class="built_in">int</span></span><br></pre></td></tr></tbody></table></figure><p>但函数只返回 <code>{"title": "FastAPI 入门"}</code>，当前环境下会发生响应校验失败，客户端通常看到 <code>500 Internal Server Error</code>。这和请求体缺少字段不同：请求体缺字段是在函数执行前返回 <code>422</code>，响应缺字段是服务器没有兑现自己声明的响应契约，应按服务端错误排查。</p><h2 id="7-400-和-422-到底有什么不同"><a href="#7-400-和-422-到底有什么不同" class="headerlink" title="7. 400 和 422 到底有什么不同"></a>7. 400 和 422 到底有什么不同</h2><table><thead><tr><th>状态码</th><th>通用语义</th><th>FastAPI 入门场景</th></tr></thead><tbody><tr><td><code>400 Bad Request</code></td><td>请求作为一个有效请求无法被服务器处理，语义范围较宽</td><td>应用或中间件主动判定请求报文/通用请求不合适，并显式抛出 <code>HTTPException(400, ...)</code></td></tr><tr><td><code>401 Unauthorized</code></td><td>缺少或无效的认证凭证</td><td>没有有效登录凭证，通常还应配合 <code>WWW-Authenticate</code></td></tr><tr><td><code>403 Forbidden</code></td><td>请求被理解，但当前身份不被允许</td><td>token 校验通过请求格式、但权限不足，或依赖主动拒绝</td></tr><tr><td><code>404 Not Found</code></td><td>路径或资源不存在</td><td><code>/unknown</code> 没有路由，或合法 <code>user_id</code> 查不到用户</td></tr><tr><td><code>405 Method Not Allowed</code></td><td>路径存在，但 HTTP 方法不允许</td><td>只有 GET 路由，却发送 POST</td></tr><tr><td><code>415 Unsupported Media Type</code></td><td>请求体媒体类型不被支持</td><td>服务只接受某种 <code>Content-Type</code></td></tr><tr><td><code>422 Unprocessable Content</code></td><td>内容语法可以理解，但字段语义不能按声明处理</td><td>FastAPI 默认的路径、查询、请求头和 Pydantic 请求体校验失败</td></tr><tr><td><code>500 Internal Server Error</code></td><td>服务端处理自身出错</td><td>函数返回值不符合 <code>response_model</code></td></tr></tbody></table><h3 id="为什么-FastAPI-经常返回-422"><a href="#为什么-FastAPI-经常返回-422" class="headerlink" title="为什么 FastAPI 经常返回 422"></a>为什么 FastAPI 经常返回 422</h3><p>在 FastAPI 中，下面这些声明式校验失败通常都会被统一包装成 <code>422</code>：</p><ul><li><code>user_id: int</code> 收到 <code>abc</code>；</li><li><code>limit: int</code> 收到 <code>abc</code>；</li><li>Pydantic 模型缺少必填字段；</li><li>字段值无法转换或不满足约束；</li><li>在本文验证的 FastAPI 0.116.1 中，请求体 JSON 解码失败也以 <code>422</code> 的 <code>json_invalid</code> 错误返回。</li></ul><p>这说明 FastAPI 的默认行为确实比“语法错误一律 400”的简化口诀更具体，但不能据此把 422 当成 FastAPI 私有定义。HTTP 的通用语义仍然来自规范；框架可以选择自己的默认映射，项目也可以通过异常处理器改写映射。</p><p>400 仍然有明确用途。例如，下面的接口绕过 Pydantic 请求体模型，手动解析 JSON，并把 JSON 语法错误主动映射为 <code>400</code>：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> json <span class="keyword">import</span> JSONDecodeError</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI, HTTPException, Request</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">"/manual-json"</span></span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">manual_json</span>(<span class="params">request: Request</span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        payload = <span class="keyword">await</span> request.json()</span><br><span class="line">    <span class="keyword">except</span> JSONDecodeError:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">400</span>, detail=<span class="string">"Malformed JSON"</span>)</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"received"</span>: payload}</span><br></pre></td></tr></tbody></table></figure><p>这里的 <code>400</code> 不是 Pydantic 自动产生的，而是应用作者主动做出的接口约定。如果改用 <code>payload: SomeModel</code> 声明请求体，解析和字段校验会交回 FastAPI；在本文版本中，坏 JSON 和字段校验错误默认都返回 <code>422</code>。实际项目应在团队 API 规范中统一选择，不要让同一类错误在不同接口间随机使用 <code>400</code> 和 <code>422</code>。</p><h2 id="8-自动文档不是运行结果，而是接口契约"><a href="#8-自动文档不是运行结果，而是接口契约" class="headerlink" title="8. 自动文档不是运行结果，而是接口契约"></a>8. 自动文档不是运行结果，而是接口契约</h2><p>启动服务后，FastAPI 默认提供：</p><ul><li><code>/docs</code>：Swagger UI；</li><li><code>/redoc</code>：ReDoc；</li><li><code>/openapi.json</code>：OpenAPI schema 原文。</li></ul><p>文档中的参数类型、是否必填、请求体结构和响应结构来自 Python 类型声明、Pydantic 模型、路径装饰器和 <code>response_model</code>。它展示的是接口的预期契约，不是某一次请求实际返回的 JSON。</p><p>例如：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BookOut</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    title: <span class="built_in">str</span></span><br><span class="line">    pages: <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">"/book"</span>, response_model=BookOut</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_book</span>():</span><br><span class="line">    <span class="keyword">return</span> {<span class="string">"title"</span>: <span class="string">"FastAPI"</span>, <span class="string">"pages"</span>: <span class="number">120</span>}</span><br></pre></td></tr></tbody></table></figure><p>删除 <code>response_model=BookOut</code> 后，函数仍可能返回同样的字典，但自动文档不再明确保证 <code>title</code> 和 <code>pages</code> 这两个响应字段。运行时数据和 OpenAPI schema 是两个层次，排查文档问题时不要把它们混在一起。</p><h2 id="9-一个完整的小型-API"><a href="#9-一个完整的小型-API" class="headerlink" title="9. 一个完整的小型 API"></a>9. 一个完整的小型 API</h2><p>下面把前面的路由、请求模型、响应模型、依赖和异常组合起来。它也是一个适合自己复制运行的练习：</p><figure class="highlight python"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> Depends, FastAPI, HTTPException</span><br><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ProductIn</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    title: <span class="built_in">str</span></span><br><span class="line">    quantity: <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ProductOut</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    title: <span class="built_in">str</span></span><br><span class="line">    <span class="built_in">id</span>: <span class="built_in">int</span></span><br><span class="line">    quantity: <span class="built_in">int</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">require_admin</span>(<span class="params">token: <span class="built_in">str</span></span>):</span><br><span class="line">    <span class="keyword">if</span> token != <span class="string">"admin"</span>:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">403</span>, detail=<span class="string">"Forbidden"</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="meta">    <span class="string">"/products/{product_id}"</span>,</span></span></span><br><span class="line"><span class="params"><span class="meta">    status_code=<span class="number">201</span>,</span></span></span><br><span class="line"><span class="params"><span class="meta">    response_model=ProductOut,</span></span></span><br><span class="line"><span class="params"><span class="meta"></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create_product</span>(<span class="params"></span></span><br><span class="line"><span class="params">    product_id: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">    product: ProductIn,</span></span><br><span class="line"><span class="params">    is_admin: <span class="built_in">bool</span> = Depends(<span class="params">require_admin</span>),</span></span><br><span class="line"><span class="params"></span>):</span><br><span class="line">    <span class="keyword">if</span> product_id == <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">raise</span> HTTPException(status_code=<span class="number">404</span>, detail=<span class="string">"Not found"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> {</span><br><span class="line">        <span class="string">"id"</span>: product_id,</span><br><span class="line">        <span class="string">"title"</span>: product.title,</span><br><span class="line">        <span class="string">"quantity"</span>: product.quantity,</span><br><span class="line">        <span class="string">"internal_cost"</span>: <span class="number">1</span>,</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>用下面的请求验证每一层：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">curl -X POST <span class="string">"http://127.0.0.1:8000/products/7?token=admin"</span> \</span><br><span class="line">  -H <span class="string">"Content-Type: application/json"</span> \</span><br><span class="line">  -d <span class="string">'{"title":"FastAPI 入门","quantity":"2"}'</span></span><br></pre></td></tr></tbody></table></figure><p>预期状态码是 <code>201</code>，响应中只有 <code>id</code>、<code>title</code> 和 <code>quantity</code>，<code>internal_cost</code> 会被 <code>ProductOut</code> 过滤。再试几个故障请求：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">POST /products/abc?token=admin       -&gt; 422，product_id 校验失败</span><br><span class="line">POST /products/0?token=admin         -&gt; 404，函数体内主动抛出异常</span><br><span class="line">POST /products/7                     -&gt; 422，依赖缺少 token</span><br><span class="line">POST /products/7?token=wrong         -&gt; 403，依赖主动拒绝</span><br></pre></td></tr></tbody></table></figure><h2 id="10-遇到错误时按阶段排查"><a href="#10-遇到错误时按阶段排查" class="headerlink" title="10. 遇到错误时按阶段排查"></a>10. 遇到错误时按阶段排查</h2><p>不要先盯着状态码猜原因，先问“请求走到了哪一步”：</p><ol><li><code>404</code>：确认路径是否注册；如果路径存在，再确认是不是把方法写错导致 <code>405</code>。</li><li><code>422</code>：查看响应体里的 <code>detail</code>，重点看 <code>loc</code> 是 <code>path</code>、<code>query</code>、<code>header</code> 还是 <code>body</code>；这是声明式请求校验没有通过。</li><li>函数没有打印或数据库查询记录：先检查校验和依赖，校验失败时函数体不会执行。</li><li>进入函数后得到 <code>404</code>：这是业务查找结果，不是参数类型错误。</li><li>返回 <code>500</code> 且使用了 <code>response_model</code>：检查函数返回字典是否缺少模型必填字段、类型是否不符合模型。</li><li>token 明明放在请求头却仍然提示缺少参数：检查依赖参数是否声明为 <code>Header</code>，普通 <code>str</code> 参数默认来自查询参数。</li></ol><p>这条排查路径比死记“某状态码代表某句话”更可靠：先定位阶段，再判断是框架默认校验、业务主动异常，还是服务器自己的响应契约出了问题。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>FastAPI 入门真正需要掌握的是处理链，而不是装饰器数量：<code>@app.get</code> 或 <code>@app.post</code> 注册方法和路径；函数签名声明参数来源与类型；Pydantic 校验请求体；<code>Depends</code> 插入认证等前置步骤；<code>response_model</code> 约束对外响应；OpenAPI 文档把这些声明展示成可操作的契约。</p><p>值得注意的是：<code>/users/abc</code> 的 <code>422</code> 是参数校验失败，<code>/users/999</code> 的 <code>404</code> 是资源查找失败；请求校验失败不会进入函数，而响应模型失败通常属于服务端错误。掌握这几个边界后，后续接数据库、认证和更复杂的业务逻辑，仍然可以沿着同一条处理链定位问题。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><a href="https://fastapi.tiangolo.com/zh/">FastAPI 中文教程</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/first-steps/">FastAPI：第一个步骤</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/body/">FastAPI：请求体</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/path-params/">FastAPI：路径参数</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/query-params/">FastAPI：查询参数</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/response-model/">FastAPI：响应模型</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/handling-errors/">FastAPI：错误处理</a></li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/dependencies/">FastAPI：依赖</a></li><li><a href="https://spec.openapis.org/oas/latest.html">OpenAPI Specification</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc9110">RFC 9110：HTTP Semantics</a></li><li><a href="https://www.rfc-editor.org/rfc/rfc4918#section-11.2">RFC 4918：422 Unprocessable Entity 的原始定义</a></li><li><a href="https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81">HTTP 状态码（Wikipedia）</a></li><li><a href="https://ask.csdn.net/questions/8580869">400/422 讨论（CSDN资料）</a></li><li><a href="https://blog.csdn.net/jsjsjs1789/article/details/136458203">ASGI 讨论（CSDN资料）</a></li></ul></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;这篇文章整理了笔者学习 FastAPI 的过程，并将其转化为一套可以按步骤操作和验证的实践教程。目标是完成两个动作：创建路由，以及正确处理请求。重点保留了学习中最容易混淆的几组边界：&lt;code&gt;400&lt;/code&gt; 和 &lt;code&gt;422&lt;/code&gt;、请求校验和响应校验、FastAPI 应用和 Uvicorn 服务器。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="AI Agent" scheme="http://example.com/categories/AI-Agent/"/>
    
    
    <category term="FastAPI" scheme="http://example.com/tags/FastAPI/"/>
    
    <category term="Python" scheme="http://example.com/tags/Python/"/>
    
    <category term="Web" scheme="http://example.com/tags/Web/"/>
    
    <category term="HTTP" scheme="http://example.com/tags/HTTP/"/>
    
    <category term="Pydantic" scheme="http://example.com/tags/Pydantic/"/>
    
  </entry>
  
  <entry>
    <title>明朝那些事儿：我想成为一个什么样的人？</title>
    <link href="http://example.com/p/c52e4253/"/>
    <id>http://example.com/p/c52e4253/</id>
    <published>2025-01-01T07:51:03.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这本书和我理解的历史书不太一样，这似乎是一本写人性的书。历史是由人组成的，也许历史就应该这样写吧，作者写了140多万字，写了明朝300年间各种各样的人。</p><span id="more"></span><p>我试着想给作者笔下的人进行分类，首先想到的是用好人和坏人来分。可刚下这个决定，一个问题把我拦住了：什么人算是好人？</p><p>海瑞是好人吗？在电视剧《大明王朝1566》中，他显然是一个不折不扣的清官，是一个好人。他是个清官，但接下来要说的两件事是从他是不是一个好人展开的。第一件事：海瑞从小由母亲拉扯大，对母亲是极度的相信，后面由于海母的强势和儿媳妇关系不好，海瑞却过度维护母亲而冷落妻子，终其一生，三任妻子都没有和海瑞走到最后，也没有一个儿子能够长大成人。第二件事：徐阶晚年辞官还乡，但朝廷中的敌对势力不愿作罢，四处找借口除掉徐阶，正当此时徐阶他儿子在地方犯了错误，二杠子海瑞明知道利害关系，确不带任何技术手段的处理了这件事，徐阶最后家破人亡。对于徐阶，我专门去查了下关于他的介绍，总结一下他是个有用的人，但不是清官。而海瑞，有一段对他十分中肯的评价：“海瑞所做的事情，如果说都是坏事，那是不对的；如果说都是好事，那也是不对的。应该说，他是一个不太能做事的人。” 这段话正是利用了海瑞整倒徐阶的那个人说的。好了，两件事件讲完了，该思考海瑞是不是一个好人的问题了。我得出的结论是：海瑞对于他的妻子和那些徐阶提拔过的人才来说，他不是一个好人，而对于贪官治下的老百姓来说，他是一个好人。他是一个时好时坏的人。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>上面好像还没解释清楚什么是好人的问题，我的内心依然对它抱有疑惑。说不出来，我想这一定是我的知识还未到能够完全掌控它的程度。噢，“掌控”这个词很应景。无法掌控，因为大部分人是时好时坏的，不能简单的评价一个人是好人还是坏人！所以，我最开始试图用好人和坏人来分类，是错误的。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>既然不能用好与坏来分类人，那能用什么来分类呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>我想答案已经在上面的第二个故事中出现过，是“有用的程度”。为什么不是“有用”和“无用”而是“有用的程度”，因为不能再犯“好人”与“坏人”这样二极管定义的错误了。评价一个人，他对事的有用程度是很好衡量的。比如：奸臣魏忠贤的有用程度就是负数，海瑞的有用程度是正数但是不高。<br>&nbsp;&nbsp;&nbsp;<br>文章的最后，我想引出标题说的问题：我想成为一个什么样的人？<br>&nbsp;&nbsp;&nbsp;<br>既然无法定义一个人是好人还是坏人，而定义一个人的有用程度是可达路径。那么我的目标就不应该是成为一个好人，而是要成为一个对事情有用程度大的人。能力小时，做到对周围事情有用程度大。能力大时，做到对环境有用程度大。<br>&nbsp;&nbsp;&nbsp;<br>在最后的最后，为了表达对作者当年明月（真名：石悦）的崇拜，也表达我此刻无以言表的感慨，特截取了书中几页放到下面保存：</p><div style="display: flex; justify-content: space-around; align-items: flex-start; gap: 5px;">    <img src="//p/c52e4253/1.jpg" style="margin: 0;">    <img src="//p/c52e4253/2.jpg" style="margin: 0;">    <img src="//p/c52e4253/3.jpg" style="margin: 0;"></div></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;这本书和我理解的历史书不太一样，这似乎是一本写人性的书。历史是由人组成的，也许历史就应该这样写吧，作者写了140多万字，写了明朝300年间各种各样的人。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="生活学习" scheme="http://example.com/categories/%E7%94%9F%E6%B4%BB%E5%AD%A6%E4%B9%A0/"/>
    
    
  </entry>
  
  <entry>
    <title>Android 音视频开发实战</title>
    <link href="http://example.com/p/8ca954b9/"/>
    <id>http://example.com/p/8ca954b9/</id>
    <published>2020-06-26T21:34:57.000Z</published>
    <updated>2026-09-20T15:23:29.480Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>声明</strong><br>这是一篇 Android 音视频开发系列文章，它将以代码实战为主，并且会把参考文章罗列出来。对于参考文章中未提及的知识、有误的知识点以及未解决的BUG，都会在<code>注意事项</code>模块补充。建议读者在查看代码时配合着参考资料学习，这样效果会更佳。欢迎<a href="https://github.com/lianjiehao/AudioAndVideoPractice/issues">提问题或建议</a>，大家共同进步～</p></blockquote><span id="more"></span><h1 id="1-代码实现及介绍"><a href="#1-代码实现及介绍" class="headerlink" title="1 代码实现及介绍"></a>1 代码实现及介绍</h1><ul><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter1-1">chapter1-1</a>：OpenGL ES 基础环境搭建以及矩形绘制。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter1-2">chapter1-2</a>：对<code>chapter1-1</code>的封装，同时增加了OpenGL ES 日志输出功能。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter2-1">chapter2-1</a>：OpenGL ES 实现对图片的渲染。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter2-2">chapter2-2</a>：解决<code>chapter2-1</code>中图片显示变形的问题。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter3">chapter3</a>：OpenGL ES 实现对 gif 的渲染。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter4-1">chapter4-1</a>：音视频硬解码实现。</li><li><a href="https://github.com/lianjiehao/AudioAndVideoPractice/tree/master/chapter4-2">chapter4-2</a>：OpenGL ES 结合硬解码播放视频。</li></ul><h1 id="2-注意事项"><a href="#2-注意事项" class="headerlink" title="2 注意事项"></a>2 注意事项</h1><h2 id="2-1-chapter4-代码"><a href="#2-1-chapter4-代码" class="headerlink" title="2.1 chapter4 代码"></a>2.1 chapter4 代码</h2><ul><li>注意：音频采样率不要从<code>MediaFormat</code>中动态获取，而是应该固定为44100，一些资源通过动态获取的值有误。</li><li>BUG：某些视频播放过程中会异常终止。</li><li>BUG：音频播放结束后视频依然会播几帧。</li></ul><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ul><li><a href="https://juejin.im/post/5dae6469e51d45249850cf6f">使用 OpenGL 渲染视频画面篇</a></li><li><a href="https://code.tutsplus.com/zh-hans/tutorials/how-to-use-android-media-effects-with-opengl-es--cms-23650">如何使用OpenGL ES的Android Media Effects框架</a></li><li><a href="https://developer.android.com/guide/topics/graphics/opengl?hl=zh-cn">Android 官方指南之 OpenGL ES</a></li><li>书籍：《OpenGL ES应用开发实践指南  Android卷》</li></ul></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;声明&lt;/strong&gt;&lt;br&gt;这是一篇 Android 音视频开发系列文章，它将以代码实战为主，并且会把参考文章罗列出来。对于参考文章中未提及的知识、有误的知识点以及未解决的BUG，都会在&lt;code&gt;注意事项&lt;/code&gt;模块补充。建议读者在查看代码时配合着参考资料学习，这样效果会更佳。欢迎&lt;a href=&quot;https://github.com/lianjiehao/AudioAndVideoPractice/issues&quot;&gt;提问题或建议&lt;/a&gt;，大家共同进步～&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="音视频" scheme="http://example.com/tags/%E9%9F%B3%E8%A7%86%E9%A2%91/"/>
    
  </entry>
  
  <entry>
    <title>Android 软键盘</title>
    <link href="http://example.com/p/5ae392c8/"/>
    <id>http://example.com/p/5ae392c8/</id>
    <published>2020-03-11T16:45:15.000Z</published>
    <updated>2026-09-20T15:23:29.430Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>说起Android软键盘，这里遇到的坑多数就是对它理解不够全面导致的，所以决定好好总结一下它。在避免自己二次掉坑的同时，如果能帮助到在这个知识区正疑惑的你，那就太好了。<span id="more"></span>本篇的讲解将分以下几个主题：<br>1、操作软键盘<br>2、监听键盘的弹起与隐藏<br>3、如何避免键盘遮挡输入框</p><h1 id="1-操作软键盘"><a href="#1-操作软键盘" class="headerlink" title="1 操作软键盘"></a>1 操作软键盘</h1><p>想要操作软键盘，需要使用到 InputMethodManager ，它是一个系统服务，可以使用 Context.getSystemService() 获取到它。</p><h2 id="1-1-显示软键盘"><a href="#1-1-显示软键盘" class="headerlink" title="1.1 显示软键盘"></a>1.1 显示软键盘</h2><p>在 InputMethodManager中，有两个方法showSoftInput()和showSoftInputFromInputMethod()，而实际上，只有showSoftInput()是有效的。</p><img src="/p/5ae392c8/1.jpeg" class=""><p>这里我们只需要传递两个参数。它首先需要一个View ，使用软键盘就是为了输入，而输入就需要有接收输入内容的View ，这里接收输入的View ，最好是一个EditText（但这不是必须的）。<br>而第二个参数flags就是个标志位，从上面截图的方法签名上的文档上可以看到，它接收0或者SHOW_INPYT_IMPLICIT 两个参数，但是实际上，它有第三个参数，另外一个是SHOW_FORCED。一般没有特别需要的话，我们直接传递0就好了(实际上SHOW_INPYT_IMPLICIT、SHOW_FORCED并不影响显示，只是在隐藏的时候，会有一些限制。而且我们一般的话，不管显示或者隐藏都将flag设置为0，特殊情况遇到再做分析)。<br>现在，简单总结一下调用showSoftInput()会生效的关键点：<br>1、第一个参数，最好是EditText或者它的子类。<br>考虑到软键盘就是为了输入，EditText就是一个接收输入的控件。而这不是绝对的，如果不是一个EditText ，就必须要求这个View有两个属性，分别是：android:focusable=”true” 和android:focusableInTouchMode=”true”。<br>2、第一个参数，必须是可获取焦点的，并且当前已经获取到焦点。<br>EditText默认是允许获取焦点的，但是假如布局中，存在多个可获取焦点的控件，就需要提前让我们传递进去的View获取到焦点。获取焦点可以使用requestFocus()方法。<br>3、布局必须加载完成。<br>在onCreate()中，如果立即调用showSoftInput()是不会生效的。想要在页面一启动的时候就弹出键盘，可以在Activity上，设置 android:windowSoftInputMode属性来完成，或者做一个延迟加载，View.postDelayed()也是一个解决方案。<br>所以最终，完整的显示软键盘的代码就如下所示了。</p><img src="/p/5ae392c8/2.jpeg" class=""><h2 id="1-2-隐藏软键盘"><a href="#1-2-隐藏软键盘" class="headerlink" title="1.2 隐藏软键盘"></a>1.2 隐藏软键盘</h2><p>虽然showSoftInput()方法是有效的，但是想要隐藏软键盘，就没有提供对应的hideSoftInput()方法，但是却有一个hideSoftInputFromWindow()方法，可以用来隐藏软键盘。<br>先来看看这个方法的签名，它同样有两个方法可以调用。</p><img src="/p/5ae392c8/3.jpeg" class=""><p>它接收两个参数，第一个参数是一个IBinder ，可以直接传递一个 View.getWindowToken()的windowToken对象就可以了。而第二个参数，就是隐藏软键盘的标志位，如果没有特殊要求的话，直接传递0就好了。</p><blockquote><p><strong>注意：</strong>这里虽然原则上需要传递一个之前弹出键盘传递的时候，传递的View的windowToken，但是实际情况是你只需要传递一个存在于当前布局ViewTree中，随意一个View的windowToken就可以了。</p></blockquote><p>最终隐藏软件的代码就是这样的。</p><img src="/p/5ae392c8/4.jpeg" class=""><h2 id="1-3-切换键盘的弹出和隐藏"><a href="#1-3-切换键盘的弹出和隐藏" class="headerlink" title="1.3 切换键盘的弹出和隐藏"></a>1.3 切换键盘的弹出和隐藏</h2><p>在InputMethodManager中，还提供了一个toggleSoftInput()方法，如同它的名字一样，它可以让软键盘在显示和隐藏之间切换。</p><img src="/p/5ae392c8/5.jpeg" class=""><p>该方法，接收两个flags ，分别是控制show和hide时候的标识，它们的含义和前面介绍的showSoftInput()和hideSoftInputFromWindow()一致，所以没有特殊要求，直接传递0就好了。<br>toggleSoftInput()方法不要求传递一个View或者windowToken，所以它并没有showSoftInput()中的一些限制，但是依然还有需要在布局绘制完成之后调用才会有效果。</p><img src="/p/5ae392c8/6.jpeg" class=""><p>虽然这个方法，限制很少，但是我们基本上不会使用它。主要原因在于，它是一个开关的方法，会根据当前的状态做相反的操作。这就导致很多时候，我们在代码中，无法直接根据InputMethodManager提供的方法判断当前软键盘的显示状态，这样也就无法确定调用它的时候的效果了。</p><h1 id="2-监听键盘的弹起与隐藏"><a href="#2-监听键盘的弹起与隐藏" class="headerlink" title="2 监听键盘的弹起与隐藏"></a>2 监听键盘的弹起与隐藏</h1><p>如果想要监听键盘的弹出和收起，可以使用<code>ViewTreeObserver.OnGlobalLayoutListener</code>这个监听，来监听布局的调整，从而判断出键盘的弹出和隐藏。下面是一个使用ViewTreeObserver.OnGlobalLayoutListener后获取屏幕中各个类型高度的例子，请注意代码中的注释：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {</span><br><span class="line">@Override</span><br><span class="line">public void onGlobalLayout() {</span><br><span class="line">Log.d("sss_main", "root.getHeight(): " + root.getHeight());//当前所引用视图的高度</span><br><span class="line">Log.d("sss_main", "root.getRootView().getHeight(): " + root.getRootView().getHeight());//屏幕高度</span><br><span class="line">Log.d("sss_main", "getWindow().getDecorView(): " + getWindow().getDecorView().getHeight());//屏幕高度</span><br><span class="line">Rect r = new Rect();</span><br><span class="line">getWindow().getDecorView().getWindowVisibleDisplayFrame(r);</span><br><span class="line">Log.d("sss_main", "r.top: " + r.top);//状态栏高度</span><br><span class="line">Log.d("sss_main", "r.bottom-r.top: " + (r.bottom - r.top));//用户界面的可视高度（除去状态栏和底部导航）</span><br><span class="line">}</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure><p>代码中<code>r.bottom - r.top</code>代表了除去状态栏、底部导航栏后，手机屏幕剩余可视高度。注意，这个剩余可视高度也不包括输入法弹窗的高度（如果屏幕中有输入法弹窗的话）。有了以上的基础，我们就可以通过<code>getWindow().getDecorView().getHeight()-r.bottom&gt;100?键盘弹出：键盘隐藏</code>来判断键盘是否隐藏了。</p><blockquote><p><strong>注意：</strong>为什么要大于100呢？因为如果手机具备底部导航区域的话，要排除这个值的干扰。当然了，直接默认底部导航区域高度为100肯定是不精确的，实际应用中我们应该获取到真实的底部导航区域高度，具体如何获取底部导航区域高度可以<a href="http://blog.csdn.net/u012764110/article/details/49783465">参考这篇文章</a>。</p></blockquote><h1 id="3-如何避免键盘遮挡输入框"><a href="#3-如何避免键盘遮挡输入框" class="headerlink" title="3 如何避免键盘遮挡输入框"></a>3 如何避免键盘遮挡输入框</h1><p>在开发中，经常会遇到键盘挡住输入框的情况，比如登录界面或注册界面，弹出的软键盘把登录或注册按钮挡住了，用户必须把软键盘收起，才能点击相应按钮，这样的用户体验非常不好。下面对几种在开发中常用的避免键盘遮挡输入框的方法进行总结。</p><h2 id="3-1-方法一：windowSoftInputMode-adjustResize和adjustPan"><a href="#3-1-方法一：windowSoftInputMode-adjustResize和adjustPan" class="headerlink" title="3.1 方法一：windowSoftInputMode:adjustResize和adjustPan"></a>3.1 方法一：windowSoftInputMode:adjustResize和adjustPan</h2><p>主要实现方法：在 AndroidManifest.xml 对应的Activity里添加 android:windowSoftInputMode=”adjustPan” 或是 android:windowSoftInputMode=”adjustResize”属性<br>这两种属性的区别，官方的解释是： </p><img src="/p/5ae392c8/7.png" class=""><p>这两个属性作用都是为了调整界面使键盘不挡住输入框,这里对这两种属性使用场景、优缺点、注意事项进行了全方面总结。对于<code>fitsystemwindows</code>还比较懵逼的你，可以参考<a href="http://blog.csdn.net/u012885461/article/details/51068553">这篇文章</a>。</p><img src="/p/5ae392c8/8.jpg" class=""><p>最后附一张官方的截图。</p><img src="/p/5ae392c8/9.jpg" class="" title="该属性是在API级别3引入的。"><blockquote><p><strong>插播知识：</strong><br>这里再提一下clipToPadding和clipChildren这两个属性吧，虽然和本篇没有关系但是同样是有一定概率出现的，所以要讲一下。clipToPadding就是说控件的绘制区域是否在padding里面的，true的情况下如果你设置了padding那么绘制的区域就往里缩，clipToPadding主要应用在listview头部和尾部设置padding后希望可以在滑动item时能滑动到padding区域。clipChildren表示是否限制子View在所属父View的范围内，我们将其值设置为false后那么当子控件的高度高于父控件时也会完全显示,而不会被压缩。注意clipChildren一定是在布局文件的根节点设置，否则不起作用,具体的使用请看<a href="http://blog.csdn.net/flymoon1201/article/details/44646473">这篇文章</a>以及<a href="https://www.zybuluo.com/TryLoveCatch/note/706916">这篇文章</a>。<strong>注意：</strong>有时候clipChildren会不起作用，如果确定考虑到了之前两篇文章所提及的内容，则应该想其它的办法替代clipChildren，这可以认为是clipChildren的BUG。</p></blockquote><h2 id="3-2-方法二：当键盘弹起时，让界面整体上移；键盘收起，让界面整体下移"><a href="#3-2-方法二：当键盘弹起时，让界面整体上移；键盘收起，让界面整体下移" class="headerlink" title="3.2 方法二：当键盘弹起时，让界面整体上移；键盘收起，让界面整体下移"></a>3.2 方法二：当键盘弹起时，让界面整体上移；键盘收起，让界面整体下移</h2><p>使用场景：针对界面全屏模式，输入框不会被键盘遮挡。主要用于一些登录界面，或是需要把界面整体都顶上去的场景。</p><h3 id="3-2-1-主要实现步骤"><a href="#3-2-1-主要实现步骤" class="headerlink" title="3.2.1 主要实现步骤"></a>3.2.1 主要实现步骤</h3><p>(1). 获取Activity布局xml的最外层控件，如xml文件如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"</span><br><span class="line">xmlns:tools="http://schemas.android.com/tools"</span><br><span class="line">android:layout_width="match_parent"</span><br><span class="line">android:layout_height="match_parent"</span><br><span class="line">android:id="@+id/main"</span><br><span class="line">tools:context="com.example.liubin1.softkeyboardhelper.MainActivity"&gt;</span><br><span class="line"></span><br><span class="line">&lt;EditText</span><br><span class="line">android:id="@+id/name"</span><br><span class="line">android:hint="请输入用户名："</span><br><span class="line">android:layout_centerInParent="true"</span><br><span class="line">android:layout_width="match_parent"</span><br><span class="line">android:layout_height="50dp"</span><br><span class="line">/&gt;</span><br><span class="line">&lt;EditText</span><br><span class="line">android:id="@+id/pas"</span><br><span class="line">android:layout_below="@id/name"</span><br><span class="line">android:hint="请输入密码："</span><br><span class="line">android:layout_centerInParent="true"</span><br><span class="line">android:layout_width="match_parent"</span><br><span class="line">android:layout_height="50dp"</span><br><span class="line">/&gt;</span><br><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/login_btn"</span><br><span class="line">android:layout_below="@id/rpas"</span><br><span class="line">android:layout_centerHorizontal="true"</span><br><span class="line">android:text="登录"</span><br><span class="line">android:layout_width="180dp"</span><br><span class="line">android:layout_height="50dp" /&gt;</span><br><span class="line">&lt;/RelativeLayout&gt;</span><br></pre></td></tr></tbody></table></figure><p>先获取到最外层控件:<br>RelativeLayout main = (RelativeLayout) findViewById(R.id.main);<br>(2). 获取到最后一个控件，如上面的xml文件，最后一个控件是Button:<br>Button login_btn = (Button) findViewById(R.id.login_btn);<br>(3). 给最外层控件和最后一个控件添加监听事件:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">//在Activity的onCreate里添加如下方法</span><br><span class="line">addLayoutListener(main,login_btn);</span><br><span class="line">/**   </span><br><span class="line">* addLayoutListener方法如下</span><br><span class="line">* @param main 根布局</span><br><span class="line">* @param scroll 需要显示的最下方View</span><br><span class="line">*/</span><br><span class="line">public void addLayoutListener(final View main, final View scroll) {</span><br><span class="line">main.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {</span><br><span class="line">@Override</span><br><span class="line">public void onGlobalLayout() {</span><br><span class="line">Rect rect = new Rect();</span><br><span class="line">//1、获取main在窗体的可视区域</span><br><span class="line">main.getWindowVisibleDisplayFrame(rect);</span><br><span class="line">//2、获取main在窗体的不可视区域高度，在键盘没有弹起时，main.getRootView().getHeight()调节度应该和rect.bottom高度一样</span><br><span class="line">int mainInvisibleHeight = main.getRootView().getHeight() - rect.bottom;</span><br><span class="line">int screenHeight = main.getRootView().getHeight();//屏幕高度</span><br><span class="line">//3、不可见区域大于屏幕本身高度的1/4：说明键盘弹起了</span><br><span class="line">if (mainInvisibleHeight &gt; screenHeight / 4) {</span><br><span class="line">int[] location = new int[2];</span><br><span class="line">scroll.getLocationInWindow(location);</span><br><span class="line">// 4､获取Scroll的窗体坐标，算出main需要滚动的高度</span><br><span class="line">int srollHeight = (location[1] + scroll.getHeight()) - rect.bottom;</span><br><span class="line">//5､让界面整体上移键盘的高度</span><br><span class="line">main.scrollTo(0, srollHeight);</span><br><span class="line">} else {</span><br><span class="line">//3、不可见区域小于屏幕高度1/4时,说明键盘隐藏了，把界面下移，移回到原有高度</span><br><span class="line">main.scrollTo(0, 0);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">});</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="3-2-2-实现原理"><a href="#3-2-2-实现原理" class="headerlink" title="3.2.2 实现原理"></a>3.2.2 实现原理</h3><p>此方法通过监听 Activity 最外层布局控件来检测软键盘是否弹出，然后去手动调用控件的scrollTo方法达到调整布局目的。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><ol><li><a href="https://segmentfault.com/a/1190000012279204">Android软键盘的显示与隐藏</a></li><li><a href="https://blog.csdn.net/smileiam/article/details/69055963">Android各种键盘挡住输入框解决办法</a></li><li><a href="https://developer.android.com/guide/topics/manifest/activity-element.html?hl=zh-cn">API指南</a></li><li><a href="https://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android">stackoverflow:监听软件的弹出与隐藏</a></li></ol></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;说起Android软键盘，这里遇到的坑多数就是对它理解不够全面导致的，所以决定好好总结一下它。在避免自己二次掉坑的同时，如果能帮助到在这个知识区正疑惑的你，那就太好了。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="View" scheme="http://example.com/tags/View/"/>
    
  </entry>
  
  <entry>
    <title>JAVA 设计模式总结</title>
    <link href="http://example.com/p/8c959f87/"/>
    <id>http://example.com/p/8c959f87/</id>
    <published>2020-03-08T14:58:23.000Z</published>
    <updated>2026-09-20T15:23:29.484Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>笔者一直想对设计模式做一个全面的学习，这次疫情期间在家的时间多了，所以决定基于网络上的优秀文章把这一块知识好好学习整理一下，原文链接会在具体引用处标注。</p><span id="more"></span><h1 id="1-UML-类图"><a href="#1-UML-类图" class="headerlink" title="1 UML 类图"></a>1 UML 类图</h1><p><a href="http://www.uml.org.cn/oobject/201211231.asp">原文链接&gt;&gt;</a></p><h1 id="2-六大设计原则"><a href="#2-六大设计原则" class="headerlink" title="2 六大设计原则"></a>2 六大设计原则</h1><p><a href="https://www.cnblogs.com/pony1223/p/7594803.html">原文链接&gt;&gt;</a></p><p>对于上面参考文章中所提的”高内聚，低耦合”原则，到底什么是内聚，什么是耦合呢？<br>1、耦合<br>耦合主要描述模块之间的关系。耦合模块之间存在依赖, 导致改动可能会互相影响, <strong>关系越紧密, 耦合越强, 模块独立性越差。</strong><br>比如模块A直接操作了模块B中数据, 则视为强耦合, 若A只是通过数据与模块B交互, 则视为弱耦合。独立的模块便于扩展, 维护, 写单元测试, 如果模块之间重重依赖, 会极大降低开发效率。</p><img src="/p/8c959f87/1.gif" class="" title="耦合"><p>2、内聚<br>内聚主要描述模块内部。模块内部的元素, <strong>关联性越强, 则内聚越高, 模块单一性更强。</strong><br>一个模块应当尽可能独立完成某个功能, 如果有各种场景需要被引入到当前模块, 代码质量将变得非常脆弱, 这种情况建议拆分为多个模块.低内聚的模块代码, 不管是维护, 扩展还是重构都相当麻烦, 难以下手。</p><img src="/p/8c959f87/2.gif" class="" title="内聚"><blockquote><p><strong>注意：</strong><br>模块的粒度可大可小, 可以是函数, 类, 功能块等等。</p></blockquote><h1 id="3-设计模式详解"><a href="#3-设计模式详解" class="headerlink" title="3 设计模式详解"></a>3 设计模式详解</h1><p>此模块前两个部分来源于<a href="https://www.cnblogs.com/pony1223/p/7608955.html">此文章</a>，第三部分<code>关键点</code>总结也大多来源于此链接。需要注意的是，链接中其余内容不用管。</p><h2 id="3-1-什么是设计模式"><a href="#3-1-什么是设计模式" class="headerlink" title="3.1 什么是设计模式"></a>3.1 什么是设计模式</h2><p>设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问，设计模式于己于他人于系统都是多赢的，设计模式使代码编制真正工程化，设计模式是软件工程的基石，如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题，每种模式在现实中都有相应的原理来与之对应，每一个模式描述了一个在我们周围不断重复发生的问题，以及该问题的核心解决方案，这也是它能被广泛应用的原因。简单说：<br><strong>模式：在某些场景下，针对某类问题的某种通用的解决方案。</strong><br>场景：项目所在的环境<br>问题：约束条件，项目目标等<br>解决方案：通用、可复用的设计，解决约束达到目标。</p><h2 id="3-2-设计模式的三个分类"><a href="#3-2-设计模式的三个分类" class="headerlink" title="3.2 设计模式的三个分类"></a>3.2 设计模式的三个分类</h2><p><strong>创建型模式：对象实例化的模式，创建型模式用于解耦对象的实例化过程。</strong><br><strong>结构型模式：把类或对象结合在一起形成一个更大的结构。</strong><br><strong>行为型模式：类和对象如何交互，及划分责任和算法。</strong><br><strong>如下图所示：</strong></p><img src="/p/8c959f87/3.png" class=""><h2 id="3-3-24种设计模式"><a href="#3-3-24种设计模式" class="headerlink" title="3.3 24种设计模式"></a>3.3 24种设计模式</h2><p><del>对于下面外链文章所涉及到的 UML 时序图，笔者不是很理解它们的细节表达，不过也不影响对知识概念的理解吧，以后彻底理解了时序图的使用再来复盘。同时，下面的链接文章中有些细节性的小纠正会在其文章的评论区有体现，某些评论对了理解还是有其它辅助作用的。</del></p><h3 id="3-3-1-单例模式"><a href="#3-3-1-单例模式" class="headerlink" title="3.3.1 单例模式"></a>3.3.1 单例模式</h3><p><strong>关键点：某个类只能有一个实例，提供一个全局的访问点。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html">单例模式详解&gt;&gt;</a><br>上面链接中讲到”双重检查加锁”为什么需要 volatile 关键字时没有讲清楚，它并没有提到 volatile 的”有序性”这一特性，实际上这才是”双重检查加锁”场景为什么需要 volatile 的本质。建议参考笔者之前写过的<a href="https://lianjiehao.github.io/2017/02/21/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8BVolatile%E5%92%8CAutomic/">这篇文章</a>来全面了解 volatile 的作用。</p><h3 id="3-3-2-简单工厂模式"><a href="#3-3-2-简单工厂模式" class="headerlink" title="3.3.2 简单工厂模式"></a>3.3.2 简单工厂模式</h3><p><strong>关键点：一个工厂类根据传入的量决定创建出哪一参种产品类的实例。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/03/22/2412308.html">简单工厂模式详解&gt;&gt;</a></p><h3 id="3-3-3-工厂方法模式"><a href="#3-3-3-工厂方法模式" class="headerlink" title="3.3.3 工厂方法模式"></a>3.3.3 工厂方法模式</h3><p><strong>关键点：定义一个创建对象的接口，让子类决定实例化那个类。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/03/25/2416227.html">工厂方法模式详解&gt;&gt;</a></p><h3 id="3-3-4-抽象工厂模式"><a href="#3-3-4-抽象工厂模式" class="headerlink" title="3.3.4 抽象工厂模式"></a>3.3.4 抽象工厂模式</h3><p><strong>关键点：创建相关对象或者依赖对象的家族，而无需明确指定具体类。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html">抽象工厂模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-5-建造者模式"><a href="#3-3-5-建造者模式" class="headerlink" title="3.3.5 建造者模式"></a>3.3.5 建造者模式</h3><p><strong>关键点：封装一个复杂对象的构建过程，并可以按步骤构造。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html">建造者模式详解&gt;&gt;&gt;</a><br>以上链接在”电子杂志系统”例子的红色字体部分讲到：虽然在这个例子里面各个产品类均有一个共同的接口，但这仅仅是本例子特有的，并不代表建造模式的特点…<br>我对这一句话的理解是：对于拥有完全不同接口的产品类的创建过程（这也是大多数场景），一种方案是，分别创建产品（Product）、导演者（Director）、抽象建造者（Builder）、具体建造者（WelcomeBuilder和GoodbyeBuilder），也就是用链文中第一个例子分别创建出各个产品。<strong>另一种方案是，复杂对象的构建可以使用链文中最后一种方法，笔者更倾向于此方案，因为能简化流程。</strong><br>总结一下就是，对于链文中第二个例子的多个产品应用共同接口的情况仅仅是个例，作者只是想把这种特殊情况单独拎出来吧，我们还是要把关注点放在大多数场景的解决方案上来，即第一种方案和第三种方案。</p><h3 id="3-3-6-原型模式"><a href="#3-3-6-原型模式" class="headerlink" title="3.3.6 原型模式"></a>3.3.6 原型模式</h3><p><strong>关键点：通过复制现有的实例来创建新的实例。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html">原型模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-7-适配器模式"><a href="#3-3-7-适配器模式" class="headerlink" title="3.3.7 适配器模式"></a>3.3.7 适配器模式</h3><p><strong>关键点：把一个类的接口变换成客户端所期待的另一种接口。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html">适配器模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-8-组合（合成）模式"><a href="#3-3-8-组合（合成）模式" class="headerlink" title="3.3.8 组合（合成）模式"></a>3.3.8 组合（合成）模式</h3><p><strong>关键点：将对象组合成树形结构以表示“部分-整体”的层次结构。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/17/2453861.html">合成模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-9-装饰模式"><a href="#3-3-9-装饰模式" class="headerlink" title="3.3.9 装饰模式"></a>3.3.9 装饰模式</h3><p><strong>关键点：动态的给对象添加新的功能，是继承关系的一个替代方案。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/20/2455726.html">装饰模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-10-代理模式"><a href="#3-3-10-代理模式" class="headerlink" title="3.3.10 代理模式"></a>3.3.10 代理模式</h3><p><strong>关键点：为其他对象提供一个代理以便控制这个对象的访问。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/23/2466712.html">代理模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-11-亨元模式"><a href="#3-3-11-亨元模式" class="headerlink" title="3.3.11 亨元模式"></a>3.3.11 亨元模式</h3><p><strong>关键点：通过共享技术来有效的支持大量细粒度的对象。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/04/26/2468499.html">亨元模式&gt;&gt;&gt;</a></p><h3 id="3-3-12-外观（门面）模式"><a href="#3-3-12-外观（门面）模式" class="headerlink" title="3.3.12 外观（门面）模式"></a>3.3.12 外观（门面）模式</h3><p><strong>关键点：门面模式提供一个高层次的接口，使得子系统更易于使用。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/02/2478101.html">门面模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-13-桥接（桥梁）模式"><a href="#3-3-13-桥接（桥梁）模式" class="headerlink" title="3.3.13 桥接（桥梁）模式"></a>3.3.13 桥接（桥梁）模式</h3><p><strong>关键点：将抽象部分和它的实现部分分离，使它们都可以独立的变化。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/07/2480938.html">桥梁模式详解&gt;&gt;&gt;</a><br>～关于<code>JDBC驱动器</code>的例子没看懂。以后在来复盘。～</p><h3 id="3-3-14-模板（模板方法）模式"><a href="#3-3-14-模板（模板方法）模式" class="headerlink" title="3.3.14 模板（模板方法）模式"></a>3.3.14 模板（模板方法）模式</h3><p><strong>关键点：定义一个算法结构，而将一些步骤延迟到子类实现。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/14/2495235.html">模板方法模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-15-解释器模式"><a href="#3-3-15-解释器模式" class="headerlink" title="3.3.15 解释器模式"></a>3.3.15 解释器模式</h3><p><strong>关键点：给定一个语言，定义它的文法的一种表示，并定义一个解释器。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/06/19/2552617.html">解释器模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-16-策略模式"><a href="#3-3-16-策略模式" class="headerlink" title="3.3.16 策略模式"></a>3.3.16 策略模式</h3><p><strong>关键点：定义一系列算法，把他们封装起来，并且使它们可以相互替换。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html">策略模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-17-状态模式"><a href="#3-3-17-状态模式" class="headerlink" title="3.3.17 状态模式"></a>3.3.17 状态模式</h3><p><strong>关键点：允许一个对象在其对象内部状态改变时改变它的行为。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/06/08/2538146.html">状态模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-18-观察者模式"><a href="#3-3-18-观察者模式" class="headerlink" title="3.3.18 观察者模式"></a>3.3.18 观察者模式</h3><p><strong>关键点：定义了一种一对多的依赖关系，让多个观察者对象同时监听某一个主题对象。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/16/2502279.html">观察者模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-19-命令模式"><a href="#3-3-19-命令模式" class="headerlink" title="3.3.19 命令模式"></a>3.3.19 命令模式</h3><p><strong>关键点：将命令请求封装为一个对象，使得可以用不同的请求来进行参数化。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/06/01/2526972.html">命令模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-20-备忘录模式"><a href="#3-3-20-备忘录模式" class="headerlink" title="3.3.20 备忘录模式"></a>3.3.20 备忘录模式</h3><p><strong>关键点：在不破坏封装的前提下，保持对象的内部状态。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/06/06/2534942.html">备忘录模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-21-迭代器（迭代子）模式"><a href="#3-3-21-迭代器（迭代子）模式" class="headerlink" title="3.3.21 迭代器（迭代子）模式"></a>3.3.21 迭代器（迭代子）模式</h3><p><strong>关键点：顺序地访问一个聚集中的元素而不必暴露聚集的内部表象。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/05/22/2511506.html">迭代子模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-22-访问者模式"><a href="#3-3-22-访问者模式" class="headerlink" title="3.3.22 访问者模式"></a>3.3.22 访问者模式</h3><p><strong>关键点：在不改变数据结构的前提下，增加作用于一组对象元素的新功能。</strong><br><a href="https://zh.wikipedia.org/wiki/%E8%AE%BF%E9%97%AE%E8%80%85%E6%A8%A1%E5%BC%8F">访问者模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-23-责任链模式"><a href="#3-3-23-责任链模式" class="headerlink" title="3.3.23 责任链模式"></a>3.3.23 责任链模式</h3><p><strong>关键点：将请求的发送者和接收者解耦，使的多个对象都有处理这个请求的机会。</strong><br><a href="https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html">责任链模式详解&gt;&gt;&gt;</a></p><h3 id="3-3-24-中介者（调停者）模式"><a href="#3-3-24-中介者（调停者）模式" class="headerlink" title="3.3.24 中介者（调停者）模式"></a>3.3.24 中介者（调停者）模式</h3><p><strong>关键点：用一个中介对象来封装一系列的对象交互。</strong><br><a href="https://www.cnblogs.com/java-my-life/archive/2012/06/20/2554024.html">调停者模式详解&gt;&gt;&gt;</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;笔者一直想对设计模式做一个全面的学习，这次疫情期间在家的时间多了，所以决定基于网络上的优秀文章把这一块知识好好学习整理一下，原文链接会在具体引用处标注。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="设计模式" scheme="http://example.com/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>《深入浅出 Android 源代码》读后感</title>
    <link href="http://example.com/p/bcc77ec5/"/>
    <id>http://example.com/p/bcc77ec5/</id>
    <published>2020-02-18T12:22:59.000Z</published>
    <updated>2026-09-20T15:23:29.481Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本篇记录了笔者在阅读《深入浅出 Android 源代码》这本书时认为比较重要的知识点，源码基于 Android 6.0 。</p><span id="more"></span><h1 id="1-Activity-的启动过程"><a href="#1-Activity-的启动过程" class="headerlink" title="1 Activity 的启动过程"></a>1 Activity 的启动过程</h1><p>正如书中所说，从点击桌面图标开始分析 Activity 的启动过程，才能全面的掌握这一块的知识点。手机的桌面其实就是一个 Activity ，名称叫 Launcher。当点击 Launcher 中图标后，进入了 Launcher 类的 startActivitySafely 方法，最终该方法调用了 Instrumentation 的 execStartActivitysAsUser 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/Instrumentation.java</span><br><span class="line"></span><br><span class="line">public void execStartActivitiesAsUser(Context who, IBinder contextThread,</span><br><span class="line">        IBinder token, Activity target, Intent[] intents, Bundle options,</span><br><span class="line">        int userId) {</span><br><span class="line">    IApplicationThread whoThread = (IApplicationThread) contextThread;</span><br><span class="line">    if (mActivityMonitors != null) {</span><br><span class="line">        synchronized (mSync) {</span><br><span class="line">            final int N = mActivityMonitors.size();</span><br><span class="line">            for (int i=0; i&lt;N; i++) {</span><br><span class="line">                final ActivityMonitor am = mActivityMonitors.get(i);</span><br><span class="line">                if (am.match(who, null, intents[0])) {</span><br><span class="line">                    am.mHits++;</span><br><span class="line">                    if (am.isBlocking()) {</span><br><span class="line">                        return;</span><br><span class="line">                    }</span><br><span class="line">                    break;</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    try {</span><br><span class="line">        String[] resolvedTypes = new String[intents.length];</span><br><span class="line">        for (int i=0; i&lt;intents.length; i++) {</span><br><span class="line">            intents[i].migrateExtraStreamToClipData();</span><br><span class="line">            intents[i].prepareToLeaveProcess();</span><br><span class="line">            resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());</span><br><span class="line">        }</span><br><span class="line">        int result = ActivityManagerNative.getDefault()</span><br><span class="line">            .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,</span><br><span class="line">                    token, options, userId);</span><br><span class="line">        checkStartActivityResult(result, intents[0]);</span><br><span class="line">    } catch (RemoteException e) {</span><br><span class="line">        throw new RuntimeException("Failure from system", e);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>ActivityManagerNative.getDefault()</code>返回 AMS 的代理对象 ActivityManagerProxy，通过 Binder 驱动最终进入到了 AMS 的 startActivity 方法。</p><p>经过一系列的往下调用，流程来到了 ActivityStack 类的 resumeTopActivityInnerLocked 方法，</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">   private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {</span><br><span class="line"></span><br><span class="line">///省略一波代码</span><br><span class="line"></span><br><span class="line">       if (mResumedActivity != null) {</span><br><span class="line">           if (DEBUG_STATES) Slog.d(TAG_STATES,</span><br><span class="line">                   "resumeTopActivityLocked: Pausing " + mResumedActivity);</span><br><span class="line">           pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);</span><br><span class="line">       }</span><br><span class="line">       if (pausing) {</span><br><span class="line">           if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,</span><br><span class="line">                   "resumeTopActivityLocked: Skip resume: need to start pausing");</span><br><span class="line">           // At this point we want to put the upcoming activity's process</span><br><span class="line">           // at the top of the LRU list, since we know we will be needing it</span><br><span class="line">           // very soon and it would be a waste to let it get killed if it</span><br><span class="line">           // happens to be sitting towards the end.</span><br><span class="line">           if (next.app != null &amp;&amp; next.app.thread != null) {</span><br><span class="line">               mService.updateLruProcessLocked(next.app, true, null);</span><br><span class="line">           }</span><br><span class="line">           if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();</span><br><span class="line">           return true;</span><br><span class="line">       }</span><br><span class="line">///省略一波代码</span><br><span class="line"></span><br><span class="line">mStackSupervisor.startSpecificActivityLocked(next, true, true);</span><br><span class="line"></span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>此时，mResumedActivity 就是 Launcher ， mResumedActivity 不为 null，于是调用 startPausingLocked 方法，之后通过应用进程的 ApplicationThread 将流程回到应用进程，最后来到了 ActivityThread 的 handlePauseActivity 类。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"></span><br><span class="line">private void handlePauseActivity(IBinder token, boolean finished,</span><br><span class="line">        boolean userLeaving, int configChanges, boolean dontReport) {</span><br><span class="line">    ActivityClientRecord r = mActivities.get(token);</span><br><span class="line">    if (r != null) {</span><br><span class="line">        //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);</span><br><span class="line">        if (userLeaving) {</span><br><span class="line">            performUserLeavingActivity(r);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        r.activity.mConfigChangeFlags |= configChanges;</span><br><span class="line">        performPauseActivity(token, finished, r.isPreHoneycomb());</span><br><span class="line"></span><br><span class="line">        // Make sure any pending writes are now committed.</span><br><span class="line">        if (r.isPreHoneycomb()) {</span><br><span class="line">            QueuedWork.waitToFinish();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        // Tell the activity manager we have paused.</span><br><span class="line">        if (!dontReport) {</span><br><span class="line">            try {</span><br><span class="line">                ActivityManagerNative.getDefault().activityPaused(token);</span><br><span class="line">            } catch (RemoteException ex) {</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        mSomeActivitiesChanged = true;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>通过调用 performPauseActivity 后，程序真正进入了 Launcher 的 onPause 方法。</p><p>之后 <code> ActivityManagerNative.getDefault().activityPaused(token)</code> 再次将流程转到了 AMS ，通过一些系列调用，到了 ActivityStack 的 completePauseLocked 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityStack.java</span><br><span class="line"></span><br><span class="line">private void completePauseLocked(boolean resumeNext) {</span><br><span class="line">    ActivityRecord prev = mPausingActivity;</span><br><span class="line">    if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);</span><br><span class="line"></span><br><span class="line">    if (prev != null) {</span><br><span class="line">        prev.state = ActivityState.PAUSED;</span><br><span class="line">        if (prev.finishing) {</span><br><span class="line">            if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);</span><br><span class="line">            prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);</span><br><span class="line">        } else if (prev.app != null) {</span><br><span class="line">            if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending stop: " + prev);</span><br><span class="line">            if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) {</span><br><span class="line">                if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE,</span><br><span class="line">                        "Complete pause, no longer waiting: " + prev);</span><br><span class="line">            }</span><br><span class="line">            if (prev.configDestroy) {</span><br><span class="line">                // The previous is being paused because the configuration</span><br><span class="line">                // is changing, which means it is actually stopping...</span><br><span class="line">                // To juggle the fact that we are also starting a new</span><br><span class="line">                // instance right now, we need to first completely stop</span><br><span class="line">                // the current instance before starting the new one.</span><br><span class="line">                if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Destroying after pause: " + prev);</span><br><span class="line">                destroyActivityLocked(prev, true, "pause-config");</span><br><span class="line">            } else if (!hasVisibleBehindActivity() || mService.isSleepingOrShuttingDown()) {</span><br><span class="line">                // If we were visible then resumeTopActivities will release resources before</span><br><span class="line">                // stopping.</span><br><span class="line">                mStackSupervisor.mStoppingActivities.add(prev);</span><br><span class="line">                if (mStackSupervisor.mStoppingActivities.size() &gt; 3 ||</span><br><span class="line">                        prev.frontOfTask &amp;&amp; mTaskHistory.size() &lt;= 1) {</span><br><span class="line">                    // If we already have a few activities waiting to stop,</span><br><span class="line">                    // then give up on things going idle and start clearing</span><br><span class="line">                    // them out. Or if r is the last of activity of the last task the stack</span><br><span class="line">                    // will be empty and must be cleared immediately.</span><br><span class="line">                    if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "To many pending stops, forcing idle");</span><br><span class="line">                    mStackSupervisor.scheduleIdleLocked();</span><br><span class="line">                } else {</span><br><span class="line">                    mStackSupervisor.checkReadyForSleepLocked();</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        } else {</span><br><span class="line">            if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "App died during pause, not stopping: " + prev);</span><br><span class="line">            prev = null;</span><br><span class="line">        }</span><br><span class="line">        // It is possible the activity was freezing the screen before it was paused.</span><br><span class="line">        // In that case go ahead and remove the freeze this activity has on the screen</span><br><span class="line">        // since it is no longer visible.</span><br><span class="line">        prev.stopFreezingScreenLocked(true /*force*/);</span><br><span class="line">        mPausingActivity = null;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    if (resumeNext) {</span><br><span class="line">        final ActivityStack topStack = mStackSupervisor.getFocusedStack();</span><br><span class="line">        if (!mService.isSleepingOrShuttingDown()) {</span><br><span class="line">            mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);</span><br><span class="line">        } else {</span><br><span class="line">            mStackSupervisor.checkReadyForSleepLocked();</span><br><span class="line">            ActivityRecord top = topStack.topRunningActivityLocked(null);</span><br><span class="line">            if (top == null || (prev != null &amp;&amp; top != prev)) {</span><br><span class="line">                // If there are no more activities available to run,</span><br><span class="line">                // do resume anyway to start something.  Also if the top</span><br><span class="line">                // activity on the stack is not the just paused activity,</span><br><span class="line">                // we need to go ahead and resume it to ensure we complete</span><br><span class="line">                // an in-flight app switch.</span><br><span class="line">                mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    if (prev != null) {</span><br><span class="line">        prev.resumeKeyDispatchingLocked();</span><br><span class="line"></span><br><span class="line">        if (prev.app != null &amp;&amp; prev.cpuTimeAtResume &gt; 0</span><br><span class="line">                &amp;&amp; mService.mBatteryStatsService.isOnBattery()) {</span><br><span class="line">            long diff = mService.mProcessCpuTracker.getCpuTimeForPid(prev.app.pid)</span><br><span class="line">                    - prev.cpuTimeAtResume;</span><br><span class="line">            if (diff &gt; 0) {</span><br><span class="line">                BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics();</span><br><span class="line">                synchronized (bsi) {</span><br><span class="line">                    BatteryStatsImpl.Uid.Proc ps =</span><br><span class="line">                            bsi.getProcessStatsLocked(prev.info.applicationInfo.uid,</span><br><span class="line">                                    prev.info.packageName);</span><br><span class="line">                    if (ps != null) {</span><br><span class="line">                        ps.addForegroundTimeLocked(diff);</span><br><span class="line">                    }</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        prev.cpuTimeAtResume = 0; // reset it</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    // Notfiy when the task stack has changed</span><br><span class="line">    mService.notifyTaskStackChangedLocked();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code> mStackSupervisor.scheduleIdleLocked()</code> 最终执行了 Launcher 的 onPause 方法。</p><p><code>mStackSupervisor.resumeTopActivitiesLocked </code> 一路调用，再次来到了 ActivityStack 的 resumeTopActivityInnerLocked 方法，该方法调用了 ActivityStackSupervisor 类的 startSpecificActivityLocked 方法，此时正式开始了新 Activity 的生命周期过程。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityStackSupervisor.java</span><br><span class="line"></span><br><span class="line">void startSpecificActivityLocked(ActivityRecord r,</span><br><span class="line">        boolean andResume, boolean checkConfig) {</span><br><span class="line">    // Is this activity's application already running?</span><br><span class="line">    ProcessRecord app = mService.getProcessRecordLocked(r.processName,</span><br><span class="line">            r.info.applicationInfo.uid, true);</span><br><span class="line"></span><br><span class="line">    r.task.stack.setLaunchTime(r);</span><br><span class="line"></span><br><span class="line">    if (app != null &amp;&amp; app.thread != null) {</span><br><span class="line">        try {</span><br><span class="line">            if ((r.info.flags&amp;ActivityInfo.FLAG_MULTIPROCESS) == 0</span><br><span class="line">                    || !"android".equals(r.info.packageName)) {</span><br><span class="line">                // Don't add this if it is a platform component that is marked</span><br><span class="line">                // to run in multiple processes, because this is actually</span><br><span class="line">                // part of the framework so doesn't make sense to track as a</span><br><span class="line">                // separate apk in the process.</span><br><span class="line">                app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,</span><br><span class="line">                        mService.mProcessStats);</span><br><span class="line">            }</span><br><span class="line">            realStartActivityLocked(r, app, andResume, checkConfig);</span><br><span class="line">            return;</span><br><span class="line">        } catch (RemoteException e) {</span><br><span class="line">            Slog.w(TAG, "Exception when starting activity "</span><br><span class="line">                    + r.intent.getComponent().flattenToShortString(), e);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        // If a dead object exception was thrown -- fall through to</span><br><span class="line">        // restart the application.</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,</span><br><span class="line">            "activity", r.intent.getComponent(), false, false, true);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>如果 app 不为 null ,就代表将要启动的 Activity 的进程已经启动(在已启动 APP 中启动Activity 就会走 app 不为 null 的逻辑)，显然这里 app 为 null，于是调用了 AMS 的 startProcessLocked 方法启动新的应用进程。startProcessLocked 通过 Socekt 通知 Zygote 进程创建新的进程，之后进入了 ActivityThread 的 main 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">//frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"></span><br><span class="line">public static void main(String[] args) {</span><br><span class="line">    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");</span><br><span class="line">    SamplingProfilerIntegration.start();</span><br><span class="line"></span><br><span class="line">    // CloseGuard defaults to true and can be quite spammy.  We</span><br><span class="line">    // disable it here, but selectively enable it later (via</span><br><span class="line">    // StrictMode) on debug builds, but using DropBox, not logs.</span><br><span class="line">    CloseGuard.setEnabled(false);</span><br><span class="line"></span><br><span class="line">    Environment.initForCurrentUser();</span><br><span class="line"></span><br><span class="line">    // Set the reporter for event logging in libcore</span><br><span class="line">    EventLogger.setReporter(new EventLoggingReporter());</span><br><span class="line"></span><br><span class="line">    AndroidKeyStoreProvider.install();</span><br><span class="line"></span><br><span class="line">    // Make sure TrustedCertificateStore looks in the right place for CA certificates</span><br><span class="line">    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());</span><br><span class="line">    TrustedCertificateStore.setDefaultUserDirectory(configDir);</span><br><span class="line"></span><br><span class="line">    Process.setArgV0("&lt;pre-initialized&gt;");</span><br><span class="line"></span><br><span class="line">    Looper.prepareMainLooper();</span><br><span class="line"></span><br><span class="line">    ActivityThread thread = new ActivityThread();</span><br><span class="line">    thread.attach(false);</span><br><span class="line"></span><br><span class="line">    if (sMainThreadHandler == null) {</span><br><span class="line">        sMainThreadHandler = thread.getHandler();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    if (false) {</span><br><span class="line">        Looper.myLooper().setMessageLogging(new</span><br><span class="line">                LogPrinter(Log.DEBUG, "ActivityThread"));</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    // End of event ActivityThreadMain.</span><br><span class="line">    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);</span><br><span class="line">    Looper.loop();</span><br><span class="line"></span><br><span class="line">    throw new RuntimeException("Main thread loop unexpectedly exited");</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>main 方法作为新 app 的入口，主要做了三件事。一是创建了应用程序进程的主线程消息队列和消息循环，让这个线程一直 loop。而是创建一个 ActivityThread 对象。三是 ActivityThread 的 attach() 方法，下面就来看看这个方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">private void attach(boolean system) {</span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        final IActivityManager mgr = ActivityManagerNative.getDefault();</span><br><span class="line">        try {</span><br><span class="line">            mgr.attachApplication(mAppThread);</span><br><span class="line">        } catch (RemoteException ex) {</span><br><span class="line">            // Ignore</span><br><span class="line">        }</span><br><span class="line">        ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>mAppThread 是当前进程的一个 Binder 对象，之后会传递给 AMS ，AMS之后就是通过该对象与 ActivityThread 通信的。</p><p><code>mgr.attachApplication</code> 会经过 Binder 驱动最终调用到 AMS 的 attachApplication 方法。</p><p>AMS 的 attachApplication 方法会调用到 AMS 的 attachApplicationLocked 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">private final boolean attachApplicationLocked(IApplicationThread thread,</span><br><span class="line">        int pid) {</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    // See if the top visible activity is waiting to run in this process...</span><br><span class="line">    if (normalMode) {</span><br><span class="line">        try {</span><br><span class="line">            if (mStackSupervisor.attachApplicationLocked(app)) {</span><br><span class="line">                didSomething = true;</span><br><span class="line">            }</span><br><span class="line">        } catch (Exception e) {</span><br><span class="line">            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);</span><br><span class="line">            badApp = true;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    if (!didSomething) {</span><br><span class="line">        //调用处理内存管理。不是很理解里面的机制，暂时不分析。</span><br><span class="line">        updateOomAdjLocked();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    return true;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>mStackSupervisor.attachApplicationLocked(app)</code> 最终会通过 Binder 驱动执行到 ActivityThread 的 scheduleLaunchActivity 方法，最终调用了 handleLaunchActivity 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"></span><br><span class="line">private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {</span><br><span class="line">    </span><br><span class="line">    ...        </span><br><span class="line"></span><br><span class="line">    Activity a = performLaunchActivity(r, customIntent);</span><br><span class="line"></span><br><span class="line">    if (a != null) {</span><br><span class="line">        r.createdConfig = new Configuration(mConfiguration);</span><br><span class="line">        Bundle oldState = r.state;</span><br><span class="line">        handleResumeActivity(r.token, false, r.isForward,</span><br><span class="line">                !r.activity.mFinished &amp;&amp; !r.startsNotResumed);</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>performLaunchActivity </code> 调用后会进入 Activity 的 onCreate 方法。</p><p><code>handleResumeActivity</code> 调用后会进入 Activity 的 onStart 和 onResume 方法。</p><p>首先来看下 performLaunchActivity 方法的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br></pre></td><td class="code"><pre><span class="line">frameworks/base/core/java/android/app/ActivityThread.java</span><br><span class="line"></span><br><span class="line">private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {</span><br><span class="line">    // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");</span><br><span class="line"></span><br><span class="line">    ActivityInfo aInfo = r.activityInfo;</span><br><span class="line">    if (r.packageInfo == null) {</span><br><span class="line">        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,</span><br><span class="line">                Context.CONTEXT_INCLUDE_CODE);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    ComponentName component = r.intent.getComponent();</span><br><span class="line">    if (component == null) {</span><br><span class="line">        component = r.intent.resolveActivity(</span><br><span class="line">            mInitialApplication.getPackageManager());</span><br><span class="line">        r.intent.setComponent(component);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    if (r.activityInfo.targetActivity != null) {</span><br><span class="line">        component = new ComponentName(r.activityInfo.packageName,</span><br><span class="line">                r.activityInfo.targetActivity);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    Activity activity = null;</span><br><span class="line">    try {</span><br><span class="line">        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();</span><br><span class="line">        activity = mInstrumentation.newActivity(</span><br><span class="line">                cl, component.getClassName(), r.intent);</span><br><span class="line">        StrictMode.incrementExpectedActivityCount(activity.getClass());</span><br><span class="line">        r.intent.setExtrasClassLoader(cl);</span><br><span class="line">        r.intent.prepareToEnterProcess();</span><br><span class="line">        if (r.state != null) {</span><br><span class="line">            r.state.setClassLoader(cl);</span><br><span class="line">        }</span><br><span class="line">    } catch (Exception e) {</span><br><span class="line">        if (!mInstrumentation.onException(activity, e)) {</span><br><span class="line">            throw new RuntimeException(</span><br><span class="line">                "Unable to instantiate activity " + component</span><br><span class="line">                + ": " + e.toString(), e);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    try {</span><br><span class="line">        Application app = r.packageInfo.makeApplication(false, mInstrumentation);</span><br><span class="line"></span><br><span class="line">        if (localLOGV) Slog.v(TAG, "Performing launch of " + r);</span><br><span class="line">        if (localLOGV) Slog.v(</span><br><span class="line">                TAG, r + ": app=" + app</span><br><span class="line">                + ", appName=" + app.getPackageName()</span><br><span class="line">                + ", pkg=" + r.packageInfo.getPackageName()</span><br><span class="line">                + ", comp=" + r.intent.getComponent().toShortString()</span><br><span class="line">                + ", dir=" + r.packageInfo.getAppDir());</span><br><span class="line"></span><br><span class="line">        if (activity != null) {</span><br><span class="line">            Context appContext = createBaseContextForActivity(r, activity);</span><br><span class="line">            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());</span><br><span class="line">            Configuration config = new Configuration(mCompatConfiguration);</span><br><span class="line">            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "</span><br><span class="line">                    + r.activityInfo.name + " with config " + config);</span><br><span class="line">            activity.attach(appContext, this, getInstrumentation(), r.token,</span><br><span class="line">                    r.ident, app, r.intent, r.activityInfo, title, r.parent,</span><br><span class="line">                    r.embeddedID, r.lastNonConfigurationInstances, config,</span><br><span class="line">                    r.referrer, r.voiceInteractor);</span><br><span class="line"></span><br><span class="line">            if (customIntent != null) {</span><br><span class="line">                activity.mIntent = customIntent;</span><br><span class="line">            }</span><br><span class="line">            r.lastNonConfigurationInstances = null;</span><br><span class="line">            activity.mStartedActivity = false;</span><br><span class="line">            int theme = r.activityInfo.getThemeResource();</span><br><span class="line">            if (theme != 0) {</span><br><span class="line">                activity.setTheme(theme);</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            activity.mCalled = false;</span><br><span class="line">            if (r.isPersistable()) {</span><br><span class="line">                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);</span><br><span class="line">            } else {</span><br><span class="line">                mInstrumentation.callActivityOnCreate(activity, r.state);</span><br><span class="line">            }</span><br><span class="line">            if (!activity.mCalled) {</span><br><span class="line">                throw new SuperNotCalledException(</span><br><span class="line">                    "Activity " + r.intent.getComponent().toShortString() +</span><br><span class="line">                    " did not call through to super.onCreate()");</span><br><span class="line">            }</span><br><span class="line">            r.activity = activity;</span><br><span class="line">            r.stopped = true;</span><br><span class="line">            if (!r.activity.mFinished) {</span><br><span class="line">                activity.performStart();</span><br><span class="line">                r.stopped = false;</span><br><span class="line">            }</span><br><span class="line">            if (!r.activity.mFinished) {</span><br><span class="line">                if (r.isPersistable()) {</span><br><span class="line">                    if (r.state != null || r.persistentState != null) {</span><br><span class="line">                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,</span><br><span class="line">                                r.persistentState);</span><br><span class="line">                    }</span><br><span class="line">                } else if (r.state != null) {</span><br><span class="line">                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">            if (!r.activity.mFinished) {</span><br><span class="line">                activity.mCalled = false;</span><br><span class="line">                if (r.isPersistable()) {</span><br><span class="line">                    mInstrumentation.callActivityOnPostCreate(activity, r.state,</span><br><span class="line">                            r.persistentState);</span><br><span class="line">                } else {</span><br><span class="line">                    mInstrumentation.callActivityOnPostCreate(activity, r.state);</span><br><span class="line">                }</span><br><span class="line">                if (!activity.mCalled) {</span><br><span class="line">                    throw new SuperNotCalledException(</span><br><span class="line">                        "Activity " + r.intent.getComponent().toShortString() +</span><br><span class="line">                        " did not call through to super.onPostCreate()");</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        r.paused = true;</span><br><span class="line"></span><br><span class="line">        mActivities.put(r.token, r);</span><br><span class="line"></span><br><span class="line">    } catch (SuperNotCalledException e) {</span><br><span class="line">        throw e;</span><br><span class="line"></span><br><span class="line">    } catch (Exception e) {</span><br><span class="line">        if (!mInstrumentation.onException(activity, e)) {</span><br><span class="line">            throw new RuntimeException(</span><br><span class="line">                "Unable to start activity " + component</span><br><span class="line">                + ": " + e.toString(), e);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    return activity;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该方法首先通过反射创建 Activity 对象，之后通过 activity 的 attach 方法做了很多 View 相关的初始化工作，之后调用 <code> mInstrumentation.callActivityOnPostCreate(activity, r.state);</code> 就会进入 Activity 的 onCreate 方法。</p><p>接下来看下 handleResumeActivity 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">final void handleResumeActivity(IBinder token,</span><br><span class="line">        boolean clearHide, boolean isForward, boolean reallyResume) {</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    ActivityClientRecord r = performResumeActivity(token, clearHide);</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    Looper.myQueue().addIdleHandler(new Idler());</span><br><span class="line"></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>调用 performResumeActivity 后进入 Activity 的 onStart 和 onResume 方法，这里就不再分析了，这个过程和进入 onCreate 方法类似。</p><p>调用 <code>Looper.myQueue().addIdleHandler(new Idler())</code> 语句后，进入 AMS activityIdle 方法，之后再次回到应用进程中执行了上个 Activity 的 onStop 方法。</p><p>至此，Activity 的启动过程就分析完毕。</p><p>最后，推荐阅读以下<a href="https://blog.csdn.net/jiejingguo/article/details/101012122">这篇文章</a>了解一下 ActivityRecord、ActivityClientRecord、Activity 三者之前的关系。</p><h1 id="2-ANR-机制"><a href="#2-ANR-机制" class="headerlink" title="2 ANR 机制"></a>2 ANR 机制</h1><h2 id="2-1-ANR-产生的原因"><a href="#2-1-ANR-产生的原因" class="headerlink" title="2.1 ANR 产生的原因"></a>2.1 ANR 产生的原因</h2><p>只有当应用程序的UI线程响应超时才会引起ANR，超时产生原因一般有两种。</p><ol><li>当前的事件没有机会得到处理，例如UI线程正在响应另一个事件，当前事件由于某种原因被阻塞了。</li><li>当前的事件正在处理，但是由于耗时太长没能及时完成。</li></ol><p>根据ANR产生的原因不同，超时事件也不尽相同，从本质上将，产生ANR的原因有三种，大致可以对应到android中四大组件中的三个（Activity/View，BroadcastReceiver和Service）。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">KeyDispatchTimeout</span><br></pre></td></tr></tbody></table></figure><p>最常见的一种类型，原因就是View的点击事件或者触摸事件在特定的时间（5s）内无法得到响应。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">BroadcastTimeout</span><br></pre></td></tr></tbody></table></figure><p>原因是BroadcastReceiver的onReceive()函数运行在主线程中，在特定的时间（10s）内无法完成处理。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ServiceTimeout</span><br></pre></td></tr></tbody></table></figure><p>比较少出现的一种类型，原因是Service的各个生命周期函数在特定时间（20s）内无法完成处理。</p><p>该模块更多知识请看 <a href="https://juejin.im/entry/597026806fb9a06bcb7fc660">这篇文章</a>。</p><h2 id="2-2-以-Service-的启动为例，分析-ANR-产生的过程"><a href="#2-2-以-Service-的启动为例，分析-ANR-产生的过程" class="headerlink" title="2.2 以 Service 的启动为例，分析 ANR 产生的过程"></a>2.2 以 Service 的启动为例，分析 ANR 产生的过程</h2><p>当我们调用 startService 方法后，最终会来到 ActiveServices 的 realStartServiceLocked 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"> frameworks/base/core/java/android/server/am/ActiveServices.java</span><br><span class="line"></span><br><span class="line"> private final void realStartServiceLocked(ServiceRecord r,</span><br><span class="line">            ProcessRecord app, boolean execInFg) throws RemoteException {</span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        bumpServiceExecutingLocked(r, execInFg, "create");</span><br><span class="line">        //该方法就是 ANR 记录器，到底特定时间就会触发 ANR 操作。</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">        app.thread.scheduleCreateService(r, r.serviceInfo,</span><br><span class="line">mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),</span><br><span class="line">                    app.repProcState);</span><br><span class="line">     //通过 Binder 驱动调用到 AMS 的 scheduleCreateService 方法。</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>bumpServiceExecutingLocked 最终会调用 scheduleServiceTimeoutLocked 方法，我来看下 scheduleServiceTimeoutLocked 的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">void scheduleServiceTimeoutLocked(ProcessRecord proc) {</span><br><span class="line">    if (proc.executingServices.size() == 0 || proc.thread == null) {</span><br><span class="line">        return;</span><br><span class="line">    }</span><br><span class="line">    long now = SystemClock.uptimeMillis();</span><br><span class="line">    Message msg = mAm.mHandler.obtainMessage(</span><br><span class="line">            ActivityManagerService.SERVICE_TIMEOUT_MSG);</span><br><span class="line">    msg.obj = proc;</span><br><span class="line">    mAm.mHandler.sendMessageAtTime(msg,</span><br><span class="line">            proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>该方法会在超时时间到达后发送 ActivityManagerService.SERVICE_TIMEOUT_MSG 消息，AMS 受该消息后进入 MainHandler 的 SERVICE_TIMEOUT_MSG case。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">case SERVICE_TIMEOUT_MSG: {</span><br><span class="line">    if (mDidDexOpt) {</span><br><span class="line">        mDidDexOpt = false;</span><br><span class="line">        Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);</span><br><span class="line">        nmsg.obj = msg.obj;</span><br><span class="line">        mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);</span><br><span class="line">        return;</span><br><span class="line">    }</span><br><span class="line">    mServices.serviceTimeout((ProcessRecord)msg.obj);</span><br><span class="line">} </span><br></pre></td></tr></tbody></table></figure><p>mServices.serviceTimeout 最终会通过 AMS 的 appNotResponding 方法发送 SHOW_NOT_RESPONDING_MSG 消息到 UiHandler 中弹出 ANR 弹窗。</p><p>由以上分析我们可以知道，要想避免 ANR 弹窗的出现，就必须抢在超时时间到达之前结束 mAm.mHandler.sendMessageAtTime 的发送，如何结束呢？</p><p>让我们回到 realStartServiceLocked 方法，执行 app.thread.scheduleCreateService 语句最终会将我们带到应用进程 ActivityThread 类的 handleCreateService 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">private void handleCreateService(CreateServiceData data) {</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    LoadedApk packageInfo = getPackageInfoNoCheck(</span><br><span class="line">            data.info.applicationInfo, data.compatInfo);</span><br><span class="line">    Service service = null;</span><br><span class="line">    try {</span><br><span class="line">        java.lang.ClassLoader cl = packageInfo.getClassLoader();</span><br><span class="line">        service = (Service) cl.loadClass(data.info.name).newInstance();</span><br><span class="line">    } catch (Exception e) {</span><br><span class="line">        if (!mInstrumentation.onException(service, e)) {</span><br><span class="line">            throw new RuntimeException(</span><br><span class="line">                "Unable to instantiate service " + data.info.name</span><br><span class="line">                + ": " + e.toString(), e);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    try {</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line"></span><br><span class="line">        service.onCreate();</span><br><span class="line">        mServices.put(data.token, service);</span><br><span class="line">        try {</span><br><span class="line">            ActivityManagerNative.getDefault().serviceDoneExecuting(</span><br><span class="line">                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);</span><br><span class="line">        } catch (RemoteException e) {</span><br><span class="line">            // nothing to do.</span><br><span class="line">        }</span><br><span class="line">    } catch (Exception e) {</span><br><span class="line">        if (!mInstrumentation.onException(service, e)) {</span><br><span class="line">            throw new RuntimeException(</span><br><span class="line">                "Unable to create service " + data.info.name</span><br><span class="line">                + ": " + e.toString(), e);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>代码首先通过反射创建了 Service 对象，之后 service.onCreate 方法调用，最后 ActivityManagerNative.getDefault().serviceDoneExecuting 语句会通过 Binder 驱动调用 AMS 的 serviceDoneExecuting 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {</span><br><span class="line">    synchronized(this) {</span><br><span class="line">        if (!(token instanceof ServiceRecord)) {</span><br><span class="line">            Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);</span><br><span class="line">            throw new IllegalArgumentException("Invalid service token");</span><br><span class="line">        }</span><br><span class="line">        mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>mServices.serviceDoneExecutingLocked 最终会调用到 ActiveServices 的 serviceDoneExecutingLocked 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,</span><br><span class="line">        boolean finishing) {</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    if (r.executeNesting &lt;= 0) {</span><br><span class="line">        if (r.app != null) {</span><br><span class="line">            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,</span><br><span class="line">                    "Nesting at 0 of " + r.shortName);</span><br><span class="line">            r.app.execServicesFg = false;</span><br><span class="line">            r.app.executingServices.remove(r);</span><br><span class="line">            if (r.app.executingServices.size() == 0) {</span><br><span class="line"></span><br><span class="line">                ...</span><br><span class="line"></span><br><span class="line">                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            ...</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>mAm.mHandler.removeMessages 语句移除了 ActivityManagerService.SERVICE_TIMEOUT_MSG 消息，之后 ANR 就不会被触发了。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;本篇记录了笔者在阅读《深入浅出 Android 源代码》这本书时认为比较重要的知识点，源码基于 Android 6.0 。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="Android源码" scheme="http://example.com/tags/Android%E6%BA%90%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title>Rxjava2 线程调度原理</title>
    <link href="http://example.com/p/a3968b0c/"/>
    <id>http://example.com/p/a3968b0c/</id>
    <published>2020-01-19T17:13:30.000Z</published>
    <updated>2026-09-20T15:23:29.481Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>这篇文章讲解 Rxjava2 中最难理解的线程调度是如何实现的。</p><span id="more"></span><p>我们来看这样一个例子。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Single.just("item")</span><br><span class="line">            .map(object : Function&lt;String, String&gt; {</span><br><span class="line">                override fun apply(t: String): String {</span><br><span class="line">                    println("1:${Thread.currentThread()}")</span><br><span class="line">                    return t</span><br><span class="line">                }</span><br><span class="line">            })</span><br><span class="line">            .observeOn(Schedulers.io())</span><br><span class="line">            .subscribe(object : SingleObserver&lt;String&gt; {</span><br><span class="line">                override fun onSuccess(t: String) {</span><br><span class="line">                    println("2:${Thread.currentThread()}")</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onSubscribe(d: Disposable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onError(e: Throwable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line">            })</span><br></pre></td></tr></tbody></table></figure><p>输出结果是:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1:Thread[main,5,main]</span><br><span class="line">2:Thread[RxCachedThreadScheduler-1,5,main]</span><br></pre></td></tr></tbody></table></figure><p>可以看到，位于 observeOn 上游的生产者( map )没有切换线程，而下游的消费者( SingleObserver )切换了线程。</p><p>再来看一个案例。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">Single.just("item")</span><br><span class="line">            .map(object : Function&lt;String, String&gt; {</span><br><span class="line">                override fun apply(t: String): String {</span><br><span class="line">                    println("1:${Thread.currentThread()}")</span><br><span class="line">                    return t</span><br><span class="line">                }</span><br><span class="line">            })</span><br><span class="line">            .subscribeOn(Schedulers.io())</span><br><span class="line">            .subscribe(object : SingleObserver&lt;String&gt; {</span><br><span class="line">                override fun onSuccess(t: String) {</span><br><span class="line">                    println("2:${Thread.currentThread()}")</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onSubscribe(d: Disposable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onError(e: Throwable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line">            })</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>我们只是把 observeOn 改成了 subscribeOn 方法，结果如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1:Thread[RxCachedThreadScheduler-1,5,main]</span><br><span class="line">2:Thread[RxCachedThreadScheduler-1,5,main]</span><br></pre></td></tr></tbody></table></figure><p>结论是 subscribeOn 上游下游的生产者和消费者都切换了线程。</p><p>下面我们从源码的角度分析产生上面不同情况的原因。</p><h2 id="observeOn"><a href="#observeOn" class="headerlink" title="observeOn"></a>observeOn</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public final Single&lt;T&gt; observeOn(final Scheduler scheduler) {</span><br><span class="line">        ObjectHelper.requireNonNull(scheduler, "scheduler is null");</span><br><span class="line">        return RxJavaPlugins.onAssembly(new SingleObserveOn&lt;T&gt;(this, scheduler));</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>代码中的 this 指的是上游的 SingleMap ，scheduler 就是传入 observeOn 的参数，这里就是 Schedulers.io() 。</p><blockquote><p>现在只需要把 Schedulers.io() 理解成是一个切换线程的工具，之后再详细分析它。</p></blockquote><p>RxJavaPlugins.onAssembly 是钩子方法，可以直接把它的参数理解为返回值。接下来，看下 SingleObserveOn 类的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line">public final class SingleObserveOn&lt;T&gt; extends Single&lt;T&gt; {</span><br><span class="line"></span><br><span class="line">    final SingleSource&lt;T&gt; source;</span><br><span class="line"></span><br><span class="line">    final Scheduler scheduler;</span><br><span class="line"></span><br><span class="line">    public SingleObserveOn(SingleSource&lt;T&gt; source, Scheduler scheduler) {</span><br><span class="line">        this.source = source;</span><br><span class="line">        this.scheduler = scheduler;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void subscribeActual(final SingleObserver&lt;? super T&gt; observer) {</span><br><span class="line">        source.subscribe(new ObserveOnSingleObserver&lt;T&gt;(observer, scheduler));</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    static final class ObserveOnSingleObserver&lt;T&gt; extends AtomicReference&lt;Disposable&gt;</span><br><span class="line">    implements SingleObserver&lt;T&gt;, Disposable, Runnable {</span><br><span class="line">        private static final long serialVersionUID = 3528003840217436037L;</span><br><span class="line"></span><br><span class="line">        final SingleObserver&lt;? super T&gt; downstream;</span><br><span class="line"></span><br><span class="line">        final Scheduler scheduler;</span><br><span class="line"></span><br><span class="line">        T value;</span><br><span class="line">        Throwable error;</span><br><span class="line"></span><br><span class="line">        ObserveOnSingleObserver(SingleObserver&lt;? super T&gt; actual, Scheduler scheduler) {</span><br><span class="line">            this.downstream = actual;</span><br><span class="line">            this.scheduler = scheduler;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onSubscribe(Disposable d) {</span><br><span class="line">            if (DisposableHelper.setOnce(this, d)) {</span><br><span class="line">                downstream.onSubscribe(this);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onSuccess(T value) {</span><br><span class="line">            this.value = value;</span><br><span class="line">            Disposable d = scheduler.scheduleDirect(this);</span><br><span class="line">            DisposableHelper.replace(this, d);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onError(Throwable e) {</span><br><span class="line">            this.error = e;</span><br><span class="line">            Disposable d = scheduler.scheduleDirect(this);</span><br><span class="line">            DisposableHelper.replace(this, d);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void run() {</span><br><span class="line">            Throwable ex = error;</span><br><span class="line">            if (ex != null) {</span><br><span class="line">                downstream.onError(ex);</span><br><span class="line">            } else {</span><br><span class="line">                downstream.onSuccess(value);</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void dispose() {</span><br><span class="line">            DisposableHelper.dispose(this);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public boolean isDisposed() {</span><br><span class="line">            return DisposableHelper.isDisposed(get());</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>类中的 source 即为上游的 SingleMap 对象，scheduler 就是 Schedulers.io()。由第一个案例可知，调用了 observeOn 后在返回结果的基础上调用了 subscribe 方法，而这个 subscribe 方法在父类 Single 中的实现中调用了子类的 subscribeActual 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">public abstract class Single&lt;T&gt; implements SingleSource&lt;T&gt; {</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">@SchedulerSupport(SchedulerSupport.NONE)</span><br><span class="line">    @Override</span><br><span class="line">    public final void subscribe(SingleObserver&lt;? super T&gt; observer) {</span><br><span class="line">        ObjectHelper.requireNonNull(observer, "subscriber is null");</span><br><span class="line"></span><br><span class="line">        observer = RxJavaPlugins.onSubscribe(this, observer);</span><br><span class="line"></span><br><span class="line">        ObjectHelper.requireNonNull(observer, "The RxJavaPlugins.onSubscribe hook returned a null SingleObserver. Please check the handler provided to RxJavaPlugins.setOnSingleSubscribe for invalid null returns. Further reading: https://github.com/ReactiveX/RxJava/wiki/Plugins");</span><br><span class="line"></span><br><span class="line">        try {</span><br><span class="line">            subscribeActual(observer);</span><br><span class="line">        } catch (NullPointerException ex) {</span><br><span class="line">            throw ex;</span><br><span class="line">        } catch (Throwable ex) {</span><br><span class="line">            Exceptions.throwIfFatal(ex);</span><br><span class="line">            NullPointerException npe = new NullPointerException("subscribeActual failed");</span><br><span class="line">            npe.initCause(ex);</span><br><span class="line">            throw npe;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当前的子类就是 SingleObserveOn 类，该类的 subscribeActual 方法中调用了上游 SingleMap 对象的 subscribe 方法，并把 ObserveOnSingleObserver 传给了上游。最终，依次调用了 SingleMap 中 MapSingleObserver 相关方法以及 SingleObserveOn 中 ObserveOnSingleObserver 相关方法。这里以 ObserveOnSingleObserver 中的 onSuccess 方法调用为例，代码实现如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">        public void onSuccess(T value) {</span><br><span class="line">            this.value = value;</span><br><span class="line">            Disposable d = scheduler.scheduleDirect(this);</span><br><span class="line">            DisposableHelper.replace(this, d);</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>这里首先将 value 赋值给当前实例，之后调用了 scheduler.scheduleDirect 方法切换线程，在切换线程后执行了 ObserveOnSingleObserver 的 run 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">        public void run() {</span><br><span class="line">            Throwable ex = error;</span><br><span class="line">            if (ex != null) {</span><br><span class="line">                downstream.onError(ex);</span><br><span class="line">            } else {</span><br><span class="line">                downstream.onSuccess(value);</span><br><span class="line">            }</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>downstream 就是下游的消费者。</p><p>所以这里可以得出结论，位于上游的逻辑（ SingleMap 中 MapSingleObserver 相关方法）在原线程中执行，位于下游的消费者（ SingleObserver ）逻辑执行在切换后的线程中。</p><h2 id="subscribeOn"><a href="#subscribeOn" class="headerlink" title="subscribeOn"></a>subscribeOn</h2><p>和 observeOn 类似，所以直接分析对应的 SingleSubscribeOn 类。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line">public final class SingleSubscribeOn&lt;T&gt; extends Single&lt;T&gt; {</span><br><span class="line">    final SingleSource&lt;? extends T&gt; source;</span><br><span class="line"></span><br><span class="line">    final Scheduler scheduler;</span><br><span class="line"></span><br><span class="line">    public SingleSubscribeOn(SingleSource&lt;? extends T&gt; source, Scheduler scheduler) {</span><br><span class="line">        this.source = source;</span><br><span class="line">        this.scheduler = scheduler;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void subscribeActual(final SingleObserver&lt;? super T&gt; observer) {</span><br><span class="line">        final SubscribeOnObserver&lt;T&gt; parent = new SubscribeOnObserver&lt;T&gt;(observer, source);</span><br><span class="line">        observer.onSubscribe(parent);</span><br><span class="line"></span><br><span class="line">        Disposable f = scheduler.scheduleDirect(parent);</span><br><span class="line"></span><br><span class="line">        parent.task.replace(f);</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    static final class SubscribeOnObserver&lt;T&gt;</span><br><span class="line">    extends AtomicReference&lt;Disposable&gt;</span><br><span class="line">    implements SingleObserver&lt;T&gt;, Disposable, Runnable {</span><br><span class="line"></span><br><span class="line">        private static final long serialVersionUID = 7000911171163930287L;</span><br><span class="line"></span><br><span class="line">        final SingleObserver&lt;? super T&gt; downstream;</span><br><span class="line"></span><br><span class="line">        final SequentialDisposable task;</span><br><span class="line"></span><br><span class="line">        final SingleSource&lt;? extends T&gt; source;</span><br><span class="line"></span><br><span class="line">        SubscribeOnObserver(SingleObserver&lt;? super T&gt; actual, SingleSource&lt;? extends T&gt; source) {</span><br><span class="line">            this.downstream = actual;</span><br><span class="line">            this.source = source;</span><br><span class="line">            this.task = new SequentialDisposable();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onSubscribe(Disposable d) {</span><br><span class="line">            DisposableHelper.setOnce(this, d);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onSuccess(T value) {</span><br><span class="line">            downstream.onSuccess(value);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void onError(Throwable e) {</span><br><span class="line">            downstream.onError(e);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void dispose() {</span><br><span class="line">            DisposableHelper.dispose(this);</span><br><span class="line">            task.dispose();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public boolean isDisposed() {</span><br><span class="line">            return DisposableHelper.isDisposed(get());</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void run() {</span><br><span class="line">            source.subscribe(this);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当调用 scheduler.scheduleDirect 方法后切换线程，之后执行 SubscribeOnObserver 中的 run 方法调用 <code>source.subscribe(this)</code>，从此刻开始，上游的调用逻辑将在切换后的线程中执行，而上游逻辑之后又会调用 SubscribeOnObserver 中的方法，所以上游生产者（ map ）和下游消费者（ SingleObserver）都会运行在切换后的线程中。</p><p><strong>思考：若 subscribeOn 和 observeOn 同时存在，如何判断各个阶段所运行的线程？</strong></p><p>比如下面的例子。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">Single.just("item")</span><br><span class="line">            .map(object : Function&lt;String, String&gt; {</span><br><span class="line">                override fun apply(t: String): String {</span><br><span class="line">                    println("1:${Thread.currentThread()}")</span><br><span class="line">                    return t</span><br><span class="line">                }</span><br><span class="line">            })</span><br><span class="line">            .observeOn(Schedulers.io())</span><br><span class="line">            .map(object : Function&lt;String, String&gt; {</span><br><span class="line">                override fun apply(t: String): String {</span><br><span class="line">                    println("2:${Thread.currentThread()}")</span><br><span class="line">                    return t</span><br><span class="line">                }</span><br><span class="line">            })</span><br><span class="line">            .subscribeOn(Schedulers.newThread())</span><br><span class="line">            .subscribe(object : SingleObserver&lt;String&gt; {</span><br><span class="line">                override fun onSuccess(t: String) {</span><br><span class="line">                    println("3:${Thread.currentThread()}")</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onSubscribe(d: Disposable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                override fun onError(e: Throwable) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line">            })</span><br></pre></td></tr></tbody></table></figure><p>结果如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1:Thread[RxNewThreadScheduler-1,5,main]</span><br><span class="line">2:Thread[RxCachedThreadScheduler-1,5,main]</span><br><span class="line">3:Thread[RxCachedThreadScheduler-1,5,main]</span><br></pre></td></tr></tbody></table></figure><p>结果不理解的话，就按照上面的思路去看下源码吧。</p><h2 id="线程调度器"><a href="#线程调度器" class="headerlink" title="线程调度器"></a>线程调度器</h2><p>上文中频繁出现了 scheduler.scheduleDirect 切线程的语句，下面就来详细说明 Rxjava2 到底是如何切换线程的。</p><p>Rxjava2 常用的线程调度器一共有 4 种：</p><table><thead><tr><th align="left">调度器类型</th><th align="left">效果</th></tr></thead><tbody><tr><td align="left">Schedulers.computation()</td><td align="left">用于计算任务，如事件循环或和回调处理，不要用于IO操作(IO操作请使用Schedulers.io())；默认线程数等于处理器的数量</td></tr><tr><td align="left">Schedulers.newThread()</td><td align="left">为每个任务创建一个新线程</td></tr><tr><td align="left">Schedulers.io()</td><td align="left">用于IO密集型任务，如异步阻塞IO操作，这个调度器的线程池会根据需要增长；对于普通的计算任务，请使用Schedulers.computation()；Schedulers.io( )默认是一个CachedThreadScheduler，很像一个有线程缓存的线程调度器</td></tr><tr><td align="left">AndroidSchedulers.mainThread( )</td><td align="left">主线程，UI线程，可以用于更新界面</td></tr></tbody></table><p>computation、newThread 以及 io 调度器的创建有一个共同特点，就是他们的初始化工作都在 Schedulers 的静态代码块中完成，也就是说他们的实例对象是全局唯一的，Schedulers 中的静态代码块如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">static {</span><br><span class="line">        SINGLE = RxJavaPlugins.initSingleScheduler(new SingleTask());</span><br><span class="line"></span><br><span class="line">        COMPUTATION = RxJavaPlugins.initComputationScheduler(new ComputationTask());</span><br><span class="line"></span><br><span class="line">        IO = RxJavaPlugins.initIoScheduler(new IOTask());</span><br><span class="line"></span><br><span class="line">        TRAMPOLINE = TrampolineScheduler.instance();</span><br><span class="line"></span><br><span class="line">        NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(new NewThreadTask());</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><h2 id="Schedulers-newThread"><a href="#Schedulers-newThread" class="headerlink" title="Schedulers.newThread()"></a>Schedulers.newThread()</h2><p>调用 Schedulers.newThread() 得到的实例对象是一个 NewThreadScheduler 对象。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">public final class NewThreadScheduler extends Scheduler {</span><br><span class="line"></span><br><span class="line">    final ThreadFactory threadFactory;</span><br><span class="line"></span><br><span class="line">    private static final String THREAD_NAME_PREFIX = "RxNewThreadScheduler";</span><br><span class="line">    private static final RxThreadFactory THREAD_FACTORY;</span><br><span class="line"></span><br><span class="line">    /** The name of the system property for setting the thread priority for this Scheduler. */</span><br><span class="line">    private static final String KEY_NEWTHREAD_PRIORITY = "rx2.newthread-priority";</span><br><span class="line"></span><br><span class="line">    static {</span><br><span class="line">        int priority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY,</span><br><span class="line">                Integer.getInteger(KEY_NEWTHREAD_PRIORITY, Thread.NORM_PRIORITY)));</span><br><span class="line"></span><br><span class="line">        THREAD_FACTORY = new RxThreadFactory(THREAD_NAME_PREFIX, priority);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    public NewThreadScheduler() {</span><br><span class="line">        this(THREAD_FACTORY);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    public NewThreadScheduler(ThreadFactory threadFactory) {</span><br><span class="line">        this.threadFactory = threadFactory;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    @NonNull</span><br><span class="line">    @Override</span><br><span class="line">    public Worker createWorker() {</span><br><span class="line">        return new NewThreadWorker(threadFactory);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>有文章开头两个案例的源码分析可知，在线程发生切换时会调用 <code>scheduler.scheduleDirect</code>语句，该语句最终会执行 Scheduler类 的 scheduleDirect 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {</span><br><span class="line">        final Worker w = createWorker();</span><br><span class="line"></span><br><span class="line">        final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);</span><br><span class="line"></span><br><span class="line">        DisposeTask task = new DisposeTask(decoratedRun, w);</span><br><span class="line"></span><br><span class="line">        w.schedule(task, delay, unit);</span><br><span class="line"></span><br><span class="line">        return task;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>Scheduler 的 <code>createWorker()</code>方法会调用子类（这里是NewThreadScheduler)的 createWorker 方法创建一个 NewThreadWorker 对象，之后调用到 NewThreadWorker 对象的 schedule 方法，最终调用到 NewThreadWorker对象的 scheduleActual 方法。</p><p><strong>这里可以看出每次切换线程都会创建一个线程池，这块逻辑同时也是几种线程调度之间最大的区别。</strong></p><p>下面我们看一下 NewThreadWorker 类的关键代码实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">public class NewThreadWorker extends Scheduler.Worker implements Disposable {</span><br><span class="line">    private final ScheduledExecutorService executor;</span><br><span class="line"></span><br><span class="line">    volatile boolean disposed;</span><br><span class="line"></span><br><span class="line">    public NewThreadWorker(ThreadFactory threadFactory) {</span><br><span class="line">        executor = SchedulerPoolFactory.create(threadFactory);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">    public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) {</span><br><span class="line">        Runnable decoratedRun = RxJavaPlugins.onSchedule(run);</span><br><span class="line"></span><br><span class="line">        ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent);</span><br><span class="line"></span><br><span class="line">        if (parent != null) {</span><br><span class="line">            if (!parent.add(sr)) {</span><br><span class="line">                return sr;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        Future&lt;?&gt; f;</span><br><span class="line">        try {</span><br><span class="line">            if (delayTime &lt;= 0) {</span><br><span class="line">                f = executor.submit((Callable&lt;Object&gt;)sr);</span><br><span class="line">            } else {</span><br><span class="line">                f = executor.schedule((Callable&lt;Object&gt;)sr, delayTime, unit);</span><br><span class="line">            }</span><br><span class="line">            sr.setFuture(f);</span><br><span class="line">        } catch (RejectedExecutionException ex) {</span><br><span class="line">            if (parent != null) {</span><br><span class="line">                parent.remove(sr);</span><br><span class="line">            }</span><br><span class="line">            RxJavaPlugins.onError(ex);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        return sr;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>构造方法中新建了一个核心线程数是 1 的线程池，具体实现如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">public static ScheduledExecutorService create(ThreadFactory factory) {</span><br><span class="line">        final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory);</span><br><span class="line">        tryPutIntoPool(PURGE_ENABLED, exec);</span><br><span class="line">        return exec;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>scheduleActual 方法中通过 <code>executor.submit</code> 或 <code>executor.schedule</code> 实现了线程的切换。</p><h2 id="Schedulers-io"><a href="#Schedulers-io" class="headerlink" title="Schedulers.io()"></a>Schedulers.io()</h2><p>Schedulers.io() 最终生成 IoScheduler 对象，IoScheduler 内部维护了一个 CachedWorkerPool ，CachedWorkerPool 内部维护了一个 expiringWorkerQueue 队列用于保存可用的 ThreadWorker。每次需要 ThreadWorker 时，优先从队列中取，取不到则新建 ThreadWorker。接下来就带领大家看下这个逻辑是怎么实现的。</p><p>下面是 expiringWorkerQueue 添加 ThreadWorker 的场景。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">void release(ThreadWorker threadWorker) {</span><br><span class="line">            // Refresh expire time before putting worker back in pool</span><br><span class="line">            threadWorker.setExpirationTime(now() + keepAliveTime);</span><br><span class="line"></span><br><span class="line">            expiringWorkerQueue.offer(threadWorker);</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>当调用 IoScheduler 的 dispose 方法时会调用到这个方法，它是 CachedWorkerPool 中的方法，此方法将已使用完成的 threadWorker 加入到 expiringWorkerQueue 队列的尾部以便重用。</p><p>接下来我们看下 threadWorker 的获取。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">public Worker createWorker() {</span><br><span class="line">         //通过调用 CachedWorkerPool 的 get 方法获取了 threadWorker。</span><br><span class="line">         return new EventLoopWorker(pool.get());</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>CachedWorkerPool 的 get 方法定义如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">ThreadWorker get() {</span><br><span class="line">            if (allWorkers.isDisposed()) {</span><br><span class="line">                return SHUTDOWN_THREAD_WORKER;</span><br><span class="line">            }</span><br><span class="line">            while (!expiringWorkerQueue.isEmpty()) {</span><br><span class="line">                ThreadWorker threadWorker = expiringWorkerQueue.poll();</span><br><span class="line">                if (threadWorker != null) {</span><br><span class="line">                    return threadWorker;</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            // No cached worker found, so create a new one.</span><br><span class="line">            ThreadWorker w = new ThreadWorker(threadFactory);</span><br><span class="line">            allWorkers.add(w);</span><br><span class="line">            return w;</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>get 方法先通过 expiringWorkerQueue.poll 方法获取 threadWorker 对象，获取不到则新建。</p><p>下面是 ThreadWorker 的定义。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">static final class ThreadWorker extends NewThreadWorker {</span><br><span class="line">        private long expirationTime;</span><br><span class="line"></span><br><span class="line">        ThreadWorker(ThreadFactory threadFactory) {</span><br><span class="line">            super(threadFactory);</span><br><span class="line">            this.expirationTime = 0L;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public long getExpirationTime() {</span><br><span class="line">            return expirationTime;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public void setExpirationTime(long expirationTime) {</span><br><span class="line">            this.expirationTime = expirationTime;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>ThreadWorker 是 NewThreadWorker 的子类，之后的操作和 Schedulers.newThread() 中讲述的一致，这里不再赘述。</p><p><strong>Schedulers.io() 采用了 pool 的方式复用了已使用完成的线程池。</strong></p><h2 id="Schedulers-computation"><a href="#Schedulers-computation" class="headerlink" title="Schedulers.computation()"></a>Schedulers.computation()</h2><p>Schedulers.computation() 会生成一个 ComputationScheduler 对象，ComputationScheduler 类内部维护了一个 FixedSchedulerPool 对象，该对象中维护了一个 PoolWorker 数组（PoolWorker 是 NewThreadWorker 的子类），数组的长度是 CPU 核数，所以 <strong>Schedulers.computation() 会生成一个长度固定为 CPU 核数的线程池数组</strong>。</p><p>数组的长度计算逻辑如下，得到的 MAX_THREADS 就是 CPU 的核数。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MAX_THREADS = cap(Runtime.getRuntime().availableProcessors(), Integer.getInteger(KEY_MAX_THREADS, 0));</span><br></pre></td></tr></tbody></table></figure><p>再来让我们看看 createWorker 方法的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public Worker createWorker() {</span><br><span class="line">        return new EventLoopWorker(pool.get().getEventLoop());</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>其中 <code>pool.get().getEventLoop()</code> 得到了一个 PoolWorker 对象，下面看下 getEventLoop 方法的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">public PoolWorker getEventLoop() {</span><br><span class="line">            int c = cores;</span><br><span class="line">            if (c == 0) {</span><br><span class="line">                return SHUTDOWN_WORKER;</span><br><span class="line">            }</span><br><span class="line">            // simple round robin, improvements to come</span><br><span class="line">            return eventLoops[(int)(n++ % c)];</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>这其实就是循环取数组中 PoolWorker 元素的逻辑。</p><h2 id="AndroidSchedulers-mainThread"><a href="#AndroidSchedulers-mainThread" class="headerlink" title="AndroidSchedulers.mainThread()"></a>AndroidSchedulers.mainThread()</h2><p>AndroidSchedulers.mainThread() 最终生成一个 HandlerScheduler 静态对象，HandlerScheduler 的创建和上面几个调度器的创建逻辑不一样，上面几个调度器都是在 Schedulers 的静态代码块中新建的，而 HandlerScheduler 属于 AndroidSchedulers 静态内部类 MainHolder 的一个静态属性，代码如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">public final class AndroidSchedulers {</span><br><span class="line"></span><br><span class="line">    private static final class MainHolder {</span><br><span class="line">        static final Scheduler DEFAULT</span><br><span class="line">            = new HandlerScheduler(new Handler(Looper.getMainLooper()), false);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>调度过程也有不一样，HandlerScheduler 中自己定义了 scheduleDirect 方法，所以发生线程调度后，即 scheduler.scheduleDirect 被调用后，HandlerScheduler 的 scheduleDirect 方法会被直接调用，而不需要执行父类 Scheduler 的 scheduleDirect 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {</span><br><span class="line">        if (run == null) throw new NullPointerException("run == null");</span><br><span class="line">        if (unit == null) throw new NullPointerException("unit == null");</span><br><span class="line"></span><br><span class="line">        run = RxJavaPlugins.onSchedule(run);</span><br><span class="line">        ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);</span><br><span class="line">        Message message = Message.obtain(handler, scheduled);</span><br><span class="line">        if (async) {</span><br><span class="line">            message.setAsynchronous(true);</span><br><span class="line">        }</span><br><span class="line">        handler.sendMessageDelayed(message, unit.toMillis(delay));</span><br><span class="line">        return scheduled;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>由上面代码可知， Runnable 对象中的 run 方法会在主线程中执行。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇文章重点讲解了 Rxjava2 的线程调度原理，总的来说，学习 Rxjava2 的源码设计思想可以从以下 4 个方面作为切入点：<br>1、Rxjava2 如何实现基于事件流的链式调用。<br>2、线程调度原理。<br>3、map 操作符是如何工作的。<br>4、Rxjava2 是如何取消订阅的。</p><p>对于以上未介绍的切入点，大家可以自行去阅读源码或参考 Hencoder plus 相关课程。</p><blockquote><p><strong>文章写作背景</strong><br>笔者在看了扔物线老师的 Hencoder plus 相关课程后觉得线程调度这块的知识讲解得不完整，后面笔者花了好些时间才梳理清晰，所以记录下这篇文章以免忘记。同时，如果对你也有帮助的话，那就更好了。</p></blockquote></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;这篇文章讲解 Rxjava2 中最难理解的线程调度是如何实现的。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="Rxjava2" scheme="http://example.com/tags/Rxjava2/"/>
    
  </entry>
  
  <entry>
    <title>Android 进程间通信机制</title>
    <link href="http://example.com/p/379c6d8a/"/>
    <id>http://example.com/p/379c6d8a/</id>
    <published>2020-01-15T14:03:32.000Z</published>
    <updated>2026-09-20T15:23:29.480Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本文将介绍 Android 实现进程间通信的几种方式，进入正题之前，我们首先来了解下多进程的概念，知道多进程是什么，以及为什么要用多进程。</p><span id="more"></span><h1 id="1-多进程"><a href="#1-多进程" class="headerlink" title="1 多进程"></a>1 多进程</h1><p>1、一般情况下，一个应用程序就是一个进程，这个进程名称就是应用程序包名。每个进程都有自己独立的资源和内存空间，别的进程是不能任意访问其他进程的内存和资源。<br>2、单进程的局限：每个进程所能使用的资源是有限，特别是内存，安卓系统对用户进程有严格的内存要求，超过此内存限制时，应用将OOM和崩溃。所以，Android引入了多进程的概念，它允许在同一个应用内，为了分担主进程的压力，将占用内存的某些页面单独开一个进程。<br>3、Android多进程创建很简单，只需要在AndroidManifest.xml的声明四大组件的标签中增加”android:process”属性即可。命名之后，就成了一个单独的进程。”android:process”属性以【:】开头为当前应用的私有进程(:remote)，否则为全局进程（如：com.xxx.xxx）。<br>4、多进程应用运行时application的onCreate()会执行多次，所以使用的时候就要根据进程名判断当前所处的进程来进行对应的初始化操作。<br>5、静态成员和单例失效：每个进程保持各自的静态成员和单例，相互独立。<br>6、Android会为每一个进程分配一个独立的虚拟机，不同的虚拟机在内存分配上有不同的地址空间。它们要访问对方的数据的时候，都要通过进程间通信的方式来进行。</p><h1 id="2-进程间通信方式"><a href="#2-进程间通信方式" class="headerlink" title="2 进程间通信方式"></a>2 进程间通信方式</h1><h2 id="2-1-Intent-Bundle"><a href="#2-1-Intent-Bundle" class="headerlink" title="2.1 Intent/Bundle"></a>2.1 Intent/Bundle</h2><p>Activity，Service，Receiver 都支持在 Intent 中传递 Bundle 数据，而 Bundle 实现了 Parcelable 接口，可以在不同的进程间进行传输。所以，在一个进程中启动了另一个进程的 Activity，Service 和 Receiver ，可以在 Bundle 中附加要传递的数据通过 Intent 发送出去。</p><blockquote><p><strong>提示</strong><br>Intent 不仅可以在进程间传输 Bundle（Parcelable）类型数据、基本类型数据，对于 Serializable 类型的数据也支持在进程间传递。但是我们一般不用 Serializable 作为进程间数据传递的载体，至于为什么，请参考<a href="https://juejin.im/post/5dadae3be51d4524d0269ba6#heading-3">这篇文章</a>的序列化部分。文中提到的 Serializable 序列化、反序列化方式是反射，以反序列化为例，源码追溯路径是 readObject-&gt; readObject0 -&gt; readOrdinaryObject -&gt; invokeReadResolve&gt;readResolveMethod.invoke(…)。</p></blockquote><h2 id="2-2-文件共享"><a href="#2-2-文件共享" class="headerlink" title="2.2 文件共享"></a>2.2 文件共享</h2><p>1、可以在进程中序列化数据对象到文件中，然后在另一个进程中反序列化这个对象。<br>2、SharedPreferences 由于系统对SP的读写有一定的缓存策略，使内存中有一份SP文件，导致系统对它的读写不可靠。当高频读写操作是，SP会有数据丢失的风险，所以IPC不建议采用这种方式。<br>3、SQLite 支持多进程并发访问。请参考<a href="https://database.51cto.com/art/201908/601243.htm">这篇文章</a>，笔者没有实践过。<br>4、使用 <a href="https://github.com/Tencent/MMKV">MMKV</a> 库（后续文章再具体介绍）。</p><h2 id="2-3-Messager"><a href="#2-3-Messager" class="headerlink" title="2.3 Messager"></a>2.3 Messager</h2><p>通过Messenger可以在不同进程之间传递Message对象，Message中可以放入我们需要传递的数据，它的底层实现是AIDL。</p><p>但是Messenger在服务端的Handler是以串行的方式处理来自客户端的Message，所以如果有大量的并发请求，Messenger就效率低下，所以Messenger适用于数据并发量低的进程间通信。</p><p>具体使用请参考<a href="https://developer.android.com/guide/components/bound-services#Messenger">官方文章&gt;&gt;</a></p><p>下面以一个音频播放器的例子讲解 Messager 的实际使用，关于音乐类软件开发思路笔者<a href="https://lianjiehao.github.io/2016/12/28/%E4%BC%98%E7%A7%80%E8%B5%84%E6%96%99%E6%94%B6%E9%9B%86/">这篇文章</a>中有列举参考资料）。</p><p>首先，我们从 MediaBrowserCompat 的 connect 方法说起。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public void connect() {</span><br><span class="line">        mImpl.connect();</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>方法中的 mImpl 是我们在构造 MediaBrowser 对象时创建的。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">public MediaBrowserCompat(Context context, ComponentName serviceComponent,</span><br><span class="line">        ConnectionCallback callback, Bundle rootHints) {</span><br><span class="line">    // To workaround an issue of {@link #unsubscribe(String, SubscriptionCallback)} on API 24</span><br><span class="line">    // and 25 devices, use the support library version of implementation on those devices.</span><br><span class="line">    if (Build.VERSION.SDK_INT &gt;= 26) {</span><br><span class="line">        mImpl = new MediaBrowserImplApi26(context, serviceComponent, callback, rootHints);</span><br><span class="line">    } else if (Build.VERSION.SDK_INT &gt;= 23) {</span><br><span class="line">        mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);</span><br><span class="line">    } else if (Build.VERSION.SDK_INT &gt;= 21) {</span><br><span class="line">        mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);</span><br><span class="line">    } else {</span><br><span class="line">        mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>以上 mImpl 的四种实现中只有 MediaBrowserImplBase 是以 Messenger 方式进行的（其它三个是 AIDL 的方式），下面我只选取 MediaBrowserImplBase 这个实现方式加以说明。</p><p>于是由<code>mImpl.connect()</code>走到了 MediaBrowserImplBase 的 connect 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public void connect() {</span><br><span class="line">            ...</span><br><span class="line">            mHandler.post(new Runnable() {</span><br><span class="line">                @Override</span><br><span class="line">                public void run() {</span><br><span class="line">                    ...</span><br><span class="line">                    mServiceConnection = new MediaServiceConnection();</span><br><span class="line">                    boolean bound = false;</span><br><span class="line">                    try {</span><br><span class="line">                        bound = mContext.bindService(intent, mServiceConnection,</span><br><span class="line">                                Context.BIND_AUTO_CREATE);</span><br><span class="line">                    } catch (Exception ex) {</span><br><span class="line">                        Log.e(TAG, "Failed binding to service " + mServiceComponent);</span><br><span class="line">                    }</span><br><span class="line">                    ...</span><br><span class="line">                }</span><br><span class="line">            });</span><br><span class="line">        }</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>只看主要部分，可见 MediaBrowserCompat 的 connect 方法最终做的工作就是调用 bindService 方法绑定了服务，其中 mServiceConnection 是一个<code>MediaServiceConnection</code>对象，他是<code>MediaBrowserImplBase </code>的内部类，下面我们看看<code>MediaServiceConnection </code>这个类做了什么。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">private class MediaServiceConnection implements ServiceConnection {</span><br><span class="line">            MediaServiceConnection() {</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            @Override</span><br><span class="line">            public void onServiceConnected(final ComponentName name, final IBinder binder) {</span><br><span class="line">                postOrRun(new Runnable() {</span><br><span class="line">                    @Override</span><br><span class="line">                    public void run() {</span><br><span class="line">                        ...</span><br><span class="line"></span><br><span class="line">                        // Save their binder</span><br><span class="line">                        mServiceBinderWrapper = new ServiceBinderWrapper(binder, mRootHints);</span><br><span class="line"></span><br><span class="line">                        // We make a new mServiceCallbacks each time we connect so that we can drop</span><br><span class="line">                        // responses from previous connections.</span><br><span class="line">                        mCallbacksMessenger = new Messenger(mHandler);</span><br><span class="line">                        mHandler.setCallbacksMessenger(mCallbacksMessenger);</span><br><span class="line"></span><br><span class="line">                        mState = CONNECT_STATE_CONNECTING;</span><br><span class="line"></span><br><span class="line">                        // Call connect, which is async. When we get a response from that we will</span><br><span class="line">                        // say that we're connected.</span><br><span class="line">                        try {</span><br><span class="line">                            if (DEBUG) {</span><br><span class="line">                                Log.d(TAG, "ServiceCallbacks.onConnect...");</span><br><span class="line">                                dump();</span><br><span class="line">                            }</span><br><span class="line">                            mServiceBinderWrapper.connect(mContext, mCallbacksMessenger);</span><br><span class="line">                        } catch (RemoteException ex) {</span><br><span class="line"></span><br><span class="line">                            ...</span><br><span class="line">                        }</span><br><span class="line">                    }</span><br><span class="line">                });</span><br><span class="line">            }</span><br><span class="line">...</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>我们重点关注 onServiceConnected 方法的实现，看代码注释，这里面做了两个重要的事，一个是由服务端返回的 binder(BinderProxy) 构造出 ServiceBinderWrapper 对象，之后在 ServiceBinderWrapper 构造方法内得到了一个 Messenger 对象，这个 messager 用于向服务端发送消息。第二个是调用 ServiceBinderWrapper 的 connect 方法将客户端构造的 Messenger 对象 mCallbacksMessenger 发送到了服务端，这样服务端就可以向这个客户端发送消息了。最终实现了由服务端到客户端，客户端到服务端的双向沟通机制。下面是 ServiceBinderWrapper 的构成。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br></pre></td><td class="code"><pre><span class="line">private static class ServiceBinderWrapper {</span><br><span class="line">       private Messenger mMessenger;</span><br><span class="line">       private Bundle mRootHints;</span><br><span class="line"></span><br><span class="line">       public ServiceBinderWrapper(IBinder target, Bundle rootHints) {</span><br><span class="line">           mMessenger = new Messenger(target);</span><br><span class="line">           mRootHints = rootHints;</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void connect(Context context, Messenger callbacksMessenger)</span><br><span class="line">               throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_PACKAGE_NAME, context.getPackageName());</span><br><span class="line">           data.putBundle(DATA_ROOT_HINTS, mRootHints);</span><br><span class="line">           sendRequest(CLIENT_MSG_CONNECT, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void disconnect(Messenger callbacksMessenger) throws RemoteException {</span><br><span class="line">           sendRequest(CLIENT_MSG_DISCONNECT, null, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void addSubscription(String parentId, IBinder callbackToken, Bundle options,</span><br><span class="line">               Messenger callbacksMessenger)</span><br><span class="line">               throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_MEDIA_ITEM_ID, parentId);</span><br><span class="line">           BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);</span><br><span class="line">           data.putBundle(DATA_OPTIONS, options);</span><br><span class="line">           sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void removeSubscription(String parentId, IBinder callbackToken,</span><br><span class="line">               Messenger callbacksMessenger)</span><br><span class="line">               throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_MEDIA_ITEM_ID, parentId);</span><br><span class="line">           BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);</span><br><span class="line">           sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void getMediaItem(String mediaId, ResultReceiver receiver, Messenger callbacksMessenger)</span><br><span class="line">               throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_MEDIA_ITEM_ID, mediaId);</span><br><span class="line">           data.putParcelable(DATA_RESULT_RECEIVER, receiver);</span><br><span class="line">           sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void registerCallbackMessenger(Messenger callbackMessenger) throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putBundle(DATA_ROOT_HINTS, mRootHints);</span><br><span class="line">           sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, data, callbackMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void unregisterCallbackMessenger(Messenger callbackMessenger) throws RemoteException {</span><br><span class="line">           sendRequest(CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER, null, callbackMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void search(String query, Bundle extras, ResultReceiver receiver,</span><br><span class="line">               Messenger callbacksMessenger) throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_SEARCH_QUERY, query);</span><br><span class="line">           data.putBundle(DATA_SEARCH_EXTRAS, extras);</span><br><span class="line">           data.putParcelable(DATA_RESULT_RECEIVER, receiver);</span><br><span class="line">           sendRequest(CLIENT_MSG_SEARCH, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       void sendCustomAction(String action, Bundle extras, ResultReceiver receiver,</span><br><span class="line">               Messenger callbacksMessenger) throws RemoteException {</span><br><span class="line">           Bundle data = new Bundle();</span><br><span class="line">           data.putString(DATA_CUSTOM_ACTION, action);</span><br><span class="line">           data.putBundle(DATA_CUSTOM_ACTION_EXTRAS, extras);</span><br><span class="line">           data.putParcelable(DATA_RESULT_RECEIVER, receiver);</span><br><span class="line">           sendRequest(CLIENT_MSG_SEND_CUSTOM_ACTION, data, callbacksMessenger);</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       private void sendRequest(int what, Bundle data, Messenger cbMessenger)</span><br><span class="line">               throws RemoteException {</span><br><span class="line">           Message msg = Message.obtain();</span><br><span class="line">           msg.what = what;</span><br><span class="line">           msg.arg1 = CLIENT_VERSION_CURRENT;</span><br><span class="line">           msg.setData(data);</span><br><span class="line">           msg.replyTo = cbMessenger;</span><br><span class="line">           mMessenger.send(msg);</span><br><span class="line">       }</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>大体流程，首先是在构造函数中根据服务端返回的 binder 创建一个 Messenger 对象，下面的方法都最终调用了 sendRequest 方法，而 sendRequest 方法最后又调用了 Messager 的 send 方法往服务端发送消息，同时也会带上本地的 Messenger 对象 mCallbacksMessenger。</p><p>以上就是客户端的流程，下面进入服务端的流程。</p><p>当客户端调用 bindService 方法后，对于服务端来说最终会调用到它的 onCreate 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public void onCreate() {</span><br><span class="line">    super.onCreate();</span><br><span class="line">    if (Build.VERSION.SDK_INT &gt;= 26) {</span><br><span class="line">        mImpl = new MediaBrowserServiceImplApi26();</span><br><span class="line">    } else if (Build.VERSION.SDK_INT &gt;= 23) {</span><br><span class="line">        mImpl = new MediaBrowserServiceImplApi23();</span><br><span class="line">    } else if (Build.VERSION.SDK_INT &gt;= 21) {</span><br><span class="line">        mImpl = new MediaBrowserServiceImplApi21();</span><br><span class="line">    } else {</span><br><span class="line">        mImpl = new MediaBrowserServiceImplBase();</span><br><span class="line">    }</span><br><span class="line">    mImpl.onCreate();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们只关注最后一种情况，即调用 MediaBrowserServiceImplBase 的 onCreate 方法（其它 3 个是 AIDL 方式 ）。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">class MediaBrowserServiceImplBase implements MediaBrowserServiceImpl {</span><br><span class="line">      private Messenger mMessenger;</span><br><span class="line"></span><br><span class="line">      @Override</span><br><span class="line">      public void onCreate() {</span><br><span class="line">          mMessenger = new Messenger(mHandler);</span><br><span class="line">      }</span><br><span class="line"></span><br><span class="line">      @Override</span><br><span class="line">      public IBinder onBind(Intent intent) {</span><br><span class="line">          if (SERVICE_INTERFACE.equals(intent.getAction())) {</span><br><span class="line">              return mMessenger.getBinder();</span><br><span class="line">          }</span><br><span class="line">          return null;</span><br><span class="line">      }</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">  }</span><br></pre></td></tr></tbody></table></figure><p>MediaBrowserServiceImplBase 中，首先在 onCreate 中创建了类型为 Messenger 的 mMessenger 对象，之后在 onBind 方法中通过 mMessenger.getBinder() 返回 IBinder 供客户端使用，客户端拿到这个 IBinder 实例之后就可以往服务器发送消息了，之后的流程就是之前提到的客户端逻辑了。<br>通过解读源码，大家可以发现 Compat 包中对于不同 API 等级，使用的进程间通信方式是不一样的，以上只是一个很小的切入点，其它的方面还需自行阅读源码。总之一个原则就是，不管是 Messenger 还是 AIDL ，要实现进程间的双向沟通，就必须都拿到对方的 IBinder 代理对象。</p><h2 id="2-4-AIDL"><a href="#2-4-AIDL" class="headerlink" title="2.4 AIDL"></a>2.4 AIDL</h2><p>AIDL 可以解决并发和跨进程调用方法的问题，本质是利用了 Binder 传输机制，其实 Messenger 本质上也是 Binder ，只不过Messager不适合处理大量并发的消息处理，也不支持跨进程调用方法。</p><p>通过编写aidl文件来设计想要暴露的接口，编译后会自动生成对应的java文件，服务器将接口的具体实现写在Stub中，用 IBinder 对象传递给客户端，客户端 bindService 的时候，用 asInterface 的形式将 iBinder 还原成接口，再调用其中的方法。</p><p><a href="https://www.jianshu.com/p/29999c1a93cd">参考链接&gt;&gt;</a></p><p>注意：AIDL 支持单独的基本类型变量以及 String，CharSequence 类型变量的传递。他们也可以作为 Parcelable 子类的一个属性存在。Serializable 不支持 AIDL 传递，但可以放置在 Bundle 中传递。除了基本类型外，其它的自定义Parcelable 类型必须在单独的aidl文件中导入。</p><p>除了使用 AIDL 工具实现以上的机制，我们还可以通过手写 AIDL 生成的各个类，实现进程间通信的需求。<a href="https://www.jianshu.com/p/37368fab05bf">参考链接&gt;&gt;</a></p><p>AIDL进阶参考<a href="https://www.jianshu.com/p/cc117a84b834">这篇文章&gt;&gt;</a></p><h2 id="2-5-ContentProvider"><a href="#2-5-ContentProvider" class="headerlink" title="2.5 ContentProvider"></a>2.5 ContentProvider</h2><p>系统四大组件之一，底层也是Binder实现，主要用来为其他APP提供数据，可以说天生就是为进程通信而生的。<a href="https://juejin.im/post/5af812966fb9a07ac85a8188">使用教程&gt;&gt;</a></p><h2 id="2-6-Socket"><a href="#2-6-Socket" class="headerlink" title="2.6 Socket"></a>2.6 Socket</h2><p>Socket 是连接应用层与传输层之间的接口，常用的 Socket 类型有两种：流式 Socket（SOCK_STREAM）和数据报式 Socket（SOCK_DGRAM）。流式是一种面向连接的 Socket，针对于面向连接的 TCP 服务应用；数据报式 Socket 是一种无连接的 Socket ，对应于无连接的 UDP 服务应用。一般我们使用流式的 Socket。关于 Socket 的介绍将在后续的篇幅中讲解。</p><p><strong>以上内容参照了下面两篇文章的部分内容模块：</strong><br>1、<a href="https://xiaozhuanlan.com/topic/3896402175">安卓进程间通信及 App 保活</a>的“多进程”和“进程间通信IPC方式”两大模块。<br>2、<a href="https://juejin.im/post/5dadae3be51d4524d0269ba6#heading-3">使用AIDL来进行进程间通信</a>的“Android进程间通信的方式”和“进程间通信的准备”两大部分。</p><h1 id="3-疑惑解答"><a href="#3-疑惑解答" class="headerlink" title="3 疑惑解答"></a>3 疑惑解答</h1><h2 id="3-1-Android-为什么要选择-Binder-机制来实现进程间通信"><a href="#3-1-Android-为什么要选择-Binder-机制来实现进程间通信" class="headerlink" title="3.1 Android 为什么要选择 Binder 机制来实现进程间通信"></a>3.1 Android 为什么要选择 Binder 机制来实现进程间通信</h2><table><thead><tr><th align="left"></th><th align="left">Binder</th><th align="left">共享内存</th><th align="left">Socket</th></tr></thead><tbody><tr><td align="left">性能</td><td align="left">数据拷贝一次</td><td align="left">无需拷贝</td><td align="left">数据拷贝两次</td></tr><tr><td align="left">稳定</td><td align="left">C/S架构，清晰明朗，稳定性好。</td><td align="left">需要自行处理同步等问题。</td><td align="left">C/S架构，传输效率低，开销大。</td></tr><tr><td align="left">安全性</td><td align="left">内核添加身份标识，可靠。</td><td align="left">依赖上层协议。访问接入点开放，不安全。</td><td align="left">依赖上层协议。访问接入点开放，不安全。</td></tr></tbody></table><p>Binder “数据拷贝一次”是什么意思？其实这里就涉及到 mmap（memory mapping）的使用了。</p><p>虚拟内存被用户系统分为两部分：用户空间和内核空间，用户空间是用户代码运行的地方，内核空间是内核代码运行的地方，内核空间由所有进程共享。为了安全，用户空间和内核空间是隔离的，即使用户空间的程序发生崩溃，内核也不会受到影响。每个用户空间（进程）之前也是互相隔离的，为了让进程之间可以互相通信，我们可以借助共享内核来实现数据传输。</p><img src="/p/379c6d8a/binder.jpg" class="" title="Binder通信"><p>当数据从用户内存中拷贝到内核空间时，会在内核区域开辟一块虚拟内存，即 Binder 缓冲区，之后利用 mmap 技术将这块虚拟内存与接收方的虚拟内存映射到同一块物理内存上，这样就省去了从内核空间到用户空间的拷贝操作，从而实现了 binder 的一次拷贝技术（从用户空间到内核空间）。同时，binder 缓冲区的空间是有限的，所以需要及时释放内存，为后续拷贝操作做准备。</p><p>mmap 是一种内存映射文件的方法，即将一个文件或者其它对象映射到进程的地址空间，实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。下面我们利用 mmap 来实现文件写入功能。NDK使用知识请参考笔者写的两篇文章：<a href="http://localhost:4000/2016/08/06/%E4%B8%83%E5%A4%95%E5%89%8D%EF%BC%8C%E6%88%91%E8%AE%A4%E8%AF%86%E4%BA%86NDK/">文章一</a>，<a href="http://localhost:4000/2016/08/14/NDK%E5%BC%80%E5%8F%91%E5%85%A8%E6%94%BB%E7%95%A5/">文章二</a>。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">extern "C"</span><br><span class="line">JNIEXPORT void JNICALL</span><br><span class="line">Java_com_cxp_ndkapplication_HelloLibrary_write(JNIEnv *env, jobject thiz) {</span><br><span class="line">    std::string file = "/storage/emulated/0/a.txt";</span><br><span class="line"></span><br><span class="line">//打开文件 new File("")</span><br><span class="line">    int m_fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);</span><br><span class="line"></span><br><span class="line">//获取一页内存大小</span><br><span class="line">//Linux采用了分页来管理内存，即内存的管理中，内存是以页为单位，一般的32位系统一页为 4096 个字节。</span><br><span class="line">    int32_t m_size = getpagesize();</span><br><span class="line"></span><br><span class="line">//将文件设置为 m_size 这么大。</span><br><span class="line">    ftruncate(m_fd, m_size);</span><br><span class="line"></span><br><span class="line">//m_size：映射区的长度。需要是整数页个字节 byte[]。</span><br><span class="line">    int8_t *m_ptr = static_cast&lt;int8_t *&gt;(mmap(0, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd,0));</span><br><span class="line"></span><br><span class="line">    std::string data("12345");</span><br><span class="line"></span><br><span class="line">//将 data 的 data.size() 个数据 拷贝到 m_ptr。</span><br><span class="line">    memcpy(m_ptr, data.data(), data.size());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><img src="/p/379c6d8a/mmap.jpg" class="" title="mmap操作文件"><p>由上图可知，利用 mmap 操作文件是没有内核来参与的，发生零次数据拷贝。</p><p>而对于普通IO操作，则需要内核的参与，总共发生了两次数据拷贝。</p><img src="/p/379c6d8a/io.jpg" class="" title="普通IO操作"><p><strong>这个时候就有疑惑了</strong>，前面讲到 binder 机制的实现利用到了 mmap 技术，可是却也经过了内核的拷贝操作啊。其实产生这个疑惑的原因是将 mmap 将内核挂钩了，这样想是不对的。我们可以把 binder 中提到的物理地址想象成文件地址，这样我们在内核区域将一段虚拟内存通过 mmap 映射到物理地址，再将用户空间的一段虚拟内存同样通过 mmap 映射到同一块物理地址后，再对内核区域虚拟空间的操作就等同于对用户空间的操作。</p><p>具体关于mmap的介绍请参考<a href="https://blog.csdn.net/u013525455/article/details/52582839">这篇文章</a>范例模块之前的内容，当然我还有一些地方不明白的，比如“匿名内存映射”，等到具体使用时再来复盘，这里只需知道什么是 mmap，以及它的基本用法。</p><h2 id="3-2-Intent-传递的数据大小限制"><a href="#3-2-Intent-传递的数据大小限制" class="headerlink" title="3.2 Intent 传递的数据大小限制"></a>3.2 Intent 传递的数据大小限制</h2><p>我们深入到源码中，在 system/libhwbinder/ProcessState.cpp 这个类下有这么一行定义</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#define DEFAULT_BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)</span><br></pre></td></tr></tbody></table></figure><p>可以看到进程为 Binder 分配的内存空间大小是1M-8k，而我们知道 Intent 是通过 binder 传递数据的，所以它的最大传输数据大小也不能超过 1M-8k（实际可传的数据大小会略小于这个数，因为 Intent 传输数据时会携带其它数据）。另外，Binder 是可以异步传输的，异步传输数据最大是（1M-8K）/2，除以 2 更多的是想对异步传输做一个限制，不希望异步传输消耗掉所有的 buffer。</p><p><strong>Binder Buffer 为什么会是(1M - 8K)，而不是整数值 1M ？</strong>这里看起来很奇怪，8K空间到底时用来做什么的？从GIT提交的记录可以看到下面的commits。</p><blockquote><p>Modify the binder to request 1M - 2 pages instead of 1M. The backing store in the kernel requires a guard page, so 1M allocations fragment memory very badly. Subtracting a couple of pages so that they fit in a power of two allows the kernel to make more efficient use of its virtual address space.</p></blockquote><p>这段解释还没有充分理解，其大致的意思是：kernel的“backing store”需要一个保护页，这使得1M用来分配碎片内存时变得很差，所以这里减去两页来提高效率，因为减去 1 页就变成了奇数。至于保护页如何影响内存分配，暂时不太清楚了。</p><p>对于Intent为什么不能传输大数据，可以再看下<a href="https://juejin.im/entry/5cc0f4d46fb9a0322d048b77">这篇文章</a>。但该文章并未解释 Intent 为什么不能传1M-8k的数据，还有是内核空间是共用的，即所有进程共用 1 个内核空间，所以文中的 B 内核空间表述有误，应该改为共享内核空间。至于为什么不能传 1M-8k 的数据，笔者认为除了 Intent 除了会带有附加数据外，传递的时不只是有这个任务在使用这个 1M-8k 的 buffer（即虚拟内存），其它的任务也可能正在使用这个空间，所以实际传输能使用的空间大小会远小于这个 1M-8k ，笔者在模拟上测得大约是 505 k。</p><p>当使用 AIDL 同步方式传输数据时，可以测得最大传输值大小接近 1M-8K ，采用 AIDL 异步方式传输数据时，可以测得最大传输值大小接近（1M-8k）/2 。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;本文将介绍 Android 实现进程间通信的几种方式，进入正题之前，我们首先来了解下多进程的概念，知道多进程是什么，以及为什么要用多进程。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="进程通信" scheme="http://example.com/tags/%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1/"/>
    
  </entry>
  
  <entry>
    <title>分析 MVX 架构模式及其运用</title>
    <link href="http://example.com/p/565f1980/"/>
    <id>http://example.com/p/565f1980/</id>
    <published>2020-01-05T07:33:19.000Z</published>
    <updated>2026-09-20T15:23:29.481Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><img src="/p/565f1980/1.jpeg" class="" title="三种模式对比"><p>在软件开发过程中，好的架构设计能显著提高软件开发的效率，这在 Android 开发中同样如此。到目前为止，有三种被广泛使用的架构设计模式，即：MVC、MVP、MVVM。网上有很多介绍他们的文章，但内容参差不齐，本篇将介绍大众对他们的主流理解，并加以实例说明。</p><span id="more"></span><h1 id="1-MVC"><a href="#1-MVC" class="headerlink" title="1 MVC"></a>1 MVC</h1><h2 id="1-1-概念"><a href="#1-1-概念" class="headerlink" title="1.1 概念"></a>1.1 概念</h2><p>Controller（控制器）：负责调度事件，并通知 Model 操作数据。<br>View（视图）：负责显示界面，并将用户事件通知给控制器。<br>Model（数据模型）：Model 是数据以及和数据相关的业务逻辑。比如 Controller 通知 Model 将数据作加 1 处理，这个加 1 的操纵应该在 Model 中完成。如果需要显示到界面的话，还需要 Model 通知 View 更新界面。</p><blockquote><p><strong>刷新界面的分工问题</strong><br>上面讲到的 Model 通知 View 刷新界面是 MVC 模式中一种刷新界面的方式，但也有通过 Controller 直接刷新界面的情况(如：Activity)， 并且网上把这种情况也归为 MCV 操作的一部分。这里不讨论对错，只是大家要清楚 MVC 可以有这些情况，而且它的概念边界模糊，具体场景出现时，能够分辨的出它的存在即可。</p></blockquote><h2 id="1-2-例子"><a href="#1-2-例子" class="headerlink" title="1.2 例子"></a>1.2 例子</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">public class MvcDemo {</span><br><span class="line">    class Model {</span><br><span class="line">        View view;</span><br><span class="line">        int data;</span><br><span class="line"></span><br><span class="line">        void processData() {</span><br><span class="line">            //处理数据。</span><br><span class="line">            data++;</span><br><span class="line">            //通知 View 显示数据。</span><br><span class="line">            view.showData(data);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    class View {</span><br><span class="line">        Controller controller;</span><br><span class="line"></span><br><span class="line">        void clicked() {</span><br><span class="line">            //产生 click 事件，并通知 Controller 处理。</span><br><span class="line">            controller.viewClicked();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        void showData(int data) {</span><br><span class="line">            //显示数据。</span><br><span class="line">            ...</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    class Controller {</span><br><span class="line">        Model model;</span><br><span class="line"></span><br><span class="line">        void viewClicked() {</span><br><span class="line">            //接收到事件后，通知 Model 处理数据。</span><br><span class="line">            model.processData();</span><br><span class="line"></span><br><span class="line">            //特别地，Android 的 Activity 在这里直接控制界面的显示了。</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="1-3-缺点"><a href="#1-3-缺点" class="headerlink" title="1.3 缺点"></a>1.3 缺点</h2><p>不仅 Model 和 View 之间有联系，同时 Controller 有时也会控制 View 的显示，如 Android 中的 Activity Controller 既负责通知 Model 操作数据，又可以负责界面显示，Controller 和 View 的代码耦合性太高。</p><h1 id="2-MVP"><a href="#2-MVP" class="headerlink" title="2 MVP"></a>2 MVP</h1><h2 id="2-1-概念"><a href="#2-1-概念" class="headerlink" title="2.1 概念"></a>2.1 概念</h2><p>Presenter（控制器）：负责调度事件，并通知 Model 操作数据，数据更新后再通知 View 显示数据。<br>View（视图）：负责显示界面，并将用户事件通知给调度器。<br>Model（数据模型）：Model 是数据以及和数据相关的业务逻辑。比如 Presenter 通知 Model 将数据作加 1 处理，这个加 1 的操纵应该在 Model 中完成。</p><h2 id="2-2-例子"><a href="#2-2-例子" class="headerlink" title="2.2 例子"></a>2.2 例子</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">public class MvpDemo {</span><br><span class="line">    class Presenter {</span><br><span class="line">        private Iview view;</span><br><span class="line"></span><br><span class="line">        public Presenter(Iview view) {</span><br><span class="line">            this.view = view;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public void load() {</span><br><span class="line">            //通知 model 操作数据。</span><br><span class="line">            Model model = new Model();</span><br><span class="line">            model.processData();</span><br><span class="line">            //通知 View 显示数据。</span><br><span class="line">            view.showData(model.data);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    class Model {</span><br><span class="line">        int data;</span><br><span class="line"></span><br><span class="line">        void processData() {</span><br><span class="line">            //处理数据</span><br><span class="line">            data++;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    class View implements Iview {</span><br><span class="line">        @Override</span><br><span class="line">        public void showData(int data) {</span><br><span class="line">            //显示数据</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    interface Iview {</span><br><span class="line">        void showData(int data);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>可以看到，MVP 将 Model 和 View 分离了，同时最重要的是它把界面的显示完全交给 View 来完成了。</p><p>讲到这里，大家可以看到 Android 的 MVC 模式的弊端就是因为 Controller（Activity） 和 View 被耦合在一起了，傻傻分不清楚，所以 MVP 最大的贡献就是把 Controller 和 View 彻底分离，让他们各自负责自己应该负责的。其实，如果我们在 MVC 模式的使用过程中，将控件自定义化，每个控件提供依据数据刷新 UI 的方法，这样也是可以让 View 和 Controller 各自负责自己的工作的。</p><h2 id="2-3-优缺点"><a href="#2-3-优缺点" class="headerlink" title="2.3 优缺点"></a>2.3 优缺点</h2><p>优点：<br>1、结构清晰，职责划分清晰。<br>2、模块间充分解耦。<br>3、有利于组件的重用。</p><p>缺点：<br>1、会引入大量的接口，导致项目文件数量激增。<br>2、增大代码结构复杂性。</p><h1 id="3-MVVM"><a href="#3-MVVM" class="headerlink" title="3 MVVM"></a>3 MVVM</h1><h2 id="3-1-概念"><a href="#3-1-概念" class="headerlink" title="3.1 概念"></a>3.1 概念</h2><p>MVVM 是加了数据绑定功能的 MVP。</p><p>ViewModel（控制器）：负责双向绑定 View 和 Model，同时通知 Model 操作数据。<br>View（视图）：负责显示界面。<br>Model（数据模型）：Model 是数据以及和数据相关的业务逻辑。比如 ViewModel 通知 Model 将数据作加 1 处理，这个加 1 的操纵应该在 Model 中完成。</p><h2 id="3-2-例子"><a href="#3-2-例子" class="headerlink" title="3.2 例子"></a>3.2 例子</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">public class MvvmDemo {</span><br><span class="line">    class ViewModel {</span><br><span class="line">        ObjectAttr data = new ObjectAttr();</span><br><span class="line"></span><br><span class="line">        public ViewModel(View view) {</span><br><span class="line">            //实现 View 和 Model 之间双向绑定。</span><br><span class="line">            Utils.binder(view, data);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public void load() {</span><br><span class="line">            //通知 model 处理数据，并获取处理后的数据。</span><br><span class="line">            Model model = new Model();</span><br><span class="line">            ObjectAttr objectAttr = model.processData();</span><br><span class="line">            //更新数据，之后视图会自动刷新。</span><br><span class="line">            data.setData(objectAttr);</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    class Model {</span><br><span class="line">        private ObjectAttr data;</span><br><span class="line"></span><br><span class="line">        public ObjectAttr processData() {</span><br><span class="line">            //处理数据。</span><br><span class="line">            data = new ObjectAttr();</span><br><span class="line">            return data;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="3-3-优缺点"><a href="#3-3-优缺点" class="headerlink" title="3.3 优缺点"></a>3.3 优缺点</h2><p>优点：<br>1、结构清晰，职责划分清晰。<br>2、模块间充分解耦。<br>3、在 MVP 的基础上，MVVM 把 View 和 ViewModel 也进行了解耦。</p><p>缺点：<br>1、Debug 困难，由于 View 和 ViewModel 解耦，导致 Debug 时难以一眼看出 View 的事件传递。<br>2、代码复杂性增大。</p><h1 id="4-总结"><a href="#4-总结" class="headerlink" title="4 总结"></a>4 总结</h1><p>传统意义上 MVC 和 MVP 最大的区别是 View 和 Model 之间是否关联的区别。Android 中的 MVC 模式与传统 MVC 有很大的不同之处，表现在 Controller（Activity） 错误负责了 View 和 Model 本应该负责的工作，导致 Controller 代码结构变得复杂。</p><p>MVP 很好的解决了各个层之间过度依赖的关系，明确了每个层的职责，即：View 只负责展示数据，Model 只负责数据处理，Presenter 则只为 View 和 Model 之间的沟通提供桥梁。</p><p>MVVM 本质意义上并不算的上一种独立的架构模式，它只是在 MVP 基础上加上了数据绑定的功能，这样就将原来 Presenter 与 View 之间的交互也解耦了。数据绑定功能的实现可以使用 DataBinding。</p><p>DataBinding 的学习可以参考 <a href="https://juejin.im/post/5b02cf8c6fb9a07aa632146d#comment">这篇文章</a>。</p><p>如需了解 DataBinding 在 RecyclerView 中的使用请参考以下两篇文章：<br><a href="https://www.jianshu.com/p/379a8f5347de">文章1</a><br><a href="https://www.jianshu.com/p/4d30efa6b500">文章2</a></p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p>1、扔物线老师的 Hencoder plus 课程。<br><a href="https://zhuanlan.zhihu.com/p/83635530">2、Android 开发中的架构模式。</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;img src=&quot;/p/565f1980/1.jpeg&quot; class=&quot;&quot; title=&quot;三种模式对比&quot;&gt;
&lt;p&gt;在软件开发过程中，好的架构设计能显著提高软件开发的效率，这在 Android 开发中同样如此。到目前为止，有三种被广泛使用的架构设计模式，即：MVC、MVP、MVVM。网上有很多介绍他们的文章，但内容参差不齐，本篇将介绍大众对他们的主流理解，并加以实例说明。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="MVX" scheme="http://example.com/tags/MVX/"/>
    
  </entry>
  
  <entry>
    <title>解读 Hander 机制</title>
    <link href="http://example.com/p/f95bcd90/"/>
    <id>http://example.com/p/f95bcd90/</id>
    <published>2019-12-25T15:47:38.000Z</published>
    <updated>2026-09-20T15:23:29.481Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>Handler机制是Android中相当经典的异步消息机制，本篇将从实用的角度来分析它。</p><span id="more"></span><p>Handler中有几个重要的类，由他们共同完成了这套消息机制的搭建，分别是：Handler、Looper、Message、MessageQueue。</p><p>下面来看一个使用案例：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">class MainActivity : AppCompatActivity() {</span><br><span class="line">    lateinit var handler: Handler</span><br><span class="line"></span><br><span class="line">    override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line">        super.onCreate(savedInstanceState)</span><br><span class="line">        setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line">        Thread {</span><br><span class="line">            Looper.prepare()</span><br><span class="line">            handler = object : Handler() {</span><br><span class="line">                override fun handleMessage(msg: Message) {</span><br><span class="line">                    super.handleMessage(msg)</span><br><span class="line">                    Log.d("TAG", msg.what.toString())</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">            Looper.loop()</span><br><span class="line">            Log.d("TAG", "Thread End.")</span><br><span class="line">        }.start()</span><br><span class="line">        </span><br><span class="line">        btnSend.setOnClickListener {</span><br><span class="line">            val message = Message.obtain(handler, 1)</span><br><span class="line">            handler.sendMessage(message)</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>运行后，我们通过 <code>Log.d("TAG", msg.what.toString())</code> 成功打印了从主线程传递过来的数据。同时， <code>Log.d("TAG", "Thread End.")</code> 一直都没有执行。</p><h2 id="问题1：为什么运行在子线程的Handler可以拿到主线程中发送的数据？"><a href="#问题1：为什么运行在子线程的Handler可以拿到主线程中发送的数据？" class="headerlink" title="问题1：为什么运行在子线程的Handler可以拿到主线程中发送的数据？"></a>问题1：为什么运行在子线程的Handler可以拿到主线程中发送的数据？</h2><p>首先，我们看下Looper.prepare里面做了什么。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public static void prepare() {</span><br><span class="line">    prepare(true);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>进入<code>prepare</code>方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">private static void prepare(boolean quitAllowed) {</span><br><span class="line">    if (sThreadLocal.get() != null) {</span><br><span class="line">        throw new RuntimeException("Only one Looper may be created per thread");</span><br><span class="line">    }</span><br><span class="line">    sThreadLocal.set(new Looper(quitAllowed));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>if 语句保证了每个线程只能存在一个Looper，sThreadLocal 在 Looper 中的定义如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">static final ThreadLocal&lt;Looper&gt; sThreadLocal = new ThreadLocal&lt;Looper&gt;();</span><br></pre></td></tr></tbody></table></figure><p>可以看到 sThreadLocal 是一个静态变量，<code>sThreadLocal.set(new Looper(quitAllowed));</code>这行的作用是让 sThreadLocal 保存当前线程所对应的 Looper 。</p><p>回到 MainActivity ，之后我们重写了 Handler 的 handleMessage 方法，在方法中执行打印操作，那么 handleMessage 方法是什么时候被调用的呢？这个疑问先放着，我们之后再拐回来。</p><p>我们来看看 <code>Looper.loop()</code> 的 loop 方法实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">public static void loop() {</span><br><span class="line">       final Looper me = myLooper();</span><br><span class="line"></span><br><span class="line">       if (me == null) {</span><br><span class="line">           throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       final MessageQueue queue = me.mQueue;// mQueue 是什么赋值的？这个等下再分析，现在把它看成一个 MessageQueue 队列就行。</span><br><span class="line"></span><br><span class="line">       for (;;) {</span><br><span class="line">           Message msg = queue.next(); // 这里会阻塞，直到 queue 中有消息加入。</span><br><span class="line">           if (msg == null) {</span><br><span class="line">               return;</span><br><span class="line">           }</span><br><span class="line"></span><br><span class="line">           try {</span><br><span class="line">               msg.target.dispatchMessage(msg); //发送消息到 handler 中。</span><br><span class="line">             </span><br><span class="line">           } catch (Exception exception) {</span><br><span class="line"></span><br><span class="line">           } finally {</span><br><span class="line"></span><br><span class="line">           }</span><br><span class="line"></span><br><span class="line">           msg.recycleUnchecked(); //回收消息</span><br><span class="line">       }</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>这里只保留了主干代码。首先，拿到当前线程的 Looper 对象，不存在则抛出异常。然后，从 Looper 拿到它对应的 MessageQueue 对象，之后轮询 MessageQueue 的 next 方法，该方法是一个阻塞方法。拿到消息后通过 <code>msg.target.dispatchMessage(msg)</code> 将消息发送到 handler。最后释放消息。</p><p>下面我们看下 dispatchMessage 对消息是怎么处理。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">public void dispatchMessage(@NonNull Message msg) {</span><br><span class="line">    if (msg.callback != null) {</span><br><span class="line">        handleCallback(msg);</span><br><span class="line">    } else {</span><br><span class="line">        if (mCallback != null) {</span><br><span class="line">            if (mCallback.handleMessage(msg)) {</span><br><span class="line">                return;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        handleMessage(msg);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>先判断 msg.callback 是否为空，该例的场景为null，所以就到了 else 代码块中 ，由于这里 mCallback 为空，该消息最终传到了位于 Looper 线程中的 Handler 对象的 handleMessage 回调方法中。</p><p>那么这个消息是怎么被 add 到 Looper 所持有的 MessageQueue 上的呢？答案就在 <code>handler.sendMessage(message)</code> 调用过程中。该过程经过层层调用，最终来到了 MessageQueue 的 enqueueMessage 方法中。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">boolean enqueueMessage(Message msg, long when) {</span><br><span class="line">       if (msg.target == null) {</span><br><span class="line">           throw new IllegalArgumentException("Message must have a target.");</span><br><span class="line">       }</span><br><span class="line">       if (msg.isInUse()) {</span><br><span class="line">           throw new IllegalStateException(msg + " This message is already in use.");</span><br><span class="line">       }</span><br><span class="line"></span><br><span class="line">       synchronized (this) {</span><br><span class="line">           if (mQuitting) {</span><br><span class="line">               IllegalStateException e = new IllegalStateException(</span><br><span class="line">                       msg.target + " sending message to a Handler on a dead thread");</span><br><span class="line">               Log.w(TAG, e.getMessage(), e);</span><br><span class="line">               msg.recycle();</span><br><span class="line">               return false;</span><br><span class="line">           }</span><br><span class="line"></span><br><span class="line">           msg.markInUse();</span><br><span class="line">           msg.when = when;</span><br><span class="line">           Message p = mMessages;</span><br><span class="line">           boolean needWake;</span><br><span class="line">           if (p == null || when == 0 || when &lt; p.when) {</span><br><span class="line">               msg.next = p;</span><br><span class="line">               mMessages = msg;</span><br><span class="line">               needWake = mBlocked;</span><br><span class="line">           } else {</span><br><span class="line">               needWake = mBlocked &amp;&amp; p.target == null &amp;&amp; msg.isAsynchronous();</span><br><span class="line">               Message prev;</span><br><span class="line">               for (;;) {</span><br><span class="line">                   prev = p;</span><br><span class="line">                   p = p.next;</span><br><span class="line">                   if (p == null || when &lt; p.when) {</span><br><span class="line">                       break;</span><br><span class="line">                   }</span><br><span class="line">                   if (needWake &amp;&amp; p.isAsynchronous()) {</span><br><span class="line">                       needWake = false;</span><br><span class="line">                   }</span><br><span class="line">               }</span><br><span class="line">               msg.next = p; // invariant: p == prev.next</span><br><span class="line">               prev.next = msg;</span><br><span class="line">           }</span><br><span class="line"></span><br><span class="line">           // We can assume mPtr != 0 because mQuitting is false.</span><br><span class="line">           if (needWake) {</span><br><span class="line">               nativeWake(mPtr);</span><br><span class="line">           }</span><br><span class="line">       }</span><br><span class="line">       return true;</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>这里保证了队列的结构，即按时间排列和先进先出原则，最后依据 needWake 的值决定是否唤醒队列。</p><p><strong>以上就是对于问题的回答。</strong></p><p>回答完问题，下面我们来分析下 next 方法实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line">Message next() {</span><br><span class="line">final long ptr = mPtr;</span><br><span class="line">        if (ptr == 0) {</span><br><span class="line">            return null;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        int pendingIdleHandlerCount = -1;</span><br><span class="line">        int nextPollTimeoutMillis = 0;</span><br><span class="line">        </span><br><span class="line">        for (;;) {</span><br><span class="line"></span><br><span class="line">if (nextPollTimeoutMillis != 0) {</span><br><span class="line">                Binder.flushPendingCommands();</span><br><span class="line">            }</span><br><span class="line">            </span><br><span class="line">//步骤1</span><br><span class="line">            nativePollOnce(ptr, nextPollTimeoutMillis); // might block</span><br><span class="line"></span><br><span class="line">            synchronized (this) {</span><br><span class="line">                final long now = SystemClock.uptimeMillis();</span><br><span class="line">                Message prevMsg = null;</span><br><span class="line">                Message msg = mMessages;</span><br><span class="line">//步骤2</span><br><span class="line">                if (msg != null &amp;&amp; msg.target == null) {</span><br><span class="line">                    do {</span><br><span class="line">                        prevMsg = msg;</span><br><span class="line">                        msg = msg.next;</span><br><span class="line">                    } while (msg != null &amp;&amp; !msg.isAsynchronous());</span><br><span class="line">                }</span><br><span class="line">//步骤3</span><br><span class="line">                if (msg != null) {</span><br><span class="line">                    if (now &lt; msg.when) {</span><br><span class="line">                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);</span><br><span class="line">                    } else {</span><br><span class="line">                        mBlocked = false;</span><br><span class="line">                        if (prevMsg != null) {</span><br><span class="line">                            prevMsg.next = msg.next;</span><br><span class="line">                        } else {</span><br><span class="line">                            mMessages = msg.next;</span><br><span class="line">                        }</span><br><span class="line">                        msg.next = null;</span><br><span class="line">                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);</span><br><span class="line">                        msg.markInUse();</span><br><span class="line">                        return msg;</span><br><span class="line">                    }</span><br><span class="line">                } else {</span><br><span class="line">                    nextPollTimeoutMillis = -1;</span><br><span class="line">                }</span><br><span class="line">//步骤4</span><br><span class="line">                if (mQuitting) {</span><br><span class="line">                    dispose();</span><br><span class="line">                    return null;</span><br><span class="line">                }</span><br><span class="line">//步骤5</span><br><span class="line">                if (pendingIdleHandlerCount &lt; 0</span><br><span class="line">                        &amp;&amp; (mMessages == null || now &lt; mMessages.when)) {</span><br><span class="line">                    pendingIdleHandlerCount = mIdleHandlers.size();</span><br><span class="line">                }</span><br><span class="line">                if (pendingIdleHandlerCount &lt;= 0) {</span><br><span class="line">                    // No idle handlers to run.  Loop and wait some more.</span><br><span class="line">                    mBlocked = true;</span><br><span class="line">                    continue;</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                if (mPendingIdleHandlers == null) {</span><br><span class="line">                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];</span><br><span class="line">                }</span><br><span class="line">                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);</span><br><span class="line">            }</span><br><span class="line">            for (int i = 0; i &lt; pendingIdleHandlerCount; i++) {</span><br><span class="line">                final IdleHandler idler = mPendingIdleHandlers[i];</span><br><span class="line">                mPendingIdleHandlers[i] = null; </span><br><span class="line">                boolean keep = false;</span><br><span class="line">                try {</span><br><span class="line">                    keep = idler.queueIdle();</span><br><span class="line">                } catch (Throwable t) {</span><br><span class="line">                    Log.wtf(TAG, "IdleHandler threw exception", t);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                if (!keep) {</span><br><span class="line">                    synchronized (this) {</span><br><span class="line">                        mIdleHandlers.remove(idler);</span><br><span class="line">                    }</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            pendingIdleHandlerCount = 0;</span><br><span class="line"></span><br><span class="line">            nextPollTimeoutMillis = 0;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p><strong>步骤 1</strong><br><code>nativePollOnce(ptr, nextPollTimeoutMillis);</code> 是一个 Native 方法，当代码运行于此时，则阻塞，直到有逻辑调用 <code>nativeWake(ptr)</code>方法唤醒，类似于 java 中的 IO 机制。</p><p><strong>步骤 2</strong><br>msg.target==null时，表示该消息是一个屏障消息，于是之后找到队列中的第一个异步消息。</p><p><strong>步骤 3</strong><br>消息不为空并且 when 不超过当前时间点，则返回该消息，否则，则继续进行到步骤 4。</p><p><strong>步骤4</strong><br>mQuitting 变量是标识是否退出 loop 循环的，调用 <code>quit(boolean safe)</code> 方法会设置其值为 true 。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">void quit(boolean safe) {</span><br><span class="line">        if (!mQuitAllowed) {</span><br><span class="line">            throw new IllegalStateException("Main thread not allowed to quit.");</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        synchronized (this) {</span><br><span class="line">            if (mQuitting) {</span><br><span class="line">                return;</span><br><span class="line">            }</span><br><span class="line">            mQuitting = true;</span><br><span class="line"></span><br><span class="line">            if (safe) {</span><br><span class="line">                removeAllFutureMessagesLocked();</span><br><span class="line">            } else {</span><br><span class="line">                removeAllMessagesLocked();</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            // We can assume mPtr != 0 because mQuitting was previously false.</span><br><span class="line">            nativeWake(mPtr);</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>方法最后调用<code>nativeWake</code>通知<code>next</code>方法往下执行。</p><p><strong>步骤5</strong><br>判断是否有闲时消息，有则处理之。所以，闲时消息在CPU空闲的时候会调用，用于页面启动时加载滞后数据，加快页面启动速度。用法如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">val messageQueue = Looper.myQueue()</span><br><span class="line">messageQueue.addIdleHandler(MessageQueue.IdleHandler {</span><br><span class="line">    Log.d("hhhh", "thread=${Thread.currentThread().name},idle")</span><br><span class="line">    false</span><br><span class="line">})</span><br></pre></td></tr></tbody></table></figure><p>返回 false 表示该闲时消息在每次到达空闲时都会执行一次，true 表示执行一次后抛弃就该消息。</p><h2 id="问题2：-Message-obtain-方法是用来干什么的？"><a href="#问题2：-Message-obtain-方法是用来干什么的？" class="headerlink" title="问题2： Message.obtain()方法是用来干什么的？"></a>问题2： Message.obtain()方法是用来干什么的？</h2><p>源码如下：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">//android.os.Message</span><br><span class="line"></span><br><span class="line">public static final Object sPoolSync = new Object();</span><br><span class="line"></span><br><span class="line">   private static Message sPool;</span><br><span class="line">   private static int sPoolSize = 0;</span><br><span class="line"></span><br><span class="line">   private static final int MAX_POOL_SIZE = 50;</span><br><span class="line"></span><br><span class="line">   public static Message obtain() {</span><br><span class="line">       synchronized (sPoolSync) {</span><br><span class="line">           if (sPool != null) {</span><br><span class="line">               Message m = sPool;</span><br><span class="line">               sPool = m.next;</span><br><span class="line">               m.next = null;</span><br><span class="line">               m.flags = 0; // clear in-use flag</span><br><span class="line">               sPoolSize--;</span><br><span class="line">               return m;</span><br><span class="line">           }</span><br><span class="line">       }</span><br><span class="line">       return new Message();</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>sPool 就是缓冲池的头节点，当 sPool 不为 null 时，复用该 Message 即可。当 sPool 为 null 时，则新建 Message 。这样以达到节省内存空间的目的。</p><p>缓冲池里的 Message 一定是可复用的，也就是使用完的 Message 。那么我们在哪里去将使用完的 Message 放入缓冲池呢？答案就是 <code>recycleUnchecked</code> 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">//android.os.Message</span><br><span class="line"></span><br><span class="line">   void recycleUnchecked() {</span><br><span class="line">       // Mark the message as in use while it remains in the recycled object pool.</span><br><span class="line">       // Clear out all other details.</span><br><span class="line">       flags = FLAG_IN_USE;</span><br><span class="line">       what = 0;</span><br><span class="line">       arg1 = 0;</span><br><span class="line">       arg2 = 0;</span><br><span class="line">       obj = null;</span><br><span class="line">       replyTo = null;</span><br><span class="line">       sendingUid = UID_NONE;</span><br><span class="line">       workSourceUid = UID_NONE;</span><br><span class="line">       when = 0;</span><br><span class="line">       target = null;</span><br><span class="line">       callback = null;</span><br><span class="line">       data = null;</span><br><span class="line"></span><br><span class="line">       synchronized (sPoolSync) {</span><br><span class="line">           if (sPoolSize &lt; MAX_POOL_SIZE) {</span><br><span class="line">               next = sPool;</span><br><span class="line">               sPool = this;</span><br><span class="line">               sPoolSize++;</span><br><span class="line">           }</span><br><span class="line">       }</span><br><span class="line">   }</span><br></pre></td></tr></tbody></table></figure><p>当 sPoolSize 小于 MAX_POOL_SIZE 时，将已使用完的 Message 作为缓冲池的头指针，同时缓冲池其它的 Message 往后移。</p><h2 id="问题3：Handler消息创建和处理在同一个线程时是同步还是异步的？"><a href="#问题3：Handler消息创建和处理在同一个线程时是同步还是异步的？" class="headerlink" title="问题3：Handler消息创建和处理在同一个线程时是同步还是异步的？"></a>问题3：Handler消息创建和处理在同一个线程时是同步还是异步的？</h2><p>请看下面的代码。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">class Main2Activity : AppCompatActivity() {</span><br><span class="line">    lateinit var handler: Handler</span><br><span class="line"></span><br><span class="line">    override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line">        super.onCreate(savedInstanceState)</span><br><span class="line">        setContentView(R.layout.activity_main2)</span><br><span class="line"></span><br><span class="line">        handler = object : Handler() {</span><br><span class="line">            override fun handleMessage(msg: Message) {</span><br><span class="line">                super.handleMessage(msg)</span><br><span class="line">                Log.d("TAG", "thread=${Thread.currentThread().name},${msg.what}")</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        btnSend.setOnClickListener {</span><br><span class="line">            val message1 = Message.obtain(handler, 101)</span><br><span class="line">            handler.sendMessage(message1)</span><br><span class="line">            Log.d("TAG", "thread=${Thread.currentThread().name},start 1")</span><br><span class="line">            val message2 = Message.obtain(handler, 102)</span><br><span class="line">            handler.sendMessage(message2)</span><br><span class="line">            Log.d("TAG", "thread=${Thread.currentThread().name},start 2")</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line">//这段代码的运行结果:</span><br><span class="line">thread=main,start 1</span><br><span class="line">thread=main,start 2</span><br><span class="line">thread=main,101</span><br><span class="line">thread=main,102</span><br></pre></td></tr></tbody></table></figure><p>运行结果可以发现，同一线程消息的发送和处理也是异步的，并不是发送一个消息后立马就处理，原因笔者认为阻塞被唤醒的时机不确定，所以并不是立即就执行后面的代码调用handleMessage回调。</p><h2 id="问题4：Handler机制Looper死循环为什么不会导致应用卡死？"><a href="#问题4：Handler机制Looper死循环为什么不会导致应用卡死？" class="headerlink" title="问题4：Handler机制Looper死循环为什么不会导致应用卡死？"></a>问题4：Handler机制Looper死循环为什么不会导致应用卡死？</h2><p>Android系统的的所有事件处理都是通过Looper机制实现的，当有事件时会通过nativeWake唤醒正在等待的Looper线程，无事件处理时通过nativePollOnce使Looper线程进入休眠状态，也正是这样的机制保证了CPU资源的合理利用。</p><h2 id="问题5：Android-执行动画或者绘制-View-时，也是-Handler-吗？"><a href="#问题5：Android-执行动画或者绘制-View-时，也是-Handler-吗？" class="headerlink" title="问题5：Android 执行动画或者绘制 View 时，也是 Handler 吗？"></a>问题5：Android 执行动画或者绘制 View 时，也是 Handler 吗？</h2><p>是，具体可参考源码。（源码部分，笔者还不是彻底搞懂，后期搞懂再补充。）</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;Handler机制是Android中相当经典的异步消息机制，本篇将从实用的角度来分析它。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="Handler" scheme="http://example.com/tags/Handler/"/>
    
  </entry>
  
  <entry>
    <title>MD5 加密算法</title>
    <link href="http://example.com/p/9706e144/"/>
    <id>http://example.com/p/9706e144/</id>
    <published>2019-12-20T17:19:02.000Z</published>
    <updated>2026-09-20T15:23:29.451Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>MD5(Message-Digest Algorithm 5,信息-摘要算法5)用于确保信息传输完整一致，是计算机广泛使用的杂凑算法之一（又译摘要算法，哈希算法）。MD5算法将数据（如汉字）运算成另一固定长度值，MD5的前生有MD2，MD3和MD4。<span id="more"></span><br>MD5算法具有如下特征：<br>1、压缩性：任意长度的数据算出的MD5值是固定的，即128 位固定长度的散列值。<br>2、容易计算：从原数据很容易算出MD5值。<br>3、抗修改性：对原数据任何一个细微的改动，所得到的MD5值都会有很大的不同。<br>4、强抗碰撞：MD5 使用的是散列函数（也称哈希函数），一定概率上也存在哈希冲突（也称哈希碰撞），即多个不同的原数据对应一个相同的 MD5 值。不过，经过 MD4、MD3 等几代算法的优化，MD5 已经充分利用散列的分散性高度避免碰撞的发生。</p><p>可以看出，MD5 是一种不可逆的算法，也就说，你无法通过得到的 MD5 值逆向算出原数据内容。正是凭借这些特点，MD5 被广泛使用。</p><h1 id="1-MD5的使用场景"><a href="#1-MD5的使用场景" class="headerlink" title="1 MD5的使用场景"></a>1 MD5的使用场景</h1><ul><li><p>客户端与服务器的 HTTP 通信，通信双方可以将报文内容做一个 MD5 计算，并将计算所得 MD5 值一并传递给彼此，这样，接收方可以通过对报文内容再次做 MD5 计算得到一个 MD5 值，与传递报文中的 MD5 值做比较，验证数据是否完整，或者是否中途被拦截篡改过。</p></li><li><p>网络云盘中的文件秒传功能也运用到 MD5 算法。服务器存储文件的时候，同时记录每一个文件的 MD5 值，不同文件对应着不同的 MD5 值。这样，遇到用户上传文件时，将上传文件的 MD5 值与服务器上所有存储的 MD5 值做比较，如果相同，则说明用户上传的文件已经在服务器存有。这样，只需要在数据库表中添加一个记录，映射到对应的文件，而不用重复上传，实现所谓秒传的功能。</p></li></ul><blockquote><p>注意：严格意义上来讲，MD5  并不属于加密算法，也不属于签名算法，而是一种摘要算法，用于数据完整性校验等。</p></blockquote><h1 id="2-Java-语言中计算-MD5-值的实现方式"><a href="#2-Java-语言中计算-MD5-值的实现方式" class="headerlink" title="2 Java 语言中计算 MD5 值的实现方式"></a>2 Java 语言中计算 MD5 值的实现方式</h1><p><strong>第一步</strong>，获取 MessageDigest 对象，参数为 MD5 字符串，表示这是一个 MD5 算法（其他还有 SHA1 算法等）：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MessageDigest md5 = MessageDigest.getInstance("MD5");</span><br></pre></td></tr></tbody></table></figure><p><strong>第二步</strong>，输入原数据，参数类型为 byte[] ：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">md5.update(buffer);</span><br></pre></td></tr></tbody></table></figure><p>注意：update() 方法有点类似 StringBuilder 对象的 append() 方法，采用的是追加模式，属于一个累计更改的过程，比如：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">md5.update(new byte[]{'a', 'b'});</span><br><span class="line">md5.update(new byte[]{'c', 'd'});</span><br></pre></td></tr></tbody></table></figure><p>与</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">md5.update(new byte[]{'a','b','c', 'd'});</span><br></pre></td></tr></tbody></table></figure><p>是等效的。<br><strong>第三步</strong>，计算 MD5 值：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">String resultArray = md5.digest();</span><br></pre></td></tr></tbody></table></figure><p>注意：digest() 方法被调用后，MessageDigest 对象就被重置，也就是说你不能紧接着再次调用该方法计算原数据的 MD5 值。当然，你可以手动调用 reset() 方法重置输入源。<br>digest() 方法返回值是一个字节数组类型的 16 位长度的哈希值，通常，我们会转化为十六进制的 32 位长度的字符串来使用，可以利用 <a href="https://www.jianshu.com/p/8b89ab19db84">BigInteger</a> 类来做这个转化：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">BigInteger bigInt = new BigInteger(1, resultArray);</span><br><span class="line">String resultStr = bigInt.toString(16);</span><br></pre></td></tr></tbody></table></figure><p>通过这层转换，得到的 MD5 值便是一个长度为 32 位的十六进制字符串，方便使用，类似这样：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">15aa7ec97a6288e46ae865a7211eb4eb</span><br></pre></td></tr></tbody></table></figure><p>有了上面的基础我们再来看如何将一个文件通过MD5加密转化成32位的十六进制字符串。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public static String fileToMD5(String filePath) {</span><br><span class="line">String md5 = null;</span><br><span class="line">MessageDigest digest = null;</span><br><span class="line">try {</span><br><span class="line">digest = MessageDigest.getInstance("MD5");</span><br><span class="line">FileInputStream inputStream = new FileInputStream(filePath);</span><br><span class="line">byte[] b = new byte[1024];</span><br><span class="line">int len = -1;</span><br><span class="line">while ((len = inputStream.read(b)) != -1) {</span><br><span class="line">digest.update(b, 0, len);</span><br><span class="line">}</span><br><span class="line">byte[] bytes = digest.digest();</span><br><span class="line">BigInteger bigInteger = new BigInteger(1, bytes);</span><br><span class="line">md5 = bigInteger.toString(16);</span><br><span class="line">} catch (Exception e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">}</span><br><span class="line">return md5;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意：文件的大小直接影响字节流的读取速度，间接影响这里 MD5 的计算时长。Java 语言提供有多种方式读取文件，除了上面用到的 FileInputStream 这种顺序读取的 API 类，还有采用随机读取方式的 RandomAccessFile 类等。对于文件读取的效率问题，研究之后再作介绍。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="http://yifeng.studio/2017/06/13/md5-profile-and-implementation-in-java/">MD5 简介，及其在 Java 中的实现方式</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;MD5(Message-Digest Algorithm 5,信息-摘要算法5)用于确保信息传输完整一致，是计算机广泛使用的杂凑算法之一（又译摘要算法，哈希算法）。MD5算法将数据（如汉字）运算成另一固定长度值，MD5的前生有MD2，MD3和MD4。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="MD5" scheme="http://example.com/tags/MD5/"/>
    
  </entry>
  
  <entry>
    <title>IO 操作案例</title>
    <link href="http://example.com/p/4c6a0453/"/>
    <id>http://example.com/p/4c6a0453/</id>
    <published>2019-11-01T15:33:13.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本文主要记录了 Java IO 相关的基本操作，涵盖传统IO、Socket、Okio、NIO、AIO，目的是为了以后再遇到 IO 操作相关的需求时，即便忘记该怎么写了，也能通过本文中对应的例子得到启发。对于本文没有涉及到的具有代表性的例子，以后遇到后再补充。</p><span id="more"></span><h1 id="1-IO"><a href="#1-IO" class="headerlink" title="1 IO"></a>1 IO</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">private static void io1() {</span><br><span class="line">        try (OutputStream outputStream = new FileOutputStream(file)) {</span><br><span class="line">            outputStream.write('a');</span><br><span class="line">            outputStream.write('b');</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>往 file 文件写数据。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">private static void io2() {</span><br><span class="line">        try (InputStream inputStream = new FileInputStream(file)) {</span><br><span class="line">            System.out.println((char) inputStream.read());</span><br><span class="line">            System.out.println((char) inputStream.read());</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从 file 文件中读数据。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">private static void io3() {</span><br><span class="line">        try (InputStream inputStream = new FileInputStream(file);</span><br><span class="line">             Reader reader = new InputStreamReader(inputStream);</span><br><span class="line">             BufferedReader bufferedReader = new BufferedReader(reader)</span><br><span class="line">        ) {</span><br><span class="line">            System.out.println(bufferedReader.readLine());</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>通过 buffer 中读数据。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">private static void io4() {</span><br><span class="line">        try (OutputStream outputStream = new FileOutputStream(file);</span><br><span class="line">             Writer writer = new OutputStreamWriter(outputStream);</span><br><span class="line">             BufferedWriter bufferedWriter = new BufferedWriter(writer)</span><br><span class="line">        ) {</span><br><span class="line">            bufferedWriter.write('a');</span><br><span class="line">            bufferedWriter.write('b');</span><br><span class="line">            //当 Buffered 被关闭时会自动 flush，而 try(...) 写法自动包含了关闭 Buffered 的逻辑，所以可以省略 flush() 方法调用。</span><br><span class="line">            //bufferedWriter.flush();</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>往 buffer 中写数据，之后保存到文件。<br>注意: BufferedWriter 中的缓冲的数据量只有达到 8192 个字节才会被刷新一次数据（共 8192 个字节）到文件中，除非你主动调用它的 flush 方法，所以我们从 buffer 写数据到文件中时总是会调用它的 flush 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">private static void io5() {</span><br><span class="line">        try (InputStream inputStream = new FileInputStream(file);</span><br><span class="line">             OutputStream outputStream = new FileOutputStream(outFile)</span><br><span class="line">        ) {</span><br><span class="line">            byte[] data = new byte[1024];</span><br><span class="line">            int read;</span><br><span class="line">            while ((read = inputStream.read(data)) != -1) {</span><br><span class="line">                outputStream.write(data, 0, read);</span><br><span class="line">            }</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>将数据从 file 文件复制到 outFile 文件。</p><img src="/p/4c6a0453/1.png" class="" title="IO类层次图"><blockquote><p><strong>知识补充</strong><br>File 文件操作可参考 <a href="https://www.jianshu.com/p/7c1cf5cad4f3">这篇文章</a> 。</p></blockquote><h1 id="2-Socket"><a href="#2-Socket" class="headerlink" title="2 Socket"></a>2 Socket</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">private static void socket1() {</span><br><span class="line">        try (ServerSocket serverSocket = new ServerSocket(8080);</span><br><span class="line">             Socket socket = serverSocket.accept();</span><br><span class="line">             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));</span><br><span class="line">             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))</span><br><span class="line">        ) {</span><br><span class="line">            String data;</span><br><span class="line">            while (true) {</span><br><span class="line">                data = reader.readLine();//读取客户端数据</span><br><span class="line">                writer.write(data);</span><br><span class="line">                writer.write("\n");</span><br><span class="line">                writer.flush();//发送数据到客户端</span><br><span class="line">            }</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>服务端代码。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">private static void socket2() {</span><br><span class="line">        try (Socket client = new Socket("serverName", 8080);</span><br><span class="line">             OutputStream outToServer = client.getOutputStream();</span><br><span class="line">             DataOutputStream out = new DataOutputStream(outToServer);</span><br><span class="line">             InputStream inFromServer = client.getInputStream();</span><br><span class="line">             DataInputStream in = new DataInputStream(inFromServer)</span><br><span class="line">        ) {</span><br><span class="line">            out.writeUTF("Hello from client");</span><br><span class="line">            System.out.println("服务器响应： " + in.readUTF());</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>客户端代码。</p><p>Socket 基础知识可参考 <a href="https://www.runoob.com/java/java-networking.html">这篇文章</a> 。</p><p>Socket 实际使用可参考笔者的 <a href="https://github.com/lianjiehao/ITalk">ITalk项目</a> 。</p><h1 id="3-Okio"><a href="#3-Okio" class="headerlink" title="3 Okio"></a>3 Okio</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">private static void okio1() {</span><br><span class="line">        try (Source source = Okio.source(file)) {</span><br><span class="line">            Buffer buffer = new Buffer();</span><br><span class="line">            source.read(buffer, 1024);</span><br><span class="line">            System.out.println(buffer.readUtf8Line());</span><br><span class="line"></span><br><span class="line">            //也可和传统 io 交互</span><br><span class="line">            //buffer.outputStream();</span><br><span class="line">            //buffer.inputStream();</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从 file 中读数据到 Buffer。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">private static void okio2() {</span><br><span class="line">    try (Source source = Okio.source(file);</span><br><span class="line">         BufferedSource bufferedSource = Okio.buffer(source)</span><br><span class="line">    ) {</span><br><span class="line">        System.out.println(bufferedSource.readUtf8());</span><br><span class="line">    } catch (FileNotFoundException e) {</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    } catch (IOException e) {</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>从 file 中读数据到 BufferedSource 。</p><h1 id="4-NIO"><a href="#4-NIO" class="headerlink" title="4 NIO"></a>4 NIO</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">private static void nio1() {</span><br><span class="line">        try (RandomAccessFile file = new RandomAccessFile("text.txt", "r");</span><br><span class="line">             FileChannel channel = file.getChannel()</span><br><span class="line">        ) {</span><br><span class="line">            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);</span><br><span class="line">            channel.read(byteBuffer);</span><br><span class="line">            byteBuffer.flip();</span><br><span class="line">            System.out.println(Charset.defaultCharset().decode(byteBuffer));</span><br><span class="line">        } catch (FileNotFoundException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从 text.txt 中读数据到 ByteBuffer 。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">private static void nio2() {</span><br><span class="line">        try {</span><br><span class="line">            ServerSocketChannel serverChannel = ServerSocketChannel.open();</span><br><span class="line">            serverChannel.configureBlocking(false);</span><br><span class="line">            serverChannel.socket().bind(new InetSocketAddress(8080));</span><br><span class="line">            Selector selector = Selector.open();</span><br><span class="line">            serverChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">            while (true) {</span><br><span class="line">                int n = selector.select();//这里是阻塞的。</span><br><span class="line">                if (n == 0) continue;</span><br><span class="line">                Iterator ite = selector.selectedKeys().iterator();</span><br><span class="line">                while (ite.hasNext()) {</span><br><span class="line">                    SelectionKey key = (SelectionKey) ite.next();</span><br><span class="line">                    if (key.isAcceptable()) {</span><br><span class="line">                        SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();</span><br><span class="line">                        clntChan.configureBlocking(false);</span><br><span class="line">                        //将选择器注册到连接到的客户端信道，</span><br><span class="line">                        //并指定该信道key值的属性为OP_READ，</span><br><span class="line">                        //同时为该信道指定关联的附件</span><br><span class="line">                        clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(1024));</span><br><span class="line">                    }</span><br><span class="line">                    if (key.isReadable()) {</span><br><span class="line">//                        handleRead(key);</span><br><span class="line">                    }</span><br><span class="line">                    if (key.isWritable() &amp;&amp; key.isValid()) {</span><br><span class="line">//                        handleWrite(key);</span><br><span class="line">                    }</span><br><span class="line">                    if (key.isConnectable()) {</span><br><span class="line">                        System.out.println("isConnectable = true");</span><br><span class="line">                    }</span><br><span class="line">                    ite.remove();</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>Selector 的使用。</p><p><a href="https://www.jianshu.com/p/0d497fe5484a">参考链接&gt;&gt;</a></p><p>PS：参考链接中 “实现原理” 部分的内容没看懂，后面有时间再研究吧。</p><h1 id="5-AIO"><a href="#5-AIO" class="headerlink" title="5 AIO"></a>5 AIO</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">private static void aio1() {</span><br><span class="line">        Path path = Paths.get("text.txt");</span><br><span class="line">        try (AsynchronousFileChannel fileChannel =</span><br><span class="line">                     AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {</span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocate(1024);</span><br><span class="line">            long position = 0;</span><br><span class="line">            Future&lt;Integer&gt; operation = fileChannel.read(buffer, position);</span><br><span class="line">            while (!operation.isDone()) ;</span><br><span class="line">            buffer.flip();</span><br><span class="line">            byte[] data = new byte[buffer.limit()];</span><br><span class="line">            buffer.get(data);</span><br><span class="line">            System.out.println(new String(data));</span><br><span class="line">            buffer.clear();</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>同步方式读取 text.txt 文件。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">private static void aio2() {</span><br><span class="line">        Path path = Paths.get("text.txt");</span><br><span class="line">        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {</span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocate((int) fileChannel.size());</span><br><span class="line">            fileChannel.read(buffer, 0, buffer, new CompletionHandler&lt;Integer, ByteBuffer&gt;() {</span><br><span class="line">                @Override</span><br><span class="line">                public void completed(Integer result, ByteBuffer byteBuffer) {</span><br><span class="line">                    System.out.println("result = " + result);</span><br><span class="line">                    byteBuffer.flip();</span><br><span class="line">                    byte[] data = new byte[byteBuffer.limit()];</span><br><span class="line">                    byteBuffer.get(data);</span><br><span class="line">                    System.out.println(new String(data));</span><br><span class="line">                    byteBuffer.clear();</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                @Override</span><br><span class="line">                public void failed(Throwable throwable, ByteBuffer attachment) {</span><br><span class="line"></span><br><span class="line">                }</span><br><span class="line">            });</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">        System.out.println("Sleeping for 5  seconds...");</span><br><span class="line">        try {</span><br><span class="line">            Thread.sleep(5000);</span><br><span class="line">        } catch (InterruptedException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>异步方式读取 text.txt 文件。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">private static void aio3() {</span><br><span class="line">        Path path = Paths.get("text.txt");</span><br><span class="line">        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE)) {</span><br><span class="line">            String data = "test data";</span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocate(data.length());</span><br><span class="line">            buffer.put(data.getBytes());</span><br><span class="line">            buffer.flip();</span><br><span class="line"></span><br><span class="line">            long position = 0;</span><br><span class="line">            Future&lt;Integer&gt; operation = fileChannel.write(buffer, position);</span><br><span class="line">            buffer.clear();</span><br><span class="line"></span><br><span class="line">            while (!operation.isDone()) ;</span><br><span class="line"></span><br><span class="line">            System.out.println("Write done");</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>同步方式往 text.txt 写数据。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">private static void aio4() {</span><br><span class="line">        Path path = Paths.get("text.txt");</span><br><span class="line">        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE)) {</span><br><span class="line">            ByteBuffer buffer = ByteBuffer.allocate(1024);</span><br><span class="line">            long position = 0;</span><br><span class="line">            buffer.put("test data".getBytes());</span><br><span class="line">            buffer.flip();</span><br><span class="line"></span><br><span class="line">            fileChannel.write(buffer, position, buffer, new CompletionHandler&lt;Integer, ByteBuffer&gt;() {</span><br><span class="line">                @Override</span><br><span class="line">                public void completed(Integer result, ByteBuffer attachment) {</span><br><span class="line">                    System.out.println("bytes written: " + result);</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                @Override</span><br><span class="line">                public void failed(Throwable exc, ByteBuffer attachment) {</span><br><span class="line">                    System.out.println("Write failed");</span><br><span class="line">                    exc.printStackTrace();</span><br><span class="line">                }</span><br><span class="line">            });</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">        System.out.println("Sleeping for 5  seconds...");</span><br><span class="line">        try {</span><br><span class="line">            Thread.sleep(5000);</span><br><span class="line">        } catch (InterruptedException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>异步方式往 text.txt 写数据。</p><p>AIO <a href="http://tutorials.jenkov.com/java-nio/asynchronousfilechannel.html">参考链接&gt;&gt;</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;本文主要记录了 Java IO 相关的基本操作，涵盖传统IO、Socket、Okio、NIO、AIO，目的是为了以后再遇到 IO 操作相关的需求时，即便忘记该怎么写了，也能通过本文中对应的例子得到启发。对于本文没有涉及到的具有代表性的例子，以后遇到后再补充。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="IO" scheme="http://example.com/tags/IO/"/>
    
  </entry>
  
  <entry>
    <title>浅析 Android 组件化、插件化、热更新</title>
    <link href="http://example.com/p/64f5b174/"/>
    <id>http://example.com/p/64f5b174/</id>
    <published>2019-10-25T10:29:42.000Z</published>
    <updated>2026-09-20T15:23:29.481Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本文主要目的是理解组件化、插件化、热更新的实现原理，并且知道他们之前的区别。</p><span id="more"></span><h1 id="1-区别"><a href="#1-区别" class="headerlink" title="1 区别"></a>1 区别</h1><p>组件化：“组件化”又名“模块化”，其实就是把每个功能独立的模块分离成独立的 module ，如：网络操作、基础库、独立业务功能等都可以抽离成单个 module ，由此可见日常开发中我们已经在使用组件化开发了。主 APP 工程可以引用这些 module ，并使用它们各自所实现的功能。</p><blockquote><p><strong>备注</strong><br>其实上面对于组件化的说法是很一种很粗旷的理解，不适合复杂项目。组件化完整理解建议参考<a href="https://juejin.im/post/6844903649102004231#comment">这篇文章</a>，这里需要说下的是，笔者在此之前只是粗旷的运用过组件化开发，对于链文中提到的用法没有证实过，后续有这方面经验再来补充。</p></blockquote><p>插件化：“插件化”是一种能让 APP 动态增加新功能的技术，它使得动态添加新功能的过程能在不进行重新打包安装的情况下进行。插件化在原来的代码中有固定的执行入口，比如：点击一个按钮的事件，加载的一个页面。</p><p>热更新：“热更新”又名“热修复”，它可以对 APP 中已有的内容做改动，而不需要预先留下固定的执行入口，通常用来修复 BUG 。</p><h1 id="2-实现原理"><a href="#2-实现原理" class="headerlink" title="2 实现原理"></a>2 实现原理</h1><h2 id="2-1-插件化"><a href="#2-1-插件化" class="headerlink" title="2.1 插件化"></a>2.1 插件化</h2><p>第一步：实现插件 App 项目。插件 app 包含了之后将在主 App 中被使用的 Util 类。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//com.cxp.plugin.Util</span><br><span class="line"></span><br><span class="line">public class Util {</span><br><span class="line">    public void print() {</span><br><span class="line">        System.out.println("这是插件中的方法！");</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>第二步：Build APk(s)，生成一个 apk 包。<br>第三步：实现主 App 项目，并将插件 apk 包放入 assets 文件夹中，命名为：”plugin.apk”。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">//com.cxp.pluginableapp. MainActivity</span><br><span class="line"></span><br><span class="line">class MainActivity : AppCompatActivity() {</span><br><span class="line"></span><br><span class="line">    override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line">        super.onCreate(savedInstanceState)</span><br><span class="line">        setContentView(R.layout.activity_main)</span><br><span class="line"></span><br><span class="line">        //复制 assets 中的文件到 externalCacheDir 目录。</span><br><span class="line">        val apk = File(externalCacheDir.absolutePath + "/plugin.apk")</span><br><span class="line">        var source: Source? = null</span><br><span class="line">        var sink: BufferedSink? = null</span><br><span class="line">        try {</span><br><span class="line">            source = assets.open("plugin.apk").source()</span><br><span class="line">            sink = apk.sink().buffer()</span><br><span class="line">            sink.writeAll(source)</span><br><span class="line">        } finally {</span><br><span class="line">            source?.close()</span><br><span class="line">            sink?.close()</span><br><span class="line">        }</span><br><span class="line">        //新建 ClassLoader 加载 dex（.class） 文件。</span><br><span class="line">        val classLoader = DexClassLoader(apk.absolutePath, cacheDir.absolutePath, null, null)</span><br><span class="line">        val utilClass = classLoader.loadClass("com.cxp.plugin.Util")</span><br><span class="line">        //反射调用类中 print 方法。</span><br><span class="line">        val o: Any = utilClass.newInstance()</span><br><span class="line">        val method=utilClass.getMethod("print")</span><br><span class="line">        method.invoke(o)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>运行程序，成功输出：“这是插件中的方法！”。</p><p><strong>问题1:为什么要新建一个 DexClassLoader 来加载类？</strong><br>应用在启动后都会生成一个加载自身 dex 文件的 classLoader，这个classLoader 能够加载的类在应用被启动的同时就已经确定下来了，所以对于插件 app 提供的 class ，不能再用这个 classLoader 加载，必须新建一个用来加载插件 app 的 classLoader。</p><p><strong>问题2:一个应用启动后会有几个 ClassLoader ？</strong><br>至少 2 个。请参看<a href="https://zhuanlan.zhihu.com/p/20524252">知乎文章</a>中“有几个ClassLoader实例？”部分的讲解。</p><p><strong>问题3:什么是双亲委托 ？</strong><br>当需要加载一个类时，自下而上查看是否已经加载过该类了，如找到了，则直接返回缓存中的 Class 对象，当到达最顶层的加载器仍然找不到该 Class 对象时，则自上而下开始调用各自的 findClass 方法生成Class 对象，直到成功为止。</p><p><strong>问题4: DexClassLoader 的创建和 loadClass 方法分别做什么事 ？</strong><br>DexClassLoader 的创建过程是依据外部提供的 dexs 创建一个或多个 DexFile 对象并最终被 pathList 持有，完成了 dex 文件载入过程。这一步完成了将 .class 文件加载到方法区的工作。</p><p>loadClass 方法经过双亲委托机制后，首次加载类会调用到<code>pathList.findClass(name)</code>方法，该方法最终通过遍历 DexFile 的 <code>defineClass </code> native 方法创建 class 对象。这一步完成了在堆区创建 class 对象的工作。</p><p>如果要自己实现一套完整的插件化方案，可以参考<a href="https://zhuanlan.zhihu.com/p/21335594">这篇文章</a>的思路。</p><p>具体的实现流程也可参考如下四篇文章：<br>1、<a href="https://www.maplejaw.com/2016/05/24/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%80%EF%BC%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8DexClassLoader/">Android插件化探索（一）类加载器DexClassLoader。</a><br>2、<a href="https://www.maplejaw.com/2016/05/26/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%BA%8C%EF%BC%89%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD/">Android插件化探索（二）资源加载。</a><br>3、<a href="https://www.maplejaw.com/2016/05/28/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E4%B8%89%EF%BC%89%E5%85%8D%E5%AE%89%E8%A3%85%E8%BF%90%E8%A1%8CActivity%EF%BC%88%E4%B8%8A%EF%BC%89/">Android插件化探索（三）免安装运行Activity（上）。</a><br>4、<a href="https://www.maplejaw.com/2016/06/05/Android%E6%8F%92%E4%BB%B6%E5%8C%96%E6%8E%A2%E7%B4%A2%EF%BC%88%E5%9B%9B%EF%BC%89%E5%85%8D%E5%AE%89%E8%A3%85%E8%BF%90%E8%A1%8CActivity%EF%BC%88%E4%B8%8B%EF%BC%89/">Android插件化探索（四）免安装运行Activity（下）。</a></p><h2 id="2-2-热更新"><a href="#2-2-热更新" class="headerlink" title="2.2 热更新"></a>2.2 热更新</h2><p>dex 文件可通过 d8 工具生成，也可以换成 apk 文件，它存放在 assets 目录下。以下是 dex 文件所含代码。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">package com.cxp.hotfix;</span><br><span class="line"></span><br><span class="line">public class Util {</span><br><span class="line">    public void print() {</span><br><span class="line">        System.out.println("这是 hotfix 后的方法！");</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>主项目中对应的代码如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">package com.cxp.hotfix;</span><br><span class="line"></span><br><span class="line">public class Util {</span><br><span class="line">    public void print() {</span><br><span class="line">        System.out.println("这是原始方法！");</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>启动主项目后应立即将加载修复包所得的 dexElements 添加到当前主项目类加载器的 dexElements 首部。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line">public class HotfixApplication extends Application {</span><br><span class="line">    File apk;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void attachBaseContext(Context base) {</span><br><span class="line">        super.attachBaseContext(base);</span><br><span class="line">        //将修复包放入 ExternalCacheDir 目录下。</span><br><span class="line">        apk = new File(getExternalCacheDir() + "/hotfix.dex");</span><br><span class="line">        try (Source source = Okio.source(getAssets().open("hotfix.dex"));</span><br><span class="line">             BufferedSink sink = Okio.buffer(Okio.sink(apk))) {</span><br><span class="line">            sink.writeAll(source);</span><br><span class="line">        } catch (IOException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">        //通过反射方式将修复包的 dexs 加入到现有 dexElements 数组的头部。</span><br><span class="line">        if (apk.exists()) {</span><br><span class="line">            try {</span><br><span class="line">                ClassLoader classLoader = getClassLoader();</span><br><span class="line">                Class loaderClass = BaseDexClassLoader.class;</span><br><span class="line">                Field pathListField = loaderClass.getDeclaredField("pathList");</span><br><span class="line">                pathListField.setAccessible(true);</span><br><span class="line">                Object pathListObject = pathListField.get(classLoader); // getClassLoader().pathList</span><br><span class="line"></span><br><span class="line">                Class pathListClass = pathListObject.getClass();</span><br><span class="line">                Field dexElementsField = pathListClass.getDeclaredField("dexElements");</span><br><span class="line">                dexElementsField.setAccessible(true);</span><br><span class="line">                PathClassLoader newClassLoader = new PathClassLoader(apk.getPath(), null);</span><br><span class="line">                Object newPathListObject = pathListField.get(newClassLoader); // newClassLoader.pathList</span><br><span class="line">                Object newDexElementsObject = dexElementsField.get(newPathListObject); // newClassLoader.pathList.dexElements</span><br><span class="line"></span><br><span class="line">                Object dexElementsObject = dexElementsField.get(pathListObject); // getClassLoader().pathList.dexElements</span><br><span class="line"></span><br><span class="line">                int oldLength = Array.getLength(dexElementsObject);</span><br><span class="line">                int newLength = Array.getLength(newDexElementsObject);</span><br><span class="line">                Object concatDexElementsObject = Array.newInstance(dexElementsObject.getClass().getComponentType(), oldLength + newLength);</span><br><span class="line">                for (int i = 0; i &lt; newLength; i++) {</span><br><span class="line">                    Array.set(concatDexElementsObject, i, Array.get(newDexElementsObject, i));</span><br><span class="line">                }</span><br><span class="line">                for (int i = 0; i &lt; oldLength; i++) {</span><br><span class="line">                    Array.set(concatDexElementsObject, newLength + i, Array.get(dexElementsObject, i));</span><br><span class="line">                }</span><br><span class="line"></span><br><span class="line">                dexElementsField.set(pathListObject, concatDexElementsObject);</span><br><span class="line">            } catch (NoSuchFieldException | IllegalAccessException e) {</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>主项目调用。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">package com.cxp.hotfix</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">class MainActivity : AppCompatActivity() {</span><br><span class="line"></span><br><span class="line">    override fun onCreate(savedInstanceState: Bundle?) {</span><br><span class="line">        super.onCreate(savedInstanceState)</span><br><span class="line">        setContentView(R.layout.activity_main)</span><br><span class="line">        Util().print()</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line">//打印结果是：“这是 hotfix 后的方法！”。</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p><strong>为什么要在 dexElements 头部加入元素呢？</strong><br>类加载过程最终会调用 DexPathList 的 findClass 方法，该方法查找生成 class 对象是按照从前往后遍历 dexElements 数组的方式进行的，找到了就直接返回，源码如下。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">//libcore/dalvik/src/main/java/dalvik/system/DexPathList.java</span><br><span class="line"></span><br><span class="line">public Class&lt;?&gt; findClass(String name, List&lt;Throwable&gt; suppressed) {</span><br><span class="line">        for (Element element : dexElements) {</span><br><span class="line">            Class&lt;?&gt; clazz = element.findClass(name, definingContext, suppressed);</span><br><span class="line">            if (clazz != null) {</span><br><span class="line">                return clazz;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        if (dexElementsSuppressedExceptions != null) {</span><br><span class="line">            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));</span><br><span class="line">        }</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><blockquote><p><strong>知识拓展</strong> 如果你想全量更新的话，把原来的 dexElements 全部替换就可以了，这要求所提供的 dex 文件内容要与原 APP 中已有内容完全一致。</p></blockquote><p>热更新增量修复的方式和官方的 MultiDex 很像，只不过 MultiDex 是将其它 Dex 添加到 Emlement 数组后面而已。不清楚 MultiDex 原理的话，请通读<a href="https://juejin.im/entry/5a3a21fcf265da430d58294e">这篇文章</a>。</p><blockquote><p><strong>注意</strong> 以上内容理解完全后，建议再阅读下知乎的<a href="https://zhuanlan.zhihu.com/p/20524252">这篇文章</a>，同时，针对链文要额外说明的是，从 API 26 开始的 optimizedDirectory 参数已被弃用了，所以链文中 “DexClassLoader 和 PathClassLoader” 的表述已错误，阅读时记得忽略该部分。</p></blockquote><p>最后要说明的是，这篇文章只是分析了个原理大概，离生产目标还很远，要想达到可生产的水平还有很多工作要做。目前很多大厂已经开源了自己的方案，按照自身需求挑选他们中的一个直接使用也未尝不可。</p><blockquote><p><strong>写作最后</strong><br>本文是笔者学习了扔物线老师的 Hencoder plus 课程后，结合网上的优秀文章而做的总结。</p></blockquote></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;本文主要目的是理解组件化、插件化、热更新的实现原理，并且知道他们之前的区别。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="组件化、插件化、热更新" scheme="http://example.com/tags/%E7%BB%84%E4%BB%B6%E5%8C%96%E3%80%81%E6%8F%92%E4%BB%B6%E5%8C%96%E3%80%81%E7%83%AD%E6%9B%B4%E6%96%B0/"/>
    
  </entry>
  
  <entry>
    <title>ConstraintLayout 布局使用指南</title>
    <link href="http://example.com/p/c9d3185a/"/>
    <id>http://example.com/p/c9d3185a/</id>
    <published>2019-10-18T06:18:40.000Z</published>
    <updated>2026-09-20T15:23:29.479Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><p>本文主要是参看了掘金网的<a href="https://juejin.im/entry/5a0f93b4f265da432717ce87">这篇文章</a>后，对 ConstraintLayout 布局的一个补充说明。<span id="more"></span></p><h1 id="1-Visibility-behavior"><a href="#1-Visibility-behavior" class="headerlink" title="1 Visibility behavior"></a>1 Visibility behavior</h1><p>ConstraintLayout 对可见行被标记为 GONE 的 View 有特殊的处理方式。下面来谈谈 android:layout_margin 和 app:layout_goneMargin 的区别：<br>1、android:layout_margin 一直有作用，即使目标 View 已经隐藏。</p><img src="/p/c9d3185a/1.png" class=""><p>2、app:layout_goneMargin 只是在目标 View 隐藏时起作用。<br>同时声明时，后者的优先级大于前者。一般情况下会同时使用这两种属性以达到目标 View 不同显示状态下当前 View 的不同 Margin 效果。</p><h1 id="2-Virtual-Helpers-objects"><a href="#2-Virtual-Helpers-objects" class="headerlink" title="2 Virtual Helpers objects"></a>2 Virtual Helpers objects</h1><h2 id="2-1-Barrier"><a href="#2-1-Barrier" class="headerlink" title="2.1 Barrier"></a>2.1 Barrier</h2><p>Barrier，直译为障碍、屏障。在约束布局中，可以使用属性 constraint_referenced_ids 来引用多个带约束的组件，从而将它们看作一个整体。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/button1"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="100dp"</span><br><span class="line">android:text="Button1"</span><br><span class="line">app:layout_constraintLeft_toLeftOf="parent"</span><br><span class="line">app:layout_constraintTop_toTopOf="parent" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/button2"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="200dp"</span><br><span class="line">android:text="Button2"</span><br><span class="line">app:layout_constraintLeft_toRightOf="@id/button1" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;android.support.constraint.Barrier</span><br><span class="line">android:id="@+id/barrier1"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">app:barrierDirection="bottom"</span><br><span class="line">app:constraint_referenced_ids="button1,button2" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;TextView</span><br><span class="line">android:id="@+id/bottom_textview"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">android:text="stone"</span><br><span class="line">app:layout_constraintLeft_toLeftOf="parent"</span><br><span class="line">app:layout_constraintTop_toBottomOf="@id/barrier1" /&gt;</span><br></pre></td></tr></tbody></table></figure><p>上面的 TextView 会在 button1 和 button2 这个整体的下面。如果将 app:constraint_referenced_ids=”button1, button2” 中的 button2 去掉，会发现，TextView 会仅在 button1 的下面。</p><h2 id="2-2-Group"><a href="#2-2-Group" class="headerlink" title="2.2 Group"></a>2.2 Group</h2><p>组的目的在于（且只能）同时控制多个控件的 Visibility 属性。<strong>但是有以下需要注意的地方。</strong><br>1、单独设置组内控件的 Visibility 无效。<br>2、当有多个组引用了同一个 View ，该 View 的 Visible 属性由最后定义的组决定。</p><h2 id="2-3-Layer-Added-in-2-0"><a href="#2-3-Layer-Added-in-2-0" class="headerlink" title="2.3 Layer(Added in 2.0)"></a>2.3 Layer(Added in 2.0)</h2><p>同时需要对一组 View 做统一操作或者需要在 View 集合后面加个背景都可以。同时，他可以实现 Group 的功能。<a href="https://github.com/feng0403/ConstraintLayoutSamples/blob/master/app/src/main/java/cn/feng/constraintlayoutsample/activity/LayerActivity.kt">实例代码参考&gt;&gt;</a></p><h2 id="2-4-Flow-Added-in-2-0"><a href="#2-4-Flow-Added-in-2-0" class="headerlink" title="2.4 Flow(Added in 2.0)"></a>2.4 Flow(Added in 2.0)</h2><p>用引用的方式来避免布局嵌套，代替 LinearLayout 的功能。<a href="https://juejin.im/post/5d0c46246fb9a07ec9561573">参考链接&gt;&gt;</a></p><h2 id="2-5-Placeholder"><a href="#2-5-Placeholder" class="headerlink" title="2.5 Placeholder"></a>2.5 Placeholder</h2><p><a href="https://www.jianshu.com/p/4b23e789befb">参考地址</a><br>Placeholder （占位符）用于和一个视图关联起来，通过 setContentId() 方法将占位符转换为指定的视图，即视图将在占位符所在位置上显示。当原视图已在 Placeholder 中显示时，原视图将从原有位置消失。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/btn_setContentId"</span><br><span class="line">android:layout_width="match_parent"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">android:onClick="setContentId"</span><br><span class="line">android:text="setContentId"</span><br><span class="line">android:textAllCaps="false" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;ImageView</span><br><span class="line">android:id="@+id/iv_target"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">android:src="@drawable/ic_launcher_background"</span><br><span class="line">app:layout_constraintEnd_toEndOf="parent"</span><br><span class="line">app:layout_constraintStart_toStartOf="parent"</span><br><span class="line">app:layout_constraintTop_toBottomOf="@id/btn_setContentId" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;android.support.constraint.Placeholder</span><br><span class="line">android:id="@+id/placeholder"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">app:layout_constraintBottom_toBottomOf="parent"</span><br><span class="line">app:layout_constraintEnd_toEndOf="parent"</span><br><span class="line">app:layout_constraintStart_toStartOf="parent"</span><br><span class="line">app:layout_constraintTop_toTopOf="parent" /&gt;</span><br></pre></td></tr></tbody></table></figure><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Placeholder placeholder = findViewById(R.id.placeholder);</span><br><span class="line">placeholder.setContentId(R.id.iv_target);</span><br></pre></td></tr></tbody></table></figure><img src="/p/c9d3185a/4.webp" class=""><p>此外也可以直接在布局文件中将占位符和视图 ID 绑定在一起</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;android.support.constraint.Placeholder</span><br><span class="line">android:id="@+id/placeholder"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">app:content="@id/iv_target"</span><br><span class="line">app:layout_constraintBottom_toBottomOf="parent"</span><br><span class="line">app:layout_constraintEnd_toEndOf="parent"</span><br><span class="line">app:layout_constraintStart_toStartOf="parent"</span><br><span class="line">app:layout_constraintTop_toTopOf="parent" /&gt;</span><br></pre></td></tr></tbody></table></figure><h2 id="2-6-ConstrainSet"><a href="#2-6-ConstrainSet" class="headerlink" title="2.6 ConstrainSet"></a>2.6 ConstrainSet</h2><p>动态修改布局，<a href="https://juejin.im/post/5b503fb2e51d45191d79ee30">参考链接&gt;&gt;</a></p><blockquote><p>防止布局中有无 id 控件，可以设置 isForceId = false 。</p></blockquote><h1 id="3-循环定位"><a href="#3-循环定位" class="headerlink" title="3 循环定位"></a>3 循环定位</h1><p>一个小部件的中心可以以一定角度和距离限制相对于另一个小部件的中心。这允许你将一个小部件放在一个圆上。可以使用以下属性：<br>1、layout_constraintCircle ：引用另一个小部件ID<br>2、layout_constraintCircleRadius ：到其他小部件中心的距离<br>3、layout_constraintCircleAngle ：小部件应处于的角度（度数，从0到360）</p><img src="/p/c9d3185a/2.png" class=""><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/bt_1"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">app:layout_constraintBottom_toBottomOf="parent"</span><br><span class="line">app:layout_constraintLeft_toLeftOf="parent"</span><br><span class="line">app:layout_constraintRight_toRightOf="parent"</span><br><span class="line">app:layout_constraintTop_toTopOf="parent" /&gt;</span><br><span class="line"></span><br><span class="line">&lt;Button</span><br><span class="line">android:id="@+id/bt_2"</span><br><span class="line">android:layout_width="wrap_content"</span><br><span class="line">android:layout_height="wrap_content"</span><br><span class="line">app:layout_constraintCircle="@+id/bt_1"</span><br><span class="line">app:layout_constraintCircleAngle="45"</span><br><span class="line">app:layout_constraintCircleRadius="100dp" /&gt;</span><br></pre></td></tr></tbody></table></figure><h1 id="4-其它"><a href="#4-其它" class="headerlink" title="4 其它"></a>4 其它</h1><p><a href="https://developer.android.google.cn/reference/android/support/constraint/Group">参考官方文档&gt;&gt;</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;本文主要是参看了掘金网的&lt;a href=&quot;https://juejin.im/entry/5a0f93b4f265da432717ce87&quot;&gt;这篇文章&lt;/a&gt;后，对 ConstraintLayout 布局的一个补充说明。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Android" scheme="http://example.com/categories/Android/"/>
    
    
    <category term="View" scheme="http://example.com/tags/View/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（八）：Set</title>
    <link href="http://example.com/p/e1236339/"/>
    <id>http://example.com/p/e1236339/</id>
    <published>2019-10-09T14:30:13.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>理解完之前几篇关于 Java 集合的文章后，对于 Set 的理解就非常容易了。Set 是用于存储不重复元素的集合，常见的 Set 类集合由如下几种：</p><p>1、HashSet</p><p>2、TreeSet</p><p>3、LinkedHashSet</p><p>4、CopyOnWriteSet</p><p>前三种 Set 都是对各自对应的 Map 的封装，并以 key 为操作对象。</p><p>CopyOnWriteArraySet 则是对 CopyOnWriteArrayList 的封装，利用了 CopyOnWriteArrayList addIfAbsent 方法不重复添加元素的特性。</p><p>下面只以 HashSet 为例讲解，其它几个实现方式类似。</p><h1 id="1-构造方法"><a href="#1-构造方法" class="headerlink" title="1 构造方法"></a>1 构造方法</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public HashSet() {</span><br><span class="line">        map = new HashMap&lt;&gt;();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashSet(Collection&lt;? extends E&gt; c) {</span><br><span class="line">        map = new HashMap&lt;&gt;(Math.max((int) (c.size()/.75f) + 1, 16));</span><br><span class="line">        addAll(c);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashSet(int initialCapacity, float loadFactor) {</span><br><span class="line">        map = new HashMap&lt;&gt;(initialCapacity, loadFactor);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashSet(int initialCapacity) {</span><br><span class="line">        map = new HashMap&lt;&gt;(initialCapacity);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">HashSet(int initialCapacity, float loadFactor, boolean dummy) {</span><br><span class="line">        map = new LinkedHashMap&lt;&gt;(initialCapacity, loadFactor);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>前三个方法都是内部创建了一个 HashMap 对象，第四个方法创建的是 LinkedHashMap 对象，我想是为了方便 HashSet 拥有 LinkedHashSet 的能力而增加的构造方法吧。</p><h1 id="2-add-E-e"><a href="#2-add-E-e" class="headerlink" title="2 add(E e)"></a>2 add(E e)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">public boolean add(E e) {</span><br><span class="line">        return m.put(e, PRESENT)==null;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>添加方法实际调用了 HashMap 的 put 方法，给它的 value 传入固定对象 PRESENT。这样就通过 HashMap 保证了传入值的唯一性（PS：其它的方法实现方式类似，自行去看源码吧）。</p><p>看完上面内容后，建议再看一下下面这篇文章，以加深对 Set 的理解.</p><p><a href="https://www.liaoxuefeng.com/wiki/1252599548343744/1265121225603904">点击跳转&gt;&gt;</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（七）：有序的 Map</title>
    <link href="http://example.com/p/67516450/"/>
    <id>http://example.com/p/67516450/</id>
    <published>2019-10-08T22:00:20.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>Java Map 类型集合提供了两种有序的Map，即：LinkedHashMap 和 TreeMap。</p><h1 id="1-LinkedHashMap"><a href="#1-LinkedHashMap" class="headerlink" title="1 LinkedHashMap"></a>1 LinkedHashMap</h1><p>LinkedHashMap 继承自 HashMap，并在 HashMap 的基础上添加了有序的功能。</p><p>LinkedHashMap 通过 accessOrder 关键属性控制了不同的有序逻辑。</p><p>1、accessOrder 为 false 时，按照插入顺序排序。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">LinkedHashMap&lt;String, String&gt; linkedHashMap = new LinkedHashMap&lt;&gt;();</span><br><span class="line">linkedHashMap.put("key1", "value1");</span><br><span class="line">linkedHashMap.put("key2", "value2");</span><br><span class="line">linkedHashMap.put("key3", "value3");</span><br><span class="line">linkedHashMap.put("key4", "value4");</span><br><span class="line">linkedHashMap.put("key5", "value5");</span><br><span class="line"></span><br><span class="line">Iterator l = linkedHashMap.entrySet().iterator();</span><br><span class="line">while (l.hasNext()) {</span><br><span class="line">    System.out.println(l.next());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">输出结果：</span><br><span class="line">key1=value1</span><br><span class="line">key2=value2</span><br><span class="line">key3=value3</span><br><span class="line">key4=value4</span><br><span class="line">key5=value5</span><br></pre></td></tr></tbody></table></figure><p>2、accessOrder 为 true 时，按照访问顺序排序。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">LinkedHashMap&lt;String, String&gt; linkedHashMap = new LinkedHashMap&lt;String, String&gt;(16,0.75f,true);</span><br><span class="line">linkedHashMap.put("key1", "value1");</span><br><span class="line">linkedHashMap.put("key2", "value2");</span><br><span class="line">linkedHashMap.put("key3", "value3");</span><br><span class="line">linkedHashMap.put("key4", "value4");</span><br><span class="line">linkedHashMap.put("key5", "value5");</span><br><span class="line">linkedHashMap.get("key1");</span><br><span class="line"></span><br><span class="line">Iterator l = linkedHashMap.entrySet().iterator();</span><br><span class="line">while (l.hasNext()) {</span><br><span class="line">    System.out.println(l.next());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">输出结果：</span><br><span class="line">key2=value2</span><br><span class="line">key3=value3</span><br><span class="line">key4=value4</span><br><span class="line">key5=value5</span><br><span class="line">key1=value1</span><br></pre></td></tr></tbody></table></figure><h2 id="1-1-put-K-key-V-value"><a href="#1-1-put-K-key-V-value" class="headerlink" title="1.1 put(K key, V value)"></a>1.1 put(K key, V value)</h2><p>添加元素时，LinkedHashMap 保证了添加元素的有序性，依旧调用了 HashMap 中的 putVal 方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">final V putVal(int hash, K key, V value, boolean onlyIfAbsent,</span><br><span class="line">                   boolean evict) {</span><br><span class="line">        Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i;</span><br><span class="line">        if ((tab = table) == null || (n = tab.length) == 0)</span><br><span class="line">            n = (tab = resize()).length;</span><br><span class="line">        if ((p = tab[i = (n - 1) &amp; hash]) == null)</span><br><span class="line">            tab[i] = newNode(hash, key, value, null);</span><br><span class="line">        else {</span><br><span class="line">            Node&lt;K,V&gt; e; K k;</span><br><span class="line">            if (p.hash == hash &amp;&amp;</span><br><span class="line">                ((k = p.key) == key || (key != null &amp;&amp; key.equals(k))))</span><br><span class="line">                e = p;</span><br><span class="line">            else if (p instanceof TreeNode)</span><br><span class="line">                e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(this, tab, hash, key, value);</span><br><span class="line">            else {</span><br><span class="line">                for (int binCount = 0; ; ++binCount) {</span><br><span class="line">                    if ((e = p.next) == null) {</span><br><span class="line">                        p.next = newNode(hash, key, value, null);</span><br><span class="line">                        if (binCount &gt;= TREEIFY_THRESHOLD - 1) // -1 for 1st</span><br><span class="line">                            treeifyBin(tab, hash);</span><br><span class="line">                        break;</span><br><span class="line">                    }</span><br><span class="line">                    if (e.hash == hash &amp;&amp;</span><br><span class="line">                        ((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))</span><br><span class="line">                        break;</span><br><span class="line">                    p = e;</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">            if (e != null) { // existing mapping for key</span><br><span class="line">                V oldValue = e.value;</span><br><span class="line">                if (!onlyIfAbsent || oldValue == null)</span><br><span class="line">                    e.value = value;</span><br><span class="line">                afterNodeAccess(e);</span><br><span class="line">                return oldValue;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        ++modCount;</span><br><span class="line">        if (++size &gt; threshold)</span><br><span class="line">            resize();</span><br><span class="line">        afterNodeInsertion(evict);</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>再来看，其中的 newNode 方法实际上是 HashMap提供的一个模版方法，子类 LinkedHashMap 重写了该方法，我们再来看下 newNode 在子类的定义。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Node&lt;K,V&gt; newNode(int hash, K key, V value, Node&lt;K,V&gt; e) {</span><br><span class="line">        LinkedHashMap.Entry&lt;K,V&gt; p =</span><br><span class="line">            new LinkedHashMap.Entry&lt;K,V&gt;(hash, key, value, e);</span><br><span class="line">        linkNodeLast(p);</span><br><span class="line">        return p;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">// link at the end of list.</span><br><span class="line">private void linkNodeLast(LinkedHashMap.Entry&lt;K,V&gt; p) {</span><br><span class="line">        LinkedHashMap.Entry&lt;K,V&gt; last = tail;</span><br><span class="line">        tail = p;</span><br><span class="line">        if (last == null)</span><br><span class="line">            head = p;</span><br><span class="line">        else {</span><br><span class="line">            p.before = last;</span><br><span class="line">            last.after = p;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>子类的 newNode 方法除了完全实现了和父类中 newNode 方法一样的功能外，还额外实现了 linkNodeLast 方法，该方法按照添加顺序将 Entry 添加到了 LinkedHashMap 自身维护链表的尾部。LinkedHashMap 的功能大部分都是按照这种子类复写的方式实现的，这里不再一一列举了。</p><p>下面，我们来利用 LinkedHashMap 实现一个 LRU 算法，这也是 LinkedHashMap 被经常运用的场景。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">static class LRU&lt;K, V&gt; extends LinkedHashMap&lt;K, V&gt; {</span><br><span class="line"></span><br><span class="line">        int maxSize;</span><br><span class="line"></span><br><span class="line">        public LRU(int initialCapacity, float loadFactor, boolean accessOrder, int maxSize) {</span><br><span class="line">            super(16, 0.75f, true);</span><br><span class="line">            this.maxSize = maxSize;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        protected boolean removeEldestEntry(Map.Entry&lt;K, V&gt; eldest) {</span><br><span class="line">            return size() &gt; maxSize; //当元素个数大于 maxSize 时，移除最少被访问的元素。</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>这里重写了 removeEldestEntry 方法，该方法在 LinkedHashMap 中默认返回 false 。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">protected boolean removeEldestEntry(Map.Entry&lt;K,V&gt; eldest) {</span><br><span class="line">        return false;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>LinkedHashMap 在每次 put 完元素后都会依据该方法判断是否移除链表中第1个元素。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//afterNodeInsertion 正是父类 HashMap 的 put 方法最后调用的逻辑。</span><br><span class="line">void afterNodeInsertion(boolean evict) { // possibly remove eldest</span><br><span class="line">        LinkedHashMap.Entry&lt;K,V&gt; first;</span><br><span class="line">        if (evict &amp;&amp; (first = head) != null &amp;&amp; removeEldestEntry(first)) {</span><br><span class="line">            K key = first.key;</span><br><span class="line">            removeNode(hash(key), key, null, false, true); //移除链表中的首个元素。</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>好了，上面就是对 LinkedHashmap 的介绍了，原理很简单，其实就是在 HashMap 的基础添加有序了功能而已。本篇是JDK 1.8 的代码讲解的，JDK 1.7 的思路一致，这里就不再赘述了。</p><h1 id="2-TreeMap"><a href="#2-TreeMap" class="headerlink" title="2 TreeMap"></a>2 TreeMap</h1><p>TreeMap 的源码笔者就不再逐个分析了。对于它的用法，认真看完下面链接中的文章就可以了。</p><p><a href="https://www.liaoxuefeng.com/wiki/1252599548343744/1265117109276544">点击跳转&gt;&gt;</a></p><p>上面链接文章中提到了 TreeMap 的 key 不需要重写 equal 和 hash 方法，<strong>那么他是如何将元素对号入座的呢？</strong></p><p>下面是 TreeMap 的 put 方法实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line">public V put(K key, V value) {</span><br><span class="line">        Entry&lt;K,V&gt; t = root;</span><br><span class="line">        if (t == null) {</span><br><span class="line">            compare(key, key); // type (and possibly null) check</span><br><span class="line"></span><br><span class="line">            root = new Entry&lt;&gt;(key, value, null);</span><br><span class="line">            size = 1;</span><br><span class="line">            modCount++;</span><br><span class="line">            return null;</span><br><span class="line">        }</span><br><span class="line">        int cmp;</span><br><span class="line">        Entry&lt;K,V&gt; parent;</span><br><span class="line">        // split comparator and comparable paths</span><br><span class="line">        Comparator&lt;? super K&gt; cpr = comparator;</span><br><span class="line">        if (cpr != null) {</span><br><span class="line">            do {</span><br><span class="line">                parent = t;</span><br><span class="line">                cmp = cpr.compare(key, t.key);</span><br><span class="line">                if (cmp &lt; 0)</span><br><span class="line">                    t = t.left;</span><br><span class="line">                else if (cmp &gt; 0)</span><br><span class="line">                    t = t.right;</span><br><span class="line">                else</span><br><span class="line">                    return t.setValue(value);</span><br><span class="line">            } while (t != null);</span><br><span class="line">        }</span><br><span class="line">        else {</span><br><span class="line">            if (key == null)</span><br><span class="line">                throw new NullPointerException();</span><br><span class="line">            @SuppressWarnings("unchecked")</span><br><span class="line">                Comparable&lt;? super K&gt; k = (Comparable&lt;? super K&gt;) key;</span><br><span class="line">            do {</span><br><span class="line">                parent = t;</span><br><span class="line">                cmp = k.compareTo(t.key);</span><br><span class="line">                if (cmp &lt; 0)</span><br><span class="line">                    t = t.left;</span><br><span class="line">                else if (cmp &gt; 0)</span><br><span class="line">                    t = t.right;</span><br><span class="line">                else</span><br><span class="line">                    return t.setValue(value);</span><br><span class="line">            } while (t != null);</span><br><span class="line">        }</span><br><span class="line">        Entry&lt;K,V&gt; e = new Entry&lt;&gt;(key, value, parent);</span><br><span class="line">        if (cmp &lt; 0)</span><br><span class="line">            parent.left = e;</span><br><span class="line">        else</span><br><span class="line">            parent.right = e;</span><br><span class="line">        fixAfterInsertion(e); //红黑树相关操作</span><br><span class="line">        size++;</span><br><span class="line">        modCount++;</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>由上面代码可知，TreeMap 维护的是一颗红黑树，Comparable｜Comparator 决定了元素是添加到左子树还是右子树。即：cmp &lt; 0 添加到左子树，cmp &gt; 0 添加到右子树，cmp = 0 则替换，所以TreeMap 的 key 不需要重写 equal 和 hash 方法来确定 key 的位置。同样，TreeMap 的 get 方法也是类似的查找策略，这里不再赘述。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://www.bilibili.com/video/BV1o4411q7UC?p=2">亮哥教你学Java</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（六）：线程安全的 Map</title>
    <link href="http://example.com/p/129a62b4/"/>
    <id>http://example.com/p/129a62b4/</id>
    <published>2019-10-07T15:48:49.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>上一篇文章我们提到 HashMap 是线程不安全的 Map ，那有什么线程安全的 Map 呢？</p><p>Java 提供了三种实现线程安全 Map 的方式：</p><p>1、HashTable</p><p>2、Collections.synchronizedMap()</p><p>3、ConcurrentHashMap</p><p>对于 HashTable ,它是 Java 1.0 版本的时候推出的 map 类，比 HashMap 要推出的早。HashTable为了线程安全，几乎是在每个方法前面都加上了 synchronized 关键字，这和 List 中的 Vector 及其类似。</p><p>对比 HashTable 和 HashMap ，主要以下几个区别：</p><p>1、HashTable 在每个操作方法加了 synchronized 关键字，而 HashMap 没有同步措施。</p><p>2、由于加了同步策略，HashTable 的执行效率很低。</p><p>3、HashTable 在扩容时，扩大到原来的 2 倍加 1，而 HashMap 则是扩大到原来的 2 倍。</p><p>4、HashTable 继承了 Dictionary，HashMap 继承了 AbstractMap。</p><p>5、HashTable 没有区分 1.7 和 1.8 版本没有什么区别，都是数组+链表结构。</p><p>第二种方式 Collections.synchronizedMap()，只是对 Map 做了一层 synchronized 包装，同样是执行效率低。</p><p>第三种方式是 ConcurrentHashMap ,分为 JDK 1.7 版本和 JDK 1.8 版本（之后的版本笔者没有研究过），直接看下面链接中的文章吧（笔者对于扩容和数据迁移没有完全理解）。</p><p><a href="https://mp.weixin.qq.com/s/My4P_BBXDnAGX1gh630ZKw">我就知道面试官接下来要问我 ConcurrentHashMap 底层原理了</a></p><blockquote><p>笔者在看以上链接文章 JDK 1.8 部分时，还结合了一个视频来看，这是<a href="https://www.bilibili.com/video/BV1x741117jq?p=6">视频地址</a>，也是没有完全看懂。</p></blockquote><p><strong>下面对链文中笔者能够理解的知识总结一下：</strong></p><p>1、JDK 1.7 中的 ConcurrentHashMap 采用分段锁的方式对 HashMap 加锁，它内部维护着一个 Segment 数组，每个 Segment 内维护着一个存放键值对的数组，Segment 就和小型 HashMap 一样，同样有扩容、数据迁移等操作。ConcurrentHashMap 的 size 计算方式有很大的不同，它是所有 Segment 中数组元素个数的总和，求和过程是：</p><p>首先尝试用 CAS 的方式求值，如果过程中有其它线程更改了 ConcurrentHashMap （通过 modCount 就能发现），则这一轮的求值失败，反复尝试多次如果不成功就把每个 Segment 都加锁，再分别获取求和。</p><p>2、JDK 1.8 中的 ConcurrentHashMap 中采用的 CAS + Synchronized 实现线程安全。</p><p>当添加的元素时，如果数组为空，则通过CAS自旋新建数组。数组不为空，但添加位为空时，直接用CAS添加。当添加位key的hash==MOVE时，表示 ConcurrentHashMap 正在扩容，则帮助其扩容。以上条件都不满足的话，就进入Synchronized代码块执行元素操作（类似 JDK 1.8 的 HashMap）。最后执行 addCount 方法将元素个数 +1 并执行扩容判断逻辑。</p><p>元素个数+1，涉及操作 CounterCell[] 和 baseCount，试想为了 size 值正确我们完全可以将 size 设置成 Volatile 类型，然后用 CAS 自旋来实现size的加1操作，但如果有很多线程同时对size做加1操作的话，效率就不高了，所以 JDK 1.8 的 ConcurrentHashMap 通过 baseCount 和 CounterCell数组两个属性同时参与元素个数的统计，具体就是通过 CAS 操作保证最后baseCount加1成功或者 CounterCell 数组中的某个对应 CounterCell 加1成功。</p><p>扩容大概流程是从数组最后一位往前遍历，确定每个线程负责转移的步长（即数组区域），每个转移完成的桶位置都放置一个 ForwardingNode ，直到转移完成。</p><p>3、ConcurrentHashMap 不论是 JDK 1.7 还是 JDK 1.8扩容后的新哈希表中对应节点都会重新新建对象，这一点和 HashMap 不一样，HashMap 还是原来的节点。</p><p><strong>一个问题：HashTable, ConcurrentHashMap为什么键和值不能为null，而 HashMap 可以 ？</strong><br>因为 HashTable , ConcurrentHashMap 它们是用于多线程的，并发的 ，如果map.get(key)得到了null，不能判断到底是映射的value是null,还是因为没有找到对应的key而为空，而对于单线程状态的HashMap 却可以用 containKey（key） 去判断到底是否包含了这个key。比如：一个线程先get(key)再containKey(key)，这两个方法的中间时刻，其他线程怎么操作这个key都会可能发生，例如删掉这个key，所以多线程场景中 value 为 null 就很容易产生歧义。</p><p>至于，为什么 key 也不能为 null ，我想不出这是为什么。</p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（五）：HashMap</title>
    <link href="http://example.com/p/8ff61c3d/"/>
    <id>http://example.com/p/8ff61c3d/</id>
    <published>2019-10-06T14:16:45.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>HashMap 是 Java 集合类中比较难搞懂的一个技术点，主要的难点是它的扩容机制，以及 JDK 1.8 之后新增的树化操作。</p><p>首先我们先来分析 JDK 1.7 中的 HashMap 。</p><h1 id="1-初始化"><a href="#1-初始化" class="headerlink" title="1 初始化"></a>1 初始化</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">public HashMap(int initialCapacity, float loadFactor) {//1</span><br><span class="line">        if (initialCapacity &lt; 0)</span><br><span class="line">            throw new IllegalArgumentException("Illegal initial capacity: " +</span><br><span class="line">                                               initialCapacity);</span><br><span class="line">        if (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">            initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line">        if (loadFactor &lt;= 0 || Float.isNaN(loadFactor))</span><br><span class="line">            throw new IllegalArgumentException("Illegal load factor: " +</span><br><span class="line">                                               loadFactor);</span><br><span class="line"></span><br><span class="line">        this.loadFactor = loadFactor;</span><br><span class="line">        threshold = initialCapacity;</span><br><span class="line">        init();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashMap(int initialCapacity) {//2</span><br><span class="line">        this(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashMap() {//3</span><br><span class="line">        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public HashMap(Map&lt;? extends K, ? extends V&gt; m) {//4</span><br><span class="line">        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,</span><br><span class="line">                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);</span><br><span class="line">        inflateTable(threshold);</span><br><span class="line"></span><br><span class="line">        putAllForCreate(m);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>几个构造方法类似，都是最终调用了第一个构造方法，第一个构造方法只是做了下赋值操作。如果是调用了无参构造参数，则 loadFactor 和 threshold 将分别被赋值为默认值。</p><p>如果调用第四个构造函数，除了调用第一个构造函数之外，还调用了 inflateTable 和 putAllForCreate 这两个方法，我们分别来看它们做啥。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">private void inflateTable(int toSize) {</span><br><span class="line">        // Find a power of 2 &gt;= toSize</span><br><span class="line">        int capacity = roundUpToPowerOf2(toSize);</span><br><span class="line"></span><br><span class="line">        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);</span><br><span class="line">        table = new Entry[capacity];</span><br><span class="line">        initHashSeedAsNeeded(capacity);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p><code>roundUpToPowerOf2</code>方法的作用是确保返回的值是2的n次幂，该值要大于或等于传入的值，且该值是满足条件的第一个值。如：toSize==15 ，那么返回值是 16 。</p><p>之后，对 threshold 赋值，这里假定 capacity * loadFactor &lt; MAXIMUM_CAPACITY + 1 ，所以threshold = capacity * loadFactor 。</p><p>然后，新建一个长度为 capacity 的数组。</p><p><code>initHashSeedAsNeeded</code>中完成了对 hashSeed 的初始化。</p><p>讲到这里必须停一下了，因为除了很多不明用途的变量，这些变量将对之后的流程产生极大的影响，所以我先说下他们各自的概念，在之后的篇幅中大家再对照着理解就行了。</p><p>1、buckets（哈希桶）：在 HashMap 的注释里形象的用哈希桶来形容数组中的每个地址位置。</p><p>2、哈希表（散列表）：指数组本身，数组是装哈希桶的。其实就是 Hashmap 中的 table 。</p><p>3、initialCapacity（初始容量）：哈希表中哈希桶初始的数量。如果我们没有通过构造方法修改这个容量值，默认为DEFAULT_INITIAL_CAPACITY = 1&lt;&lt;4 即 16。</p><p>4、哈希表的容量：table.length。</p><p>5、加载因子(load factor)：加载因子是哈希表在其容量自动增加之前被允许获得的最大数量的度量。当哈希表中的条目数量超过负载因子和当前容量的乘积时，哈希表就会被重新映射（即重建内部数据结构），重新创建的散列表容量大约是之前散列表哈系统桶数量的两倍。默认加载因子（0.75）在时间和空间成本之间提供了良好的折衷。加载因子过大会导致很容易链表过长，加载因子很小又容易导致频繁的扩容。所以不要轻易试着去改变这个默认值。</p><p>6、threshold（扩容阈值）。扩容阈值 = 哈希表容量 * 加载因子。从上面加载因子的介绍可知，他是哈希表发生扩容的评判基准。</p><p>7、size（哈希表的键值对总数）= 所有哈希桶中所有链表节点数的加和。</p><p>讲完了 inflateTable ,我们再来看下 putAllForCreate 做了什么。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">private void putAllForCreate(Map&lt;? extends K, ? extends V&gt; m) {</span><br><span class="line">        for (Map.Entry&lt;? extends K, ? extends V&gt; e : m.entrySet())</span><br><span class="line">            putForCreate(e.getKey(), e.getValue());</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">private void putForCreate(K key, V value) {</span><br><span class="line">        int hash = null == key ? 0 : hash(key);</span><br><span class="line">        int i = indexFor(hash, table.length);</span><br><span class="line"></span><br><span class="line">        //看是否存在相同的key，如果有则直接将value替换并返回。</span><br><span class="line">        for (Entry&lt;K,V&gt; e = table[i]; e != null; e = e.next) {</span><br><span class="line">            Object k;</span><br><span class="line">            if (e.hash == hash &amp;&amp;</span><br><span class="line">                ((k = e.key) == key || (key != null &amp;&amp; key.equals(k)))) {</span><br><span class="line">                e.value = value;</span><br><span class="line">                return;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        //新建 Entry ,并将其插入到该桶位置上链表的头节点。</span><br><span class="line">        createEntry(hash, key, value, i);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">void createEntry(int hash, K key, V value, int bucketIndex) {</span><br><span class="line">        Entry&lt;K,V&gt; e = table[bucketIndex];</span><br><span class="line">        table[bucketIndex] = new Entry&lt;&gt;(hash, key, value, e);</span><br><span class="line">        size++;</span><br><span class="line">    }</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>这里思路就是遍历 m 中的 key 和 value，然后将它们一个一个添加到 table 中。细节在代码中已经注释，注意这里有一个很有意思的点，就是新添加的元素会被添加到对应链表的头结点位置。</p><h1 id="2-put-K-key-V-value"><a href="#2-put-K-key-V-value" class="headerlink" title="2 put(K key, V value)"></a>2 put(K key, V value)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">public V put(K key, V value) {</span><br><span class="line">        //如果数组为空，则新建数组。</span><br><span class="line">        if (table == EMPTY_TABLE) {</span><br><span class="line">            inflateTable(threshold);</span><br><span class="line">        }</span><br><span class="line">        //如果 key 为 null，则将值存放在第 0 号桶上。</span><br><span class="line">        if (key == null)</span><br><span class="line">            return putForNullKey(value);</span><br><span class="line">        int hash = hash(key);</span><br><span class="line">        //根据 hash 值，确定插入到桶的位置。</span><br><span class="line">        int i = indexFor(hash, table.length);</span><br><span class="line">        //确定当前桶位置的链表中是否存在相同的key。</span><br><span class="line">        for (Entry&lt;K,V&gt; e = table[i]; e != null; e = e.next) {</span><br><span class="line">            Object k;</span><br><span class="line">            if (e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))) {</span><br><span class="line">                V oldValue = e.value;</span><br><span class="line">                e.value = value;</span><br><span class="line">                e.recordAccess(this);</span><br><span class="line">                return oldValue;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        modCount++;</span><br><span class="line">        //扩容判断，并且添加元素。</span><br><span class="line">        addEntry(hash, key, value, i);</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">private V putForNullKey(V value) {</span><br><span class="line">        //查找第 0 号桶是否存在 key==null ，存在则替换并返回。</span><br><span class="line">        for (Entry&lt;K,V&gt; e = table[0]; e != null; e = e.next) {</span><br><span class="line">            if (e.key == null) {</span><br><span class="line">                V oldValue = e.value;</span><br><span class="line">                e.value = value;</span><br><span class="line">                e.recordAccess(this);</span><br><span class="line">                return oldValue;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        modCount++;</span><br><span class="line">        addEntry(0, null, value, 0);</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">static int indexFor(int h, int length) {</span><br><span class="line">        //因为 length 必定是 2 的 n 次幂，这里算出的结果范围是0～(length-1)。</span><br><span class="line">        return h &amp; (length-1);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">void addEntry(int hash, K key, V value, int bucketIndex) {</span><br><span class="line">        //当 size 超过阈值时并且当前位置的值不为 null 时，执行扩容操作。</span><br><span class="line">        if ((size &gt;= threshold) &amp;&amp; (null != table[bucketIndex])) {</span><br><span class="line">            //扩容到原来的2倍。</span><br><span class="line">            resize(2 * table.length);</span><br><span class="line">            hash = (null != key) ? hash(key) : 0;</span><br><span class="line">            bucketIndex = indexFor(hash, table.length);</span><br><span class="line">        }</span><br><span class="line">//往链表头部插入元素。</span><br><span class="line">        createEntry(hash, key, value, bucketIndex);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>大体的代码逻辑已经在上面代码中注释，只剩下扩容的代码了。下面我们来看看 resize 方法内部的逻辑。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">void resize(int newCapacity) {</span><br><span class="line">        Entry[] oldTable = table;</span><br><span class="line">        int oldCapacity = oldTable.length;</span><br><span class="line">        //当哈希表的容量等于 1 &lt;&lt; 30 时，直接返回，不进行扩容操作。</span><br><span class="line">        if (oldCapacity == MAXIMUM_CAPACITY) {</span><br><span class="line">            threshold = Integer.MAX_VALUE;</span><br><span class="line">            return;</span><br><span class="line">        }</span><br><span class="line">        //新建数组，该数组大小时原来的 2 倍。</span><br><span class="line">        Entry[] newTable = new Entry[newCapacity];</span><br><span class="line">        transfer(newTable, initHashSeedAsNeeded(newCapacity));</span><br><span class="line">        table = newTable;</span><br><span class="line">        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>再来看下 transfer 方法的逻辑。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Transfers all entries from current table to newTable.</span><br><span class="line"> */</span><br><span class="line">void transfer(Entry[] newTable, boolean rehash) {</span><br><span class="line">        int newCapacity = newTable.length;</span><br><span class="line">        for (Entry&lt;K,V&gt; e : table) {</span><br><span class="line">            while(null != e) {</span><br><span class="line">                Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">                //rehash 为 true 时，重新 hash。</span><br><span class="line">                //我想这也是为了减少碰撞发生的概率吧。</span><br><span class="line">                if (rehash) {</span><br><span class="line">                    e.hash = null == e.key ? 0 : hash(e.key);</span><br><span class="line">                }</span><br><span class="line">                int i = indexFor(e.hash, newCapacity);</span><br><span class="line">                e.next = newTable[i];</span><br><span class="line">                newTable[i] = e;</span><br><span class="line">                e = next;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>transfer 方法的英文注释写的很清楚了，功能是元素从当前数组转移到新的数组中，之后在把新的数组赋值给当前数组。</p><p>好了，put 方法讲完了。流程是：</p><p>1、判断数组是否为空，为空则新建数组，顺便将 threshold 赋值为 capacity * loadFactor。</p><p>2、判断是 key 是否为 null，为 null 则保存到 0 号桶位置。</p><p>3、根据 hash 值寻找桶的位置。</p><p>4、查找是否具备相同的 key 值的 Entry。</p><p>5、如果 size &gt;= threshold &amp;&amp; null != table[bucketIndex] ，则扩容。</p><p>6、添加数据。</p><p>注意，判断 key 是否相等是根据<code>e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))</code>判断的，这也是为什么要求作为key的类必须正确重写 equals 和 hashcode 方法。</p><blockquote><p><strong>作为 Hashmap 的 key 需要有什么前提条件？</strong><br>尽量是final修饰的类型，一般用String这种不可变的类型。如果使用自定义类，还需要正确重写它的equals 和 hashcode 方法。</p></blockquote><p>不知道你注意到没，HashMap 在进行 put 操作时，也是会修改 modCount 的，说明 HashMap 同样是有fast-fail 机制的。实际上，Java 集合类除了 java.util.concurrent 包下的CopyOnWriteArrayList 和 ConcurrentHashMap，其它集合类都是有 fast-fail 机制，以后再遇到 modCount 时，笔者将不再赘述其作用。</p><p>到这里 put 方法的解读就到完成了。</p><p>这里不再列出 remove\get 方法的代码，原理很简单，请自行查阅。</p><h1 id="3-遍历"><a href="#3-遍历" class="headerlink" title="3 遍历"></a>3 遍历</h1><p>HashMap 的遍历只能通过迭代器（Iterator）遍历。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">for (Map.Entry entry : map.entrySet()) {</span><br><span class="line">            System.out.println(entry.getKey().toString());</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>上面的写法等价于下面：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Iterator&lt;Map.Entry&lt;String, String&gt;&gt; iterator = map.entrySet().iterator();</span><br><span class="line"></span><br><span class="line">while (iterator.hasNext()) {</span><br><span class="line">    System.out.println(iterator.next());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里得到的 iterator 对象，是一个 HashIterator 对象。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">private final class EntryIterator extends HashIterator&lt;Map.Entry&lt;K,V&gt;&gt; {</span><br><span class="line">        public Map.Entry&lt;K,V&gt; next() {</span><br><span class="line">            return nextEntry();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>我们来看下 HashIterator 类结构。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">private abstract class HashIterator&lt;E&gt; implements Iterator&lt;E&gt; {</span><br><span class="line">        Entry&lt;K,V&gt; next;        // next entry to return</span><br><span class="line">        int expectedModCount;   // For fast-fail</span><br><span class="line">        int index;              // current slot</span><br><span class="line">        Entry&lt;K,V&gt; current;     // current entry</span><br><span class="line"></span><br><span class="line">        HashIterator() {</span><br><span class="line">            expectedModCount = modCount;</span><br><span class="line">            if (size &gt; 0) { // advance to first entry</span><br><span class="line">                Entry[] t = table;</span><br><span class="line">                while (index &lt; t.length &amp;&amp; (next = t[index++]) == null)</span><br><span class="line">                    ;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public final boolean hasNext() {</span><br><span class="line">            return next != null;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        final Entry&lt;K,V&gt; nextEntry() {</span><br><span class="line">            if (modCount != expectedModCount)</span><br><span class="line">                throw new ConcurrentModificationException();</span><br><span class="line">            Entry&lt;K,V&gt; e = next;</span><br><span class="line">            if (e == null)</span><br><span class="line">                throw new NoSuchElementException();</span><br><span class="line"></span><br><span class="line">            if ((next = e.next) == null) {</span><br><span class="line">                Entry[] t = table;</span><br><span class="line">                while (index &lt; t.length &amp;&amp; (next = t[index++]) == null)</span><br><span class="line">                    ;</span><br><span class="line">            }</span><br><span class="line">            current = e;</span><br><span class="line">            return e;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public void remove() {</span><br><span class="line">            if (current == null)</span><br><span class="line">                throw new IllegalStateException();</span><br><span class="line">            if (modCount != expectedModCount)</span><br><span class="line">                throw new ConcurrentModificationException();</span><br><span class="line">            Object k = current.key;</span><br><span class="line">            current = null;</span><br><span class="line">            HashMap.this.removeEntryForKey(k);</span><br><span class="line">            expectedModCount = modCount;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>HashIterator 的构造方法中存下了 modCount 的值给 expectedModCount ，之后定位到首个 entry 不为 null 的桶位置。</p><p>nextEntry 方法负责一个一个地遍历桶中的链表。</p><p>HashIterator 还提供了 remove 方法。</p><p>整个 HashIterator 类 和 ArrayList 中的 Itr 类很像。</p><p>HashMap 除了能通过 map.entrySet() 遍历 Entry ，还可以通过 map.keySet() 遍历 key 值，通过 map.values() 遍历 value 值。</p><p>好了，JDK 1.7 的 HashMap 就分析完了，其实它就是一个数组+链表的结构。对于它的扩容时机、扩容时的元素转移、链表插入方式（头插）以及几个重要的概念一定要搞清楚。HashMap 是线程不安全的，并且由于 JDK 1.7 中 HashMap 链表的插入方式是头插法，所以多线程操作还可能会导致循环链表的产生。</p><p>采用数组+链表的结构存储数据的一个弊端是可能会导致链表长度过长，即：Hash 冲突严重时，查询效率变低。于是，JDK 1.8 对 HashMap 做了重构，下面介绍JDK 1.8 HashMap 相关的内容。</p><p>JDK 1.8 中的 HashMap 最大的改变就是采用了数组+链表+红黑树的方式来组织数据。</p><h1 id="4-put-K-key-V-value"><a href="#4-put-K-key-V-value" class="headerlink" title="4 put(K key, V value)"></a>4 put(K key, V value)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">public V put(K key, V value) {</span><br><span class="line">        return putVal(hash(key), key, value, false, true);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">final V putVal(int hash, K key, V value, boolean onlyIfAbsent,</span><br><span class="line">                   boolean evict) {</span><br><span class="line">        Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i;</span><br><span class="line">        if ((tab = table) == null || (n = tab.length) == 0) //数组为空时，新建数组。</span><br><span class="line">            n = (tab = resize()).length;</span><br><span class="line">        if ((p = tab[i = (n - 1) &amp; hash]) == null) //位置为 i 位置的桶不存在元素时，就将新元素赋值到 i 位置上。</span><br><span class="line">            tab[i] = newNode(hash, key, value, null);</span><br><span class="line">        else {</span><br><span class="line">            Node&lt;K,V&gt; e; K k;</span><br><span class="line">            if (p.hash == hash &amp;&amp;</span><br><span class="line">                ((k = p.key) == key || (key != null &amp;&amp; key.equals(k))))</span><br><span class="line">                e = p; //key 相等，则替换。</span><br><span class="line">            else if (p instanceof TreeNode) //判断是否是红黑树</span><br><span class="line">                e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(this, tab, hash, key, value);</span><br><span class="line">            else {</span><br><span class="line">                for (int binCount = 0; ; ++binCount) {</span><br><span class="line">                    if ((e = p.next) == null) {</span><br><span class="line">                        p.next = newNode(hash, key, value, null);</span><br><span class="line">                        if (binCount &gt;= TREEIFY_THRESHOLD - 1) // 链表长度大于 8 ，则树化。</span><br><span class="line">                            treeifyBin(tab, hash);</span><br><span class="line">                        break;</span><br><span class="line">                    }</span><br><span class="line">                    if (e.hash == hash &amp;&amp;</span><br><span class="line">                        ((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))</span><br><span class="line">                        break;</span><br><span class="line">                    p = e;</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">            if (e != null) { // key 已经存在。</span><br><span class="line">                V oldValue = e.value;</span><br><span class="line">                if (!onlyIfAbsent || oldValue == null)</span><br><span class="line">                    e.value = value;</span><br><span class="line">                afterNodeAccess(e);</span><br><span class="line">                return oldValue;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        ++modCount;</span><br><span class="line">        if (++size &gt; threshold) // size+1 大于阈值时，扩容。</span><br><span class="line">            resize();</span><br><span class="line">        afterNodeInsertion(evict);</span><br><span class="line">        return null;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>我们来看下树化的逻辑。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">final void treeifyBin(Node&lt;K,V&gt;[] tab, int hash) {</span><br><span class="line">        int n, index; Node&lt;K,V&gt; e;</span><br><span class="line">        if (tab == null || (n = tab.length) &lt; MIN_TREEIFY_CAPACITY)</span><br><span class="line">            resize();</span><br><span class="line">        else if ((e = tab[index = (n - 1) &amp; hash]) != null) {</span><br><span class="line">            TreeNode&lt;K,V&gt; hd = null, tl = null;</span><br><span class="line">            //Node 转化成 TreeNode 。</span><br><span class="line">            do {</span><br><span class="line">                TreeNode&lt;K,V&gt; p = replacementTreeNode(e, null);</span><br><span class="line">                if (tl == null)</span><br><span class="line">                    hd = p;</span><br><span class="line">                else {</span><br><span class="line">                    p.prev = tl;</span><br><span class="line">                    tl.next = p;</span><br><span class="line">                }</span><br><span class="line">                tl = p;</span><br><span class="line">            } while ((e = e.next) != null);</span><br><span class="line">            //链表转化成红黑树。</span><br><span class="line">            if ((tab[index] = hd) != null)</span><br><span class="line">                hd.treeify(tab);</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>当 tab == null 或者 (n = tab.length) &lt; MIN_TREEIFY_CAPACITY 时，执行扩容操作。所以，HashMap 在树化时如果发现桶的数量小于 64 的话，是执行扩容操作，这样做也是为了性能可以达到最优吧。</p><p>下面让我们先来学习一下红黑树的原理吧。</p><p><strong>一、红黑树是什么:</strong><br>1、是二叉查找树。<br>2、每个结点要么是红的，要么是黑的。<br>3、根结点是黑的。<br>4、每个叶结点（叶结点即指树尾端NIL指针或NULL结点）是黑的。<br>5、如果一个结点是红的，那么它的俩个儿子都是黑的。<br>6、对于任一结点而言，其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。  </p><p><strong>二、红黑树的构建规则:</strong><br>1、所有插入的点默认是红色。<br>2、按照二叉查找树的规则插入。<br>3、如果插入的是根结点，因为原树是空树，直接把此结点涂为黑色。<br>4、如果插入的结点的父结点是黑色，此时什么也不做。<br>5、按照不同情况，执行三种变换规则。</p><ul><li>变颜色：当前节点的父节点是红色，且父亲的兄弟节点也是红色，则将父亲节点和父亲的兄弟节点变成黑色，同时爷爷节点变成红色。最后，把当前节点设置到爷爷节点。</li><li>左旋：当前节点的父亲节点是红色，且父亲节点的兄弟节点是黑色且当前的节点是右子树的时候，则以当前节点的父亲节点为轴左旋。最后，把当前节点设置到当前节点的父节点。</li><li>右旋：当前节点的父亲节点是红色，且父亲节点的兄弟节点是黑色且当前的节点是左子树的时候，则以当前节点的爷爷节点为轴右旋，同时父亲节点变成黑色，爷爷节点变成红色。最后，把当前节点设置到轴节点。</li></ul><p><a href="https://www.cnblogs.com/cangqinglang/p/12455433.html">参考文章&gt;&gt;</a>（PS：文章中的删除操作没弄懂）<br><a href="https://www.bilibili.com/video/BV1tE411f7tP?from=search&amp;seid=4151296973908698746">参考视频&gt;&gt;</a></p><p>看完上面链接中的文章和视频，对于红黑树的理解也就差不多。至于Hashmap中关于红黑树的操作，笔者没有深入代码研究，这里难度还是很大的，以后再来复盘。</p><p>接下来，我们看下JDK 1.8 中 HashMap 是如何扩容的，它的扩容操作是在 resize 方法中进行的。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line">final Node&lt;K,V&gt;[] resize() {</span><br><span class="line">        Node&lt;K,V&gt;[] oldTab = table;</span><br><span class="line">        int oldCap = (oldTab == null) ? 0 : oldTab.length;</span><br><span class="line">        int oldThr = threshold;</span><br><span class="line">        int newCap, newThr = 0;</span><br><span class="line">        if (oldCap &gt; 0) {</span><br><span class="line">            if (oldCap &gt;= MAXIMUM_CAPACITY) {</span><br><span class="line">                threshold = Integer.MAX_VALUE;</span><br><span class="line">                return oldTab;</span><br><span class="line">            }</span><br><span class="line">            else if ((newCap = oldCap &lt;&lt; 1) &lt; MAXIMUM_CAPACITY &amp;&amp;</span><br><span class="line">                     oldCap &gt;= DEFAULT_INITIAL_CAPACITY)</span><br><span class="line">                newThr = oldThr &lt;&lt; 1; // double threshold</span><br><span class="line">        }</span><br><span class="line">        else if (oldThr &gt; 0) // initial capacity was placed in threshold</span><br><span class="line">            newCap = oldThr;</span><br><span class="line">        else {               // zero initial threshold signifies using defaults</span><br><span class="line">            newCap = DEFAULT_INITIAL_CAPACITY;</span><br><span class="line">            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);</span><br><span class="line">        }</span><br><span class="line">        if (newThr == 0) {</span><br><span class="line">            float ft = (float)newCap * loadFactor;</span><br><span class="line">            newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (float)MAXIMUM_CAPACITY ?</span><br><span class="line">                      (int)ft : Integer.MAX_VALUE);</span><br><span class="line">        }</span><br><span class="line">        threshold = newThr;</span><br><span class="line">        //上面的代码是初始化新哈希表对应的阈值（newThr）和容量（newCap）。</span><br><span class="line">        @SuppressWarnings({"rawtypes","unchecked"})</span><br><span class="line">        Node&lt;K,V&gt;[] newTab = (Node&lt;K,V&gt;[])new Node[newCap];</span><br><span class="line">        table = newTab;</span><br><span class="line">        if (oldTab != null) {</span><br><span class="line">            for (int j = 0; j &lt; oldCap; ++j) {</span><br><span class="line">                Node&lt;K,V&gt; e;</span><br><span class="line">                if ((e = oldTab[j]) != null) {</span><br><span class="line">                    oldTab[j] = null;</span><br><span class="line">                    if (e.next == null) //该桶位置只有一个元素，则直接赋值到新数组相应位置。</span><br><span class="line">                        newTab[e.hash &amp; (newCap - 1)] = e;</span><br><span class="line">                    else if (e instanceof TreeNode) </span><br><span class="line">                        //该桶位置是树结构时，则拆分到新数组上。</span><br><span class="line">                        ((TreeNode&lt;K,V&gt;)e).split(this, newTab, j, oldCap);</span><br><span class="line">                    else { </span><br><span class="line">                        //该桶位置是链表结构时，复制到新数组上维持顺序。</span><br><span class="line">                        Node&lt;K,V&gt; loHead = null, loTail = null;</span><br><span class="line">                        Node&lt;K,V&gt; hiHead = null, hiTail = null;</span><br><span class="line">                        Node&lt;K,V&gt; next;</span><br><span class="line">                        do {</span><br><span class="line">                            next = e.next;</span><br><span class="line">                            if ((e.hash &amp; oldCap) == 0) {</span><br><span class="line">                                if (loTail == null)</span><br><span class="line">                                    loHead = e;</span><br><span class="line">                                else</span><br><span class="line">                                    loTail.next = e;</span><br><span class="line">                                loTail = e;</span><br><span class="line">                            }</span><br><span class="line">                            else {</span><br><span class="line">                                if (hiTail == null)</span><br><span class="line">                                    hiHead = e;</span><br><span class="line">                                else</span><br><span class="line">                                    hiTail.next = e;</span><br><span class="line">                                hiTail = e;</span><br><span class="line">                            }</span><br><span class="line">                        } while ((e = next) != null);</span><br><span class="line">                        if (loTail != null) {</span><br><span class="line">                            loTail.next = null;</span><br><span class="line">                            newTab[j] = loHead;</span><br><span class="line">                        }</span><br><span class="line">                        if (hiTail != null) {</span><br><span class="line">                            hiTail.next = null;</span><br><span class="line">                            newTab[j + oldCap] = hiHead;</span><br><span class="line">                        }</span><br><span class="line">                    }</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        return newTab;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>对于桶节点是树或者链表的情况，Hashmap 转移到新数组上的策略都是将原结构拆分成两个链表，然后分别加入到新数组的对应桶上，以此来最大限度的让元素分散。对于是红黑树的情况，我们来看下 split 方法做了什么。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">final void split(HashMap&lt;K,V&gt; map, Node&lt;K,V&gt;[] tab, int index, int bit) {</span><br><span class="line">            TreeNode&lt;K,V&gt; b = this;</span><br><span class="line">            // Relink into lo and hi lists, preserving order</span><br><span class="line">            TreeNode&lt;K,V&gt; loHead = null, loTail = null;</span><br><span class="line">            TreeNode&lt;K,V&gt; hiHead = null, hiTail = null;</span><br><span class="line">            int lc = 0, hc = 0;</span><br><span class="line">            for (TreeNode&lt;K,V&gt; e = b, next; e != null; e = next) {</span><br><span class="line">                next = (TreeNode&lt;K,V&gt;)e.next;</span><br><span class="line">                e.next = null;</span><br><span class="line">                if ((e.hash &amp; bit) == 0) {</span><br><span class="line">                    if ((e.prev = loTail) == null)</span><br><span class="line">                        loHead = e;</span><br><span class="line">                    else</span><br><span class="line">                        loTail.next = e;</span><br><span class="line">                    loTail = e;</span><br><span class="line">                    ++lc;</span><br><span class="line">                }</span><br><span class="line">                else {</span><br><span class="line">                    if ((e.prev = hiTail) == null)</span><br><span class="line">                        hiHead = e;</span><br><span class="line">                    else</span><br><span class="line">                        hiTail.next = e;</span><br><span class="line">                    hiTail = e;</span><br><span class="line">                    ++hc;</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line"></span><br><span class="line">            if (loHead != null) {</span><br><span class="line">                if (lc &lt;= UNTREEIFY_THRESHOLD)</span><br><span class="line">                    tab[index] = loHead.untreeify(map);</span><br><span class="line">                else {</span><br><span class="line">                    tab[index] = loHead;</span><br><span class="line">                    if (hiHead != null) // (else is already treeified)</span><br><span class="line">                        loHead.treeify(tab);</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">            if (hiHead != null) {</span><br><span class="line">                if (hc &lt;= UNTREEIFY_THRESHOLD)</span><br><span class="line">                    tab[index + bit] = hiHead.untreeify(map);</span><br><span class="line">                else {</span><br><span class="line">                    tab[index + bit] = hiHead;</span><br><span class="line">                    if (loHead != null)</span><br><span class="line">                        hiHead.treeify(tab);</span><br><span class="line">                }</span><br><span class="line">            }</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>首先，将树结构分成两个双向链表，之后分别对他们进行树化或者链表化。如果长度&lt;=6，就将树转变成单向链表，否则调用 treeify 树化。</p><p>除了扩容时，树可能会转化为链表外，在元素删除后，树也有可能转化为链表。具体什么情况下会转化成链表，笔者看代码没有搞懂，不过从代码中注释可以得知当树的元素足够小时就会退化成链表，这里我只粘贴出部分代码。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">final void removeTreeNode(HashMap&lt;K,V&gt; map, Node&lt;K,V&gt;[] tab,</span><br><span class="line">                                  boolean movable) {</span><br><span class="line">            int n;</span><br><span class="line">            if (tab == null || (n = tab.length) == 0)</span><br><span class="line">                return;</span><br><span class="line">            int index = (n - 1) &amp; hash;</span><br><span class="line">            TreeNode&lt;K,V&gt; first = (TreeNode&lt;K,V&gt;)tab[index], root = first, rl;</span><br><span class="line">            TreeNode&lt;K,V&gt; succ = (TreeNode&lt;K,V&gt;)next, pred = prev;</span><br><span class="line">            if (pred == null)</span><br><span class="line">                tab[index] = first = succ;</span><br><span class="line">            else</span><br><span class="line">                pred.next = succ;</span><br><span class="line">            if (succ != null)</span><br><span class="line">                succ.prev = pred;</span><br><span class="line">            if (first == null)</span><br><span class="line">                return;</span><br><span class="line">            if (root.parent != null)</span><br><span class="line">                root = root.root();</span><br><span class="line">            if (root == null</span><br><span class="line">                || (movable</span><br><span class="line">                    &amp;&amp; (root.right == null</span><br><span class="line">                        || (rl = root.left) == null</span><br><span class="line">                        || rl.left == null))) {</span><br><span class="line">                tab[index] = first.untreeify(map);  // too small</span><br><span class="line">                return;</span><br><span class="line">            }</span><br><span class="line">            ///////省略之后的代码</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>关键代码就是<code> tab[index] = first.untreeify(map)</code>这一句了，这个 too smal 到底如何量化，笔者代码没看懂，以后有精力再来复盘吧。</p><h1 id="5-总结"><a href="#5-总结" class="headerlink" title="5 总结"></a>5 总结</h1><p>一、JDK 1.7 的哈希表以数组+链表结构存储键值对，JDK 1.8 的哈希表以数组+链表+树结构存储键值对。</p><p>二、JDK 1.7 和 1.8 的哈希表扩容时都是扩大到原来的 2 倍，同时阈值也到达原来的 2 倍。当发生扩容元素转移时，JDK 1.7 会通过根据最新的 hashSeed 重新计算 hash 值来确定元素在新的数组中的位置，JDK 1.8 则是通过固定的 hash 值和原来哈希表容量发生与操作来确定元素决定新数组中的位置。两个版本的HashMap 发生元素转移后在新数组中的位置都有两种：原来位置或者原来位置+原来数组容量，从性能上考虑，1.8 版本的 Hashmap 省去了重 hash 计算，性能更高。</p><p>三、JDK1.7中rehash的时候，旧链表迁移新链表的时候，如果在新表的数组索引位置相同，则链表元素会倒置，JDK1.8 不会倒置。</p><p>上面第二条和第三条参考了<a href="https://blog.csdn.net/qq32933432/article/details/86668385">这篇文章</a>，笔者没有仔细研读完，只是结合自己的理解大概看了下思路。</p><p>四、JDK 1.8 对加载因子的作用了做了具体的说明，笔者理解的意思是如果加载因子为 0.75 ,那么链表长度达到 8 的概率是最小的，这样能够减少树化的可能，毕竟树化是有计算和内存损耗的。<a href="https://www.jianshu.com/p/dff8f4641814">思路来源&gt;&gt;</a></p><p>五、JDK 1.7 Hashmap的扩容需要满足两个条件：当前数据存储的数量（即size()）大小必须大于等于阈值；当前加入的数据是否发生了hash冲突。</p><p>六、JDK 1.8 Hashmap的扩容只需满足 1 个条件：当前数据存储的数量（即size()）大小必须大于等于阈值。</p><p>七、JDK 1.8 以后哈希表的 添加、删除、查找、扩容方法都增加了一种节点为 TreeNode 的情况：<br>1、添加时，当桶中链表个数超过 8 并且哈希表容量大于等于 64 时会转换成红黑树；<br>2、删除、扩容时，如果桶中结构为红黑树，并且树中元素个数太少的话，会进行修剪或者直接还原成链表结构。扩容时，桶中树元素小于等于 6 时，会还原成链表。<br>3、查找时即使哈希函数不优，大量元素集中在一个桶中，由于有红黑树结构，性能也不会差。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://juejin.im/post/6844903588179755021#heading-12">搞懂 Java HashMap 源码</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（四）：LinkedList</title>
    <link href="http://example.com/p/5e058738/"/>
    <id>http://example.com/p/5e058738/</id>
    <published>2019-10-04T08:39:52.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>在开始研究 LinkedList 之前，强烈建议先阅读廖雪峰老师的下面三篇文章:<br>1、<a href="https://www.liaoxuefeng.com/wiki/1252599548343744/1265121791832960">使用Queue</a><br>2、<a href="https://www.liaoxuefeng.com/wiki/1252599548343744/1265122668445536">使用Deque</a><br>3、<a href="https://www.liaoxuefeng.com/wiki/1252599548343744/1265121668997888">使用Stack</a></p><h1 id="1-初始化"><a href="#1-初始化" class="headerlink" title="1 初始化"></a>1 初始化</h1><p>LinkList 的属性很少，就三个。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">transient int size = 0;</span><br><span class="line"></span><br><span class="line">transient Node&lt;E&gt; first;</span><br><span class="line"></span><br><span class="line">transient Node&lt;E&gt; last;</span><br></pre></td></tr></tbody></table></figure><p>size 就是当前的元素个数，first、last分别指头尾元素。为什么要分别定义头尾节点呢，这个问题我们先放着，文章结尾有总结。</p><p>下面是它的构造方法。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public LinkedList() {</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public LinkedList(Collection&lt;? extends E&gt; c) {</span><br><span class="line">        this();</span><br><span class="line">        addAll(c);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>无参数的方法什么也没做，有参数的调用了addAll方法，并最终调用了addAll(int index, Collection&lt;? extends E&gt; c) ，此时 index==0 。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">public boolean addAll(int index, Collection&lt;? extends E&gt; c) {</span><br><span class="line">        checkPositionIndex(index);</span><br><span class="line"></span><br><span class="line">        Object[] a = c.toArray();</span><br><span class="line">        int numNew = a.length;</span><br><span class="line">        if (numNew == 0)</span><br><span class="line">            return false;</span><br><span class="line"></span><br><span class="line">        Node&lt;E&gt; pred, succ;</span><br><span class="line">        if (index == size) {</span><br><span class="line">            succ = null;</span><br><span class="line">            pred = last;</span><br><span class="line">        } else {</span><br><span class="line">            succ = node(index);</span><br><span class="line">            pred = succ.prev;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        for (Object o : a) {</span><br><span class="line">            @SuppressWarnings("unchecked") E e = (E) o;</span><br><span class="line">            Node&lt;E&gt; newNode = new Node&lt;&gt;(pred, e, null);</span><br><span class="line">            if (pred == null)</span><br><span class="line">                first = newNode;</span><br><span class="line">            else</span><br><span class="line">                pred.next = newNode;</span><br><span class="line">            pred = newNode;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        if (succ == null) {</span><br><span class="line">            last = pred;</span><br><span class="line">        } else {</span><br><span class="line">            pred.next = succ;</span><br><span class="line">            succ.prev = pred;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        size += numNew;</span><br><span class="line">        modCount++;</span><br><span class="line">        return true;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从代码看出，这里就是一个依据提供的集合构造双向链表的过程。</p><h1 id="2-add-E-e"><a href="#2-add-E-e" class="headerlink" title="2 add(E e)"></a>2 add(E e)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">public boolean add(E e) {</span><br><span class="line">        linkLast(e);</span><br><span class="line">        return true;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">void linkLast(E e) {</span><br><span class="line">        final Node&lt;E&gt; l = last; //将尾元素保存。</span><br><span class="line">        final Node&lt;E&gt; newNode = new Node&lt;&gt;(l, e, null); //构建新节点。</span><br><span class="line">        last = newNode; //尾元素重新赋值为新节点。</span><br><span class="line">        if (l == null) //之前LinkList为空。</span><br><span class="line">            first = newNode; //头节点重新赋值。</span><br><span class="line">        else</span><br><span class="line">            l.next = newNode; //之前尾元素的 next 指向新元素。</span><br><span class="line">        size++;</span><br><span class="line">        modCount++;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>代码逻辑在注释中已经标出了。</p><p>对于 LinkedList 的其它增删操作，原理差不多，这里不再赘述。</p><h1 id="3-get-int-index"><a href="#3-get-int-index" class="headerlink" title="3 get(int index)"></a>3 get(int index)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">public E get(int index) {</span><br><span class="line">        checkElementIndex(index);//检查是否越界。</span><br><span class="line">        return node(index).item;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">Node&lt;E&gt; node(int index) {</span><br><span class="line">        // assert isElementIndex(index);</span><br><span class="line"></span><br><span class="line">        if (index &lt; (size &gt;&gt; 1)) {</span><br><span class="line">            Node&lt;E&gt; x = first;</span><br><span class="line">            for (int i = 0; i &lt; index; i++)</span><br><span class="line">                x = x.next;</span><br><span class="line">            return x;</span><br><span class="line">        } else {</span><br><span class="line">            Node&lt;E&gt; x = last;</span><br><span class="line">            for (int i = size - 1; i &gt; index; i--)</span><br><span class="line">                x = x.prev;</span><br><span class="line">            return x;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p><code>node(int index)</code>方法的作用是依据传入的 index 找到对应的 Node 元素。查找的逻辑很巧妙，通过判断 index 处在 LinkedList 的前部分还是后半部分来决定遍历的方向，这有助于减少遍历的次数。</p><h1 id="4-set-int-index-E-element"><a href="#4-set-int-index-E-element" class="headerlink" title="4 set(int index, E element)"></a>4 set(int index, E element)</h1><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public E set(int index, E element) {</span><br><span class="line">        checkElementIndex(index);</span><br><span class="line">        Node&lt;E&gt; x = node(index);</span><br><span class="line">        E oldVal = x.item;</span><br><span class="line">        x.item = element;</span><br><span class="line">        return oldVal;</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>代码结构很清晰，通过 node(int index) 找到对应的 node 替换即可。node(int index) 方法在上面讲过了，这里不再赘述。</p><h1 id="5-遍历"><a href="#5-遍历" class="headerlink" title="5 遍历"></a>5 遍历</h1><p>同ArrayList一样，LinkedList 也有两种遍历方式。</p><p>方式一:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">for (int i=0;i&lt;size;i++){</span><br><span class="line">            linkedList.get(i);</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>方式二：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">//LinkedList 的 iterator() 和 listIterator() 返回的对象是一样的。</span><br><span class="line">ListIterator listIterator = linkedList.listIterator();</span><br><span class="line">while (listIterator.hasNext()) {</span><br><span class="line">      listIterator.next();</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>对于方式二，listIterator 是一个 ListItr 对象，在讲 ArrayList 时我们已经介绍过该迭代器了，这里的逻辑和之前的一致，如：fast-fail机制、增删改操作逻辑等。只不过之前是通过外部类 Arraylist 操作数组，现在是通过外部类 LinkedList 操作双向链表。</p><h1 id="6-其它操作"><a href="#6-其它操作" class="headerlink" title="6 其它操作"></a>6 其它操作</h1><p>从 LinkedList 的继承关系图我们知道，它还实现了 Deque 接口，而 Deque 又继承了 Queue，所以可以用 LinkedList 实现队列\双向队列\栈的功能。</p><p>比如，我们要实现一个队列操作:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Queue&lt;String&gt; queue = new LinkedList&lt;&gt;();</span><br><span class="line">queue.xxx();</span><br></pre></td></tr></tbody></table></figure><p>要实现双向队列、栈的操作:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Deque&lt;String&gt; deque = new LinkedList&lt;&gt;();</span><br><span class="line">deque.xxx();</span><br></pre></td></tr></tbody></table></figure><p>注意，双向队列和栈的操作都是通过 Deque 来实现的，在使用时分别调用他们各自的方法即可（虽然可以混用，但还是建议按照规范来使用），比如当我们把Deque作为Stack使用时，注意只调用push()/pop()/peek()方法，不要调用addFirst()/removeFirst()/peekFirst()方法，这样代码更加清晰。</p><h1 id="7-总结"><a href="#7-总结" class="headerlink" title="7 总结"></a>7 总结</h1><p>再来看文章刚开始的问题：为什么要分别定义头尾节点呢？这里有两个原因：</p><p>一、查询时可依据 index 在链表中的位置来决定遍历的方向，知道了头尾节点，按照不同的方向遍历也就方便了，提升了查找效率。</p><p>二、为 Queue 和 Deque 的实现提供便利。</p><p>通过这篇文章的研究，我们知道了LinkedList是基于双向链表实现的，没有扩容操作。同时，Linkedlist 也为 Queue 和 Deque 的实现提供了便利。LinkedList 不适合查询、修改频繁的场景，它适合插入、删除操作频繁的场景（操作指定位置时，需要遍历），而 ArrayList 则适合查询、修改频繁，插入、删除不频繁的场景。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://juejin.im/post/6844903586095169549#comment">搞懂 Java LinkedList 源码</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
  <entry>
    <title>Java 集合（三）：线程安全的 List</title>
    <link href="http://example.com/p/f4555d82/"/>
    <id>http://example.com/p/f4555d82/</id>
    <published>2019-10-03T17:57:49.000Z</published>
    <updated>2026-09-20T15:23:29.487Z</updated>
    
    <content type="html"><![CDATA[<html><head></head><body><blockquote><p><strong>注意</strong><br>本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。</p></blockquote><span id="more"></span><p>紧接着上一篇文章，我们知道了 ArrayList 是线程不安全的，那 Java 又为我们提供了哪些方式实现线程安全的 List 呢?</p><p>有三种方式：<br>1、Vector。<br>2、Collections.synchronizedList(List l)。<br>3、CopyOnWriteArrayList。</p><h1 id="1-Vector"><a href="#1-Vector" class="headerlink" title="1 Vector"></a>1 Vector</h1><p>Vector 的代码思路几乎和ArrayList一致，不同点有如下：<br>1、每个操作方法都加了 synchronized 关键字保证同步。<br>2、调用 Vector 构造方法一定会新建数组。<br>3、当 capacityIncrement &lt;=0 时，扩容后的数组大小会是原来的2倍，否则就在原来的基础上增加 capacityIncrement 。</p><p>相同点如下：<br>1、增删改查、遍历逻辑一致。<br>2、遍历过程中操作 Vector 或 List 都会导致 ConcurrentModificationException 异常，即他们都有 fast-fail 机制。</p><p>特别地，对于多线程操作 Vector 时，特殊场景依然会导致错误发生，具体请参考文末的参考资料中 1.2 部分的内容。</p><h1 id="2-synchronizedList-方式"><a href="#2-synchronizedList-方式" class="headerlink" title="2 synchronizedList 方式"></a>2 synchronizedList 方式</h1><p>Collections 类下面的 synchronizedList 方法则是在原 List 外面包了一层 synchronized 而已，遇到高并发的场景，依然和 Vector 一样，性能很低。</p><h1 id="3-CopyOnWriteArrayList"><a href="#3-CopyOnWriteArrayList" class="headerlink" title="3 CopyOnWriteArrayList"></a>3 CopyOnWriteArrayList</h1><p>CopyOnWriteArrayList 内部维护着一个叫 array 的数组，它的定义如下:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/** The array, accessed only via getArray/setArray. */</span><br><span class="line">private transient volatile Object[] array;</span><br></pre></td></tr></tbody></table></figure><p>由注释可以知道，该变量只能通过 getArray/setArray 来取值赋值。</p><h2 id="3-1-add-E-e"><a href="#3-1-add-E-e" class="headerlink" title="3.1 add(E e)"></a>3.1 add(E e)</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">public boolean add(E e) {</span><br><span class="line">        final ReentrantLock lock = this.lock;</span><br><span class="line">        lock.lock();</span><br><span class="line">        try {</span><br><span class="line">            Object[] elements = getArray();</span><br><span class="line">            int len = elements.length;</span><br><span class="line">            Object[] newElements = Arrays.copyOf(elements, len + 1);</span><br><span class="line">            newElements[len] = e;</span><br><span class="line">            setArray(newElements);</span><br><span class="line">            return true;</span><br><span class="line">        } finally {</span><br><span class="line">            lock.unlock();</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>大致思路是：加锁-&gt;依据旧数组新建一个长度比旧数组多1的新数组-&gt;将值放在新数组最后-&gt;替换旧数组为新数组的值-&gt;释放锁。</p><p>不知道你发现没有，CopyOnWriteArrayList 的扩容策略和 ArrayList、Vector 不一样了，它是按需分配新数组的大小来实现的扩容。</p><p>CopyOnWriteArrayList 的增删改方法在逻辑上类似，这里不一一介绍了。</p><h2 id="3-2-get-int-index"><a href="#3-2-get-int-index" class="headerlink" title="3.2 get(int index)"></a>3.2 get(int index)</h2><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public E get(int index) {</span><br><span class="line">        return get(getArray(), index);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">private E get(Object[] a, int index) {</span><br><span class="line">        return (E) a[index];</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>获取方法没有加锁的逻辑，直接是从旧数组中获取。</p><h2 id="3-3-遍历"><a href="#3-3-遍历" class="headerlink" title="3.3 遍历"></a>3.3 遍历</h2><p>CopyOnWriteArrayList 的遍历方式也是分为两类。</p><p>第一类:</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">for (int i = 0; i &lt; size; i++) {</span><br><span class="line">            copyOnWriteArrayList.get(i);</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>第二类：</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Iterator iterator = copyOnWriteArrayList.iterator();</span><br><span class="line">        while (iterator.hasNext()){</span><br><span class="line">            iterator.next();</span><br><span class="line">        }</span><br></pre></td></tr></tbody></table></figure><p>在第二类写法中，我们在讲ArrayList时说过不能在循环过程中对List进行增删改操作，原因就不再重复了，不知道的可以回过头去看看。CopyOnWriteArrayList 是没有这个限制的，它是如何做到的呢？</p><p>我们来看下  copyOnWriteArrayList.iterator() 做了什么。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">public Iterator&lt;E&gt; iterator() {</span><br><span class="line">        return new COWIterator&lt;E&gt;(getArray(), 0);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">public ListIterator&lt;E&gt; listIterator() {</span><br><span class="line">        return new COWIterator&lt;E&gt;(getArray(), 0);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>iterator()和listIterator()是一样的，都生成了一个 COWIterator 对象，并传入了当前数组的值（注意是旧值）以及 initialCursor。</p><p>再来看看 COWIterator 类的实现。</p><figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line">static final class COWIterator&lt;E&gt; implements ListIterator&lt;E&gt; {</span><br><span class="line">        /** Snapshot of the array */</span><br><span class="line">        private final Object[] snapshot;</span><br><span class="line">        /** Index of element to be returned by subsequent call to next.  */</span><br><span class="line">        private int cursor;</span><br><span class="line"></span><br><span class="line">        private COWIterator(Object[] elements, int initialCursor) {</span><br><span class="line">            cursor = initialCursor;</span><br><span class="line">            snapshot = elements;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public boolean hasNext() {</span><br><span class="line">            return cursor &lt; snapshot.length;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public boolean hasPrevious() {</span><br><span class="line">            return cursor &gt; 0;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @SuppressWarnings("unchecked")</span><br><span class="line">        public E next() {</span><br><span class="line">            if (! hasNext())</span><br><span class="line">                throw new NoSuchElementException();</span><br><span class="line">            return (E) snapshot[cursor++];</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @SuppressWarnings("unchecked")</span><br><span class="line">        public E previous() {</span><br><span class="line">            if (! hasPrevious())</span><br><span class="line">                throw new NoSuchElementException();</span><br><span class="line">            return (E) snapshot[--cursor];</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public int nextIndex() {</span><br><span class="line">            return cursor;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        public int previousIndex() {</span><br><span class="line">            return cursor-1;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        /**</span><br><span class="line">         * Not supported. Always throws UnsupportedOperationException.</span><br><span class="line">         * @throws UnsupportedOperationException always; {@code remove}</span><br><span class="line">         *         is not supported by this iterator.</span><br><span class="line">         */</span><br><span class="line">        public void remove() {</span><br><span class="line">            throw new UnsupportedOperationException();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        /**</span><br><span class="line">         * Not supported. Always throws UnsupportedOperationException.</span><br><span class="line">         * @throws UnsupportedOperationException always; {@code set}</span><br><span class="line">         *         is not supported by this iterator.</span><br><span class="line">         */</span><br><span class="line">        public void set(E e) {</span><br><span class="line">            throw new UnsupportedOperationException();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        /**</span><br><span class="line">         * Not supported. Always throws UnsupportedOperationException.</span><br><span class="line">         * @throws UnsupportedOperationException always; {@code add}</span><br><span class="line">         *         is not supported by this iterator.</span><br><span class="line">         */</span><br><span class="line">        public void add(E e) {</span><br><span class="line">            throw new UnsupportedOperationException();</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void forEachRemaining(Consumer&lt;? super E&gt; action) {</span><br><span class="line">            Objects.requireNonNull(action);</span><br><span class="line">            Object[] elements = snapshot;</span><br><span class="line">            final int size = elements.length;</span><br><span class="line">            for (int i = cursor; i &lt; size; i++) {</span><br><span class="line">                @SuppressWarnings("unchecked") E e = (E) elements[i];</span><br><span class="line">                action.accept(e);</span><br><span class="line">            }</span><br><span class="line">            cursor = size;</span><br><span class="line">        }</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>从代码中可以看出，COWIterator不支持增删改，也没有fast-fail机制，只提供了遍历功能。当在遍历过程中修改了 CopyOnWriteArrayList ,对遍历没有任何影响。</p><h2 id="3-4-总结"><a href="#3-4-总结" class="headerlink" title="3.4 总结"></a>3.4 总结</h2><p>CopyOnWriteArrayList缺点：<br>1、内存占用：如果CopyOnWriteArrayList经常要增删改里面的数据，经常要执行add()、set()、remove()的话，那是比较耗费内存的。因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。<br>2、数据一致性：CopyOnWrite容器只能保证数据的最终一致性，不能保证数据的实时一致性。<br>从上面的例子也可以看出来，比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。<br>CopyOnWriteArrayList优点：<br>保证了并发时的可靠性。</p><h1 id="4-快速失败（fast-fail）和-安全失败（safe-fail"><a href="#4-快速失败（fast-fail）和-安全失败（safe-fail" class="headerlink" title="4 快速失败（fast-fail）和 安全失败（safe-fail)"></a>4 快速失败（fast-fail）和 安全失败（safe-fail)</h1><p><a href="https://blog.csdn.net/shipfei_csdn/article/details/81911991">参考链接&gt;&gt;</a></p><p>一：快速失败（fail—fast）</p><p>在用迭代器遍历一个集合对象时，如果遍历过程中对集合对象的内容进行了修改（增加、删除、修改），则会抛出Concurrent Modification Exception。</p><p>原理：迭代器在遍历时直接访问集合中的内容，并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化，就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前，都会检测modCount变量是否为expectedmodCount值，是的话就返回遍历；否则抛出异常，终止遍历。</p><p>注意：这里异常的抛出条件是检测到 modCount！=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值，则异常不会抛出。因此，不能依赖于这个异常是否抛出而进行并发操作的编程，这个异常只建议用于检测并发修改的bug。</p><p>场景：java.util包下的集合类都是快速失败的，不能在多线程下发生并发修改（迭代过程中被修改）。</p><p>二：安全失败（fail—safe）</p><p>采用安全失败机制的集合容器，在遍历时不是直接在集合内容上访问的，而是先复制原有集合内容，在拷贝的集合上进行遍历。</p><p>原理：由于迭代时是对原集合的拷贝进行遍历，所以在遍历过程中对原集合所作的修改并不能被迭代器检测到，所以不会触发Concurrent Modification Exception。</p><p>缺点：基于拷贝内容的优点是避免了Concurrent Modification Exception，但同样地，迭代器并不能访问到修改后的内容，即：迭代器遍历的是开始遍历那一刻拿到的集合拷贝，在遍历期间原集合发生的修改迭代器是不知道的。</p><p>场景：java.util.concurrent包下的容器都是安全失败，可以在多线程下并发使用，并发修改。</p><hr><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><a href="https://zhuanlan.zhihu.com/p/48784500">CopyOnWriteArrayList你都不知道，怎么拿offer？</a></p></body></html>]]></content>
    
    
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;br&gt;本系列文章是笔者在参考了大量优秀文章的基础上的理解，由于水平有限，难免会出现错误，望批评指正，同时我也会把自己最新的理解及时更新到文章中去。最后，我会在文中注明参考文章，特别感谢这些无私奉献的作者。&lt;/p&gt;
&lt;/blockquote&gt;&lt;/body&gt;&lt;/html&gt;</summary>
    
    
    
    <category term="Java基础" scheme="http://example.com/categories/Java%E5%9F%BA%E7%A1%80/"/>
    
    
    <category term="集合类" scheme="http://example.com/tags/%E9%9B%86%E5%90%88%E7%B1%BB/"/>
    
  </entry>
  
</feed>
