<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>左言右午的日常</title>
  
  <subtitle>会写一点程序;</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://zuoyanyouwu.com/"/>
  <updated>2018-02-22T07:51:13.000Z</updated>
  <id>http://zuoyanyouwu.com/</id>
  
  <author>
    <name>zuoyanyouwu</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>2017 - 总结</title>
    <link href="http://zuoyanyouwu.com/2018/02/my-2017/"/>
    <id>http://zuoyanyouwu.com/2018/02/my-2017/</id>
    <published>2018-02-14T05:14:21.000Z</published>
    <updated>2018-02-22T07:51:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>2017 年经历了太多，像过山车一样，工作、生活发生了很大变化。</p><h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>公司业务拓展，增加了几条产品线，我负责其中的「文件安全产品线」，主要针对云上对象存储的安全代理，承担的工作也比之前多，不只是研发管理，还要产品设计，好在有很棒的同事协助，产品做的也比较顺手。</p><p>11 月份公司因为某些原因难以维持，便离职加入了端点网络科技，从事供采产品线研发。不到三个月时间，学习到了很多后端的技术，希望今年可以弥补自己业务上的短板。</p><h3 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h3><p>年初认识了我老婆，并顺利领证，前几天也完美的举办了婚礼，非常感谢老婆的好脾气以及对我的包容和支持。</p><p>买房了，生活压力也大了，接下来希望今年可以顺利收房。</p><h3 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h3><p>技术方向主要放在了后端上，Netty 的应用及底层原理也更加了解，并根据 Netty 的特性完成了公司多个项目的性能优化(参见<a href="http://www.zuoyanyouwu.com/2017/10/netty-data-flow-optimize/" target="_blank" rel="noopener">时空域联合约束技术在 Netty 中的应用</a>)。</p><p>加入新公司后，学习了 SpringBoot、Dubbo、Docker、ElasticSearch、分布式技术的应用，丰富了后端技术，下一步需要更了解技术原理，同时拓展技术栈，学习 Kotlin 以及前端(如 Vue 等)。</p><p>Golang 也要继续学习，希望可以在项目中应用上。</p><p>「高性能MySQL 」继续阅读、学习。</p><p>断断续续写了几篇博客。</p><h3 id="2018-年计划"><a href="#2018-年计划" class="headerlink" title="2018 年计划"></a>2018 年计划</h3><p>加入新公司后，很少有时间去健身了，希望可以利用空余时间健身，保持身体健康。</p><p>多学习一些框架的设计理念和原理，技术上要有更多的收获，继续写博客。</p><p>住进新房。</p>]]></content>
    
    <summary type="html">
    
      总结下已经过去的 2017。
    
    </summary>
    
      <category term="Life" scheme="http://zuoyanyouwu.com/categories/Life/"/>
    
    
      <category term="Life" scheme="http://zuoyanyouwu.com/tags/Life/"/>
    
  </entry>
  
  <entry>
    <title>时空域联合约束技术在 Netty 中的应用</title>
    <link href="http://zuoyanyouwu.com/2017/10/netty-data-flow-optimize/"/>
    <id>http://zuoyanyouwu.com/2017/10/netty-data-flow-optimize/</id>
    <published>2017-10-26T12:24:56.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们有一款面向对象存储的安全网关服务，用于实时加密、解密文件数据流，对使用公有云安全存储文件起到很大的作用。</p><p>项目初期在构造 ChannelInitializer 时采用了常用的构造方式：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpResponseEncoder());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpRequestDecoder());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpContentCompressor());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpObjectAggregator(<span class="number">10485760</span>));</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> ChunkedWriteHandler());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 业务处理完毕后返回数据的 ChannelOutboundHandlerAdapter</span></span><br><span class="line">    pipeline.addLast(outbound);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 业务层处理请求的 ChannelInboundHandlerAdapter</span></span><br><span class="line">    pipeline.addLast(inbound);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式使用起来很简单，通过 HttpObjectAggregator 将请求数据包装成 <code>FullHttpRequest</code> 对象，使用时直接通过 <code>headers()</code> 和 <code>content()</code> 方法获取 header 和 body 内容。通过业务逻辑处理更新相应的 header 和 body 内容后转发至对应的云存储服务。接收响应后，HttpObjectAggregator 再次将响应数据包装成 <code>FullHttpResponse</code> 对象，业务逻辑处理完成后响应至 Client 端。</p><p>从上述描述可知，如果请求的数据内容很大，在构造 <code>FullHttpRequest</code> 或 <code>FullHttpResponse</code> 时会产生较大的内存消耗和延时：</p><ul><li><code>HttpObjectAggregator</code> 构造时必须指定所能处理的最大 content 值，因为 content 保存了从 socket 中读取到的所有字节数据，不指定该值势必会造成内存的暴增导致 OutOfMemory 异常</li><li>构造 <code>FullHttpRequest</code> 或 <code>FullHttpResponse</code> 是一个等待的过程，必须等待数据完全读取完毕才可以使用，延时增加导致并发量下降</li></ul><p>在这种情形下必然需要解决这个问题。</p><p>时空域联合约束技术充分利用 Netty 特性，在处理非结构化数据时，采用分段读取的方式，边读取、边处理、边转发，充分利用网络带宽，避免数据阻塞。</p><p>应用时空域技术后， ChannelInitializer  构造方式如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel socketChannel)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpResponseEncoder());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpRequestDecoder());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> HttpContentCompressor());</span><br><span class="line">    pipeline.addLast(<span class="keyword">new</span> ChunkedWriteHandler());</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 业务处理完毕后返回数据的 ChannelOutboundHandlerAdapter</span></span><br><span class="line">    pipeline.addLast(outbound);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 业务层处理请求的 ChannelInboundHandlerAdapter</span></span><br><span class="line">    pipeline.addLast(inbound);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对比发现少了向 pipeline 中添加 <code>HttpObjectAggregator</code>，数据处理流程变成了如下方式：</p><p><img src="/images/netty-data-flow.png" alt="netty-data-flow"></p><p><code>ChannelInboundHandlerAdapter</code> 处理请求时会首先接收到 <code>HttpRequest</code> ，接着是 <code>HttpContent</code>，最后是 <code>LastHttpContent</code>。</p><p><code>ChannelOutboundHandlerAdapter</code> 处理响应时首先接收到 <code>HttpResonse</code>，接着是 <code>HttpContent</code>，最后是 <code>LastHttpContent</code>。</p><p>从请求流程图可以看出，在通过 HttpObjectDecoder 从 socket 读取数据时，先读取 HTTP 头部分，并将之分发至业务层，接着分段读取 body 内容，并依次分发至业务层，业务层可以由此实时处理请求/响应数据，充分利用带宽。因为少了 <code>HttpObjectAggregator</code>  包装数据，流式数据分段为 KB 级别，极大的节省内存占用，单个请求的处理时间、内存占用降低，充分利用时间和空间，提高了吞吐量。</p><p>通过压测观察，应用该技术后，性能提高 3倍以上，吞吐量更多的取决于带宽能力。</p><p>欢迎拍砖。</p>]]></content>
    
    <summary type="html">
    
      Netty 在对象存储安全网关代理上的应用，采用时空域联合约束技术对性能进行优化，边读取、边处理、边转发，充分利用网络带宽，避免数据阻塞，性能优化上的新方法。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Netty" scheme="http://zuoyanyouwu.com/tags/Netty/"/>
    
      <category term="时空域" scheme="http://zuoyanyouwu.com/tags/%E6%97%B6%E7%A9%BA%E5%9F%9F/"/>
    
  </entry>
  
  <entry>
    <title>HDFS 读写过程分析</title>
    <link href="http://zuoyanyouwu.com/2017/10/hdfs-write-read-flow/"/>
    <id>http://zuoyanyouwu.com/2017/10/hdfs-write-read-flow/</id>
    <published>2017-10-23T06:42:19.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>HDFS 是一个分布式文件系统，它和现有的分布式文件系统有很多共同点。但同时，它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统，适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问，非常适合大规模数据集上的应用。HDFS 上读写文件的过程与我们平时使用的单机文件系统非常不同，本文就介绍 HDFS 上读写文件过程。</p><h3 id="一、写文件过程"><a href="#一、写文件过程" class="headerlink" title="一、写文件过程"></a>一、写文件过程</h3><p>在 HDFS 文件系统上创建并写一个文件，流程如下图所示：</p><p><img src="/images/hdfs-write-flow.png" alt="hdfs-write-flow"></p><p>具体过程描述如下：</p><ol><li>Client 调用 DistributedFileSystem 对象的 create 方法，创建一个文件输出流（FSDataOutputStream）对象</li><li>通过 DistributedFileSystem 对象与 Hadoop 集群的 NameNode 进行一次RPC远程调用，在HDFS的 Namespace 中创建一个文件条目（Entry），该条目没有任何的 Block</li><li>通过 FSDataOutputStream 对象，向 DataNode 写入数据，数据首先被写入 FSDataOutputStream 对象内部的 Buffer 中，然后数据被分割成一个个 Packet 数据包</li><li>以Packet最小单位，基于 Socket 连接发送到按特定算法选择的 HDFS 集群中一组 DataNode（正常是3个，可能大于等于1）中的一个节点上，在这组 DataNode 组成的 Pipeline 上依次传输 Packet</li><li>这组 DataNode 组成的 Pipeline 反方向上，发送 ack ，最终由 Pipeline 中第一个 DataNode 节点将 Pipeline  ack 发送给 Client</li><li>完成向文件写入数据，Client 在文件输出流（FSDataOutputStream）对象上调用 close 方法，关闭流</li><li>调用 DistributedFileSystem 对象的 complete 方法，通知 NameNode 文件写入成功</li></ol><h4 id="Packet结构与定义"><a href="#Packet结构与定义" class="headerlink" title="Packet结构与定义"></a>Packet结构与定义</h4><p>Client 向 HDFS 写数据，数据会被组装成 Packet，然后发送到 Datanode 节点。Packet 分为两类，一类是实际数据包，另一类是 heatbeat 包。</p><p>packet 相关参数如下：</p><table><thead><tr><th><strong>参数名称</strong></th><th><strong>参数值</strong></th><th><strong>参数含义</strong></th></tr></thead><tbody><tr><td>chunkSize</td><td>512+4=516</td><td>每个chunk的字节数（数据+校验和）</td></tr><tr><td>csize</td><td>512</td><td>每个chunk数据的字节数</td></tr><tr><td>psize</td><td>64*1024</td><td>每个packet的最大字节数（不包含header）</td></tr><tr><td>DataNode.PKT_HEADER_LEN</td><td>21</td><td>每个packet的header的字节数</td></tr><tr><td>chunksPerPacket</td><td>127</td><td>组成每个packet的chunk的个数</td></tr><tr><td>packetSize</td><td>25+516*127=65557</td><td>每个packet的字节数（一个header+一组chunk）</td></tr></tbody></table><p>Packet数据包的组成结构，如图所示：</p><p><img src="/images/hdfs-write-packet-structure.png" alt="hdfs-packet-structure"></p><h3 id="二、读文件过程"><a href="#二、读文件过程" class="headerlink" title="二、读文件过程"></a>二、读文件过程</h3><p>在 HDFS 文件系统上读取一个文件，流程如下图所示：</p><p><img src="/images/hdfs-read-flow.png" alt="hdfs-read-flow"></p><ol><li>Client 调用 DistributedFileSystem 对象的 open 方法，创建一个文件输入流（FSDataInputStream）对象</li><li>通过 DistributedFileSystem 对象与 Hadoop 集群的 NameNode 进行一次RPC远程调用，确定需要打开的文件一批数据块的存储位置</li><li>通过 FSDataInputStream 对象，读取距离客户端最近的 datanode，该 datanode 读取完成后接着寻找下一个 datanode 节点并读取数据</li><li>继续询问 namenode 下一批数据块的 datanode 的位置</li><li>读取完成后调用 FSDataInputStream 的 close 方法结束整个流程</li></ol>]]></content>
    
    <summary type="html">
    
      HDFS 是一个分布式文件系统，它和现有的分布式文件系统有很多共同点。但同时，它和其他的分布式文件系统的区别也是很明显的。HDFS是一个高度容错性的系统，适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问，非常适合大规模数据集上的应用。HDFS 上读写文件的过程与我们平时使用的单机文件系统非常不同，本文就介绍 HDFS 上读写文件过程。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="HDFS" scheme="http://zuoyanyouwu.com/tags/HDFS/"/>
    
      <category term="Hadoop" scheme="http://zuoyanyouwu.com/tags/Hadoop/"/>
    
  </entry>
  
  <entry>
    <title>fastDFS 部署</title>
    <link href="http://zuoyanyouwu.com/2017/08/fastdfs-deployment/"/>
    <id>http://zuoyanyouwu.com/2017/08/fastdfs-deployment/</id>
    <published>2017-08-14T09:30:09.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>fastDFS 是一个轻量级的开源分布式文件系统，主要解决了大容量的文件存储和高并发访问的问题，文件存取时实现了负载均衡，同时支持存储服务器在线扩容。<br>fastDFS 是基于源代码本地编译、安装、部署的，所以在使用前首先要部署。网上也有不少 fastDFS 的部署教程，不过大部分都不能正确操作完毕或者最后不能正确使用，本文经过在 CentOS 7 上实测，可正常使用。</p><h3 id="1-安装-libfastcommon"><a href="#1-安装-libfastcommon" class="headerlink" title="1. 安装 libfastcommon"></a>1. 安装 libfastcommon</h3><p>下载：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O libfastcommon-1.0.7.tar.gz https://github.com/happyfish100/libfastcommon/archive/V1.0.7.tar.gz</span><br></pre></td></tr></table></figure><p>解压：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf libfastcommon-1.0.7.tar.gz</span><br></pre></td></tr></table></figure><p>编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> libfastcommon-1.0.7</span><br><span class="line">sudo ./make.sh</span><br></pre></td></tr></table></figure><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ./make.sh install</span><br></pre></td></tr></table></figure><h3 id="2-安装-FastDFS"><a href="#2-安装-FastDFS" class="headerlink" title="2. 安装 FastDFS"></a>2. 安装 FastDFS</h3><p>下载：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -O fastdfs-5.05.tar.gz https://github.com/happyfish100/fastdfs/archive/V5.05.tar.gz</span><br></pre></td></tr></table></figure><p>解压：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf fastdfs-5.05.tar.gz</span><br></pre></td></tr></table></figure><p>编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> fastdfs-5.05</span><br><span class="line">sudo ./make.sh</span><br></pre></td></tr></table></figure><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo ./make.sh install</span><br></pre></td></tr></table></figure><h3 id="3-配置-Tracker-服务"><a href="#3-配置-Tracker-服务" class="headerlink" title="3. 配置 Tracker 服务"></a>3. 配置 Tracker 服务</h3><p>复制 tracker.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /etc/fdfs/tracker.conf.sample /home/deployer/fastdfs/tracker.conf</span><br></pre></td></tr></table></figure><p>配置 tracker.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># the base path to store data and log files</span></span><br><span class="line">base_path=/home/deployer/fastdfs</span><br></pre></td></tr></table></figure><p>启动 tracker 服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># start fdfs_trackerd</span></span><br><span class="line">fdfs_trackerd /home/deployer/fastdfs/tracker.conf start</span><br><span class="line"><span class="comment"># restart fdfs_trackerd</span></span><br><span class="line">fdfs_trackerd /home/deployer/fastdfs/tracker.conf restart</span><br><span class="line"><span class="comment"># stop fdfs_trackerd</span></span><br><span class="line">fdfs_trackerd /home/deployer/fastdfs/tracker.conf stop</span><br></pre></td></tr></table></figure><p>检查服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstat -unltp|grep fdfs</span><br></pre></td></tr></table></figure><h3 id="4-配置-Storage-服务"><a href="#4-配置-Storage-服务" class="headerlink" title="4. 配置  Storage 服务"></a>4. 配置  Storage 服务</h3><p>复制 storage.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /etc/fdfs/storage.conf.sample /home/deployer/fastdfs/storage.conf</span><br></pre></td></tr></table></figure><p>配置 storage.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># the base path to store data and log files</span></span><br><span class="line">base_path=/home/deployer/fastdfs/storage</span><br><span class="line"></span><br><span class="line"><span class="comment"># store_path#, based 0, if store_path0 not exists, it's value is base_path</span></span><br><span class="line"><span class="comment"># the paths must be exist</span></span><br><span class="line">store_path0=/home/deployer/fastdfs/storage</span><br><span class="line"></span><br><span class="line"><span class="comment"># tracker_server can ocur more than once, and tracker_server format is</span></span><br><span class="line"><span class="comment">#  "host:port", host can be hostname or ip address</span></span><br><span class="line">tracker_server=192.168.10.204:22122</span><br></pre></td></tr></table></figure><p>启动 storage 服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># start fdfs_storaged</span></span><br><span class="line">fdfs_storaged /home/deployer/fastdfs/storage.conf start</span><br><span class="line"><span class="comment"># restart fdfs_storaged</span></span><br><span class="line">fdfs_storaged /home/deployer/fastdfs/storage.conf restart</span><br><span class="line"><span class="comment"># stop fdfs_storaged</span></span><br><span class="line">fdfs_storaged /home/deployer/fastdfs/storage.conf stop</span><br></pre></td></tr></table></figure><p>检查服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">netstat -unltp|grep fdfs</span><br></pre></td></tr></table></figure><p>启动成功后可以在 <code>/home/deployer/fastdfs/storage</code> 目录下看到众多文件夹。</p><p>检查连通性：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fdfs_monitor /home/deployer/fastdfs/storage.conf</span><br></pre></td></tr></table></figure><h3 id="5-测试"><a href="#5-测试" class="headerlink" title="5.测试"></a>5.测试</h3><p>复制 client.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp /etc/fdfs/client.conf.sample /home/deployer/fastdfs/client.conf</span><br></pre></td></tr></table></figure><p>配置 client.conf :</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># the base path to store log files</span></span><br><span class="line">base_path=/home/deployer/server/fastdfs/data</span><br><span class="line"></span><br><span class="line"><span class="comment"># tracker_server can ocur more than once, and tracker_server format is</span></span><br><span class="line"><span class="comment">#  "host:port", host can be hostname or ip address</span></span><br><span class="line">tracker_server=192.168.10.204:22122</span><br></pre></td></tr></table></figure><p>上传文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fdfs_test /home/deployer/fastdfs/client.conf upload /home/deployer/hello.txt</span><br></pre></td></tr></table></figure><p>上传文件会输出类似如下内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">This is FastDFS client test program v5.05</span><br><span class="line"></span><br><span class="line">Copyright (C) 2008, Happy Fish / YuQing</span><br><span class="line"></span><br><span class="line">FastDFS may be copied only under the terms of the GNU General</span><br><span class="line">Public License V3, which may be found in the FastDFS source kit.</span><br><span class="line">Please visit the FastDFS Home Page http://www.csource.org/</span><br><span class="line">for more detail.</span><br><span class="line"></span><br><span class="line">[2017-08-01 17:12:09] DEBUG - base_path=/home/deployer/server/fastdfs/data, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0</span><br><span class="line"></span><br><span class="line">tracker_query_storage_store_list_without_group:</span><br><span class="line">    server 1. group_name=, ip_addr=192.168.10.204, port=23000</span><br><span class="line"></span><br><span class="line">group_name=group1, ip_addr=192.168.10.204, port=23000</span><br><span class="line">storage_upload_by_filename</span><br><span class="line">group_name=group1, remote_filename=M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542.txt</span><br><span class="line">source ip address: 192.168.10.204</span><br><span class="line">file timestamp=2017-08-01 17:12:09</span><br><span class="line">file size=19</span><br><span class="line">file crc32=1777012043</span><br><span class="line">example file url: http://192.168.10.204/group1/M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542.txt</span><br><span class="line">storage_upload_slave_by_filename</span><br><span class="line">group_name=group1, remote_filename=M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542_big.txt</span><br><span class="line">source ip address: 192.168.10.204</span><br><span class="line">file timestamp=2017-08-01 17:12:09</span><br><span class="line">file size=19</span><br><span class="line">file crc32=1777012043</span><br><span class="line">example file url: http://192.168.10.204/group1/M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542_big.txt</span><br></pre></td></tr></table></figure><p>group_name=group1 表示上传文件到 group1 组下</p><p>remote_filename=M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542.txt 表示保存在 fdfs server 中的文件名</p><p>下载文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fdfs_test client.conf download group1 M00/00/00/wKgKzFmARemAXLmOAAAAE2nrDUs542_big.txt</span><br></pre></td></tr></table></figure><p>下载文件会输出类似如下内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">This is FastDFS client test program v5.05</span><br><span class="line"></span><br><span class="line">Copyright (C) 2008, Happy Fish / YuQing</span><br><span class="line"></span><br><span class="line">FastDFS may be copied only under the terms of the GNU General</span><br><span class="line">Public License V3, which may be found in the FastDFS source kit.</span><br><span class="line">Please visit the FastDFS Home Page http://www.csource.org/</span><br><span class="line">for more detail.</span><br><span class="line"></span><br><span class="line">[2017-08-01 17:59:27] DEBUG - base_path=/home/deployer/server/fastdfs/data, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0</span><br><span class="line"></span><br><span class="line">storage=192.168.10.123:23000</span><br><span class="line">download file success, file size=19, file save to wKgKzFmARemAXLmOAAAAE2nrDUs542_big.txt</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      fastDFS 是一个轻量级的开源分布式文件系统，主要解决了大容量的文件存储和高并发访问的问题，文件存取时实现了负载均衡，同时支持存储服务器在线扩容，本文将会演示 fastDFS 的部署流程。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="fastDFS" scheme="http://zuoyanyouwu.com/tags/fastDFS/"/>
    
  </entry>
  
  <entry>
    <title>fastDFS 介绍</title>
    <link href="http://zuoyanyouwu.com/2017/08/fastdfs-introduct/"/>
    <id>http://zuoyanyouwu.com/2017/08/fastdfs-introduct/</id>
    <published>2017-08-11T06:22:09.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>FastDFS 是一个轻量级的开源分布式文件系统，主要解决了大容量的文件存储和高并发访问的问题，文件存取时实现了负载均衡，同时支持存储服务器在线扩容。</p><h3 id="1-系统架构图"><a href="#1-系统架构图" class="headerlink" title="1. 系统架构图"></a>1. 系统架构图</h3><p><img src="/images/fastdfs-introduct-preview-0.png" alt="fastDFS_Introduct_0"></p><h4 id="Storage-Server"><a href="#Storage-Server" class="headerlink" title="Storage Server"></a>Storage Server</h4><p>Storage server（后简称storage）以组（卷，group或volume）为单位组织，一个 group 内包含多台 storage 机器，数据互为备份，存储空间以group内容量最小的storage为准，所以建议group内的多个storage尽量配置相同，以免造成存储空间的浪费。</p><h4 id="Tracker-Server"><a href="#Tracker-Server" class="headerlink" title="Tracker Server"></a>Tracker Server</h4><p>Tracker 是 fastDFS 的协调者，负责管理所有的 Storage Server 和 group，每个 Storage Server 在启动后会连接 Tracker，告知自己所属的 group 等信息，并保持周期性的心跳，Tracker 根据 Storage 的心跳信息，建立 group ==&gt; [Storage Server List] 的映射表。</p><h4 id="Client"><a href="#Client" class="headerlink" title="Client"></a>Client</h4><p>客户端，作为业务请求的发起方，通过专有接口，使用 TCP/IP 协议与跟踪器服务器或存储节点进行数据交互。<br>协议内容如下:</p><blockquote><ul><li>协议包由两部分组成：header和body</li><li>header共10字节，格式如下：<ul><li>8 bytes body length</li><li>1byte command</li><li>1byte status</li></ul></li><li>body数据包格式由取决于具体的命令，body可以为空</li></ul></blockquote><h3 id="2-上传流程"><a href="#2-上传流程" class="headerlink" title="2. 上传流程"></a>2. 上传流程</h3><p><img src="/images/fastdfs-introduct-preview-1.png" alt="fastDFS_Introduct_1"></p><p>• 1. client 询问 tracker 上传到的 storage，不需要附加参数；<br>• 2. tracker 返回一台可用的 storage；<br>• 3. client 直接和 storage 通讯完成文件上传。</p><h3 id="3-下载流程"><a href="#3-下载流程" class="headerlink" title="3. 下载流程"></a>3. 下载流程</h3><p><img src="/images/fastdfs-introduct-preview-2.png" alt="fastDFS_Introduct_2"></p><p>• 1. client 询问 tracker 下载文件的 storage，参数为文件标识（组名和文件名）；<br>• 2. tracker 返回一台可用的 storage；<br>• 3. client 直接和 storage 通讯完成文件下载。</p><p><strong>参考：</strong></p><blockquote><ol><li><a href="https://github.com/happyfish100/fastdfs" target="_blank" rel="noopener">fastDFS GitHub repo</a>.</li></ol></blockquote>]]></content>
    
    <summary type="html">
    
      fastDFS 是一个轻量级的开源分布式文件系统，主要解决了大容量的文件存储和高并发访问的问题，文件存取时实现了负载均衡，同时支持存储服务器在线扩容，本文对 fastDFS 做一些简单介绍。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="fastDFS" scheme="http://zuoyanyouwu.com/tags/fastDFS/"/>
    
  </entry>
  
  <entry>
    <title>HTTP 协议简介</title>
    <link href="http://zuoyanyouwu.com/2017/04/intro-about-http-protocol/"/>
    <id>http://zuoyanyouwu.com/2017/04/intro-about-http-protocol/</id>
    <published>2017-04-30T13:29:41.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>HTTP 是超文本传输协议 (HyperText Transfer Protocol) 的缩写，是互联网上应用最为广泛的一种网络协议。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifies, URI) 来标识。</p><p>HTTP 构建与 TCP/IP 协议之上，默认端口是 80。</p><h3 id="请求报文"><a href="#请求报文" class="headerlink" title="请求报文"></a>请求报文</h3><p>HTTP 协议规范把 HTTP 请求分为：请求行、请求头、空行、请求消息体。类似于下面这样：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;method&gt; &lt;request-URL&gt; &lt;version&gt;</span><br><span class="line">&lt;headers&gt;</span><br><span class="line"></span><br><span class="line">&lt;entity-body&gt;</span><br></pre></td></tr></table></figure><p>其中，请求头和请求行必须以 <code>&lt;CR&gt;&lt;CF&gt;</code> 作为结尾。空行内必须只有 <code>&lt;CR&gt;&lt;CF&gt;</code> 而无其他空格。在 HTTP/1.1 协议中，所有的请求头，除 Host 外，都是可选的。</p><p>以下是访问 <a href="https://www.locket.tt" target="_blank" rel="noopener">https://www.locket.tt</a> 的请求报文：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><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">GET</span> <span class="string">/</span> HTTP/1.1</span><br><span class="line"><span class="attribute">Host</span>: www.locket.tt</span><br><span class="line"><span class="attribute">Connection</span>: keep-alive</span><br><span class="line"><span class="attribute">Cache-Control</span>: max-age=0</span><br><span class="line"><span class="attribute">User-Agent</span>: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36</span><br><span class="line"><span class="attribute">Accept</span>: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8</span><br><span class="line"><span class="attribute">Accept-Encoding</span>: gzip, deflate, sdch, br</span><br><span class="line"><span class="attribute">Accept-Language</span>: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4</span><br></pre></td></tr></table></figure><p>第 1 行为请求行，其中 <code>GET</code> 是请求方法，<code>/</code> 为请求资源路径， <code>HTTP/1.1</code> 为所使用的 HTTP 协议版本；</p><p>第 2 行至第 9 行为请求头，其中 <code>Host</code> 指定了主机地址为 www.locket.tt。</p><p>GET 方法一般是没有请求消息体的，所有请求报文中只有请求行和请求头。</p><h3 id="请求方法"><a href="#请求方法" class="headerlink" title="请求方法"></a>请求方法</h3><p>HTTP/1.1 协议中共定义了八种方法来以不同方式操作指定的资源：</p><ul><li>OPTIONS：这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*’来代替资源名称，向Web服务器发送OPTIONS请求，可以测试服务器功能是否正常运作。</li></ul><ul><li>HEAD：与GET方法一样，都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于，使用这个方法可以在不必传输全部内容的情况下，就可以获取其中“关于该资源的信息”（元信息或称元数据）。</li></ul><ul><li>GET：向指定的资源发出“显示”请求，使用GET方法应该只用在读取数据。</li></ul><ul><li>POST：向指定资源提交数据，请求服务器进行处理（例如提交表单或者上传文件）。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源，或二者皆有。</li></ul><ul><li>PUT：向指定资源位置上传其最新内容。</li></ul><ul><li>DELETE：请求服务器删除Request-URI所标识的资源。</li></ul><ul><li>TRACE：回显服务器收到的请求，主要用于测试或诊断。</li></ul><ul><li>CONNECT：HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接（经由非加密的HTTP代理服务器）。</li></ul><p>其中方法名是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候，服务器会返回 405 状态码(Method Not Allowed)，当服务器不认识或者不支持对应的请求方法的时候，应当返回 501 状态码(Not Implemented)。</p><p>HTTP/1.1 协议要求服务器至少应该实现 GET 和 HEAD 方法，其他方法都是可选的。当然我们最常用的还是 GET、POST、PUT、DELETE 等 4 种方法。</p><p>通常我们在网页中填写信息，一般会使用 POST 方法，请求报文大概如下：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">POST</span> <span class="string">/</span> HTTP/1.1</span><br><span class="line"><span class="attribute">Host</span>: www.locket.tt</span><br><span class="line"><span class="attribute">Connection</span>: keep-alive</span><br><span class="line"><span class="attribute">Content-Length</span>: 38</span><br><span class="line"><span class="attribute">Cache-Control</span>: max-age=0</span><br><span class="line"><span class="attribute">User-Agent</span>: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36</span><br><span class="line"><span class="attribute">Content-Type</span>: application/x-www-form-urlencoded</span><br><span class="line"><span class="attribute">Accept</span>: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8</span><br><span class="line"><span class="attribute">Accept-Encoding</span>: gzip, deflate, sdch, br</span><br><span class="line"><span class="attribute">Accept-Language</span>: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4</span><br><span class="line"></span><br><span class="line">name=locket&amp;email=service@zenzet.com</span><br></pre></td></tr></table></figure><p>第 1 行定义了通过 POST 请求方法提交数据；</p><p>第 4 行 的 Cointent-Type 定义了请求消息体的内容长度；</p><p>第 6 行 Content-Type 定义了请求消息体的内容格式：<code>application/x-www-form-urlencoded</code>，application/x-www-form-urlencoded 类型的数据，会按照 Url 编码将 body 内容编码后发往服务器。</p><p>第 11 行为消息体内容。</p><h3 id="响应报文"><a href="#响应报文" class="headerlink" title="响应报文"></a>响应报文</h3><p>HTTP 响应报文与请求报文类似，分为：状态行、响应头、空行、响应消息体。类似于下面这样：</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">&lt;version&gt;</span> <span class="section">&lt;status&gt;</span> <span class="section">&lt;reason-phrase&gt;</span></span><br><span class="line"><span class="section">&lt;headers&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="section">&lt;entity-body&gt;</span></span><br></pre></td></tr></table></figure><p>以下是响应报文示例：</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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">HTTP/1.1 <span class="number">200</span> OK</span><br><span class="line"><span class="attribute">Date</span>: Sun, 30 Apr 2017 02:55:45 GMT</span><br><span class="line"><span class="attribute">Content-Type</span>: text/html; charset=utf-8</span><br><span class="line"><span class="attribute">Content-Length</span>: 42</span><br><span class="line"><span class="attribute">Connection</span>: keep-alive</span><br><span class="line"><span class="attribute">ETag</span>: W/"7327-W/VsZ13C3Q5gt8dbytBcY/EfP1U"</span><br><span class="line"><span class="attribute">Content-Encoding</span>: gzip</span><br><span class="line"></span><br><span class="line">&lt;html&gt;</span><br><span class="line">  &lt;body&gt;Hello World&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>第 3 行 Content-Type 指定了响应消息体的内容格式。</p><h3 id="Content-Type"><a href="#Content-Type" class="headerlink" title="Content-Type"></a>Content-Type</h3><p>上文中的请求报文和响应报文示例均在 Header 中指定了 Content-Type 字段，在请求报文中，用于客户端告诉服务端请求数据格式；在响应报文中，用于服务器端告诉客户端相应数据格式。</p><p>下面是一些常见的 Content-Type 字段的值：</p><ul><li>text/plain</li><li>text/html</li><li>text/css</li><li>image/jpeg</li><li>image/png</li><li>audio/mp3</li><li>video/mp4</li><li>application/javascript</li><li>application/xml</li><li>application/json</li><li>application/x-www-form-urlencoded</li><li>multipart/form-data</li></ul><p>其中 application/x-www-form-urlencoded 和 multipart/form-data 用于请求报文中，application/x-www-form-urlencoded 多用于普通表单提交，multipart/form-data 用于上传文件。</p><p>以下是上传文件到百度网盘的请求报文：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">POST</span> <span class="string">/rest/2.0/pcs/superfile2?method=upload&amp;app_id=123456&amp;channel=chunlei&amp;clienttype=0&amp;web=1&amp;path=/hello.txt</span> HTTP/1.1</span><br><span class="line"><span class="attribute">Host</span>: c3.pcs.baidu.com</span><br><span class="line"><span class="attribute">Connection</span>: keep-alive</span><br><span class="line"><span class="attribute">Content-Length</span>: 204</span><br><span class="line"><span class="attribute">Origin</span>: https://pan.baidu.com</span><br><span class="line"><span class="attribute">User-Agent</span>: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36</span><br><span class="line"><span class="attribute">Content-Type</span>: multipart/form-data; boundary=----WebKitFormBoundaryFPu1lm1ZYFB7tHve</span><br><span class="line"><span class="attribute">Accept</span>: */*</span><br><span class="line"><span class="attribute">Referer</span>: https://pan.baidu.com/disk/home</span><br><span class="line"><span class="attribute">Accept-Encoding</span>: gzip, deflate, br</span><br><span class="line"><span class="attribute">Accept-Language</span>: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4</span><br><span class="line"></span><br><span class="line">------WebKitFormBoundaryFPu1lm1ZYFB7tHve</span><br><span class="line"><span class="attribute">Content-Disposition</span>: form-data; name="file"; filename="blob"</span><br><span class="line"><span class="attribute">Content-Type</span>: application/octet-stream</span><br><span class="line"></span><br><span class="line">Hello World</span><br><span class="line"></span><br><span class="line">------WebKitFormBoundaryFPu1lm1ZYFB7tHve--</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>multipart/form-data 通过在 Content-Type 中设定 boundary (—-WebKitFormBoundaryFPu1lm1ZYFB7tHve) ，在 body 中通过 boundary 来分隔不同的字段，第 17 行即为上传文件内容。</p><h3 id="Content-Encoding"><a href="#Content-Encoding" class="headerlink" title="Content-Encoding"></a>Content-Encoding</h3><p>由于发送的数据可以是任何格式，因此可以把数据压缩后发送。Content-Encoding 字段说明数据的压缩方法。</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Content-Encoding</span>: gzip</span><br><span class="line"><span class="attribute">Content-Encoding</span>: compress</span><br><span class="line"><span class="attribute">Content-Encoding</span>: deflate</span><br></pre></td></tr></table></figure><p>客户端在请求时用 Accept-Encoding 字段说明可接收的压缩方法：</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">Accept-Encoding</span>: gzip, deflate</span><br></pre></td></tr></table></figure><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>HTTP/1.1 版本引入了持久连接，即TCP连接默认不关闭，可以被多个请求复用，不用声明<code>Connection: keep-alive</code></p><p>服务器端发送回应前，在不知道回应的数据长度时，是无法指定 Content-Length 字段来声明响应消息体大小，在 HTTP/1.1 版本中增加了分块传输编码支持：<code>Transfer-Encoding: chunked</code> 。每个非空的数据块之前，会有一个16进制的数值，表示这个块的长度。最后是一个大小为0的块，就表示本次回应的数据发送完了。</p><p>参考：</p><blockquote><p><a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" title="wiki_http" target="_blank" rel="noopener">Hypertext_Transfer_Protocol</a></p></blockquote>]]></content>
    
    <summary type="html">
    
      HTTP 是超文本传输协议 (HyperText Transfer Protocol) 的缩写，是互联网上应用最为广泛的一种网络协议。通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifies, URI) 来标识。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="HTTP" scheme="http://zuoyanyouwu.com/tags/HTTP/"/>
    
  </entry>
  
  <entry>
    <title>Netty SSL/TLS 的使用与分析</title>
    <link href="http://zuoyanyouwu.com/2017/01/netty-ssl-using-and-analyze/"/>
    <id>http://zuoyanyouwu.com/2017/01/netty-ssl-using-and-analyze/</id>
    <published>2017-01-26T12:32:48.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间公司在做的产品需要支持 HTTPS 协议，这篇文章就来分享下通过 Netty 使用 SSL/TLS 以及相关源码分析。</p><p>SSL/TLS 的原理就不介绍了，直接上使用教程。</p><p>首先，自定义证书：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SelfSignedCertificate ssc = <span class="keyword">new</span> SelfSignedCertificate();</span><br><span class="line">SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();</span><br></pre></td></tr></table></figure><p>上面代码直接使用 Netty 自带的自签名证书工具，另外也可以使用 jdk 的 keytool 或 openssl 等工具生成证书。</p><p>构造 ChannelInitializer：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NettySslHandlerInitializer</span> <span class="keyword">extends</span> <span class="title">ChannelInitializer</span>&lt;<span class="title">SocketChannel</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SslContext sslCtx;</span><br><span class="line"></span><br><span class="line">    NettySslHandlerInitializer(SslContext sslCtx) &#123;</span><br><span class="line">        <span class="keyword">this</span>.sslCtx = sslCtx;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        ChannelPipeline pipeline = ch.pipeline();</span><br><span class="line">        <span class="comment">// 创建一个新的 SSLEngine 对象</span></span><br><span class="line">        SSLEngine sslEngine = sslCtx.newEngine(UnpooledByteBufAllocator.DEFAULT);</span><br><span class="line">        <span class="comment">// 配置为 server 模式</span></span><br><span class="line">        sslEngine.setUseClientMode(<span class="keyword">false</span>);</span><br><span class="line">        <span class="comment">// 选择需要启用的 SSL 协议，如 SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2 等</span></span><br><span class="line">        sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols());</span><br><span class="line">        <span class="comment">// 选择需要启用的 CipherSuite 组合，如 ECDHE-ECDSA-CHACHA20-POLY1305 等</span></span><br><span class="line">        sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites());</span><br><span class="line">        <span class="comment">// 添加 SslHandler 之 pipeline 中</span></span><br><span class="line">        pipeline.addLast(<span class="string">"ssl"</span>, <span class="keyword">new</span> SslHandler(sslEngine));</span><br><span class="line"></span><br><span class="line">        pipeline.addLast(<span class="string">"encoder,"</span>, <span class="keyword">new</span> HttpResponseEncoder());</span><br><span class="line">        pipeline.addLast(<span class="string">"decoder"</span>, <span class="keyword">new</span> HttpRequestDecoder());</span><br><span class="line">        pipeline.addLast(<span class="string">"deflate"</span>, <span class="keyword">new</span> HttpContentCompressor());</span><br><span class="line">        pipeline.addLast(<span class="string">"aggregator"</span>, <span class="keyword">new</span> HttpObjectAggregator(<span class="number">1024</span>*<span class="number">1024</span>*<span class="number">100</span>));</span><br><span class="line">        pipeline.addLast(<span class="string">"chunk"</span>, <span class="keyword">new</span> ChunkedWriteHandler());</span><br><span class="line"></span><br><span class="line">        pipeline.addLast(<span class="string">"demo"</span>, <span class="keyword">new</span> NettySslHandler());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>SSL 协议各版本参考：<a href="https://en.wikipedia.org/wiki/Transport_Layer_Security" target="_blank" rel="noopener">维基百科</a></p><p>基本的使用方法很简单，如上述所示。我们来重点分析下 Netty SSL 的内部实现原理。</p><p>首先看下 SslContext 是怎么通过 SslContextBuilder 构造的，直接看 SslContextBuilder  的 build 方法即可：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> SslContext <span class="title">build</span><span class="params">()</span> <span class="keyword">throws</span> SSLException </span>&#123;</span><br><span class="line">    <span class="comment">// 如果是 forServer，则构建 newServerContextInternal</span></span><br><span class="line">    <span class="keyword">if</span> (forServer) &#123;</span><br><span class="line">        <span class="keyword">return</span> SslContext.newServerContextInternal(provider, trustCertCollection,</span><br><span class="line">            trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,</span><br><span class="line">            ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, startTls);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 否则构建 newClientContextInternal</span></span><br><span class="line">        <span class="keyword">return</span> SslContext.newClientContextInternal(provider, trustCertCollection,</span><br><span class="line">            trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,</span><br><span class="line">            ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>newServerContextInternal 实现如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> SslContext <span class="title">newServerContextInternal</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">        SslProvider provider,</span></span></span><br><span class="line"><span class="function"><span class="params">        X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,</span></span></span><br><span class="line"><span class="function"><span class="params">        X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,</span></span></span><br><span class="line"><span class="function"><span class="params">        Iterable&lt;String&gt; ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="keyword">long</span> sessionCacheSize, <span class="keyword">long</span> sessionTimeout, ClientAuth clientAuth, <span class="keyword">boolean</span> startTls)</span> <span class="keyword">throws</span> SSLException </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (provider == <span class="keyword">null</span>) &#123;</span><br><span class="line">        provider = defaultServerProvider();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (provider) &#123;</span><br><span class="line">        <span class="keyword">case</span> JDK:</span><br><span class="line">        <span class="comment">// jdk 版本实现</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> JdkSslServerContext(</span><br><span class="line">            trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,</span><br><span class="line">            keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,</span><br><span class="line">            clientAuth, startTls);</span><br><span class="line">        <span class="keyword">case</span> OPENSSL:</span><br><span class="line">        <span class="comment">// openssl 版本实现</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> OpenSslServerContext(</span><br><span class="line">            trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,</span><br><span class="line">            keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,</span><br><span class="line">            clientAuth, startTls);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>OpenSSL 版本实现内部基本上采用 native 代码，方便起见，我们直接看 JDK 版本实现。</p><p>OpenSslServerContext 的构造很简单，就是基本的初始化赋值操作。</p><p>我们在构造 ChannelInitializer 时，将 SslHandler 添加到 pipeline 中，那么就来看下  SslHandler 的 decode 方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> SSLException </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">int</span> startOffset = in.readerIndex();</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">int</span> endOffset = in.writerIndex();</span><br><span class="line">    <span class="keyword">int</span> offset = startOffset;</span><br><span class="line">    <span class="keyword">int</span> totalLength = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">boolean</span> nonSslRecord = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (totalLength &lt; OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">int</span> readableBytes = endOffset - offset;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 packet 长度</span></span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">int</span> packetLength = getEncryptedPacketLength(in, offset);</span><br><span class="line">        <span class="keyword">if</span> (packetLength == -<span class="number">1</span>) &#123;</span><br><span class="line">            nonSslRecord = <span class="keyword">true</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (packetLength &gt; readableBytes) &#123;</span><br><span class="line">                <span class="comment">// wait until the whole packet can be read</span></span><br><span class="line">            <span class="keyword">this</span>.packetLength = packetLength;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">int</span> newTotalLength = totalLength + packetLength;</span><br><span class="line">        <span class="keyword">if</span> (newTotalLength &gt; OpenSslEngine.MAX_ENCRYPTED_PACKET_LENGTH) &#123;</span><br><span class="line">                <span class="comment">// Don't read too much.</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// We have a whole packet.</span></span><br><span class="line">        <span class="comment">// Increment the offset to handle the next packet.</span></span><br><span class="line">        offset += packetLength;</span><br><span class="line">        totalLength = newTotalLength;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (totalLength &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        in.skipBytes(totalLength);</span><br><span class="line">        firedChannelRead = unwrap(ctx, in, startOffset, totalLength) || firedChannelRead;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (nonSslRecord) &#123;</span><br><span class="line">            <span class="comment">// Not an SSL/TLS packet</span></span><br><span class="line">        NotSslRecordException e = <span class="keyword">new</span> NotSslRecordException(</span><br><span class="line">            <span class="string">"not an SSL/TLS record: "</span> + ByteBufUtil.hexDump(in));</span><br><span class="line">        in.skipBytes(in.readableBytes());</span><br><span class="line">        setHandshakeFailure(ctx, e);</span><br><span class="line">        ctx.fireExceptionCaught(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>decode 方法主要是获取 packet 长度、解析 packet 内容。</p><p>获取 packet 长度自然是通过 getEncryptedPacketLength 方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">getEncryptedPacketLength</span><span class="params">(ByteBuf buffer, <span class="keyword">int</span> offset)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> packetLength = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// SSLv3 or TLS - Check ContentType</span></span><br><span class="line">    <span class="keyword">boolean</span> tls;</span><br><span class="line">    <span class="keyword">switch</span> (buffer.getUnsignedByte(offset)) &#123;</span><br><span class="line">        <span class="keyword">case</span> SSL_CONTENT_TYPE_CHANGE_CIPHER_SPEC:</span><br><span class="line">        <span class="keyword">case</span> SSL_CONTENT_TYPE_ALERT:</span><br><span class="line">        <span class="comment">// SSL 握手 SSL_CONTENT_TYPE_HANDSHAKE = 22</span></span><br><span class="line">        <span class="keyword">case</span> SSL_CONTENT_TYPE_HANDSHAKE:</span><br><span class="line">        <span class="keyword">case</span> SSL_CONTENT_TYPE_APPLICATION_DATA:</span><br><span class="line">            tls = <span class="keyword">true</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="comment">// SSLv2 or bad data</span></span><br><span class="line">            tls = <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (tls) &#123;</span><br><span class="line">        <span class="comment">// SSLv3 or TLS - Check ProtocolVersion</span></span><br><span class="line">        <span class="keyword">int</span> majorVersion = buffer.getUnsignedByte(offset + <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (majorVersion == <span class="number">3</span>) &#123;</span><br><span class="line">            <span class="comment">// SSLv3 or TLS</span></span><br><span class="line">            packetLength = buffer.getUnsignedShort(offset + <span class="number">3</span>) + SSL_RECORD_HEADER_LENGTH;</span><br><span class="line">            <span class="keyword">if</span> (packetLength &lt;= SSL_RECORD_HEADER_LENGTH) &#123;</span><br><span class="line">                <span class="comment">// Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data)</span></span><br><span class="line">                tls = <span class="keyword">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data)</span></span><br><span class="line">            tls = <span class="keyword">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!tls) &#123;</span><br><span class="line">        <span class="comment">// SSLv2 or bad data - Check the version</span></span><br><span class="line">        <span class="keyword">int</span> headerLength = (buffer.getUnsignedByte(offset) &amp; <span class="number">0x80</span>) != <span class="number">0</span> ? <span class="number">2</span> : <span class="number">3</span>;</span><br><span class="line">        <span class="keyword">int</span> majorVersion = buffer.getUnsignedByte(offset + headerLength + <span class="number">1</span>);</span><br><span class="line">        <span class="keyword">if</span> (majorVersion == <span class="number">2</span> || majorVersion == <span class="number">3</span>) &#123;</span><br><span class="line">            <span class="comment">// SSLv2</span></span><br><span class="line">            <span class="keyword">if</span> (headerLength == <span class="number">2</span>) &#123;</span><br><span class="line">                packetLength = (buffer.getShort(offset) &amp; <span class="number">0x7FFF</span>) + <span class="number">2</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                packetLength = (buffer.getShort(offset) &amp; <span class="number">0x3FFF</span>) + <span class="number">3</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (packetLength &lt;= headerLength) &#123;</span><br><span class="line">                <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> packetLength;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从 getEncryptedPacketLength 方法可以看出，首先会读取 buffer 的第一个字节，判断是否使用的是 SSL 协议，其中  0x16 表示 SSLv3 或 TLS 握手， 另外因为 SSLv2 版第一个字节是 0x87，会特殊再判断一次(阿里云 PTS 服务居然用的还是 SSLv2 握手-_-!)。buffer 的第二个字节定义了 SSL 的主版本号，第三个字节定义了次版本号。</p><p>获取 packet 的长度及握手版本号后，就会开始执行握手操作。</p><p>在 SslHandler 的 decode 方法中可以看到，在获取 packet 长度后，会执行 unwrap 方法，unwrap 方法内部实际上是调用了 SSLEngineImpl 的 unwrap 方法，SSLEngineImpl.unwrap 会读取 Record 协议内容，流程大致是</p><p>SSLEngineImpl.unwrap -> SSLEngineImpl.readNetRecord -> SSLEngineImpl.readRecord -> Handshaker.process_record -> Handshaker.processLoop -> ServerHandshaker.processMessage</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">processMessage</span><span class="params">(<span class="keyword">byte</span> var1, <span class="keyword">int</span> var2)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">this</span>.state &gt;= var1 &amp;&amp; <span class="keyword">this</span>.state != <span class="number">16</span> &amp;&amp; var1 != <span class="number">15</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> SSLProtocolException(<span class="string">"Handshake message sequence violation, state = "</span> + <span class="keyword">this</span>.state + <span class="string">", type = "</span> + var1);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span>(var1) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>: &#123;</span><br><span class="line">                <span class="comment">// 与 client 握手</span></span><br><span class="line">                ClientHello var3 = <span class="keyword">new</span> ClientHello(<span class="keyword">this</span>.input, var2);</span><br><span class="line">                <span class="keyword">this</span>.clientHello(var3);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">default</span>: &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> SSLProtocolException(<span class="string">"Illegal server handshake msg, "</span> + var1);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>processMessage 很简单，就是构造 ClientHello 对象，完成握手操作。握手操作的完成过程还是要看 ClientHello 的实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><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><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">clientHello</span><span class="params">(ClientHello var1)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line"></span><br><span class="line">    ServerNameExtension var2 = (ServerNameExtension)var1.extensions.get(ExtensionType.EXT_SERVER_NAME);</span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">this</span>.sniMatchers.isEmpty() &amp;&amp; var2 != <span class="keyword">null</span> &amp;&amp; !var2.isMatched(<span class="keyword">this</span>.sniMatchers)) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fatalSE(<span class="number">112</span>, <span class="string">"Unrecognized server name indication"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">boolean</span> var3 = <span class="keyword">false</span>;</span><br><span class="line">    CipherSuiteList var4 = var1.getCipherSuites();</span><br><span class="line">    <span class="keyword">if</span>(var4.contains(CipherSuite.C_SCSV)) &#123;</span><br><span class="line">        var3 = <span class="keyword">true</span>;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.isInitialHandshake) &#123;</span><br><span class="line">            <span class="keyword">this</span>.secureRenegotiation = <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.secureRenegotiation) &#123;</span><br><span class="line">            <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"The SCSV is present in a secure renegotiation"</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"The SCSV is present in a insecure renegotiation"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    RenegotiationInfoExtension var5 = (RenegotiationInfoExtension)var1.extensions.get(ExtensionType.EXT_RENEGOTIATION_INFO);</span><br><span class="line">    <span class="keyword">if</span>(var5 != <span class="keyword">null</span>) &#123;</span><br><span class="line">        var3 = <span class="keyword">true</span>;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.isInitialHandshake) &#123;</span><br><span class="line">            <span class="keyword">if</span>(!var5.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"The renegotiation_info field is not empty"</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">this</span>.secureRenegotiation = <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(!<span class="keyword">this</span>.secureRenegotiation) &#123;</span><br><span class="line">                <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"The renegotiation_info is present in a insecure renegotiation"</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!MessageDigest.isEqual(<span class="keyword">this</span>.clientVerifyData, var5.getRenegotiatedConnection())) &#123;</span><br><span class="line">                <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"Incorrect verify data in ClientHello renegotiation_info message"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>(!<span class="keyword">this</span>.isInitialHandshake &amp;&amp; <span class="keyword">this</span>.secureRenegotiation) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"Inconsistent secure renegotiation indication"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(!var3 || !<span class="keyword">this</span>.secureRenegotiation) &#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.isInitialHandshake) &#123;</span><br><span class="line">            <span class="keyword">if</span>(!allowLegacyHelloMessages) &#123;</span><br><span class="line">                <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"Failed to negotiate the use of secure renegotiation"</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(debug != <span class="keyword">null</span> &amp;&amp; Debug.isOn(<span class="string">"handshake"</span>)) &#123;</span><br><span class="line">                System.out.println(<span class="string">"Warning: No renegotiation indication in ClientHello, allow legacy ClientHello"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(!allowUnsafeRenegotiation) &#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">this</span>.activeProtocolVersion.v &gt;= ProtocolVersion.TLS10.v) &#123;</span><br><span class="line">                <span class="keyword">this</span>.warningSE(<span class="number">100</span>);</span><br><span class="line">                <span class="keyword">this</span>.invalidated = <span class="keyword">true</span>;</span><br><span class="line">                <span class="keyword">if</span>(<span class="keyword">this</span>.input.available() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">this</span>.fatalSE(<span class="number">10</span>, <span class="string">"ClientHello followed by an unexpected  handshake message"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"Renegotiation is not allowed"</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(debug != <span class="keyword">null</span> &amp;&amp; Debug.isOn(<span class="string">"handshake"</span>)) &#123;</span><br><span class="line">            System.out.println(<span class="string">"Warning: continue with insecure renegotiation"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">this</span>.input.digestNow();</span><br><span class="line">    ServerHello var6 = <span class="keyword">new</span> ServerHello();</span><br><span class="line">    <span class="keyword">this</span>.clientRequestedVersion = var1.protocolVersion;</span><br><span class="line">    ProtocolVersion var7 = <span class="keyword">this</span>.selectProtocolVersion(<span class="keyword">this</span>.clientRequestedVersion);</span><br><span class="line">    <span class="keyword">if</span>(var7 == <span class="keyword">null</span> || var7.v == ProtocolVersion.SSL20Hello.v) &#123;</span><br><span class="line">        <span class="keyword">this</span>.fatalSE(<span class="number">40</span>, <span class="string">"Client requested protocol "</span> + <span class="keyword">this</span>.clientRequestedVersion + <span class="string">" not enabled or not supported"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1. 选定协议版本号</span></span><br><span class="line">    <span class="keyword">this</span>.handshakeHash.protocolDetermined(var7);</span><br><span class="line">    <span class="keyword">this</span>.setVersion(var7);</span><br><span class="line">    var6.protocolVersion = <span class="keyword">this</span>.protocolVersion;</span><br><span class="line">    <span class="keyword">this</span>.clnt_random = var1.clnt_random;</span><br><span class="line">    <span class="keyword">this</span>.svr_random = <span class="keyword">new</span> RandomCookie(<span class="keyword">this</span>.sslContext.getSecureRandom());</span><br><span class="line">    var6.svr_random = <span class="keyword">this</span>.svr_random;</span><br><span class="line">    <span class="keyword">this</span>.session = <span class="keyword">null</span>;</span><br><span class="line">    <span class="comment">// -- 省略部分 session 重用的代码 --</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">this</span>.session == <span class="keyword">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.supportedCurves = (SupportedEllipticCurvesExtension)var1.extensions.get(ExtensionType.EXT_ELLIPTIC_CURVES);</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.protocolVersion.v &gt;= ProtocolVersion.TLS12.v) &#123;</span><br><span class="line">            SignatureAlgorithmsExtension var18 = (SignatureAlgorithmsExtension)var1.extensions.get(ExtensionType.EXT_SIGNATURE_ALGORITHMS);</span><br><span class="line">            <span class="keyword">if</span>(var18 != <span class="keyword">null</span>) &#123;</span><br><span class="line">                Collection var24 = var18.getSignAlgorithms();</span><br><span class="line">                <span class="keyword">if</span>(var24 == <span class="keyword">null</span> || var24.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> SSLHandshakeException(<span class="string">"No peer supported signature algorithms"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                Collection var25 = SignatureAndHashAlgorithm.getSupportedAlgorithms(<span class="keyword">this</span>.algorithmConstraints, var24);</span><br><span class="line">                <span class="keyword">if</span>(var25.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> SSLHandshakeException(<span class="string">"No signature and hash algorithm in common"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">this</span>.setPeerSupportedSignAlgs(var25);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.session = <span class="keyword">new</span> SSLSessionImpl(<span class="keyword">this</span>.protocolVersion, CipherSuite.C_NULL, <span class="keyword">this</span>.getLocalSupportedSignAlgs(), <span class="keyword">this</span>.sslContext.getSecureRandom(), <span class="keyword">this</span>.getHostAddressSE(), <span class="keyword">this</span>.getPortSE());</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.protocolVersion.v &gt;= ProtocolVersion.TLS12.v &amp;&amp; <span class="keyword">this</span>.peerSupportedSignAlgs != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.session.setPeerSupportedSignatureAlgorithms(<span class="keyword">this</span>.peerSupportedSignAlgs);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        List var19 = Collections.emptyList();</span><br><span class="line">        <span class="keyword">if</span>(var2 != <span class="keyword">null</span>) &#123;</span><br><span class="line">            var19 = var2.getServerNames();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">this</span>.session.setRequestedServerNames(var19);</span><br><span class="line">        <span class="keyword">this</span>.setHandshakeSessionSE(<span class="keyword">this</span>.session);</span><br><span class="line">        <span class="comment">// 2. 选择使用的 CipherSuite</span></span><br><span class="line">        <span class="keyword">this</span>.chooseCipherSuite(var1);</span><br><span class="line">        <span class="keyword">this</span>.session.setSuite(<span class="keyword">this</span>.cipherSuite);</span><br><span class="line">        <span class="keyword">this</span>.session.setLocalPrivateKey(<span class="keyword">this</span>.privateKey);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.setHandshakeSessionSE(<span class="keyword">this</span>.session);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    var6.cipherSuite = <span class="keyword">this</span>.cipherSuite;</span><br><span class="line">    var6.sessionId = <span class="keyword">this</span>.session.getSessionId();</span><br><span class="line">    var6.compression_method = <span class="keyword">this</span>.session.getCompression();</span><br><span class="line"></span><br><span class="line">    var6.write(<span class="keyword">this</span>.output);</span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">this</span>.resumingSession) &#123;</span><br><span class="line">        <span class="keyword">this</span>.calculateConnectionKeys(<span class="keyword">this</span>.session.getMasterSecret());</span><br><span class="line">        <span class="keyword">this</span>.sendChangeCipherAndFinish(<span class="keyword">false</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 3. 交换 key</span></span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.keyExchange != KeyExchange.K_KRB5 &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_KRB5_EXPORT) &#123;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">this</span>.keyExchange != KeyExchange.K_DH_ANON &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_ECDH_ANON) &#123;</span><br><span class="line">                <span class="keyword">if</span>(<span class="keyword">this</span>.certs == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"no certificates"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                CertificateMsg var26 = <span class="keyword">new</span> CertificateMsg(<span class="keyword">this</span>.certs);</span><br><span class="line">                <span class="keyword">this</span>.session.setLocalCertificates(<span class="keyword">this</span>.certs);</span><br><span class="line">                <span class="keyword">if</span>(debug != <span class="keyword">null</span> &amp;&amp; Debug.isOn(<span class="string">"handshake"</span>)) &#123;</span><br><span class="line">                    var26.print(System.out);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                var26.write(<span class="keyword">this</span>.output);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.certs != <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"anonymous keyexchange with certs"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        Object var27;</span><br><span class="line">        <span class="keyword">switch</span>(<span class="keyword">null</span>.$SwitchMap$sun$security$ssl$CipherSuite$KeyExchange[<span class="keyword">this</span>.keyExchange.ordinal()]) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line">            var27 = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">            <span class="keyword">if</span>(JsseJce.getRSAKeyLength(<span class="keyword">this</span>.certs[<span class="number">0</span>].getPublicKey()) &gt; <span class="number">512</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    var27 = <span class="keyword">new</span> RSA_ServerKeyExchange(<span class="keyword">this</span>.tempPublicKey, <span class="keyword">this</span>.privateKey, <span class="keyword">this</span>.clnt_random, <span class="keyword">this</span>.svr_random, <span class="keyword">this</span>.sslContext.getSecureRandom());</span><br><span class="line">                    <span class="keyword">this</span>.privateKey = <span class="keyword">this</span>.tempPrivateKey;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (GeneralSecurityException var15) &#123;</span><br><span class="line">                    throwSSLException(<span class="string">"Error generating RSA server key exchange"</span>, var15);</span><br><span class="line">                    var27 = <span class="keyword">null</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                var27 = <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                var27 = <span class="keyword">new</span> DH_ServerKeyExchange(<span class="keyword">this</span>.dh, <span class="keyword">this</span>.privateKey, <span class="keyword">this</span>.clnt_random.random_bytes, <span class="keyword">this</span>.svr_random.random_bytes, <span class="keyword">this</span>.sslContext.getSecureRandom(), <span class="keyword">this</span>.preferableSignatureAlgorithm, <span class="keyword">this</span>.protocolVersion);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (GeneralSecurityException var14) &#123;</span><br><span class="line">                throwSSLException(<span class="string">"Error generating DH server key exchange"</span>, var14);</span><br><span class="line">                var27 = <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">7</span>:</span><br><span class="line">            var27 = <span class="keyword">new</span> DH_ServerKeyExchange(<span class="keyword">this</span>.dh, <span class="keyword">this</span>.protocolVersion);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">8</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">9</span>:</span><br><span class="line">            var27 = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">11</span>:</span><br><span class="line">            <span class="keyword">case</span> <span class="number">12</span>:</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                var27 = <span class="keyword">new</span> ECDH_ServerKeyExchange(<span class="keyword">this</span>.ecdh, <span class="keyword">this</span>.privateKey, <span class="keyword">this</span>.clnt_random.random_bytes, <span class="keyword">this</span>.svr_random.random_bytes, <span class="keyword">this</span>.sslContext.getSecureRandom(), <span class="keyword">this</span>.preferableSignatureAlgorithm, <span class="keyword">this</span>.protocolVersion);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (GeneralSecurityException var13) &#123;</span><br><span class="line">                throwSSLException(<span class="string">"Error generating ECDH server key exchange"</span>, var13);</span><br><span class="line">                var27 = <span class="keyword">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"internal error: "</span> + <span class="keyword">this</span>.keyExchange);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(var27 != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span>(debug != <span class="keyword">null</span> &amp;&amp; Debug.isOn(<span class="string">"handshake"</span>)) &#123;</span><br><span class="line">                ((ServerKeyExchange)var27).print(System.out);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            ((ServerKeyExchange)var27).write(<span class="keyword">this</span>.output);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 4. 客户端认证</span></span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">this</span>.doClientAuth != <span class="number">0</span> &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_DH_ANON &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_ECDH_ANON &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_KRB5 &amp;&amp; <span class="keyword">this</span>.keyExchange != KeyExchange.K_KRB5_EXPORT) &#123;</span><br><span class="line">            Collection var31 = <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">if</span>(<span class="keyword">this</span>.protocolVersion.v &gt;= ProtocolVersion.TLS12.v) &#123;</span><br><span class="line">                var31 = <span class="keyword">this</span>.getLocalSupportedSignAlgs();</span><br><span class="line">                <span class="keyword">if</span>(var31.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> SSLHandshakeException(<span class="string">"No supported signature algorithm"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                Set var12 = SignatureAndHashAlgorithm.getHashAlgorithmNames(var31);</span><br><span class="line">                <span class="keyword">if</span>(var12.isEmpty()) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> SSLHandshakeException(<span class="string">"No supported signature algorithm"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            X509Certificate[] var28 = <span class="keyword">this</span>.sslContext.getX509TrustManager().getAcceptedIssuers();</span><br><span class="line">            CertificateRequest var29 = <span class="keyword">new</span> CertificateRequest(var28, <span class="keyword">this</span>.keyExchange, var31, <span class="keyword">this</span>.protocolVersion);</span><br><span class="line">            <span class="keyword">if</span>(debug != <span class="keyword">null</span> &amp;&amp; Debug.isOn(<span class="string">"handshake"</span>)) &#123;</span><br><span class="line">                var29.print(System.out);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            var29.write(<span class="keyword">this</span>.output);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 5. 握手完成</span></span><br><span class="line">        ServerHelloDone var30 = <span class="keyword">new</span> ServerHelloDone();</span><br><span class="line">        var30.write(<span class="keyword">this</span>.output);</span><br><span class="line">        <span class="keyword">this</span>.output.flush();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>握手基本是以下几个步骤：</p><ol><li>选定使用的协议版本号</li><li>选定使用的加密套件</li><li>交换密钥</li><li>客户端认证</li><li>握手完成</li></ol><p>握手完成后就是数据请求流程了，留在以后来分析。</p>]]></content>
    
    <summary type="html">
    
      前段时间公司在做的产品需要支持 HTTPS 协议，这篇文章就来分享下通过 Netty 使用 SSL/TLS 以及相关源码分析。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Netty" scheme="http://zuoyanyouwu.com/tags/Netty/"/>
    
      <category term="HTTPS" scheme="http://zuoyanyouwu.com/tags/HTTPS/"/>
    
      <category term="SSL/TLS" scheme="http://zuoyanyouwu.com/tags/SSL-TLS/"/>
    
  </entry>
  
  <entry>
    <title>Netty HTTP form-data 编码简析</title>
    <link href="http://zuoyanyouwu.com/2017/01/netty-http-form-data/"/>
    <id>http://zuoyanyouwu.com/2017/01/netty-http-form-data/</id>
    <published>2017-01-17T02:23:31.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>很多人应该对 HTTP  的 multipart/form-data 不陌生，一般常用于上传文件，虽然大部分人可以很熟练的使用，但是对于 multipart/form-data 如何对请求编码的却知之甚少，这篇博客就来简单介绍下其中的编码过程以及 Netty 的实现。</p><p>首先我们使用 Postman 发送一个 HTTP multipart/form-data 类型的请求，通过抓包发现请求内容。</p><p><img src="/images/multipart_0.jpeg" alt="Request Header"></p><p>Request Header：</p><p><img src="/images/multipart_1.jpeg" alt="Request Header"></p><p>Request Body ：</p><p><img src="/images/multipart_2.jpeg" alt="Request Header"></p><p>Request Body 完成内容：</p><p><img src="/images/multipart_3.jpeg" alt="Request Header"></p><p>从第二个图中可以看出，Reqeust Body 内容是由 Content-Type 中的 boundary 所分隔，内容中的 Content-Disposition 指定 form 的 key 和上传的文件名，Content-Type 指定文件类型，接着一个换行，后面就是文件的内容，同样，服务器在接受到请求后，也会按照这种格式来进行解码。首先来看看 Netty 是如何构造 multipart 请求内容的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// setup the factory: here using a mixed memory/disk based on size threshold</span></span><br><span class="line"><span class="comment">// Disk if MINSIZE exceed</span></span><br><span class="line">HttpDataFactory factory = <span class="keyword">new</span> DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); </span><br><span class="line"><span class="comment">// setup an http request</span></span><br><span class="line">HttpRequest request = <span class="keyword">new</span> DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, <span class="string">"http://127.0.0.1:1028/test-file"</span>);</span><br><span class="line"><span class="comment">// Use the PostBody encoder</span></span><br><span class="line">HttpPostRequestEncoder bodyRequestEncoder = <span class="keyword">new</span> HttpPostRequestEncoder(factory, request, <span class="keyword">true</span>, HttpConstants.DEFAULT_CHARSET, EncoderMode.RFC1738));</span><br><span class="line">File file = <span class="keyword">new</span> File(<span class="string">"test.txt"</span>);</span><br><span class="line"><span class="comment">// add Form attribute</span></span><br><span class="line">bodyRequestEncoder.addBodyFileUpload(<span class="string">"file"</span>, file, <span class="string">"text/plain"</span>, <span class="keyword">true</span>);</span><br><span class="line"><span class="comment">// finalize request</span></span><br><span class="line">bodyRequestEncoder.finalizeRequest();</span><br><span class="line"><span class="comment">// send request</span></span><br><span class="line">channel.write(request);</span><br><span class="line"></span><br><span class="line"><span class="comment">// test if request was chunked and if so, finish the write</span></span><br><span class="line"><span class="keyword">if</span> (bodyRequestEncoder.isChunked()) &#123;</span><br><span class="line">    channel.write(bodyRequestEncoder);</span><br><span class="line">&#125;</span><br><span class="line">channel.flush();</span><br><span class="line"></span><br><span class="line"><span class="comment">// Now no more use of file representation (and list of HttpData)</span></span><br><span class="line">bodyRequestEncoder.cleanFiles();</span><br></pre></td></tr></table></figure><p>其中最关键的地方是第 7 行 和 10 行代码。</p><p>HttpPostRequestEncoder 在初始化时会根据传入的 multipart 是否为 true 来构造对应的 RequestEncoder：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">HttpPostRequestEncoder</span><span class="params">(HttpDataFactory factory, HttpRequest request, <span class="keyword">boolean</span> multipart, Charset charset, EncoderMode encoderMode)</span> <span class="keyword">throws</span> ErrorDataEncoderException </span>&#123;</span><br><span class="line">    <span class="comment">// 省略部分无关代码</span></span><br><span class="line">    isMultipart = multipart;</span><br><span class="line">    multipartHttpDatas = <span class="keyword">new</span> ArrayList&lt;InterfaceHttpData&gt;();</span><br><span class="line">    <span class="keyword">this</span>.encoderMode = encoderMode;</span><br><span class="line">    <span class="keyword">if</span> (isMultipart) &#123;</span><br><span class="line">        <span class="comment">// 生成 multipartDataBoundary</span></span><br><span class="line">        initDataMultipart();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们传入 multipart 为 true 时，HttpPostRequestEncoder 就会生成一个唯一的 multipart boundary，而这个 boundary 就是之前图中的分割界线。</p><p>接着来看 <code>HttpPostRequestEncoder.addBodyFileUpload()</code> 实现：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addBodyFileUpload</span><span class="params">(String name, File file, String contentType, <span class="keyword">boolean</span> isText)</span> <span class="keyword">throws</span> ErrorDataEncoderException </span>&#123;</span><br><span class="line">    <span class="comment">// 省略部分无关代码</span></span><br><span class="line">    FileUpload fileUpload = factory.createFileUpload(request, name, file.getName(), scontentType,</span><br><span class="line">        contentTransferEncoding, <span class="keyword">null</span>, file.length());</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 读取 file 中的内容，保存至 FileUpload 中</span></span><br><span class="line">        fileUpload.setContent(file);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> ErrorDataEncoderException(e);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// fileUpload 添加到 bodyListDatas 中</span></span><br><span class="line">    addBodyHttpData(fileUpload);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 HttpDataFactory 构造 FileUpload 后，添加至 bodyListDatas 中：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addBodyHttpData</span><span class="params">(InterfaceHttpData data)</span> <span class="keyword">throws</span> ErrorDataEncoderException </span>&#123;</span><br><span class="line"></span><br><span class="line">    bodyListDatas.add(data);</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Logic:</span></span><br><span class="line"><span class="comment">     * if not Attribute:</span></span><br><span class="line"><span class="comment">     *      add Data to body list</span></span><br><span class="line"><span class="comment">     *      if (duringMixedMode)</span></span><br><span class="line"><span class="comment">     *          add endmixedmultipart delimiter</span></span><br><span class="line"><span class="comment">     *          currentFileUpload = null</span></span><br><span class="line"><span class="comment">     *          duringMixedMode = false;</span></span><br><span class="line"><span class="comment">     *      add multipart delimiter, multipart body header and Data to multipart list</span></span><br><span class="line"><span class="comment">     *      reset currentFileUpload, duringMixedMode</span></span><br><span class="line"><span class="comment">     * if FileUpload: take care of multiple file for one field =&gt; mixed mode</span></span><br><span class="line"><span class="comment">     *      if (duringMixeMode)</span></span><br><span class="line"><span class="comment">     *          if (currentFileUpload.name == data.name)</span></span><br><span class="line"><span class="comment">     *              add mixedmultipart delimiter, mixedmultipart body header and Data to multipart list</span></span><br><span class="line"><span class="comment">     *          else</span></span><br><span class="line"><span class="comment">     *              add endmixedmultipart delimiter, multipart body header and Data to multipart list</span></span><br><span class="line"><span class="comment">     *              currentFileUpload = data</span></span><br><span class="line"><span class="comment">     *              duringMixedMode = false;</span></span><br><span class="line"><span class="comment">     *      else</span></span><br><span class="line"><span class="comment">     *          if (currentFileUpload.name == data.name)</span></span><br><span class="line"><span class="comment">     *              change multipart body header of previous file into multipart list to</span></span><br><span class="line"><span class="comment">     *                      mixedmultipart start, mixedmultipart body header</span></span><br><span class="line"><span class="comment">     *              add mixedmultipart delimiter, mixedmultipart body header and Data to multipart list</span></span><br><span class="line"><span class="comment">     *              duringMixedMode = true</span></span><br><span class="line"><span class="comment">     *          else</span></span><br><span class="line"><span class="comment">     *              add multipart delimiter, multipart body header and Data to multipart list</span></span><br><span class="line"><span class="comment">     *              currentFileUpload = data</span></span><br><span class="line"><span class="comment">     *              duringMixedMode = false;</span></span><br><span class="line"><span class="comment">     * Do not add last delimiter! Could be:</span></span><br><span class="line"><span class="comment">     * if duringmixedmode: endmixedmultipart + endmultipart</span></span><br><span class="line"><span class="comment">     * else only endmultipart</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (data <span class="keyword">instanceof</span> FileUpload) &#123;</span><br><span class="line">        FileUpload fileUpload = (FileUpload) data;</span><br><span class="line">        InternalAttribute internal = <span class="keyword">new</span> InternalAttribute(charset);</span><br><span class="line">        <span class="keyword">if</span> (!multipartHttpDatas.isEmpty()) &#123;</span><br><span class="line">            <span class="comment">// previously a data field so CRLF</span></span><br><span class="line">            internal.addValue(<span class="string">"\r\n"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 添加  multipart boundary</span></span><br><span class="line">        internal.addValue(<span class="string">"--"</span> + multipartDataBoundary + <span class="string">"\r\n"</span>);</span><br><span class="line">        <span class="comment">// Content-Disposition: form-data; name="files";</span></span><br><span class="line">        <span class="comment">// filename="file1.txt"</span></span><br><span class="line">        <span class="comment">// 这部分就是上文途中看到的 Content-Disposition</span></span><br><span class="line">        internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + <span class="string">": "</span> + HttpHeaderValues.FORM_DATA + <span class="string">"; "</span></span><br><span class="line">            + HttpHeaderValues.NAME + <span class="string">"=\""</span> + fileUpload.getName() + <span class="string">"\"; "</span></span><br><span class="line">            + HttpHeaderValues.FILENAME + <span class="string">"=\""</span> + fileUpload.getFilename() + <span class="string">"\"\r\n"</span>);</span><br><span class="line">            <span class="comment">// Add Content-Length: xxx</span></span><br><span class="line">        internal.addValue(HttpHeaderNames.CONTENT_LENGTH + <span class="string">": "</span> +</span><br><span class="line">            fileUpload.length() + <span class="string">"\r\n"</span>);</span><br><span class="line">            <span class="comment">// Content-Type: image/gif</span></span><br><span class="line">            <span class="comment">// Content-Type: text/plain; charset=ISO-8859-1</span></span><br><span class="line">            <span class="comment">// Content-Transfer-Encoding: binary</span></span><br><span class="line">        internal.addValue(HttpHeaderNames.CONTENT_TYPE + <span class="string">": "</span> + fileUpload.getContentType());</span><br><span class="line">        String contentTransferEncoding = fileUpload.getContentTransferEncoding();</span><br><span class="line">        <span class="keyword">if</span> (contentTransferEncoding != <span class="keyword">null</span></span><br><span class="line">            &amp;&amp; contentTransferEncoding.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) &#123;</span><br><span class="line">            internal.addValue(<span class="string">"\r\n"</span> + HttpHeaderNames.CONTENT_TRANSFER_ENCODING + <span class="string">": "</span></span><br><span class="line">                + HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value() + <span class="string">"\r\n\r\n"</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (fileUpload.getCharset() != <span class="keyword">null</span>) &#123;</span><br><span class="line">            internal.addValue(<span class="string">"; "</span> + HttpHeaderValues.CHARSET + <span class="string">'='</span> + fileUpload.getCharset().name() + <span class="string">"\r\n\r\n"</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            internal.addValue(<span class="string">"\r\n\r\n"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 先讲 attribute 添加至 multipartHttpDatas 中</span></span><br><span class="line">        <span class="comment">// 再将 fileUpload 添加址 multipartHttpDatas 中</span></span><br><span class="line">        <span class="comment">// 主要是需要将 fileUpload 的内容保存在 boundary 和 fileUpload 的 attribute 之后</span></span><br><span class="line">        <span class="comment">// 完整内容构造会在 finalizeRequest() 中生成</span></span><br><span class="line">        multipartHttpDatas.add(internal);</span><br><span class="line">        multipartHttpDatas.add(data);</span><br><span class="line">        globalBodySize += fileUpload.length() + internal.size();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只保留了一些关键代码，删除了 attribute 和 mix 相关代码。</p><p>根据 Netty 原有的注释以及我后面补充的注释，已经有了非常完整的 FileUpload 添加过程，接下来看 finalizeRequest() 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> HttpRequest <span class="title">finalizeRequest</span><span class="params">()</span> <span class="keyword">throws</span> ErrorDataEncoderException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (isMultipart) &#123;</span><br><span class="line">        <span class="comment">// 在 header 中设置 Content-Type</span></span><br><span class="line">        String value = HttpHeaderValues.MULTIPART_FORM_DATA + <span class="string">"; "</span> + HttpHeaderValues.BOUNDARY + <span class="string">'='</span></span><br><span class="line">        + multipartDataBoundary;</span><br><span class="line">        headers.add(HttpHeaderNames.CONTENT_TYPE, value);</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> (isMultipart) &#123;</span><br><span class="line">        <span class="comment">// 获取 iterator ，会在 nextChunk 中使用</span></span><br><span class="line">        iterator = multipartHttpDatas.listIterator();</span><br><span class="line">    &#125; </span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略部分无关代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>finalizeRequest 除了设置 Content-Type 外还构造了 ListIterator\<interfacehttpdata\> iterator ，而实际的数据内容则是遍历 iterator 来读取的。</interfacehttpdata\></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> HttpContent <span class="title">nextChunk</span><span class="params">()</span> <span class="keyword">throws</span> ErrorDataEncoderException </span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (size &gt; <span class="number">0</span> &amp;&amp; iterator.hasNext()) &#123;</span><br><span class="line">        currentData = iterator.next();</span><br><span class="line">        HttpContent chunk;</span><br><span class="line">        <span class="keyword">if</span> (isMultipart) &#123;</span><br><span class="line">            chunk = encodeNextChunkMultipart(size);</span><br><span class="line">        &#125; </span><br><span class="line">        <span class="comment">// NextChunk from data</span></span><br><span class="line">        <span class="keyword">return</span> chunk;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在数据传输时，通过 nextChunk 中遍历 iterator 获取每一个 InterfaceHttpData 的内容并传输到对应的 server 中。</p><p>至此 HTTP multipart/form-data 及 Netty 中的实现基本分析完毕，错误之处欢迎指出。</p>]]></content>
    
    <summary type="html">
    
      很多人应该对 HTTP 的 multipart/form-data 不陌生，一般常用于上传文件，虽然大部分人可以很熟练的使用，但是对于 multipart/form-data 如何对请求编码的却知之甚少，这篇博客就来简单介绍下其中的编码过程以及 Netty 的实现。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Netty" scheme="http://zuoyanyouwu.com/tags/Netty/"/>
    
      <category term="HTTP" scheme="http://zuoyanyouwu.com/tags/HTTP/"/>
    
  </entry>
  
  <entry>
    <title>2016 - 总结</title>
    <link href="http://zuoyanyouwu.com/2017/01/my-2016/"/>
    <id>http://zuoyanyouwu.com/2017/01/my-2016/</id>
    <published>2017-01-16T08:04:22.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>虽然 2016 年已经过去了，还是来简单的总结下吧。</p><h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>因为公司业务上的调整和其他同事的转岗，下半年开始我的工作内容也由 Android 转向 Web Server，负责公司 Gateway 项目的研发。</p><p>也经历了脱离舒适区的各种问题，比如对 HTTP 协议了解不够、缺乏 Web Server 开发相关经验等。通过各方面的学习，这些问题基本也解决，包括后面对公司其他项目的改造等也处理的比较顺利。</p><h3 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h3><p>2016 年应该是对自己的各方面生活影响很大的一年。</p><p>今年去了重庆、上海、苏州旅游。</p><p>今年跑步的次数比较少，基本上没多少时间去跑步，国庆后办了张健身卡，偶尔在工作后去游几圈泳。</p><h3 id="技术"><a href="#技术" class="headerlink" title="技术"></a>技术</h3><p>随着工作内容的变化，Android 上新学习的技术比较少，主要是 RxJava 和 DataBinding 方面的原理和使用，另外对比较火的各种热修复方案也进行了了解。</p><p>今年主要学习的还是关于 Web Server 方面，如利用 Netty 搭建服务、HTTP 协议解析、多线程优化、Redis 等。</p><p>上半年学习了 Golang，准备利用 beego 搭建一个 REST 服务。</p><p>书籍方面看了 JVM 相关的书籍以及 HTTP 协议的书，希望可以对底层有更多的理解。</p><p>买了本 Redis 方面的书，还没看。</p><p>断断续续写了几篇博客。</p><h3 id="2017-年计划"><a href="#2017-年计划" class="headerlink" title="2017 年计划"></a>2017 年计划</h3><p>身体很重要，健身不能拉下。</p><p>多学习，技术上要有更多的收获，继续写博客。</p><p>达成买房成就。</p>]]></content>
    
    <summary type="html">
    
      总结下已经过去的 2016。
    
    </summary>
    
      <category term="Life" scheme="http://zuoyanyouwu.com/categories/Life/"/>
    
    
      <category term="Life" scheme="http://zuoyanyouwu.com/tags/Life/"/>
    
  </entry>
  
  <entry>
    <title>Netty Handler 单元测试</title>
    <link href="http://zuoyanyouwu.com/2016/12/netty-handler-tester/"/>
    <id>http://zuoyanyouwu.com/2016/12/netty-handler-tester/</id>
    <published>2016-12-05T02:47:10.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>Netty 是一个高性能、异步事件驱动的 NIO 框架，它提供了对 TCP 、 UDP 和文件传输的支持，作为一个异步 NIO 框架，Netty 的所有 IO 操作都是异步非阻塞的，通过 Future-Listener 机制，用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。<br>Netty 在保持高性能的同时又提供了非常易用的 API，实际使用中只需要实现自定义的 Handler，那么快速完成开发后保证 Handler 中数据流的正确性就成为了重中之重。</p><p>好在 Netty 提供了专门用来测试 Handler 的类： <strong>EmbeddedChannel</strong>。通过 EmbeddedChannel 可以测试 inbound&amp;outbound 两个方向的数据流来达到测试 Handler 的目的。<br>EmbeddedChannel 提供如下方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">writeInbound</span><span class="params">(Object... msgs)</span> <span class="comment">// 向 Inbound channel 写消息</span></span></span><br><span class="line"><span class="function">&lt;T&gt; T <span class="title">readInbound</span><span class="params">()</span> <span class="comment">// 从 Inbound channel读消息</span></span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">writeOutbound</span><span class="params">(Object... msgs)</span> <span class="comment">// 向 Outbound channel 写消息</span></span></span><br><span class="line"><span class="function">&lt;T&gt; T <span class="title">readOutbound</span><span class="params">()</span> <span class="comment">// 从 Outbound channel 读消息</span></span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span><span class="title">finish</span><span class="params">()</span> <span class="comment">// 将 channel 标记为 finished</span></span></span><br></pre></td></tr></table></figure></p><p>下面就使用以上几个方法来完成 Handler 的测试。<br>首先将创建以下几个简单的 Handler：<br><strong>NettyHttpRequestHandler(Inbound)</strong>: 接收 Client 的 Request 并作简单处理<br><strong>NettyHttpRelayHandler(Inbound)</strong>: 将处理过的 Request 包装成 Response 并转交给下面的 Outbound<br><strong>NettyHttpResponseHandler(Outbound)</strong>: 接收 Response 并作简单处理<br><strong>NettyHttpSenderHandler(Outbound)</strong>: 将 Response 返回给 Client<br>其中  NettyHttpRelayHandler 和 NettyHttpSenderHandler 无需做单元测试，一个仅是将 request 包装成 response，另一个仅是将 response 返回给 cilent。</p><p><strong> NettyHttpRequestHandler(Inbound)</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NettyHttpRequestHandler</span> <span class="keyword">extends</span> <span class="title">ChannelInboundHandlerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// update http content to Base64 encode value</span></span><br><span class="line">        FullHttpRequest httpRequest = (FullHttpRequest) msg;</span><br><span class="line">        ByteBuf contentBuf = httpRequest.content();</span><br><span class="line">        String content = contentBuf.toString(CharsetUtil.UTF_8);</span><br><span class="line">        contentBuf.clear();</span><br><span class="line">        contentBuf.writeBytes(Base64.getEncoder().encode(content.getBytes(CharsetUtil.UTF_8)));</span><br><span class="line">        <span class="comment">// update content length</span></span><br><span class="line">        HttpHeaders.setHeader(httpRequest, HttpHeaders.Names.CONTENT_LENGTH, contentBuf.readableBytes());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">super</span>.channelRead(ctx, msg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>NettyHttpRequestHandler 将接收到的 body 内容 base64 编码。</p><p><strong>NettyHttpResponseHandler</strong><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NettyHttpResponseHandler</span> <span class="keyword">extends</span> <span class="title">ChannelOutboundHandlerAdapter</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">write</span><span class="params">(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// update http content to Base64 decode value</span></span><br><span class="line">        FullHttpResponse response = (FullHttpResponse) msg;</span><br><span class="line">        ByteBuf contentBuf = response.content();</span><br><span class="line">        String content = contentBuf.toString(CharsetUtil.UTF_8);</span><br><span class="line">        contentBuf.clear();</span><br><span class="line">        contentBuf.writeBytes(Base64.getDecoder().decode(content.getBytes(CharsetUtil.UTF_8)));</span><br><span class="line">        <span class="comment">// update content length</span></span><br><span class="line">        HttpHeaders.setHeader(response, HttpHeaders.Names.CONTENT_LENGTH, contentBuf.readableBytes());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">super</span>.write(ctx, msg, promise);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>NettyHttpResponseHandler 将接收到的 body 内容 base64 解码。</p><p>开始进入到如何对 NettyHttpRequestHandler 和 NettyHttpResponseHandler 进行单元测试的重头戏。<br>创建一个 EmbeddedChannel 对象：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">EmbeddedChannel channel = <span class="keyword">new</span> EmbeddedChannel(</span><br><span class="line">                <span class="keyword">new</span> HttpRequestDecoder(),</span><br><span class="line">                <span class="keyword">new</span> HttpObjectAggregator(<span class="number">10485760</span>),</span><br><span class="line">                <span class="keyword">new</span> NettyHttpResponseHandler(),</span><br><span class="line">                <span class="keyword">new</span> NettyHttpRequestHandler()</span><br><span class="line">        );</span><br></pre></td></tr></table></figure></p><p>构造 HttpRequest:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">FullHttpRequest request = <span class="keyword">new</span> DefaultFullHttpRequest(HTTP_1_1, POST, <span class="string">"/test"</span>);</span><br><span class="line">setRequestHeaders(request);</span><br></pre></td></tr></table></figure></p><p>向 channel 中发送 request:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">HttpContent chunk = <span class="keyword">new</span> DefaultHttpContent(Unpooled.wrappedBuffer(JSON_DATA.getBytes(UTF_8)));</span><br><span class="line">HttpHeaders.setHeader(request, CONTENT_LENGTH, chunk.content().readableBytes());</span><br><span class="line"><span class="comment">// write request to Inbound</span></span><br><span class="line">channel.writeInbound(request);</span><br><span class="line"><span class="comment">// write content to Inbound</span></span><br><span class="line">channel.writeInbound(chunk);</span><br><span class="line"><span class="comment">// write last conent to Inbound</span></span><br><span class="line">channel.writeInbound(LastHttpContent.EMPTY_LAST_CONTENT);</span><br></pre></td></tr></table></figure></p><p>检查经过 NettyHttpRequestHandler 处理后的数据：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">FullHttpRequest reqMsg = (FullHttpRequest) channel.readInbound();</span><br><span class="line"> assertNotNull(reqMsg);</span><br><span class="line"></span><br><span class="line">String body = reqMsg.content().toString(UTF_8);</span><br><span class="line">assertNotNull(body);</span><br><span class="line">assertEquals(body, <span class="string">"eyJkYXRhIjogInRoaXMgaXMganNvbiBib2R5In0="</span>);</span><br></pre></td></tr></table></figure></p><p>构造 HttpResponse 并发送到 channel :<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">FullHttpResponse response = <span class="keyword">new</span> DefaultFullHttpResponse(HTTP_1_1,</span><br><span class="line">                HttpResponseStatus.OK, Unpooled.copiedBuffer(body, UTF_8));</span><br><span class="line">setResponseHeaders(response);</span><br><span class="line">channel.writeOutbound(response);</span><br></pre></td></tr></table></figure></p><p>检查经过 NettyHttpResponseHandler 处理后的数据:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">FullHttpResponse resMsg = (FullHttpResponse) channel.readOutbound();</span><br><span class="line">assertNotNull(resMsg);</span><br><span class="line"></span><br><span class="line">body = resMsg.content().toString(UTF_8);</span><br><span class="line">assertNotNull(body);</span><br><span class="line">assertEquals(body, JSON_DATA);</span><br><span class="line"><span class="comment">// mark channel as finished</span></span><br><span class="line">channel.finish();</span><br></pre></td></tr></table></figure></p><p>以上内容展示了 Netty Handler 完整的单元测试流程，开发过程中可以编写充分的单元测试，尽量保证程序的正确性。<br>完整版源码下载地址：<a href="https://github.com/zuoyanyouwu/netty-handler-tester" title="netty-handler-tester" target="_blank" rel="noopener">netty-handler-tester</a></p>]]></content>
    
    <summary type="html">
    
      合理使用 EmbeddedChannel 对 Netty Handler 进行单元测试。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Netty" scheme="http://zuoyanyouwu.com/tags/Netty/"/>
    
      <category term="Java" scheme="http://zuoyanyouwu.com/tags/Java/"/>
    
      <category term="JUnit" scheme="http://zuoyanyouwu.com/tags/JUnit/"/>
    
  </entry>
  
  <entry>
    <title>Chrome Custom Tabs --- 更好的展示网页</title>
    <link href="http://zuoyanyouwu.com/2016/06/chrome-custom-tabs/"/>
    <id>http://zuoyanyouwu.com/2016/06/chrome-custom-tabs/</id>
    <published>2016-06-08T02:22:36.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>用过 Andriod 版官方 Twitter 的用户可能会发现在打击链接后，打开的是一个不同寻常的界面：<br><img src="/images/chrome_custom_tabs_0.png" alt="Twitter Web"><br>在使用 Telegram 时也会见到类似的界面：<br><img src="/images/chrome_custom_tabs_1.png" alt="Telgram Web"><br>今天就来讲一下如何实现上面的几个效果。</p><p>在 Android 4.4 和 Chrome 45 以上的版本，可以使用 「Chrome Custom Tabs」 特性来实现以上的效果，将 Chrome Tab 作为应用的一部分来使用，并能非常好的展示网页内容，另外 Chrome 还提供了多个定制选项。</p><p>首先，需要引入 customtabs 包。打开 build.gradle ，添加依赖：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><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">dependencies &#123;</span><br><span class="line">    ...</span><br><span class="line">    compile &apos;com.android.support:customtabs:23.4.0&apos;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>添加依赖后，可以进行 UI 定制、网页加载优化等操作。</p><h4 id="使用-Chrome-Custom-Tab-打开网页"><a href="#使用-Chrome-Custom-Tab-打开网页" class="headerlink" title="使用 Chrome Custom Tab 打开网页"></a>使用 Chrome Custom Tab 打开网页</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Use a CustomTabsIntent.Builder to configure CustomTabsIntent.</span></span><br><span class="line"><span class="comment">// Once ready, call CustomTabsIntent.Builder.build() to create a CustomTabsIntent</span></span><br><span class="line"><span class="comment">// and launch the desired Url with CustomTabsIntent.launchUrl()</span></span><br><span class="line"></span><br><span class="line">String url = ¨http:<span class="comment">//www.zuoyanyouwu.com/¨;</span></span><br><span class="line">CustomTabsIntent.Builder builder = <span class="keyword">new</span> CustomTabsIntent.Builder();</span><br><span class="line">CustomTabsIntent customTabsIntent = builder.build();</span><br><span class="line">customTabsIntent.launchUrl(<span class="keyword">this</span>, Uri.parse(url));</span><br></pre></td></tr></table></figure><h4 id="自定义地址栏颜色"><a href="#自定义地址栏颜色" class="headerlink" title="自定义地址栏颜色"></a>自定义地址栏颜色</h4><p>为了和应用保持统一，Chrome 提供了自定义地址栏颜色的接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Changes the background color for the omnibox. colorInt is an int</span></span><br><span class="line"><span class="comment">// that specifies a Color.</span></span><br><span class="line"></span><br><span class="line">builder.setToolbarColor(colorInt);</span><br></pre></td></tr></table></figure></p><h4 id="自定义-Action-Button"><a href="#自定义-Action-Button" class="headerlink" title="自定义 Action Button"></a>自定义 Action Button</h4><p>可以在 Chrome Tab 中自定义 Action Button， 如「分享」操作：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Adds an Action Button to the Toolbar.</span></span><br><span class="line"><span class="comment">// 'icon' is a Bitmap to be used as the image source for the</span></span><br><span class="line"><span class="comment">// action button.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 'description' is a String be used as an accessible description for the button.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 'pendingIntent is a PendingIntent to launch when the action button</span></span><br><span class="line"><span class="comment">// or menu item was tapped. Chrome will be calling PendingIntent#send() on</span></span><br><span class="line"><span class="comment">// taps after adding the url as data. The client app can call</span></span><br><span class="line"><span class="comment">// Intent#getDataString() to get the url.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 'tint' is a boolean that defines if the Action Button should be tinted.</span></span><br><span class="line"></span><br><span class="line">builder.setActionButton(icon, description, pendingIntent, tint);</span><br></pre></td></tr></table></figure></p><h4 id="自定义-menu"><a href="#自定义-menu" class="headerlink" title="自定义 menu"></a>自定义 menu</h4><p>Chrome Tabs 也提供了自定义 menu 的功能，默认情况下， Chrome Tabs 提供了横向的三个图标：「前进」、「网页信息」、「刷新」，以及下面的「在网页中查找」、「在 Chrome 中打开」等五个操作，开发者可以在三个图标下方添加最多五个菜单：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">builder.addMenuItem(menuItemTitle, menuItemPendingIntent);</span><br></pre></td></tr></table></figure></p><h4 id="自定义打开和关闭动画"><a href="#自定义打开和关闭动画" class="headerlink" title="自定义打开和关闭动画"></a>自定义打开和关闭动画</h4><p>为了使应用的动画保持统一，Chrome Tabs 同样提供了自定义动画的接口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">builder.setStartAnimations(<span class="keyword">this</span>, R.anim.slide_in_right, R.anim.slide_out_left);</span><br><span class="line">builder.setExitAnimations(<span class="keyword">this</span>, R.anim.slide_in_left, R.anim.slide_out_right);</span><br></pre></td></tr></table></figure></p><h4 id="加载优化"><a href="#加载优化" class="headerlink" title="加载优化"></a>加载优化</h4><p>默认情况下，可以直接使用 CustomTabsIntent#launchUrl 方法直接打开网页，但是可能会因为冷启动的原因浪费一些启动时间，所以 Chrome 提供了后台热启动 Chrome，需要加载网页时即可直接打开网页，提升用户体验。<br>Chrome 热启动的流程：</p><ul><li>使用 CustomTabsClient#bindCustomTabsService 连接至 Chrome Service</li><li>与 Service 建立后，调用 CustomTabsClient#warmup 在后台启动 Chrome</li><li>调用 CustomTabsClient#newSession 新建一个 session，之后的请求都会在这个 session 中进行，可以在 CustomTabsClient#newSession 方法中传递 CustomTabsCallback 参数，以便获取页面的加载回调</li><li>调用 CustomTabsSession#mayLaunchUrl 告知 Chrome 将要加载的网页</li><li>将 session 作为参数传给 CustomTabsIntent.Builder</li></ul><h4 id="自定义-Action-Button-Icon"><a href="#自定义-Action-Button-Icon" class="headerlink" title="自定义 Action Button Icon"></a>自定义 Action Button Icon</h4><p>开发者可以根据设计，自定义 Action Button 的图标，如分享等：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">String shareLabel = getString(R.string.label_action_share);</span><br><span class="line">Bitmap icon = BitmapFactory.decodeResource(getResources(),</span><br><span class="line">        android.R.drawable.ic_menu_share);</span><br><span class="line"></span><br><span class="line"><span class="comment">//Create a PendingIntent to your BroadCastReceiver implementation</span></span><br><span class="line">Intent actionIntent = <span class="keyword">new</span> Intent(</span><br><span class="line">        <span class="keyword">this</span>.getApplicationContext(), ShareBroadcastReceiver.class);</span><br><span class="line">PendingIntent pendingIntent =</span><br><span class="line">        PendingIntent.getBroadcast(getApplicationContext(), <span class="number">0</span>, actionIntent, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//Set the pendingIntent as the action to be performed when the button is clicked.</span></span><br><span class="line">intentBuilder.setActionButton(icon, shareLabel, pendingIntent);</span><br></pre></td></tr></table></figure></p><h4 id="自定义关闭按钮"><a href="#自定义关闭按钮" class="headerlink" title="自定义关闭按钮"></a>自定义关闭按钮</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Setting a custom back button</span></span><br><span class="line">CustomTabsIntent.Builder intentBuilder = <span class="keyword">new</span> CustomTabsIntent.Builder();</span><br><span class="line">intentBuilder.setCloseButtonIcon(BitmapFactory.decodeResource(</span><br><span class="line">    getResources(), R.drawable.ic_arrow_back));</span><br></pre></td></tr></table></figure><p>通过以上方式，就可以实现 Twitter 和 Telegram 上的效果。</p><p>参考：<a href="https://developer.chrome.com/multidevice/android/customtabs" target="_blank" rel="noopener">Chrome Custom Tabs</a></p>]]></content>
    
    <summary type="html">
    
      利用「 Chrome Custom Tabs」更快、更好地展示网页。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Android" scheme="http://zuoyanyouwu.com/tags/Android/"/>
    
      <category term="Chrome" scheme="http://zuoyanyouwu.com/tags/Chrome/"/>
    
      <category term="WebView" scheme="http://zuoyanyouwu.com/tags/WebView/"/>
    
  </entry>
  
  <entry>
    <title>Handler-Message-Looper 源码分析</title>
    <link href="http://zuoyanyouwu.com/2016/06/handler-message-looper/"/>
    <id>http://zuoyanyouwu.com/2016/06/handler-message-looper/</id>
    <published>2016-06-02T08:18:02.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>Handler 是 Android 中引入的异步消息处理机制，通常会通过如下方式使用 Handler：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MyHandler</span> <span class="keyword">extends</span> <span class="title">Handler</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 对Activity的弱引用</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> WeakReference&lt;MyActivity&gt; mActivityRef;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">MyHandler</span><span class="params">(MyActivity activity)</span> </span>&#123;</span><br><span class="line">        mActivityRef = <span class="keyword">new</span> WeakReference&lt;MyActivity&gt;(activity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</span><br><span class="line">        MyActivity activity = mRef.get();</span><br><span class="line">        <span class="keyword">if</span> (activity == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">switch</span>(msg.what) &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">            <span class="comment">// do something</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>其中，handleMessage(Message msg) 即为异步消息处理接口，那么何时才会调用此接口呢？后面再讲。<br>先来看一下发送消息，通常我们会调用 Handler.sendMessage() 方法来发送消息，查看 Handler 源码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 省略之前的调用代码，直接看核心部分</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">sendMessageAtTime</span><span class="params">(Message msg, <span class="keyword">long</span> uptimeMillis)</span> </span>&#123;</span><br><span class="line">    MessageQueue queue = mQueue;</span><br><span class="line">    <span class="comment">// MessageQueue 不能为空</span></span><br><span class="line">    <span class="keyword">if</span> (queue == <span class="keyword">null</span>) &#123;</span><br><span class="line">        RuntimeException e = <span class="keyword">new</span> RuntimeException(</span><br><span class="line">                <span class="keyword">this</span> + <span class="string">" sendMessageAtTime() called with no mQueue"</span>);</span><br><span class="line">        Log.w(<span class="string">"Looper"</span>, e.getMessage(), e);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> enqueueMessage(queue, msg, uptimeMillis);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">enqueueMessage</span><span class="params">(MessageQueue queue, Message msg, <span class="keyword">long</span> uptimeMillis)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 设置 target 为自己</span></span><br><span class="line">    msg.target = <span class="keyword">this</span>;</span><br><span class="line">    <span class="keyword">if</span> (mAsynchronous) &#123;</span><br><span class="line">        msg.setAsynchronous(<span class="keyword">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 将 Message 插入 MessageQueue 队列中</span></span><br><span class="line">    <span class="keyword">return</span> queue.enqueueMessage(msg, uptimeMillis);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上述代码分析，可以看到我们发送的消息会进入到 MessageQueue 队列中，那么 MessageQueue 是如何管理队列的呢？<br>查看 MessageQueue.enqueueMessage() 代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">enqueueMessage</span><span class="params">(Message msg, <span class="keyword">long</span> when)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 删除部分无关代码</span></span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="keyword">this</span>) &#123;</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">        <span class="keyword">boolean</span> needWake;</span><br><span class="line">        <span class="keyword">if</span> (p == <span class="keyword">null</span> || when == <span class="number">0</span> || when &lt; p.when) &#123;</span><br><span class="line">            <span class="comment">// 如果队列为空或当前消息设定的发送时间已到达，则唤醒队列</span></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">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 否则将消息插入到队列中</span></span><br><span class="line">            needWake = mBlocked &amp;&amp; p.target == <span class="keyword">null</span> &amp;&amp; msg.isAsynchronous();</span><br><span class="line">            Message prev;</span><br><span class="line">            <span class="comment">// 通过 for 循环，将消息插入到合适的位置(根据 when 判断)</span></span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                prev = p;</span><br><span class="line">                p = p.next;</span><br><span class="line">                <span class="keyword">if</span> (p == <span class="keyword">null</span> || when &lt; p.when) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (needWake &amp;&amp; p.isAsynchronous()) &#123;</span><br><span class="line">                    needWake = <span class="keyword">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            msg.next = p; <span class="comment">// invariant: p == prev.next</span></span><br><span class="line">            prev.next = msg;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// We can assume mPtr != 0 because mQuitting is false.</span></span><br><span class="line">        <span class="keyword">if</span> (needWake) &#123;</span><br><span class="line">            nativeWake(mPtr);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>消息发送后，被系统存放至 MessageQueue 中，那么是如何被消费的呢？这就轮到了关键的 Looper 类。<br>在 Looper 中的 loop() 方法展示了如何消费消息：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">final</span> Looper me = myLooper();</span><br><span class="line">    <span class="keyword">if</span> (me == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"No Looper; Looper.prepare() wasn't called on this thread."</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">final</span> MessageQueue queue = me.mQueue;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Make sure the identity of this thread is that of the local process,</span></span><br><span class="line">    <span class="comment">// and keep track of what that identity token actually is.</span></span><br><span class="line">    Binder.clearCallingIdentity();</span><br><span class="line">    <span class="keyword">final</span> <span class="keyword">long</span> ident = Binder.clearCallingIdentity();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="comment">// 1. 从 MessageQueue 中取出下一个消息</span></span><br><span class="line">        Message msg = queue.next(); <span class="comment">// might block</span></span><br><span class="line">        <span class="keyword">if</span> (msg == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// No message indicates that the message queue is quitting.</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// This must be in a local variable, in case a UI event sets the logger</span></span><br><span class="line">        Printer logging = me.mLogging;</span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="keyword">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">"&gt;&gt;&gt;&gt;&gt; Dispatching to "</span> + msg.target + <span class="string">" "</span> +</span><br><span class="line">                    msg.callback + <span class="string">": "</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2. 调用 Handler.dispatchMessage 消费消息</span></span><br><span class="line">        msg.target.dispatchMessage(msg);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (logging != <span class="keyword">null</span>) &#123;</span><br><span class="line">            logging.println(<span class="string">"&lt;&lt;&lt;&lt;&lt; Finished to "</span> + msg.target + <span class="string">" "</span> + msg.callback);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Make sure that during the course of dispatching the</span></span><br><span class="line">        <span class="comment">// identity of the thread wasn't corrupted.</span></span><br><span class="line">        <span class="keyword">final</span> <span class="keyword">long</span> newIdent = Binder.clearCallingIdentity();</span><br><span class="line">        <span class="keyword">if</span> (ident != newIdent) &#123;</span><br><span class="line">            Log.wtf(TAG, <span class="string">"Thread identity changed from 0x"</span></span><br><span class="line">                    + Long.toHexString(ident) + <span class="string">" to 0x"</span></span><br><span class="line">                    + Long.toHexString(newIdent) + <span class="string">" while dispatching to "</span></span><br><span class="line">                    + msg.target.getClass().getName() + <span class="string">" "</span></span><br><span class="line">                    + msg.callback + <span class="string">" what="</span> + msg.what);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        msg.recycleUnchecked();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>至此已经完成了 Handler 发送、处理消息的整个流程，有兴趣的同学可以自己阅读 MessageQueue.next() 方法，查看 MessageQueue 是如何从队列中取出消息的。</p>]]></content>
    
    <summary type="html">
    
      Handler 作为 Android 四大组件中非常常用的类，今天就来看看 Android 是如何通过 Handler 实现异步消息处理的。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Android" scheme="http://zuoyanyouwu.com/tags/Android/"/>
    
      <category term="Handler" scheme="http://zuoyanyouwu.com/tags/Handler/"/>
    
      <category term="Message" scheme="http://zuoyanyouwu.com/tags/Message/"/>
    
      <category term="Looper" scheme="http://zuoyanyouwu.com/tags/Looper/"/>
    
  </entry>
  
  <entry>
    <title>ViewPager 滑动时的视差效果</title>
    <link href="http://zuoyanyouwu.com/2015/11/viewpager-roller/"/>
    <id>http://zuoyanyouwu.com/2015/11/viewpager-roller/</id>
    <published>2015-11-17T07:26:12.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>无意间发现「格瓦拉@电影」iOS 版的顶部 Banner 在滑动时，图片会有视差效果，遂尝试在 Android 中实现此效果。</p><p>效果图：</p><p><img src="/images/viewpager_roller_preview_0.gif" alt="ViewPagerRoller_Preview_0"></p><p>下面来讲一下实现原理。</p><p>首先主容器使用 ViewPager，无需自己做一个滑动控件。接着通过实现 ViewPager.PageTransformer 接口的 transformPage(View page, float position) 方法来达到效果。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transformPage</span><span class="params">(View page, <span class="keyword">float</span> position)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> pageWidth = page.getWidth();</span><br><span class="line">    <span class="keyword">float</span> x = position * pageWidth;</span><br><span class="line">    ImageView imageView = (ImageView) page.findViewById(R.id.fragment_image);</span><br><span class="line">    <span class="keyword">if</span> (imageView == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    imageView.setTranslationX(<span class="number">0.75f</span> * -x);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ViewPager 使用自定义的 PageTransformer 效果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ViewPager pager = (ViewPager) findViewById(R.id.main_content_pager);</span><br><span class="line">pager.setAdapter(<span class="keyword">new</span> ImagePagerAdapter(getSupportFragmentManager()));</span><br><span class="line">pager.setPageTransformer(<span class="keyword">true</span>, <span class="keyword">this</span>);</span><br></pre></td></tr></table></figure><p>源码下载地址：<a href="https://github.com/zuoyanyouwu/ViewPagerRoller" target="_blank" rel="noopener">ViewPagerRoller</a></p>]]></content>
    
    <summary type="html">
    
      ViewPager 是一个非常有用的控件，今天我们就来通过 ViewPager 来实现一个非常酷炫的滑动效果。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="ViewPager" scheme="http://zuoyanyouwu.com/tags/ViewPager/"/>
    
  </entry>
  
  <entry>
    <title>RecyclerView 实现 Photos 及选中效果</title>
    <link href="http://zuoyanyouwu.com/2015/09/recycler-photos/"/>
    <id>http://zuoyanyouwu.com/2015/09/recycler-photos/</id>
    <published>2015-09-18T12:13:56.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在做一个拍照应用，查看图库时要有类似于 Google Photos 的效果：</p><blockquote><ol><li>长按图片时，图库进入多选模式，被选中的图片则需要缩放</li><li>在选中的图片点击或长按时，则取消选中，图片需要恢复原始大小</li><li>在多选模式时，点击返回键，被选中的图片需要统一恢复原始大小</li><li>全选和取消全选的操作，同样需要缩放、恢复原始大小</li></ol></blockquote><p>为了更好的操作单个/多个 Grid Item 控件，将控件改为了 RecyclerView。</p><p>在继承自 RecyclerView.Adapter 的 GalleryRecyclerAdapter 中，使用 SparseArray\<integer\> 存储选中的 Item。</integer\></p><p>下面直接讲一下在选中图片时如何进行缩放、取消选中时恢复原始大小。<br>在图片未进入多选模式时，长按任何一个图片，则进入多选模式，点击图片则执行如下操作：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">performGalleryItemCheck</span><span class="params">(<span class="keyword">int</span> position)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 改变对应位置图片的选中状态</span></span><br><span class="line">    mAdapter.toggleItemChoice(position);</span><br><span class="line">    GalleryRecyclerViewHolder viewHolder = (GalleryRecyclerViewHolder) mPhotoGrid.findViewHolderForAdapterPosition(position);</span><br><span class="line">    <span class="keyword">if</span> (viewHolder != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 对 view 执行动画</span></span><br><span class="line">        playHolderAnimatorSet(viewHolder, mAdapter.getItemSelected(position));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> count = mAdapter.getSelectedItemsCount();</span><br><span class="line">    <span class="keyword">if</span> (count == <span class="number">0</span>) &#123;</span><br><span class="line">        mSelectActionMode.setTitle(<span class="string">""</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mSelectActionMode.setTitle(String.valueOf(count));</span><br><span class="line">    &#125;</span><br><span class="line">    mSelectActionMode.invalidate();</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>playHolderAnimatorSet 方法的实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">playHolderAnimatorSet</span><span class="params">(<span class="keyword">final</span> GalleryRecyclerViewHolder holder, <span class="keyword">final</span> <span class="keyword">boolean</span> selected)</span> </span>&#123;</span><br><span class="line">    ObjectAnimator scaleX;</span><br><span class="line">    ObjectAnimator scaleY;</span><br><span class="line">    holder.mCheckableImageView.setImageResource(selected ? R.mipmap.ic_photo_corner_checked : R.mipmap.ic_photo_corner_normal);</span><br><span class="line">    holder.mCheckableImageView.setVisibility(View.VISIBLE);</span><br><span class="line">    <span class="keyword">if</span> (selected) &#123;</span><br><span class="line">        scaleX = ObjectAnimator.ofFloat(holder.mPhotoView, <span class="string">"scaleX"</span>, <span class="number">1.0f</span>, <span class="number">0.8F</span>);</span><br><span class="line">        scaleY = ObjectAnimator.ofFloat(holder.mPhotoView, <span class="string">"scaleY"</span>, <span class="number">1.0f</span>, <span class="number">0.8F</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        scaleX = ObjectAnimator.ofFloat(holder.mPhotoView, <span class="string">"scaleX"</span>, <span class="number">0.8f</span>, <span class="number">1.0F</span>);</span><br><span class="line">        scaleY = ObjectAnimator.ofFloat(holder.mPhotoView, <span class="string">"scaleY"</span>, <span class="number">0.8f</span>, <span class="number">1.0F</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    AnimatorSet animatorSet = <span class="keyword">new</span> AnimatorSet();</span><br><span class="line">    animatorSet.play(scaleX).with(scaleY);</span><br><span class="line">    animatorSet.setDuration(<span class="number">150</span>);</span><br><span class="line">    animatorSet.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 ViewHolder 获取到 ImageView，对 ImageView 执行选中/未选中的动画。</p><p>到这里应该已经清楚如何实现全选/取消全选的操作了。</p><p>全选：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">performSelectAllAction</span><span class="params">()</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> size = Data.URLS.length;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">        GalleryRecyclerViewHolder viewHolder = (GalleryRecyclerViewHolder) mPhotoGrid.findViewHolderForAdapterPosition(i);</span><br><span class="line">        <span class="keyword">if</span> (viewHolder != <span class="keyword">null</span>) &#123;</span><br><span class="line">            viewHolder.mCheckableImageView.setImageResource(R.mipmap.ic_photo_corner_checked);</span><br><span class="line">            viewHolder.mCheckableImageView.setVisibility(View.VISIBLE);</span><br><span class="line">            <span class="keyword">if</span> (!mAdapter.getItemSelected(i)) &#123;</span><br><span class="line">                mAdapter.toggleItemChoice(i);</span><br><span class="line">                playHolderAnimatorSet(viewHolder, <span class="keyword">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!mAdapter.getItemSelected(i)) &#123;</span><br><span class="line">                mAdapter.toggleItemChoice(i);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>取消全选：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">clearSelectionWithAnimator</span><span class="params">(<span class="keyword">boolean</span> showCheckImage)</span> </span>&#123;</span><br><span class="line">    SparseArray&lt;Integer&gt; mSelections = mAdapter.getMultiSelections();</span><br><span class="line">    <span class="keyword">int</span> size = mSelections.size();</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; size; i++) &#123;</span><br><span class="line">        <span class="keyword">int</span> position = mSelections.valueAt(i);</span><br><span class="line">        GalleryRecyclerViewHolder viewHolder = (GalleryRecyclerViewHolder) mPhotoGrid.findViewHolderForAdapterPosition(position);</span><br><span class="line">        <span class="keyword">if</span> (viewHolder != <span class="keyword">null</span>) &#123;</span><br><span class="line">            viewHolder.mCheckableImageView.setImageResource(R.mipmap.ic_photo_corner_normal);</span><br><span class="line">            viewHolder.mCheckableImageView.setVisibility(showCheckImage ? View.VISIBLE : View.GONE);</span><br><span class="line">            playHolderAnimatorSet(viewHolder, <span class="keyword">false</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    mAdapter.clearMultiSelections();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>显示效果：</p><p><img src="/images/recycler_photos_preview_0.jpg" alt="Recycler_Photos_Preview_0"></p><p><img src="/images/recycler_photos_preview_1.jpg" alt="Recycler_Photos_Preview_1"></p><p><img src="/images/recycler_photos_preview_2.jpg" alt="Recycler_Photos_Preview_2"></p><p>源码下载地址：<a href="https://github.com/zuoyanyouwu/RecyclerPhotos" target="_blank" rel="noopener">RecyclerPhotos</a></p>]]></content>
    
    <summary type="html">
    
      相信不少使用过 Google Photos 的用户，都会对选中照片时的动画印象深刻，那么就来看一下这种动画的如何实现的。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="RecyclerView" scheme="http://zuoyanyouwu.com/tags/RecyclerView/"/>
    
      <category term="Photos" scheme="http://zuoyanyouwu.com/tags/Photos/"/>
    
  </entry>
  
  <entry>
    <title>FIR.IM 事件</title>
    <link href="http://zuoyanyouwu.com/2015/08/fir.im-error/"/>
    <id>http://zuoyanyouwu.com/2015/08/fir.im-error/</id>
    <published>2015-08-30T04:47:05.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>周五上午把「<a href="http://www.zuoyanyouwu.com/2015/app-time-story/" target="_blank" rel="noopener">时间的故事</a>」放到了 <a href="http://fir.im/v3lw" target="_blank" rel="noopener">fir.im</a> 上，下午两点多准备去看看下载量的时候，发现 fir.im 首页上贴出个公告：</p><p><img src="/images/fir_error_0.jpg" alt="FIR_ERROR_0"></p><p>等到晚上的时候，再次打开 fir.im ，他们更换为了通知：</p><p><img src="/images/fir_error_1.jpg" alt="FIR_ERROR_1"></p><p>据说是因为网站上有 VPN 类的软件，而过几天又是一个对 Party 非常重要的日子，估计 fir.im 恢复访问要等到 9 月 5 号之后了。</p>]]></content>
    
    <summary type="html">
    
      FIR.IM 出事啦。
    
    </summary>
    
    
      <category term="FIR" scheme="http://zuoyanyouwu.com/tags/FIR/"/>
    
  </entry>
  
  <entry>
    <title>和时间有关的故事</title>
    <link href="http://zuoyanyouwu.com/2015/08/app-time-story/"/>
    <id>http://zuoyanyouwu.com/2015/08/app-time-story/</id>
    <published>2015-08-28T06:58:28.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>花了几天时间，写了一款非常用性的软件，只是在偶然想起来的时候才会打开，这就是「时间的故事」。</p><p>它可以帮助你记录和时间有关的故事，记录生活中的美好时刻。帮你回忆，从不遗忘。</p><p>应用效果预览：</p><p><img src="/images/time_story_preview_0.png" alt="Time_Story_Preview_0"></p><p><img src="/images/time_story_preview_1.jpeg" alt="Time_Story_Preview_1"></p><p><img src="/images/time_story_preview_2.jpeg" alt="Time_Story_Preview_2"></p><p>下载地址：<a href="http://fir.im/v3lw" target="_blank" rel="noopener">时间的故事</a></p><p><img src="/images/time_story_qrcode.jpg" alt="Time_Story_QRCode"></p>]]></content>
    
    <summary type="html">
    
      推荐一个不太常用的小软件。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Android" scheme="http://zuoyanyouwu.com/tags/Android/"/>
    
      <category term="和时间有关的故事" scheme="http://zuoyanyouwu.com/tags/%E5%92%8C%E6%97%B6%E9%97%B4%E6%9C%89%E5%85%B3%E7%9A%84%E6%95%85%E4%BA%8B/"/>
    
  </entry>
  
  <entry>
    <title>Activity.setContentView(int layoutResID) 被调用时发生了什么</title>
    <link href="http://zuoyanyouwu.com/2015/07/what-happended-when-call-Activity.setContentView/"/>
    <id>http://zuoyanyouwu.com/2015/07/what-happended-when-call-Activity.setContentView/</id>
    <published>2015-07-22T07:45:42.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>继续 Activity 相关的源码阅读。</p><p>打开 Activity 的 setContentView(int layoutResID) 方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setContentView</span><span class="params">(<span class="keyword">int</span> layoutResID)</span> </span>&#123;</span><br><span class="line">    getWindow().setContentView(layoutResID);</span><br><span class="line">    initActionBar();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>先来看 getWindow() 方法，发现实际上是直接返回了一个 Window 抽象类，查找后发现，mWindow 是在 attach 方法中初始化的，而前一篇博客(<a href="http://www.zuoyanyouwu.com/2015/analyse-activity-launch/" title="Activity 的启动过程分析" target="_blank" rel="noopener">Activity 的启动过程分析</a>{:target=”_blank”})中末尾讲解到， Activity 最终会在 ActivityThread.performLaunchActivity() 中调用 onCreate 等方法，开始进入 Activity 的生命周期，而在调用 onCreate 之前，则会先调用 attach 方法，完成 Window 等成员的初始化工作。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">attach</span><span class="params">(Context context, ActivityThread aThread,</span></span></span><br><span class="line"><span class="function"><span class="params">            Instrumentation instr, IBinder token, <span class="keyword">int</span> ident,</span></span></span><br><span class="line"><span class="function"><span class="params">            Application application, Intent intent, ActivityInfo info,</span></span></span><br><span class="line"><span class="function"><span class="params">            CharSequence title, Activity parent, String id,</span></span></span><br><span class="line"><span class="function"><span class="params">            NonConfigurationInstances lastNonConfigurationInstances,</span></span></span><br><span class="line"><span class="function"><span class="params">            Configuration config)</span> </span>&#123;</span><br><span class="line">    attachBaseContext(context);</span><br><span class="line">    mFragments.attachActivity(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">// 初始化 mWindow</span></span><br><span class="line">    mWindow = PolicyManager.makeNewWindow(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>打开 PolicyManager 类，可以发现，mWindow 实际上是由 IPolicy 接口的某一个实现类所创建，PolicyManager 顶部可以发现：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String POLICY_IMPL_CLASS_NAME =</span><br><span class="line">        <span class="string">"com.android.internal.policy.impl.Policy"</span>;</span><br></pre></td></tr></table></figure><br>因此，直接打开 Policy.makeNewWindow(Context context) 即可查看到实际上是返回的是 new PhoneWindow(context)，花这些时间来查看 Window 的创建，实际上是要查看 Window.setContentView(int resLayoutID) 的具体实现方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setContentView</span><span class="params">(<span class="keyword">int</span> layoutResID)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (mContentParent == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 初始化 mDector 和 mContentParent</span></span><br><span class="line">        installDecor();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mContentParent.removeAllViews();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//</span></span><br><span class="line">    mLayoutInflater.inflate(layoutResID, mContentParent);</span><br><span class="line">    <span class="keyword">final</span> Callback cb = getCallback();</span><br><span class="line">    <span class="keyword">if</span> (cb != <span class="keyword">null</span> &amp;&amp; !isDestroyed()) &#123;</span><br><span class="line">        cb.onContentChanged();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>有时间的话，可以查看 installDector() 方法，可以发现 Dector 实际上是继承自 FrameLayout ，这也是为什么 Activity 的布局最顶层是 FrameLayout 的原因。<br>接着查看 mLayoutInflater.inflate() 方法，直接跟进实际调用的方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> View <span class="title">inflate</span><span class="params">(XmlPullParser parser, ViewGroup root, <span class="keyword">boolean</span> attachToRoot)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (mConstructorArgs) &#123;</span><br><span class="line">        <span class="keyword">final</span> AttributeSet attrs = Xml.asAttributeSet(parser);</span><br><span class="line">        Context lastContext = (Context)mConstructorArgs[<span class="number">0</span>];</span><br><span class="line">        mConstructorArgs[<span class="number">0</span>] = mContext;</span><br><span class="line">        View result = root;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 查找根节点</span></span><br><span class="line">            <span class="keyword">int</span> type;</span><br><span class="line">            <span class="keyword">while</span> ((type = parser.next()) != XmlPullParser.START_TAG &amp;&amp;</span><br><span class="line">                    type != XmlPullParser.END_DOCUMENT) &#123;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">final</span> String name = parser.getName();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 解析 &lt;merge /&gt; 标签</span></span><br><span class="line">            <span class="keyword">if</span> (TAG_MERGE.equals(name)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (root == <span class="keyword">null</span> || !attachToRoot) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> InflateException(<span class="string">"&lt;merge /&gt; can be used only with a valid "</span></span><br><span class="line">                            + <span class="string">"ViewGroup root and attachToRoot=true"</span>);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                rInflate(parser, root, attrs, <span class="keyword">false</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Temp is the root view that was found in the xml</span></span><br><span class="line">                View temp;</span><br><span class="line">                <span class="keyword">if</span> (TAG_1995.equals(name)) &#123;</span><br><span class="line">                    temp = <span class="keyword">new</span> BlinkLayout(mContext, attrs);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 根据 Tag 创建 View</span></span><br><span class="line">                    temp = createViewFromTag(root, name, attrs);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                ViewGroup.LayoutParams params = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (root != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="comment">// 解析 rootView 属性</span></span><br><span class="line">                    params = root.generateLayoutParams(attrs);</span><br><span class="line">                    <span class="keyword">if</span> (!attachToRoot) &#123;</span><br><span class="line">                        <span class="comment">// Set the layout params for temp if we are not</span></span><br><span class="line">                        <span class="comment">// attaching. (If we are, we use addView, below)</span></span><br><span class="line">                        temp.setLayoutParams(params);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// Inflate 子节点控件</span></span><br><span class="line">                rInflate(parser, temp, attrs, <span class="keyword">true</span>);</span><br><span class="line">                <span class="comment">// We are supposed to attach all the views we found (int temp)</span></span><br><span class="line">                <span class="comment">// to root. Do that now.</span></span><br><span class="line">                <span class="comment">// 将解析的数据添加进 root 中</span></span><br><span class="line">                <span class="keyword">if</span> (root != <span class="keyword">null</span> &amp;&amp; attachToRoot) &#123;</span><br><span class="line">                    root.addView(temp, params);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// Decide whether to return the root that was passed in or the</span></span><br><span class="line">                <span class="comment">// top view found in xml.</span></span><br><span class="line">                <span class="keyword">if</span> (root == <span class="keyword">null</span> || !attachToRoot) &#123;</span><br><span class="line">                    result = temp;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (XmlPullParserException e) &#123;</span><br><span class="line">            InflateException ex = <span class="keyword">new</span> InflateException(e.getMessage());</span><br><span class="line">            ex.initCause(e);</span><br><span class="line">            <span class="keyword">throw</span> ex;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            InflateException ex = <span class="keyword">new</span> InflateException(</span><br><span class="line">                    parser.getPositionDescription()</span><br><span class="line">                    + <span class="string">": "</span> + e.getMessage());</span><br><span class="line">            ex.initCause(e);</span><br><span class="line">            <span class="keyword">throw</span> ex;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// Don't retain static reference on context.</span></span><br><span class="line">            mConstructorArgs[<span class="number">0</span>] = lastContext;</span><br><span class="line">            mConstructorArgs[<span class="number">1</span>] = <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></p><p>接着看一下是如何通过 createViewFromTag() 创建 View 的：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">View <span class="title">createViewFromTag</span><span class="params">(View parent, String name, AttributeSet attrs)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (name.equals(<span class="string">"view"</span>)) &#123;</span><br><span class="line">        name = attrs.getAttributeValue(<span class="keyword">null</span>, <span class="string">"class"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        View view;</span><br><span class="line">        <span class="comment">// 会调用 PhoneLayoutInflater.onCreateVew()，进而调用 LayoutInflater.createView()</span></span><br><span class="line">        <span class="keyword">if</span> (mFactory2 != <span class="keyword">null</span>) view = mFactory2.onCreateView(parent, name, mContext, attrs);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (mFactory != <span class="keyword">null</span>) view = mFactory.onCreateView(name, mContext, attrs);</span><br><span class="line">        <span class="keyword">else</span> view = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (view == <span class="keyword">null</span> &amp;&amp; mPrivateFactory != <span class="keyword">null</span>) &#123;</span><br><span class="line">            view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (view == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (-<span class="number">1</span> == name.indexOf(<span class="string">'.'</span>)) &#123;</span><br><span class="line">                view = onCreateView(parent, name, attrs);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                view = createView(name, <span class="keyword">null</span>, attrs);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (DEBUG) System.out.println(<span class="string">"Created view is: "</span> + view);</span><br><span class="line">        <span class="keyword">return</span> view;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (InflateException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">        InflateException ie = <span class="keyword">new</span> InflateException(attrs.getPositionDescription()</span><br><span class="line">                + <span class="string">": Error inflating class "</span> + name);</span><br><span class="line">        ie.initCause(e);</span><br><span class="line">        <span class="keyword">throw</span> ie;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        InflateException ie = <span class="keyword">new</span> InflateException(attrs.getPositionDescription()</span><br><span class="line">                + <span class="string">": Error inflating class "</span> + name);</span><br><span class="line">        ie.initCause(e);</span><br><span class="line">        <span class="keyword">throw</span> ie;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>直接查看 LayoutInflater.createView() 方法可以发现，实际上是通过 ClassLoader 来加载类并生成实例对象。<br>回到 inflate 方法，加载子控件也是使用的此方法，有兴趣的可以自行查看。</p>]]></content>
    
    <summary type="html">
    
      上一篇讲了 Activity 的启动过程，这一篇讲 Activity 是如何解析 layout 布局的。
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Android" scheme="http://zuoyanyouwu.com/tags/Android/"/>
    
      <category term="Activity" scheme="http://zuoyanyouwu.com/tags/Activity/"/>
    
  </entry>
  
  <entry>
    <title>Activity 的启动过程分析</title>
    <link href="http://zuoyanyouwu.com/2015/07/analyse-activity-launch/"/>
    <id>http://zuoyanyouwu.com/2015/07/analyse-activity-launch/</id>
    <published>2015-07-21T02:19:20.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近开始阅读 Android 源码，从 Activity 的启动过程开始入手。</p><p>打开 Activity 源码可以发现，startActivity(Intent intent) 方法实际上是调用 startActivityForResult(Intent intent, int requestCode)：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startActivity</span><span class="params">(Intent intent)</span> </span>&#123;</span><br><span class="line">    startActivityForResult(intent, -<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>继续跟进查看 startActivityForResult(Intent intent, int requestCode)：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startActivityForResult</span><span class="params">(Intent intent, <span class="keyword">int</span> requestCode)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (mParent == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 启动新的 Activity，核心功能在 ApplicationThread 类中完成</span></span><br><span class="line">        Instrumentation.ActivityResult ar =</span><br><span class="line">            mInstrumentation.execStartActivity(<span class="keyword">this</span>,</span><br><span class="line">            mMainThread.getApplicationThread(),</span><br><span class="line">            mToken, <span class="keyword">this</span>,  intent, requestCode);</span><br><span class="line">        <span class="keyword">if</span> (ar != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 发送结果</span></span><br><span class="line">            mMainThread.sendActivityResult(mToken, mEmbeddedID,</span><br><span class="line">            requestCode, ar.getResultCode(), ar.getResultData());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (requestCode &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// If this start is requesting a result, we can avoid making</span></span><br><span class="line">        <span class="comment">// the activity visible until the result is received.  Setting</span></span><br><span class="line">        <span class="comment">// this code during onCreate(Bundle savedInstanceState)</span></span><br><span class="line">        <span class="comment">// or onResume() will keep the</span></span><br><span class="line">        <span class="comment">// activity hidden during this time, to avoid flickering.</span></span><br><span class="line">        <span class="comment">// This can only be done when a result is requested because</span></span><br><span class="line">        <span class="comment">// that guarantees we will get information back when the</span></span><br><span class="line">        <span class="comment">// activity is finished, no matter what happens to it.</span></span><br><span class="line">        mStartedActivity = <span class="keyword">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mParent.startActivityFromChild(<span class="keyword">this</span>, intent, requestCode);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>打开 Instrumentation 类，查看 execStartActivity() 方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> ActivityResult <span class="title">execStartActivity</span><span class="params">(Context who, IBinder contextThread,</span></span></span><br><span class="line"><span class="function"><span class="params">        IBinder token, Activity target, Intent intent, <span class="keyword">int</span> requestCode)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    IApplicationThread whoThread = (IApplicationThread) contextThread;</span><br><span class="line">    . . .</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        intent.setAllowFds(<span class="keyword">false</span>);</span><br><span class="line">        <span class="comment">// 打开 Activity ，具体实现在 ApplicationThread 中</span></span><br><span class="line">        <span class="keyword">int</span> result = ActivityManagerNative.getDefault().startActivity(</span><br><span class="line">                whoThread, intent,</span><br><span class="line">                intent.resolveTypeIfNeeded(who.getContentResolver()),</span><br><span class="line">                <span class="keyword">null</span>, <span class="number">0</span>, token, target != <span class="keyword">null</span> ? target.mEmbeddedID : <span class="keyword">null</span>,</span><br><span class="line">                requestCode, <span class="keyword">false</span>, <span class="keyword">false</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line">        <span class="comment">// 检查结果，如果 result &lt; 0，则会抛出各种异常，如 ActivityNotFound 等</span></span><br><span class="line">        checkStartActivityResult(result, intent);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>打开 ApplicationThread (在 ActivityThread 的中，为其内部类)，会发现 schedule**Activity(..) 类似的方法，直接查看 scheduleLaunchActivity 方法：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title">scheduleLaunchActivity</span><span class="params">(Intent intent, IBinder token,</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="keyword">int</span> ident, ActivityInfo info, Configuration curConfig,</span></span></span><br><span class="line"><span class="function"><span class="params">        CompatibilityInfo compatInfo, Bundle state, List&lt;ResultInfo&gt; pendingResults,</span></span></span><br><span class="line"><span class="function"><span class="params">        List&lt;Intent&gt; pendingNewIntents, <span class="keyword">boolean</span> notResumed, <span class="keyword">boolean</span> isForward,</span></span></span><br><span class="line"><span class="function"><span class="params">        String profileName, ParcelFileDescriptor profileFd, <span class="keyword">boolean</span> autoStopProfiler)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    ActivityClientRecord r = <span class="keyword">new</span> ActivityClientRecord();</span><br><span class="line"></span><br><span class="line">    r.token = token;</span><br><span class="line">    r.ident = ident;</span><br><span class="line">    r.intent = intent;</span><br><span class="line">    r.activityInfo = info;</span><br><span class="line">    r.compatInfo = compatInfo;</span><br><span class="line">    r.state = state;</span><br><span class="line"></span><br><span class="line">    r.pendingResults = pendingResults;</span><br><span class="line">    r.pendingIntents = pendingNewIntents;</span><br><span class="line"></span><br><span class="line">    r.startsNotResumed = notResumed;</span><br><span class="line">    r.isForward = isForward;</span><br><span class="line"></span><br><span class="line">    r.profileFile = profileName;</span><br><span class="line">    r.profileFd = profileFd;</span><br><span class="line">    r.autoStopProfiler = autoStopProfiler;</span><br><span class="line"></span><br><span class="line">    updatePendingConfiguration(curConfig);</span><br><span class="line"></span><br><span class="line">    queueOrSendMessage(H.LAUNCH_ACTIVITY, r);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>构造一个 ActivityClientRecord 后，调用 queueOrSendMessage(..) 方法，交由一个继承 Handler 的 H 类处理。<br>查看 H 类的 handleMessage(Message msg)，可以发现处理各种 Activity 的状态事件。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleMessage</span><span class="params">(Message msg)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (DEBUG_MESSAGES) Slog.v(TAG, <span class="string">"&gt;&gt;&gt; handling: "</span> + msg.what);</span><br><span class="line">    <span class="keyword">switch</span> (msg.what) &#123;</span><br><span class="line">        <span class="keyword">case</span> LAUNCH_ACTIVITY: &#123;</span><br><span class="line">            ActivityClientRecord r = (ActivityClientRecord)msg.obj;</span><br><span class="line">            r.packageInfo =</span><br><span class="line">                getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);</span><br><span class="line">            <span class="comment">// 真正启动 Activity 的地方</span></span><br><span class="line">            handleLaunchActivity(r, <span class="keyword">null</span>);</span><br><span class="line">        &#125; <span class="keyword">break</span>;</span><br><span class="line">        . . .</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br>在 handleLaunchActivity(ActivityClientRecord r, Intent customIntent) 中，会通过 performLaunchActivity(ActivityClientRecord r, Intent customIntent) 创建一个 Activity，查看方法实现：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Activity <span class="title">performLaunchActivity</span><span class="params">(ActivityClientRecord r,</span></span></span><br><span class="line"><span class="function"><span class="params">        Intent customIntent)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    ActivityInfo aInfo = r.activityInfo;</span><br><span class="line">    <span class="keyword">if</span> (r.packageInfo == <span class="keyword">null</span>) &#123;</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">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从 Intent 中解析目标 Activity 的启动参数</span></span><br><span class="line">    ComponentName component = r.intent.getComponent();</span><br><span class="line">    <span class="keyword">if</span> (component == <span class="keyword">null</span>) &#123;</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">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (r.activityInfo.targetActivity != <span class="keyword">null</span>) &#123;</span><br><span class="line">        component = <span class="keyword">new</span> ComponentName(r.activityInfo.packageName,</span><br><span class="line">            r.activityInfo.targetActivity);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Activity activity = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();</span><br><span class="line">        <span class="comment">// 通过 ClassLoader 实例化 Activity 对象</span></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">        <span class="keyword">if</span> (r.state != <span class="keyword">null</span>) &#123;</span><br><span class="line">            r.state.setClassLoader(cl);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mInstrumentation.onException(activity, e)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(</span><br><span class="line">                <span class="string">"Unable to instantiate activity "</span> + component</span><br><span class="line">                + <span class="string">": "</span> + e.toString(), e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Application app = r.packageInfo.makeApplication(<span class="keyword">false</span>, mInstrumentation);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (localLOGV) Slog.v(TAG, <span class="string">"Performing launch of "</span> + r);</span><br><span class="line">        <span class="keyword">if</span> (localLOGV) Slog.v(</span><br><span class="line">            TAG, r + <span class="string">": app="</span> + app</span><br><span class="line">            + <span class="string">", appName="</span> + app.getPackageName()</span><br><span class="line">            + <span class="string">", pkg="</span> + r.packageInfo.getPackageName()</span><br><span class="line">            + <span class="string">", comp="</span> + r.intent.getComponent().toShortString()</span><br><span class="line">            + <span class="string">", dir="</span> + r.packageInfo.getAppDir());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (activity != <span class="keyword">null</span>) &#123;</span><br><span class="line">            ContextImpl appContext = <span class="keyword">new</span> ContextImpl();</span><br><span class="line">            appContext.init(r.packageInfo, r.token, <span class="keyword">this</span>);</span><br><span class="line">            appContext.setOuterContext(activity);</span><br><span class="line">            CharSequence title =</span><br><span class="line">                r.activityInfo.loadLabel(appContext.getPackageManager());</span><br><span class="line">            Configuration config = <span class="keyword">new</span> Configuration(mCompatConfiguration);</span><br><span class="line">            <span class="keyword">if</span> (DEBUG_CONFIGURATION) Slog.v(TAG, <span class="string">"Launching activity "</span></span><br><span class="line">                + r.activityInfo.name + <span class="string">" with config "</span> + config);</span><br><span class="line">                activity.attach(appContext, <span class="keyword">this</span>, 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"></span><br><span class="line">            <span class="keyword">if</span> (customIntent != <span class="keyword">null</span>) &#123;</span><br><span class="line">                activity.mIntent = customIntent;</span><br><span class="line">            &#125;</span><br><span class="line">            r.lastNonConfigurationInstances = <span class="keyword">null</span>;</span><br><span class="line">            activity.mStartedActivity = <span class="keyword">false</span>;</span><br><span class="line">            <span class="keyword">int</span> theme = r.activityInfo.getThemeResource();</span><br><span class="line">            <span class="keyword">if</span> (theme != <span class="number">0</span>) &#123;</span><br><span class="line">                activity.setTheme(theme);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            activity.mCalled = <span class="keyword">false</span>;</span><br><span class="line">            <span class="comment">// 调用 Activity 的 onCreate 方法，开始进入 Activity 启动的生命周期</span></span><br><span class="line">            mInstrumentation.callActivityOnCreate(activity, r.state);</span><br><span class="line">            <span class="keyword">if</span> (!activity.mCalled) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> SuperNotCalledException(</span><br><span class="line">                    <span class="string">"Activity "</span> + r.intent.getComponent().toShortString() +</span><br><span class="line">                    <span class="string">" did not call through to super.onCreate()"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            r.activity = activity;</span><br><span class="line">            r.stopped = <span class="keyword">true</span>;</span><br><span class="line">            <span class="keyword">if</span> (!r.activity.mFinished) &#123;</span><br><span class="line">                <span class="comment">// 调用 Activity 的 onStart 方法</span></span><br><span class="line">                activity.performStart();</span><br><span class="line">                r.stopped = <span class="keyword">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!r.activity.mFinished) &#123;</span><br><span class="line">                <span class="keyword">if</span> (r.state != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    mInstrumentation.callActivityOnRestoreInstanceState(activity,</span><br><span class="line">                        r.state);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!r.activity.mFinished) &#123;</span><br><span class="line">                activity.mCalled = <span class="keyword">false</span>;</span><br><span class="line">                mInstrumentation.callActivityOnPostCreate(activity, r.state);</span><br><span class="line">                <span class="keyword">if</span> (!activity.mCalled) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> SuperNotCalledException(</span><br><span class="line">                        <span class="string">"Activity "</span> + r.intent.getComponent().toShortString() +</span><br><span class="line">                        <span class="string">" did not call through to super.onPostCreate()"</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        r.paused = <span class="keyword">true</span>;</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">    &#125; <span class="keyword">catch</span> (SuperNotCalledException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line"></span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!mInstrumentation.onException(activity, e)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(</span><br><span class="line">                <span class="string">"Unable to start activity "</span> + component</span><br><span class="line">                + <span class="string">": "</span> + e.toString(), e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> activity;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>至此，Activity 已经完成创建和启动，在 performLaunchActivity 执行完毕后，会继续执行 Activity 的 onResume 等方法。</p>]]></content>
    
    <summary type="html">
    
      Activity 作为 Android 中最重要的组件，经常会用到，那么对 Activity 的启动过程是否了解呢？
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="Android" scheme="http://zuoyanyouwu.com/tags/Android/"/>
    
      <category term="Activity" scheme="http://zuoyanyouwu.com/tags/Activity/"/>
    
  </entry>
  
  <entry>
    <title>开始写博客</title>
    <link href="http://zuoyanyouwu.com/2015/07/start-writing-blog/"/>
    <id>http://zuoyanyouwu.com/2015/07/start-writing-blog/</id>
    <published>2015-07-17T07:28:46.000Z</published>
    <updated>2018-01-12T09:08:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用 GithubPages 和 jekyll 搭建了一个简易的博客站点，开始写博客了。</p><p><strong>参考：</strong></p><blockquote><ol><li><a href="http://jekyllrb.com" target="_blank" rel="noopener">Jekyll docs</a></li><li><a href="https://github.com/mojombo/jekyll" target="_blank" rel="noopener">Jekyll’s GitHub repo</a>.</li></ol></blockquote>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;使用 GithubPages 和 jekyll 搭建了一个简易的博客站点，开始写博客了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;http://jekyllrb.com&quot; target=&quot;
      
    
    </summary>
    
      <category term="Technology" scheme="http://zuoyanyouwu.com/categories/Technology/"/>
    
    
      <category term="jekyll" scheme="http://zuoyanyouwu.com/tags/jekyll/"/>
    
      <category term="GithubPages" scheme="http://zuoyanyouwu.com/tags/GithubPages/"/>
    
  </entry>
  
</feed>
