<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  
  <title><![CDATA[Jason's Blog]]></title>
  <subtitle><![CDATA[无他，但手熟耳]]></subtitle>
  <link href="/atom.xml" rel="self"/>
  <link href="http://jiajixin.cn//"/>
  <updated>2017-03-18T11:13:04.000Z</updated>
  <id>http://jiajixin.cn//</id>
  
  <author>
    <name><![CDATA[Jason Ross]]></name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title><![CDATA[编写多类型列表视图，哪家强？]]></title>
    <link href="http://jiajixin.cn/2016/10/12/android_multi_type_list_adapter/"/>
    <id>http://jiajixin.cn/2016/10/12/android_multi_type_list_adapter/</id>
    <published>2016-10-12T15:25:55.000Z</published>
    <updated>2017-03-18T11:13:04.000Z</updated>
    <content type="html"><![CDATA[<p>在日常开发中，我们经常会遇到一些复杂的列表视图，一般都是通过多个viewtype的方式来解决，添加类型常量和ViewHolder，步骤繁琐，各种if/else判断，对扩展也不太友好。</p>
<p>很多人都在试图简化多类型的写法，在Github就有不少的开源项目。</p>
<h3 id="MultiType">MultiType</h3><p>@drakeet 的<a href="https://github.com/drakeet/MultiType" target="_blank" rel="external">MultiType</a>，添加了Item和ItemViewProvider的概念，通过全局类型池连接Item和ItemViewProvider，只需要提供Item列表，就可以自动填充View了。</p>
<p>感觉Item的概念有些强硬，有的Cell根本不需要Item只需要一个layout，这种情况下去创建一个空Item有些多余，当然也不是什么大问题。</p>
<h3 id="DataBindingAdapter">DataBindingAdapter</h3><p>@markzhai 的<a href="https://github.com/markzhai/DataBindingAdapter" target="_blank" rel="external">DataBindingAdapter</a>，主要是使用DataBinding库简化了数据绑定的操作，数据和layout之间通过viewtype连接。</p>
<p>还是要有各种类型常量，有些麻烦。</p>
<h3 id="Epoxy">Epoxy</h3><p>@airbnb 的 <a href="https://github.com/airbnb/epoxy" target="_blank" rel="external">Epoxy</a>添加了Model的概念，相当于是把ItemViewProvider和Item直接放在一起，Item可有可无，layoutId单独提出来作为viewType，为了避免在spanSizeLookup中各种instanceOf，spanSize放在了Model中，可以自由配置。</p>
<p>不强制要求Item，不需要类型常量，Epoxy的使用更加简单一些。</p>
<p>Epoxy也对Model操作进行了封装，支持insertModelBefore等类似操作，可以很方便的局部刷新。比如一个页面中，有上、下两部分都在进行网络请求，上面的数据返回后，需要修改ModelList，可以直接执行insertModelBefore(loadingModel)。我们现在项目中主要使用的是ListView，这个情况主要是通过Merge上、下两个Adapter，监听两个Adapter的数据变化，也是不错的思路，但是做不到局部刷新，性能上不是很好，当然这是ListView和RecyclerView的区别。</p>
<p>Epoxy也做了自动Diff，在Android官方的DiffUtil前，后面可能会提供两种算法支持。</p>
<hr>
<p>之前在贴吧做iOS的时候，几乎所有的页面都是用UITableView实现的。Android上也可以这么做，RecyclerView+GridLayoutManager直接搞定大部分页面，再加上Epoxy这样的库，如虎添翼。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>在日常开发中，我们经常会遇到一些复杂的列表视图，一般都是通过多个viewtype的方式来解决，添加类型常量和ViewHolder，步骤繁琐，各种if/else判断，对扩展也不太友好。</p>
<p>很多人都在试图简化多类型的写法，在Github就有不少的开源项目。</p>
]]>
    </summary>
    
      <category term="Adapter" scheme="http://jiajixin.cn/tags/Adapter/"/>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Epoxy" scheme="http://jiajixin.cn/tags/Epoxy/"/>
    
      <category term="ListView" scheme="http://jiajixin.cn/tags/ListView/"/>
    
      <category term="MultiType" scheme="http://jiajixin.cn/tags/MultiType/"/>
    
      <category term="RecyclerView" scheme="http://jiajixin.cn/tags/RecyclerView/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android App 适配RTL]]></title>
    <link href="http://jiajixin.cn/2016/10/08/android_adapt_rtl/"/>
    <id>http://jiajixin.cn/2016/10/08/android_adapt_rtl/</id>
    <published>2016-10-08T15:51:19.000Z</published>
    <updated>2017-03-18T11:04:49.000Z</updated>
    <content type="html"><![CDATA[<p>我们的APP打算进入阿拉伯市场，但阿拉伯语不同于其他语言，布局和文字方向都是从右向左，也就是本文要讲的RTL语言，right-to-left。</p>
<p>下面对RTL的适配进行一下总结。</p>
<h3 id="AS一键适配">AS一键适配</h3><p>AS支持一键适配RTL，主要是更新Manifest文件和在Layout中添加Start和End属性（你是不是从来没有用过paddingStart、marginStart?)</p>
<p>Start属性在LTR中对应Left，在RTL中对应Right，在API 17开始支持，为了兼容低版本，需要同时有Left和Start. </p>
<p><img src="http://mmbiz.qpic.cn/mmbiz_png/Nb5MFRT1ibkcJkVwkIibfWXEJK8RTMGzpu2N4c5oric3jibmX0cuumQaayl6CDq2IE2ibXx5JTSSaCYLw3cESuakoicw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt=""></p>
<h3 id="图标翻转">图标翻转</h3><p>有些图标（比如箭头）本身是有方向的，在RTL下也需要进行翻转。可以使用autoMirrored特性，API 19开始支持。</p>
<h3 id="动画翻转">动画翻转</h3><p>可以新建anim-ldrtl文件夹，将对应的动画进行反向处理。</p>
<h3 id="include/style不支持">include/style不支持</h3><p>AS的一键化工具不支持include和style标签，需要自己手动添加Start/End。</p>
<h3 id="TextView_wrap_content">TextView wrap_content</h3><p>大多数情况下，TextView的宽度使用wrap_content而不是match_parent, 在RTL显示英文会更正常。</p>
<h3 id="paddingleft和paddingStart">paddingleft和paddingStart</h3><p>如果同时设置了paddingLeft和paddingStart，而没有设置paddingRight，在RTL下，会同时具备paddingLeft和paddingRight属性，显示会出现问题。可以考虑使用margin替换padding，同时设置marginLeft和marginStart, 在RTL下只会有marginLeft属性。</p>
<h3 id="ViewPager适配">ViewPager适配</h3><p>android官方控件大多支持RTL，ViewPager除外，可以考虑将list翻转处理。</p>
<h3 id="自定义view适配">自定义view适配</h3><p>对于自定义View，也要单独针对RTL进行适配。判断RTL代码如下：<br><img src="http://mmbiz.qpic.cn/mmbiz_png/Nb5MFRT1ibkcJkVwkIibfWXEJK8RTMGzpu09MWLyYibxCX8tfs2HoibBZwHIat7zOJHGN3xP2iatKRkWiavd32rE3qTw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt=""></p>
<h3 id="RTL预览">RTL预览</h3><p>AS在xml文件的Design标签下支持RTL预览，在日常开发中可以使用。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>我们的APP打算进入阿拉伯市场，但阿拉伯语不同于其他语言，布局和文字方向都是从右向左，也就是本文要讲的RTL语言，right-to-left。</p>
<p>下面对RTL的适配进行一下总结。</p>
<h3 id="AS一键适配">AS一键适配</h3><p>AS支持一键适]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="RTL" scheme="http://jiajixin.cn/tags/RTL/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android 组件化思考]]></title>
    <link href="http://jiajixin.cn/2016/09/28/android_modularization/"/>
    <id>http://jiajixin.cn/2016/09/28/android_modularization/</id>
    <published>2016-09-27T16:16:16.000Z</published>
    <updated>2017-03-18T10:59:43.000Z</updated>
    <content type="html"><![CDATA[<p>4月份曾在薄荷做过一个关于组件化的演讲，讲解了大众点评Android客户端在组件化方面的实践。</p>
<h2 id="点评方案">点评方案</h2><p>随着业务的不断发展，点评Android客户端已经由团购、预订、结婚等多个团队协同开发，当时进行组件化主要有两个原因：</p>
<ol>
<li>解耦隔离，减少彼此之间的影响。</li>
<li>提高代码复用性，当业务拓展需要开发新App的时候，可以直接复用之前的底层库。</li>
</ol>
<p>在Android上，组件化进行模块拆分，首先可以拆分为多Module，但拆分为多Module后，数十个Module跑Gradle的task会对构建速度产生影响，所以我们需要改源码集成为二进制集成，子Module全部打包生成AAR，由一个壳工程去组装构建APK，使用AAR集成后构建速度得到了很大的提升。</p>
<p>为了提高更强的隔离性，各个团队各玩各的，责任明确，我们还把每个Module都拆分成独立的代码仓库。</p>
<p><img src="http://mmbiz.qpic.cn/mmbiz_jpg/Nb5MFRT1ibkcbaBet4fbR2ic6wcCHrKhUjbc6ZOu93Y90sLPoTfeopf70MiagOIibYmCr27EFY7MM1XBmYTR17LorQ/640?wx_fmt=jpeg&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt=""></p>
<h2 id="影响">影响</h2><p>采用组件化模式进行开发后，对开发也发生了比较大的影响。</p>
<ol>
<li>默认AAR集成，源码调试的时候，需要把代码拉下来，并且修改gradle配置文件，当然这个可以有一套自动化的机制。</li>
<li>你需要在合并代码后去发布AAR，如果上下层都有修改，你需要先发布下层的AAR，再发布上层的AAR，有人还可能忘记进行AAR的发布。</li>
<li>版本号的升级和同步需要控制，下层版本号修改，上层引用怎么改，自动还是手动？</li>
<li>底层库修改了某个方法签名，上层库使用AAR集成，可能编译时不会报错，运行时报错，如果使用全源码编译检查，耗时很长。</li>
<li>Field超65535，需要编译时修改class文件。</li>
<li>如果项目依赖比较复杂，ManifestMerge时间耗时很长，导致AS卡死。</li>
</ol>
<p>从上面可以看到，组件化在某种程度上降低了开发效率，你很难说组件化利大于弊，我更觉得组件化是件不得已而为之的事情。</p>
<h2 id="冯氏方案">冯氏方案</h2><p>上周MDCC大会上，冯森林做了一场《回归初心，从容器化到组件化》的演讲，讲解了他的组件化方案。</p>
<p><img src="http://mmbiz.qpic.cn/mmbiz_png/Nb5MFRT1ibkcbaBet4fbR2ic6wcCHrKhUjianbUXEeRKgbpwMGCCOm95z2WReIcWntic7KWP1ibfLILB3yzN9OwraXQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1" alt=""></p>
<p>一种轻量的组件化方案，亮点是module在debug模式下作为application，在release模式下作为library，通过共享UID多个module APK运行在同一进程。通过这种方式，各个Module独立运行安装，编译速度也有很大的提升。</p>
<h2 id="反思">反思</h2><p>看到冯老师的组件化方案，我反思了一下之前在点评的做法。</p>
<ol>
<li><p>底层库打包AAR是没有问题的，当然冯氏方案没有提及太多AAR的事情，当项目之间共享底层库的时候，AAR远程依赖是一种不错的方式。</p>
</li>
<li><p>之前为了提高编译速度，我们把上层业务代码（团购、电影等）也打包生成了AAR，这就造成平常开发需要不断发布AAR，尤其是项目灰度后，每次可能是最后一次修改，修改代码后就要发布AAR。</p>
<p> 但如果我们采用冯氏方案，上层业务代码就没有打AAR的必要了，可以单库独立编译运行，dex和install时间缩短，只是首次完整编译耗时相对较长，但省去AAR发布的烦恼。同时，单库资源ID不超过65535，不需要编译时修改class文件，省去部分编译时间。单库依赖一般也不会导致ManifestMerge耗时很长。</p>
<p> 那如果上层业务代码不打包AAR，怎么集合生成APK呢？要么上层业务代码就不要分库，直接多Module集合在一个项目下，要么拉取源代码修改gradle文件组装项目。第二种集合方式可能不够优雅，需要遵守一些规范，同时完整编译一个项目耗时较长。一般来说，上层业务模块分库的必要性没有那么大，如果分库不那么重要的话，可以考虑第一种方案。</p>
</li>
</ol>
]]></content>
    <summary type="html">
    <![CDATA[<p>4月份曾在薄荷做过一个关于组件化的演讲，讲解了大众点评Android客户端在组件化方面的实践。</p>
<h2 id="点评方案">点评方案</h2><p>随着业务的不断发展，点评Android客户端已经由团购、预订、结婚等多个团队协同开发，当时进行组件化主要有两个原因：<]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="组件化" scheme="http://jiajixin.cn/tags/%E7%BB%84%E4%BB%B6%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[国内Top500Android应用分析报告]]></title>
    <link href="http://jiajixin.cn/2016/07/11/domestic_top_500_android_app_analysis/"/>
    <id>http://jiajixin.cn/2016/07/11/domestic_top_500_android_app_analysis/</id>
    <published>2016-07-10T16:16:16.000Z</published>
    <updated>2017-03-18T10:48:11.000Z</updated>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2><p>笔者目前在开发的应用在线上有不少OOM的问题，经过一番优化后，OOM降了一个量级，但由于产品中Gif是一特色，内存占用优化空间比较有限，想要减少OOM最简单的办法可能就是开启largeHeap。Google官方是不推荐使用largeheap的，更大的内存意味着更长的垃圾回收时间，所以我就想看看国内的这些应用是否有打开largeheap。用AS的APK Analyzer看了下微信和QQ空间，发现大家都打开了，然后我就安心了。</p>
<p>后来，我们打算把Jackson库换掉，太占用方法数了，可选择的有Gson和Fastjson。同样去看看国内的App都在用什么吧，本以为Fastjson号称速度最快，国内又是主场，应该用的人更多一些，但在手动分析了几个APP后，发现大家都在用Gson，Fastjson反而没见到几个。</p>
<p>再后来，有一天晚上，好像看了篇TK教主的文章就来了灵感，觉得可以批量分析一下国内主流的APP，看看大家都在用什么库，教父整天说据我所知，国内用哪些团队在用rxjava，我可以来个更全面准确的分析，顺便可以看看我的Nuwa是否有人在用？</p>
<h2 id="分析">分析</h2><p>豌豆荚正好那几天被收购了，那就拿他开刀吧，脚本下载了排行榜（周下载量）Top500的应用，使用Android SDK自带的appt和dexdump，分析Manifest文件和dex文件，拿到一些APK基本信息和dex中的Class Descriptor。</p>
<p>有些有插件化能力的或者没有用MultiDex的应用，可能dex不在常规位置，比如淘宝把dex伪装成.so文件，所以需要查看文件的magic number来找到dex文件。</p>
<p>涉及到数据的排序和整理，所以使用了mysql数据库，Class Descriptor通过package分段聚合，package最多保留四段，比如Lcom/jiajixin/nuwa/utils/dex/,会有<code>com/*</code>、<code>com/jiajixin/</code>、<code>com/jiajixin/nuwa/</code>、<code>com/jiajixin/nuwa/utils/</code>入库,然后group by后根据count倒序输出，人工匹配对应库名。</p>
<h2 id="数据">数据</h2><p>废话不多说，直接展示数据。</p>
<h3 id="APK基本信息">APK基本信息</h3><p><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-0%402x.png" alt="minSdkVersion"></p>
<p><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-1%402x.png" alt="largeHeap"></p>
<p><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-2%402x.png" alt="process count"><br><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-4%402x.png" alt="method count"></p>
<h3 id="开源项目">开源项目</h3><p>Network:</p>
<table>
<thead>
<tr>
<th>Okttp</th>
<th>Volley</th>
<th>android-async-http</th>
</tr>
</thead>
<tbody>
<tr>
<td>135</td>
<td>105</td>
<td>58</td>
</tr>
</tbody>
</table>
<p>ImagecLoader:</p>
<table>
<thead>
<tr>
<th>Universal Image Loader</th>
<th>Fresco</th>
<th>Glide</th>
<th>Picasso</th>
</tr>
</thead>
<tbody>
<tr>
<td>130</td>
<td>88</td>
<td>54</td>
<td>38</td>
</tr>
</tbody>
</table>
<p>Dependency Injection:</p>
<table>
<thead>
<tr>
<th>ButterKnife</th>
<th>Dagger</th>
<th>Android Annotations</th>
<th>Roboguice</th>
</tr>
</thead>
<tbody>
<tr>
<td>63</td>
<td>16</td>
<td>11</td>
<td>5</td>
</tr>
</tbody>
</table>
<p>Json:</p>
<table>
<thead>
<tr>
<th>Gson</th>
<th>Fastjson</th>
<th>Jackson</th>
</tr>
</thead>
<tbody>
<tr>
<td>227</td>
<td>87</td>
<td>26</td>
</tr>
</tbody>
</table>
<p>Protocol Buffers:</p>
<table>
<thead>
<tr>
<th>Wire</th>
<th>Protobuf</th>
</tr>
</thead>
<tbody>
<tr>
<td>72</td>
<td>67</td>
</tr>
</tbody>
</table>
<p>DataBase:</p>
<table>
<thead>
<tr>
<th>GreenDao</th>
<th>OrmLite</th>
</tr>
</thead>
<tbody>
<tr>
<td>41</td>
<td>27</td>
</tr>
</tbody>
</table>
<p>Event:</p>
<table>
<thead>
<tr>
<th>EventBus</th>
<th>Otto</th>
</tr>
</thead>
<tbody>
<tr>
<td>132</td>
<td>12</td>
</tr>
</tbody>
</table>
<p>HotFix:</p>
<table>
<thead>
<tr>
<th>Andfix</th>
<th>Dexposed</th>
<th>Nuwa</th>
</tr>
</thead>
<tbody>
<tr>
<td>34</td>
<td>24</td>
<td>7</td>
</tr>
</tbody>
</table>
<p>Plugin:</p>
<table>
<thead>
<tr>
<th>Droidplugin</th>
<th>Dynamic-Load-Apk</th>
<th>DynamicAPK</th>
<th>Pluginmgr</th>
</tr>
</thead>
<tbody>
<tr>
<td>8</td>
<td>5</td>
<td>7</td>
<td>1</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>PullToRefresh:</p>
<table>
<thead>
<tr>
<th>Android-PullToRefresh</th>
<th>android-Ultra-Pull-To-Refresh</th>
</tr>
</thead>
<tbody>
<tr>
<td>92</td>
<td>27</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Name</th>
<th>Users </th>
</tr>
</thead>
<tbody>
<tr>
<td>NineOldAndroids</td>
<td>150</td>
</tr>
<tr>
<td>zxing</td>
<td>121</td>
</tr>
<tr>
<td>okio</td>
<td>119</td>
</tr>
<tr>
<td>thrift</td>
<td>97</td>
</tr>
<tr>
<td>bolts</td>
<td>76</td>
</tr>
<tr>
<td>android-gif-drawable</td>
<td>72</td>
</tr>
<tr>
<td>spdy</td>
<td>63</td>
</tr>
<tr>
<td>photoview</td>
<td>62</td>
</tr>
<tr>
<td>iflytek</td>
<td>61</td>
</tr>
<tr>
<td>rxjava</td>
<td>50</td>
</tr>
<tr>
<td>pinyin4j</td>
<td>46</td>
</tr>
<tr>
<td>viewpagerindicator</td>
<td>46</td>
</tr>
<tr>
<td>jsr305</td>
<td>42</td>
</tr>
<tr>
<td>ijkplayer</td>
<td>41</td>
</tr>
<tr>
<td>DanmakuFlameMaster</td>
<td>36</td>
</tr>
<tr>
<td>retrofit</td>
<td>26</td>
</tr>
<tr>
<td>DragSortListView</td>
<td>23</td>
</tr>
<tr>
<td>exoplayer</td>
<td>23</td>
</tr>
<tr>
<td>MPAndroidChart</td>
<td>23</td>
</tr>
<tr>
<td>rebound</td>
<td>22</td>
</tr>
<tr>
<td>jsoup</td>
<td>21</td>
</tr>
<tr>
<td>leakcanary</td>
<td>21</td>
</tr>
<tr>
<td>android-gpuimage</td>
<td>20</td>
</tr>
<tr>
<td>daimajia</td>
<td>20</td>
</tr>
<tr>
<td>xutils</td>
<td>19</td>
</tr>
<tr>
<td>Android-wheel</td>
<td>18</td>
</tr>
<tr>
<td>roundedimageview</td>
<td>18</td>
</tr>
<tr>
<td>PagerSlidingTabStrip</td>
<td>16</td>
</tr>
<tr>
<td>SmoothProgressBar</td>
<td>16</td>
</tr>
<tr>
<td>stetho</td>
<td>16</td>
</tr>
<tr>
<td>aspectj</td>
<td>15</td>
</tr>
<tr>
<td>bouncycastle</td>
<td>14</td>
</tr>
<tr>
<td>soloader</td>
<td>14</td>
</tr>
<tr>
<td>tagsoup</td>
<td>14</td>
</tr>
<tr>
<td>vitamio</td>
<td>14</td>
</tr>
<tr>
<td>dom4j</td>
<td>13</td>
</tr>
<tr>
<td>afinal</td>
<td>12</td>
</tr>
<tr>
<td>cropper</td>
<td>12</td>
</tr>
<tr>
<td>Android-Easing</td>
<td>11</td>
</tr>
<tr>
<td>react-native</td>
<td>11</td>
</tr>
<tr>
<td>slidingmenu</td>
<td>11</td>
</tr>
<tr>
<td>zip4j</td>
<td>11</td>
</tr>
<tr>
<td>disklrucache</td>
<td>10</td>
</tr>
<tr>
<td>swipebacklayout</td>
<td>10</td>
</tr>
<tr>
<td>java_websocket</td>
<td>9</td>
</tr>
<tr>
<td>realm</td>
<td>4</td>
</tr>
</tbody>
</table>
<p>这些产品在使用rxjava：<br><img src="http://7fviov.com1.z0.glb.clouddn.com/ECharts.png" alt=""></p>
<p>这些产品在使用react-native：<br><img src="http://7fviov.com1.z0.glb.clouddn.com/rn.png" alt=""></p>
<p>上面这两张图献给教父。</p>
<h3 id="第三方服务">第三方服务</h3><p><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-5%402x.png" alt=""><br><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-6%402x.png" alt=""><br><img src="http://7fviov.com1.z0.glb.clouddn.com/QQ20160711-7%402x.png" alt=""></p>
]]></content>
    <summary type="html">
    <![CDATA[<h2 id="背景">背景</h2><p>笔者目前在开发的应用在线上有不少OOM的问题，经过一番优化后，OOM降了一个量级，但由于产品中Gif是一特色，内存占用优化空间比较有限，想要减少OOM最简单的办法可能就是开启largeHeap。Google官方是不推荐使用largehe]]>
    </summary>
    
      <category term="APP" scheme="http://jiajixin.cn/tags/APP/"/>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="RN" scheme="http://jiajixin.cn/tags/RN/"/>
    
      <category term="RxJava" scheme="http://jiajixin.cn/tags/RxJava/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android优质信息源推荐]]></title>
    <link href="http://jiajixin.cn/2016/05/07/excellent_blogs/"/>
    <id>http://jiajixin.cn/2016/05/07/excellent_blogs/</id>
    <published>2016-05-06T16:16:16.000Z</published>
    <updated>2017-03-18T10:40:45.000Z</updated>
    <content type="html"><![CDATA[<p>每一个Android开发者，都应该关注行业动态，及时获取开发信息，这样可以开阔视野，提高技术实力。然而在这个信息爆炸的时代，还是要宁缺毋滥，况且网络鱼龙混杂，不乏滥竽充数之辈，所以你需要订阅一些优质的信息源。</p>
<p>下面是我个人日常订阅的信息源，主要包含博客、微博和微信公众号三种类型，基本上可以覆盖各种Android开发的最新资讯。</p>
<h2 id="博客">博客</h2><p>博客需要订阅，GR远去后，最理想的RSS工具应该要属Feedly了，有Chrome插件，可以及时收到博客更新通知。</p>
<h3 id="团队博客">团队博客</h3><ul>
<li><p><a href="http://android-developers.blogspot.com/" target="_blank" rel="external">http://android-developers.blogspot.com/</a></p>
<p>  Android官方博客，android官方最新的一些动态，比如系统新版本特性，Instant Run技术等一般都会在这首发介绍。</p>
</li>
<li><p><a href="https://sites.google.com/a/android.com/tools/recent" target="_blank" rel="external">https://sites.google.com/a/android.com/tools/recent</a></p>
<p>  相比起官方博客，这个网站订阅的人就比较少了，里面主要是一些Android Studio更新日志和新特性介绍，AS作为我们日常使用的开发工具，跟踪了解其发展，有利于提高开发效率。</p>
</li>
<li><p><a href="http://tech.meituan.com/" target="_blank" rel="external">http://tech.meituan.com/</a></p>
<p>  美团技术团队博客，Android方向的每一篇文章都是精品。美团内部使用Confluence，鼓励员工写Wiki，有幸看过很多文章，但精华都放到此博客上了。</p>
</li>
</ul>
<h3 id="个人博客">个人博客</h3><p>现在写博客的人少了，写独立博客的更是少之又少。CSDN也有很多优秀的博客，之前也订阅过一些，感觉有些内容不是很感兴趣，大多数都退订了。无妨，失之东隅，收之桑榆，这边看不到，微博上很可能就看到了。</p>
<ul>
<li><p><a href="http://blog.zhaiyifan.cn/" target="_blank" rel="external">http://blog.zhaiyifan.cn/</a></p>
<p>  翟一帆，BlockCanary作者，前腾讯、阿里工程师，目前创业中。我曾说这个行业里崔健很少、林忆莲比较多，他应该属于崔健这种的。基础扎实，文风浮夸，喜欢装逼，今年高产，各种系列连载。</p>
</li>
<li><p><a href="https://drakeet.me/" target="_blank" rel="external">https://drakeet.me/</a></p>
<p>  许晓峰，Android自学成才，贝壳单词作者，Github有不少开源项目，现在阿里工作。文章朴实，很多是自己开源项目的介绍或者技术成长经历。</p>
</li>
<li><p><a href="http://www.race604.com/" target="_blank" rel="external">http://www.race604.com/</a></p>
<p>  吴晶，Qing Camera作者，就职于小米，微博@Android笔记。博客之前有Android周报，现在好像不怎么连载了。</p>
</li>
<li><p><a href="http://weishu.me/" target="_blank" rel="external">http://weishu.me/</a></p>
<p>  田维术，就职于360。博客中讲了一些插件化的东西，作者自己说『坚决不要纪录性内容，避免各种环境搭建，BUG解决等碎碎念内容；确保内容高质量。』</p>
</li>
<li><p><a href="http://mrpeak.cn/" target="_blank" rel="external">http://mrpeak.cn/</a></p>
<p>  一个学长的博客，前天猫员工。Android和iOS通吃，文章蛮有深度，看过一篇《HTTP 2.0的那些事》，感觉讲的不错。 </p>
</li>
<li><p><a href="http://www.androidweekly.cn/" target="_blank" rel="external">http://www.androidweekly.cn/</a></p>
<p>  Android开发技术周报，作者张会超，同时也是<a href="http://www.androiddevtools.cn/" target="_blank" rel="external">http://www.androiddevtools.cn/</a> 的站长。持续连载，几十年如一日，辛苦辛苦。</p>
</li>
</ul>
<h2 id="微博">微博</h2><p>微博上之前关注了很多人，有很多噪音，现在精简到这么几个人，大都是相互关注的朋友，很多都在一些大公司，具备视野和深度。</p>
<ul>
<li>@Trinea</li>
<li>@胡凯me</li>
<li>@hi大头鬼hi</li>
<li>@任玉刚Coder</li>
<li>@light_sky</li>
<li>@陈启超_V</li>
<li>@脉脉不嘚語</li>
<li>@宅一番markzhai</li>
<li>@liangfeizc-Android</li>
<li>@愚公编程MrPeak</li>
</ul>
<h2 id="微信公众号">微信公众号</h2><p>公众号获取信息的比例越来越高，文章质量也比较好。微信公众号，我只关注两个，腾讯和其他。</p>
<h3 id="腾讯系">腾讯系</h3><ul>
<li>WeMobileDev</li>
<li>QQ音乐技术团队</li>
<li>QQ空间终端开发团队</li>
<li>腾讯Bugly</li>
</ul>
<h3 id="其他">其他</h3><ul>
<li><p>移动开发前线</p>
<p>  infoQ背景，投稿作者很多来自BAT等大公司，资源优势，文章质量较高。</p>
</li>
<li><p>Android程序员</p>
<p>  海豚浏览器的汤涛在维护，投稿模式，运营的很不错，文章质量较高。</p>
</li>
<li><p>codeKK</p>
<p>  Trinea在维护，不多说了。</p>
</li>
</ul>
<hr>
<p>独学而无友，则孤陋而寡闻。大家可以尝试去联系你认可的作者，大多数程序员人都非常nice，我看到很多人通过网上交流就认识了，成为工作学习上的良师益友。</p>
<p>有些人工作了一段时间，比较迷茫，感觉没有什么提升，我觉得可以多关注一些信息，开阔视野，尝试去研究一些技术点。</p>
<p>几年前，也有人整理过类似的信息源，很多现在都不怎么活跃了，长江后浪推前浪，说不准某年某天，你就会被某个高二的天才少年，拍死在沙滩上。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>每一个Android开发者，都应该关注行业动态，及时获取开发信息，这样可以开阔视野，提高技术实力。然而在这个信息爆炸的时代，还是要宁缺毋滥，况且网络鱼龙混杂，不乏滥竽充数之辈，所以你需要订阅一些优质的信息源。</p>
<p>下面是我个人日常订阅的信息源，主要包含博客、微博和]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Blog" scheme="http://jiajixin.cn/tags/Blog/"/>
    
      <category term="信息" scheme="http://jiajixin.cn/tags/%E4%BF%A1%E6%81%AF/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[在路上·2015]]></title>
    <link href="http://jiajixin.cn/2015/12/26/ontheway-2015/"/>
    <id>http://jiajixin.cn/2015/12/26/ontheway-2015/</id>
    <published>2015-12-26T15:05:41.000Z</published>
    <updated>2017-03-18T10:21:45.000Z</updated>
    <content type="html"><![CDATA[<p>去年写了篇《在路上·2014》，今年写《在路上·2015》，看来在路上要成为一个系列了。</p>
<h2 id="在路上">在路上</h2><p>高中的时候，看过《赢在中国》，马云、俞敏洪、牛根生在里面轮番讲鸡汤，给我稚嫩的心灵留下了深刻的印象。这个节目的主题曲是《在路上》，刘欢演唱，歌蛮好听，但我更喜欢的，还是『在路上』这三个字。</p>
<p>在路上，说明一个人还活着，要知道很多人三十岁就已经死了，只是八十岁才埋葬。</p>
<p>所以，如果我还在写，那我一定还活着。</p>
<h2 id="一帆风顺">一帆风顺</h2><p>刚刚看了去年的总结，实习找工作都不是很顺利，今年就很不一样了，各方面都顺风顺水。</p>
<p>去创业公司待了三个月，追到女朋友花了两个月，点评开心工作六个月。另外，写了不少技术文章，获得了不少星星，认识了不少朋友。</p>
<p>别慌，且听我一一道来。</p>
<h2 id="创业">创业</h2><p>没有想到，会在2015年加入一个创业公司。</p>
<p>机缘巧合之下，<a href="http://weibo.com/dismory" target="_blank" rel="external">@dismory</a>在微博上找到了我，新年过后，老乡见面，相谈甚欢，故欣然加入egg。三个月的工作时间，还要忙着毕业论文，所以没有太多的精力花费在程序设计上，主要还是好好体验一下创业这个过程。</p>
<p>喜欢egg的随意，都是自己人的感觉，完全放的开，没有那么多规矩，可以用一万种姿势编程。在大公司，就感觉缚手缚脚，在百度写个邮件还被组长质疑，我也只能呵呵。</p>
<p>喜欢egg的团队，当一个小游戏，在朋友圈迅速传播，PV突破百万，马化腾都被看到的时候，你会对这个团队有一种自豪感，觉得这个团队能搞事，有一种时代弄潮儿的感觉。能够亲眼目睹整个事情的发展，更是深感幸运。</p>
<p>这三个月学到不少东西，长了不少见识。更加了解创业，了解创业公司的发展，了解期权和融资，了解形形色色的人。</p>
<p>六月，研究生毕业，站在人生的十字路口，我有些迷茫。是选择去优秀的技术公司百度深造，还是去年轻的创业公司egg闯荡，还是回到点评守护心仪的姑娘？</p>
<p>最后选择了回到点评，如果现在来看的话，这应该是最好的选择。</p>
<h2 id="姑娘">姑娘</h2><p>如果不是这个姑娘，我绝对不会回上海，因为我不喜欢这座城市的技术氛围。当然，如果不是我，在那个时间点，可能很多人也回不到上海，毕竟校招季已过。但如果我不去北京，可能也不会再见到她，也就没有了然后。凡此种种，让我相信冥冥之中自有天意。</p>
<p>去年，点评第一届极客马拉松比赛，一见倾心。3月份她因事来京，再见如故，引刘邦语，大丈夫生当娶她。</p>
<p>她的名字是静静，QQ空间里留下的『我想静静』、『我要静静』是我开战前的宣言。两个月的时间，制定三步走战略，一个礼物、一曲吉他、一封情书，稳稳取得战争的胜利。在北京和我一起睡觉的旭神，见证了我的忐忑和坚决。</p>
<p>同样没有想到，会在2015年追到一个姑娘，桃花运来的时候真是拦不住。</p>
<p>静静美的乱七八糟，我帅的一塌糊涂，所以我打算2016年结婚了，哈哈哈哈。</p>
<h2 id="工作">工作</h2><p>回到上海，去点评是当时最好的选择。因为在这实习过，这个城市，只有这里认可我的两把刷子。</p>
<p>本来是做商户详情页的需求的，做了一个需求后，听说分库遇到了问题，就多嘴去问了问，然后花了点时间，把field 65535的问题草草解决了，正好大家都忙着业务，然后我就去专业搞分库了。研究gradle，制定分库细节，包含二进制依赖、打AAR、Maven更新、版本管理等等，全程参与了分库的过程，收获了很多，这里也特别感谢一下旭东，从他身上学到了不少东西。</p>
<p>两三个月分库的时间，我工作的很快乐。因为在百度和点评实习的时候，一到下午四五点我就已经烦躁瞌睡了，怎么还不下班，无聊至极，但今年的这几个月里，经常的感觉是，怎么又到六点多了，好快啊。每天遇到问题，解决问题，也是一件很爽的事。</p>
<p>分库完成，我的感触就是一句话，Done is better than perfect。</p>
<h2 id="文章">文章</h2><p>本博客，在2015年继续坚持不翻译、不转载的态度，发表原创文章十篇，写别人所不能写。</p>
<p>其中CSDN约稿了一篇<a href="http://www.csdn.net/article/2015-08-10/2825420" target="_blank" rel="external">文章</a>，反响也蛮不错，我至今没看到这方面更好的文章，因为分库实战也不是很多人有的经历。经历真的很重要，到了一定程度，大家比拼的就是这个了。</p>
<p><a href="http://jiajixin.cn/2015/10/21/field-65535/">《当Field邂逅65535》</a>这篇文章，在后台检测到有大象的流量，而大象是美团自研的IM，所以我估计美团在看我的文章，毕竟业务也很相近。美团大众点评合并后，第一时间去看了美团的源码，解决这个问题，他们基本上直接copy了我的代码，我只能说朕心甚慰，哈哈哈哈。</p>
<p>开源</p>
<p><a href="http://jiajixin.cn/2015/12/16/nvwa/">《聊聊女娲》</a>，详细描述了我创建开源项目女娲的过程，这个热修复的思想也不是自己首创，所以还是有些不满意，权作一次尝试吧。</p>
<p>朋友</p>
<p>今年认识了很多圈里的朋友，大头鬼、light_sky、Trinea、胡凯等，都在线下见过聊过，最大的感触就是，网上活跃的这些人都太年轻了，90后的时代已经来了。</p>
<p>且看来年<br>阿里巴巴一般是奇数年变革，偶数年蜇伏，貌似和我气运差不多。你看2014偶数年不顺，2015奇数年就蛮不错，所以2016年我打算蛰伏了，哈哈哈哈。</p>
<p>2016年，加入一家创业公司。</p>
<p>2016年，把妹子娶回家。</p>
<p>2016年，多看点书。</p>
<p>2016年，减肥。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>去年写了篇《在路上·2014》，今年写《在路上·2015》，看来在路上要成为一个系列了。</p>
<h2 id="在路上">在路上</h2><p>高中的时候，看过《赢在中国》，马云、俞敏洪、牛根生在里面轮番讲鸡汤，给我稚嫩的心灵留下了深刻的印象。这个节目的主题曲是《在路上》]]>
    </summary>
    
      <category term="在路上" scheme="http://jiajixin.cn/tags/%E5%9C%A8%E8%B7%AF%E4%B8%8A/"/>
    
      <category term="总结" scheme="http://jiajixin.cn/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[聊聊女娲]]></title>
    <link href="http://jiajixin.cn/2015/12/16/nvwa/"/>
    <id>http://jiajixin.cn/2015/12/16/nvwa/</id>
    <published>2015-12-16T12:13:13.000Z</published>
    <updated>2017-03-18T10:14:15.000Z</updated>
    <content type="html"><![CDATA[<p><img src="http://7fviov.com1.z0.glb.clouddn.com/nuwa.jpg" alt=""><br>开源一个月，<a href="https://github.com/jasonross/Nuwa" target="_blank" rel="external">Nuwa</a>收获了1000个star，虽然不是很理想，但也算是个不错的成绩吧。之前考虑到Github上外国人的传播能力，Nuwa的readme采用英文书写，结果证明这并没有什么卵用，有些人说要看中文的介绍，你看他们多么懒。中文介绍不写了，这篇文章我打算聊聊女娲，聊聊开源。</p>
<h2 id="背景">背景</h2><p>2014年，代码家写了很多自定义控件，成了Github榜上非常有名的男人。</p>
<p>人们开始意识到，自定义控件是个好东西啊，简单且受欢迎，从某种角度来说，很适合做成开源项目。</p>
<p>于是，Android的圈子里炸裂了，一个又一个的自定义控件出现了，人们在微博上求转发求扩散求星星，直到今天，这种现象仍在继续。</p>
<p>你看，大头鬼和我现在是很好的朋友。认识他，也是看到了他写的一个控件，后来把他挖到了点评。半年的时间，他已经成了RxJava教父，工资也是涨得飞起，真是开源改变人生。</p>
<p>程序员都想有自己的作品，都希望Github上有能拿出手的东西。本来也有写自定义控件的打算，但我这个人追求B格，又苦于没有好的idea，所以迟迟未曾动手。</p>
<p>我想，我是在等待一个女孩，哦不，一个机会。</p>
<h2 id="机会">机会</h2><p>当Bugly的那篇<a href="https://bugly.qq.com/v2/blog/?p=781" target="_blank" rel="external">文章</a>出来的时候，我想我的机会来了。</p>
<p>首先，热修复这事有B格啊，偷天换日，偷梁换柱。市场有需求，又不是件很容易做的事情，人们已经嗷嗷待哺，饥渴难耐。</p>
<p>另外，国内还有人比我了解Android Gradle吗？当然有，嘿嘿。但如果你读过<a href="http://jiajixin.cn/2015/08/07/gradle-android/">构建神器Gradle</a>这篇文章的话，就知道使用Gradle更改构建流程，我已经非常得心应手。另外，更改字节码这块，我对ASM也比较了解。总之，我可以用Gradle稳稳地搞定热修复。</p>
<p>我想，我又被历史选中了。</p>
<h2 id="战斗">战斗</h2><p>很快，在公司的项目上做了一个实验，添加Gradle任务修改字节码，dx生成补丁文件，替换成功了，呵呵，这么简单。</p>
<p>然后打算写成开源项目，打算写一个插件用来修改字节码和生成补丁文件，然后写一个SDK用来操作dex文件。</p>
<p>写库的过程中，遇到了各种问题。</p>
<p>首先是新建了一个项目，不用插件，直接在build.gradle中添加任务，热修复竟然不成功了，为什么？这究竟是为什么？百思不得其解。那晚，月黑风高，在加班回家的住租车上，我终于想明白了，manifest中没有加SD卡读权限，关键是整个过程中没有error没有warning。我只想说，what the fuck。</p>
<p>然后，另一个问题来了，需要修改Class文件，那怎么找到全部Class呢？</p>
<p>公司的项目使用了MultiDex，并且没有混淆，这种情况很简单，dex任务之前会生成一个jar文件，包含了所有的class，所以做起来很容易。但是如果添加了混淆怎么办？试了一下，也是proguard后也是生成了一个jar包，也没啥问题。</p>
<p>世界是这么简单的吗？当然不是，AS默认新建的工程没有开启MultiDex和Proguard，没有找到我想要的那个包含所有类的jar包。查看任务输入输出，发现这种情况下，会有一个preDex的任务。查看dex任务的输入，了解到preDex会在dex任务之前把所有的库工程和第三方jar包提前打成dex，下次运行只需重新dex被修改的库，以此节省时间。dex任务会把preDex生成的dex文件和主工程中的class文件一起生成class.dex，这样就需要针对有无preDex，做不同的修改字节码策略。看了下gradle plugin的源代码，主要是这两种情况吧，那时候gradle还是1.3版本，后来略过1.4出了1.5，添加了transform接口，正好解决了操纵class文件时机的问题。</p>
<p>还有一个问题，gradle脚本常写，但插件没写过啊，比着葫芦画瓢，中间也走了不少弯路，当时愚蠢的没有用本地仓库，每次调试代码，上传到jcenter，jcenter本来就卡，还经常遇到上传错误，总之写的很是纠结。</p>
<h2 id="发布">发布</h2><p>熬了好几次夜，大约两周的时候，这个库基本成型了。疲惫的我，意外发现Github上已经出了一个类似的库HotFix，看了一下基本上属于一个Demo，没有考虑各种情况，Gradle支持的也不是很完美，我的地盘怎么能由你做主，于是我还是决定即刻发布这个项目。</p>
<p>做事追求B格的我，给这个库取了个高大上的名字，叫女娲，英文名是Nuwa，不要愚蠢地问我为啥不叫Nvwa。阿里有个热修复的库叫andFix，360有个插件叫droidPlugin，搞android的库不是and就是droid，本来我也有这想法的，但感觉还是太low了，当然在我发布女娲那段时间，我看到DroidFix，AndPatch，DroidPatch等等。你看，很多人想到了一个idea，然后四五个做出来，然后有一个做的最好，然后他得到了1000颗星。</p>
<p>B格不能停止，当初看LayoutCast的readme，流利的英文让我不能忘怀，所以Nuwa必须用英文写，那天晚上我就急急忙忙的写了英文文档，本来也想照顾一下国际友人，结果发现然并卵，并没有几个老外star我的项目。</p>
<p>凌晨的时候，发布了这个项目，当然不忘在微博上@一下各位老友，我能预料当我一觉醒来，这个江湖注定又是一片沸沸扬扬。</p>
<h2 id="结果">结果</h2><p>一切都没有悬念，第二天，微博上人们转发着，Github上星星增长着，但到200多颗星的时候，Nuwa还没有上trending榜，看了下trending的介绍说，说一天会刷新8次数据的，那为啥我还没上榜，Github上的今天到底是什么时候开始，我想我能做的只有等待，然后第三天的时候，才上了榜，我只想说，trending你不懂爱。</p>
<p>后来，大头鬼强迫我去天天动听做了一次分享，我又梳理了一下知识点，PDF在<a href="http://7fviov.com1.z0.glb.clouddn.com/Nuwa.pdf" target="_blank" rel="external">这里</a>，这可能是仅有的关于Nuwa的中文介绍。PDF里也提到了Nuwa存在的一些问题，本来打算后面慢慢优化的，但也不知道会不会搞了，如果公司要上热修复的话再说吧。</p>
<p>第一天200多个star，一个月才到1000，所以现在的情况是低于预期的，我总结了一下原因，是因为代码家没有转发我的微博，嗯，是的。</p>
<p>就这样吧，也好。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p><img src="http://7fviov.com1.z0.glb.clouddn.com/nuwa.jpg" alt=""><br>开源一个月，<a href="https://github.com/jasonross/Nuwa" target="_blank" re]]>
    </summary>
    
      <category term="ASM" scheme="http://jiajixin.cn/tags/ASM/"/>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Github" scheme="http://jiajixin.cn/tags/Github/"/>
    
      <category term="Gradle" scheme="http://jiajixin.cn/tags/Gradle/"/>
    
      <category term="Nuwa" scheme="http://jiajixin.cn/tags/Nuwa/"/>
    
      <category term="开源" scheme="http://jiajixin.cn/tags/%E5%BC%80%E6%BA%90/"/>
    
      <category term="热修复" scheme="http://jiajixin.cn/tags/%E7%83%AD%E4%BF%AE%E5%A4%8D/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Instant Run 浅析]]></title>
    <link href="http://jiajixin.cn/2015/11/25/instant-run/"/>
    <id>http://jiajixin.cn/2015/11/25/instant-run/</id>
    <published>2015-11-24T18:50:34.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>AS2.0 Preview 版本发布了，早上醒来就被各种刷屏，有人在担心更新，有人在拍手称快，有人在厕所哭晕。而我当然没有那么肤浅，暗想要去研究一下AS2.0最重要特性Instant Run的原理。Instant Run实际上就是一个热补丁，<a href="http://weibo.com/u/1851118441" target="_blank" rel="external">@别闹腾啊</a>在搞LayoutCast，我在搞Nuwa，对Instant Run热补丁的思路都非常感兴趣，所以就坐在一起看了两三个小时代码，对代码修复的这一部分基本上搞清楚了。</p>
<h2 id="Instant_Run">Instant Run</h2><p>google官方关于Instant Run特性的介绍在这里：<a href="https://sites.google.com/a/android.com/tools/tech-docs/instant-run" target="_blank" rel="external">https://sites.google.com/a/android.com/tools/tech-docs/instant-run</a>，按其所述，运行<strong>FloatingActionButtonBasic</strong>样例，实验Instant Run，豪爽豪爽。</p>
<p>但是，修改方法竟然不用重启Activity，这是怎么做到的？</p>
<p>阅读一帆关于几个热补丁思路的<a href="http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/" target="_blank" rel="external">介绍</a>，你会发现andfix和dexposed采用jni hook方法，不用重启就能修复，而Nuwa的ClassLoader思路因为类被虚拟机加载后，不会重新加载，所以需要重启。Instant Run是怎么实现不重启加载的呢，难道也是jni hook？</p>
<h2 id="实现原理">实现原理</h2><p>和nuwa类似，一个插件一个库，Instant Run用的是gradle plugin 2.0.0-alpha1和instant-run.jar。</p>
<h3 id="gradle_plugin_2-0-0-alpha1">gradle plugin 2.0.0-alpha1</h3><p>gradle plugin 2.0.0-alpha1主要有两个作用：</p>
<ul>
<li><p>第一次运行，应用<a href="http://tools.android.com/tech-docs/new-build-system/transform-api" target="_blank" rel="external">transform API</a>修改字节码。</p>
<p>  输出目录为<em>Application/build/intermediates/transforms/instantRun/debug/folders/1</em>。</p>
<ul>
<li><p>给所有的类添加<strong>$change</strong>字段</p>
<p>  <strong>$change</strong>为<strong>IncrementalChange</strong>类型，<strong>IncrementalChange</strong>是个接口，该接口后面会讲。</p>
</li>
<li><p>修改类的全部方法</p>
<p>  新的逻辑是：如果<strong>$change</strong>不为空，去调用<strong>$change</strong>的<strong>access$dispatch</strong>方法，参数为方法签名字符串和方法参数数组，否则调用原逻辑。</p>
</li>
</ul>
</li>
<li><p>后续运行，dx补丁类，生成补丁dex。</p>
<p> 输出目录为<em>Application/build/intermediates/transforms/instantRun/debug/folders/4000</em>。</p>
<ul>
<li><p>被修改类对应的补丁类</p>
<p>  补丁类，并不是你修改后的类，而是由gradle plugin自动生成，实现了<strong>IncrementalChange</strong>接口的类。</p>
<p>  该类类名在原名后面添加<strong>$override</strong>，复制修改后类的大部分方法，实现<strong>IncrementalChange</strong> 接口的<strong>access$dispatch</strong>方法，该方法会根据传递过来的方法签名，调用本类的同名方法。</p>
<p>  只要把原类的<strong>$change</strong>字段设置为该类，那就会调用该类的<strong>access$dispatch</strong>方法，就会使用修改后的方法了。</p>
</li>
<li><p>被修改类的记录类</p>
<p>  <strong>AppPatchesLoaderImpl</strong>记录了所有被修改的类，也会被打进补丁dex。</p>
</li>
</ul>
</li>
</ul>
<h3 id="instant-run-jar">instant-run.jar</h3><p><strong>instant-run.jar</strong>的路径为<em>Application/build/intermediates/incremental-runtime-classes/debug/instant-run.jar</em>。</p>
<p><strong>instant-run.jar</strong>是gradle plugin帮我们自动打到dex中去的，省去了compile dependency这一步。它的主要作用有两个：</p>
<ul>
<li><p>设置原类的<strong>$change</strong>字段为补丁类</p>
<p>  需要对被修改的类设置<strong>$change</strong>字段，那怎么知道哪些类被修改了？</p>
<p>  <strong>AppPatchesLoaderImpl</strong>类不但记录了全部被修改的类，还提供<strong>load</strong>方法支持设置被修改原类<strong>$change</strong>字段，当收到补丁通知时，只需新建一个DexClassLoader，去反射加载补丁dex中的<strong>AppPatchesLoaderImpl</strong>类，调用load方法即可，load方法中会去加载全部补丁类，并赋值给对应原类的<strong>$change</strong>。</p>
</li>
<li><p>重启加载补丁类</p>
<p>  重启后怎么办？原来的补丁文件需要加载进来。</p>
<p>  <strong>IncrementalClassLoader</strong>会在Application中去加载该应用cache目录中的补丁dex，把它设置为默认<strong>PathClassLoader</strong>的parent，由于ClassLoader采用双亲委托模型，会先去parent查找类，所以就可以加载补丁类了。</p>
</li>
</ul>
<h2 id="总结">总结</h2><p>google的介绍中，也提到了Instant Run的一些局限之处，比如变量、添加删除方法等，当然这一切都会持续优化，gradle plugin 2.0也还仅仅是个alpha版本而已。</p>
<p>Instant Run的实现思路蛮笨的，几乎所有的方法都修改逻辑，但不用重启这一点很赞，用这个思路可以搞一个热修复库了，有官方背景的库。</p>
<p>本文主要是探究Instant Run处理代码变化的思路，对资源变化没有分析，对于一些细节比如server实现、socket传输、application替换、updateMode处理亦未提及。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>AS2.0 Preview 版本发布了，早上醒来就被各种刷屏，有人在担心更新，有人在拍手称快，有人在厕所哭晕。而我当然没有那么肤浅，暗想要去研究一下AS2.0最重要特性Instant Run的原理。Instant Run实际上就是一个热补丁，<a href="http://]]>
    </summary>
    
      <category term="Android Studio" scheme="http://jiajixin.cn/tags/Android-Studio/"/>
    
      <category term="Gradle" scheme="http://jiajixin.cn/tags/Gradle/"/>
    
      <category term="Instant Run" scheme="http://jiajixin.cn/tags/Instant-Run/"/>
    
      <category term="hotfix" scheme="http://jiajixin.cn/tags/hotfix/"/>
    
      <category term="instant run" scheme="http://jiajixin.cn/tags/instant-run/"/>
    
      <category term="热补丁" scheme="http://jiajixin.cn/tags/%E7%83%AD%E8%A1%A5%E4%B8%81/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[资源漂流记]]></title>
    <link href="http://jiajixin.cn/2015/10/29/res-divide/"/>
    <id>http://jiajixin.cn/2015/10/29/res-divide/</id>
    <published>2015-10-28T17:20:53.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>在我们的代码中，十几个业务线的下方都依赖一个公用的base库，base库的东西很多，包含Push、Share、Util、Web、SSO、Pay和部分共用的业务逻辑。我们打算拆分Base，每个模块弄成一个代码仓库，方便模块复用，解开部分耦合。</p>
<p>代码拆分出去比较简单，本来模块基本上都是按照package区分的，拿出去即可，但资源就不好拆分了。如果代码拆出去，用到的资源也拿出去，可能会导致多个库用了名称相同的资源，由于资源的合并机制，同样的名字最后只有一份资源会被打到APK中，如果有一个库更改了资源内容，可能就会导致APP运行时失败。比如A库和B库都复制了一份c.layout,B改了自己的c.layout,添加了一个控件，在代码中会用findViewById去获取控件，自己编译能通过，但A的资源优先级更高，最终APK中只保留了A的c.layout，运行时B就会找不到添加的控件，代码崩溃。关于资源合并的优先级，请看<a href="http://tools.android.com/tech-docs/new-build-system/resource-merging" target="_blank" rel="external">官方文档</a>。官方文档是个宝啊，谁看谁知道。</p>
<h2 id="拆分方案">拆分方案</h2><p>资源拆分有两种方案：</p>
<ul>
<li><p>资源复制多份并且修改资源名称，比如加前缀。</p>
<p>  这种做法要改动的源代码太多，并且资源冗余，会增加APK大小。</p>
</li>
<li><p>资源保留一份沉在下层。</p>
<p>  保留一份资源，合情合理，关键是把资源放在哪里？都放在Base库中，代码肯定可以跑的通，但肯定不是合理的。资源应该谁用谁负责，和代码在一个仓库里，如果多个库用，就应该放在这多个库更上层的公共依赖。</p>
</li>
</ul>
<p>我们决定采用第二种方案，但Base拆库的过程是同步进行的，代码拆出去再建立依赖关系，在没有依赖关系的前提下，根本不知道Base中的资源最后能放到哪个仓库里。所以我们分库的时候，首先只拆分代码，拆完打成aar，所有仓库都能编译通过就可以了。最后，资源拆分的重担就落到了我的肩上。</p>
<h2 id="拆分原则">拆分原则</h2><p>资源的使用情况有多种：</p>
<ul>
<li><p>没有库使用</p>
<p>  应用大了，这种问题在所难免，直接删掉吧</p>
</li>
<li><p>一个库使用</p>
<p> 直接放在那一个库就可以了</p>
</li>
<li><p>多个库使用</p>
<p>  这种情况比较复杂，我说过，如果多个库用，就应该放在这多个库更高层的公共依赖。什么意思呢，比如A，B都依赖C，C依赖D，这时候你应该放在哪里？C里面比较合适，C、D都是A和B的公共依赖，C在更上层。</p>
</li>
</ul>
<p>但还有一个问题，A依赖B，B依赖D，C依赖D，D依赖Base，如果资源在A和C里都用到了，这时候应该把资源放到哪里？A和C都间接依赖D，但如果B以后不依赖D了，那A也不会依赖到D，资源放在D里也不好。所以我们要修正上面说的公共依赖为公共直接依赖。像刚才这种情况没有公共直接依赖的，那就暂时放到Base里面吧，方正放哪里也不合适，Base最后内容很少改动很小，如果后面嫌更改Base比较烦，那就复制一份改名到上层也可以，这就是所谓的历史遗留问题吧。</p>
<h2 id="AAR依赖">AAR依赖</h2><p>各个代码仓库最终打成AAR，在build.gradle中配置依赖关系，由于Gradle支持依赖传递，这种依赖都是间接依赖，需要自己去找直接依赖。这个依赖主要是找代码之间的依赖，资源调用代码的依赖可以暂时不用考虑。</p>
<p>你会发现，在Android开发中，可以借鉴Java Web开发的很多东西，后端帮我们踩坑这么多年，已经积累了丰富的经验。</p>
<h3 id="Tattletale">Tattletale</h3><p>找到了一个库，<a href="http://tattletale.jboss.org/" target="_blank" rel="external">Tattletale</a>，可以查找Jar包之间的直接依赖关系。我们把所有的Jar包放在一起，用这个工具扫一下就可以了，所有的Jar包肯定也包含第三方库的代码，这些Jar包都在主工程的<code>build/intermediates/exploded-aar</code>中，遍历.jar结尾的都放到一个文件夹就可以了。</p>
<p>Tattletale提供的结果是HTML页面，需要我们自己解析，用了<a href="http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html" target="_blank" rel="external">BeautifulSoup</a>库。之前写Pyhton很多都是按照Java的思路来写，去查对应的语法，没有感觉到Python的简洁，最近系统的看了一下Python的一些知识，我靠，写起来真是痛快，豪爽豪爽。</p>
<h3 id="拓扑排序">拓扑排序</h3><p>AAR的依赖关系是一个有向无环图，我们要在图中找到两个结点的更上公共依赖，怎么搞？我想到了拓扑排序，不学算法好多年，竟然还能想到这个东东。拓扑排序可以使得依赖关系有序，拓扑序列中，后面的不会依赖前面。比如，一个资源在多个库中使用，那就可以找到这几个库在拓扑序列中哪个更靠后，从这个库开始逐个检查是否同时被这几个库依赖，找到就可以停止遍历了，这个库就是更上层的公共依赖，遍历完还找不到，那就放Base里吧。</p>
<h3 id="绘制依赖图">绘制依赖图</h3><p>把依赖绘制成图，看起来比较直观。发现了<a href="http://www.graphviz.org/" target="_blank" rel="external">Graphviz</a>，非常简单的语句描述，自动绘制成图，简单易用。</p>
<h2 id="资源使用">资源使用</h2><p>聊了那么多，怎么查资源被哪些库所使用的呢？</p>
<p>由于我们分库后，有几十个代码仓库，使用aar依赖，使用AS是不方便的。有同事之前搞ROM开发，了解到Android源代码是用<a href="http://source.android.com/source/downloading.html#installing-repo" target="_blank" rel="external">repo</a>工具管理的，就果断的引用到我们的项目里。repo在分库的时候用处蛮大的，可以同时操纵多个代码仓库，平常开发的话，就是全局搜索源代码比较方便，期待AS能够早日支持搜索AAR关联的源代码。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>在我们的代码中，十几个业务线的下方都依赖一个公用的base库，base库的东西很多，包含Push、Share、Util、Web、SSO、Pay和部分共用的业务逻辑。我们打算拆分Base，每个模块弄成一个代码仓库，方便模块复用，解开部分耦合。</p>
<p>代码拆分出去比较简]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Graphviz" scheme="http://jiajixin.cn/tags/Graphviz/"/>
    
      <category term="Python" scheme="http://jiajixin.cn/tags/Python/"/>
    
      <category term="Resources" scheme="http://jiajixin.cn/tags/Resources/"/>
    
      <category term="Tattletale" scheme="http://jiajixin.cn/tags/Tattletale/"/>
    
      <category term="aar" scheme="http://jiajixin.cn/tags/aar/"/>
    
      <category term="repo" scheme="http://jiajixin.cn/tags/repo/"/>
    
      <category term="图" scheme="http://jiajixin.cn/tags/%E5%9B%BE/"/>
    
      <category term="拓扑排序" scheme="http://jiajixin.cn/tags/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[当Field邂逅65535]]></title>
    <link href="http://jiajixin.cn/2015/10/21/field-65535/"/>
    <id>http://jiajixin.cn/2015/10/21/field-65535/</id>
    <published>2015-10-21T13:09:24.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>很多Android开发者都知道，同一个dex中方法数不能超过65535。为什么呢？来看一下<a href="http://blog.csdn.net/t12x3456/article/details/40837287" target="_blank" rel="external">这篇文章</a>，有3万的阅读量啊，当年一定风靡一时，作者说65535是dexopt引起的，我特马真是呵呵了，写文章不动脑子，dexopt都到了什么阶段了？再来看<a href="http://blog.csdn.net/yzzst/article/details/48290701" target="_blank" rel="external">这篇文章</a>，解释和上面如出一辙，可以看到前面那篇文章流毒不浅啊，关键这哥们貌似最近还出了本书，这种狗屁态度还好意思出书？</p>
<p>『天下熙熙，皆为利来；天下攘攘，皆为利往。』大家写文章，为名为利无可厚非，有错误都是有情可原，但关键是要有点认真的态度啊。当年写<a href="http://www.csdn.net/article/2015-08-10/2825420" target="_blank" rel="external">构建神器Gradle</a>花了好几天的时间，投稿后又修改到深夜，依然还是有些错误，后来也在自己的博客上做了勘误更新。我看扔物线那篇RxJava的文章都有内测读者群，想来也是极为认真，非常尊敬这种态度，非常喜欢这样的开发者。</p>
<h2 id="Field_65K">Field 65K</h2><p>言归正传，来聊聊为什么方法数不能超过65535？搬上Dalvik工程师在SF上的<a href="http://stackoverflow.com/questions/21490382/does-the-android-art-runtime-have-the-same-method-limit-limitations-as-dalvik/21492160#21492160" target="_blank" rel="external">回答</a>，因为在<a href="http://source.android.com/devices/tech/dalvik/dalvik-bytecode.html" target="_blank" rel="external">Dalvik指令集</a>里，调用方法的<code>invoke-kind</code>指令中，method reference index只给了16bits，最多能调用65535个方法，所以在生成dex文件的过程中，当方法数超过65535就会报错。细看指令集，除了method，field和class的index也是16bits，所以也存在65535的问题。一般来说，method的数目会比field和class多，所以method数会首先遇到65535问题，你可能都没机会见到field过65535的情况。</p>
<p>幸运的我见到了，呵呵。</p>
<p><img src="http://7fviov.com1.z0.glb.clouddn.com/171CE22C-3940-4ABA-AAB0-171B44394349.png" alt="错误"></p>
<p>明明显示的是<code>DexException: Too many classes in --main-dex-list</code>，和field有毛关系？你可能想问我脑子是不是进水了，唉，明明是Google工程师脑子有问题，你看看Google自家兄弟提的<a href="https://code.google.com/p/android/issues/detail?id=184051&amp;q=subcomponent%3DTools-gradle-ide%20attachments%3D0%20label%3Acomponent-Tools&amp;colspec=ID%20Type%20Status%20Owner%20Summary%20Stars" target="_blank" rel="external">issue</a>就知道了。在使用multidex的情况下，主dex不管method还是field数目超了65535都会报Too many classes的错误，不使用multidex，就报正常的错误了，呵呵。</p>
<p>你可能要纳闷了，我为啥会遇到field超65535的问题？看看我”新大美”同事写的，哈哈：</p>
<blockquote>
<p>实际应用中我们还遇到另外一个比较棘手的问题， 就是Field的过多的问题，Field过多是由我们目前采用的代码组织结构引入的，我们为了方便多业务线、多团队并发协作的情况下开发，我们采用的aar的方式进行开发，并同时在aar依赖链的最底层引入了一个通用业务aar，而这个通用业务aar中包含了很多资源，而ADT14以及更高的版本中对Library资源处理时，Library的R资源不再是static final的了，详情请查看google官方说明，这样在最终打包时Library中的R没法做到内联，这样带来了R field过多的情况。</p>
</blockquote>
<p>差不多的情况，我们上层十几个业务线为独立module，都依赖base，而base的资源id有个三四千，上层R文件会把下层的R文件合并过来，使用multidex后，会把manifest里的activity、service等和其直接引用类加到main dex中，所以很多R文件涌入，field超个65535那都不叫事。</p>
<h2 id="修改R">修改R</h2><p>field这么多怎么办呢？我们大胆假设只保留最顶层的R文件，因为这个R文件会把下层R文件合并过来，所有的R引用都可以指向这个文件。下层的类要引用最上层的R文件，下层不可能依赖上层，所以修改源代码肯定是走不通的，那就改class文件字节码吧。遍历class文件，把R的引用都指向最上层，把其他没用的R文件删掉。</p>
<h3 id="问题">问题</h3><p>思路有了，接下来的操作有以下问题：</p>
<ul>
<li><p>什么时候修改？</p>
<p>  dex过程是把全部class文件转换成dex文件，所以class字节码的修改要在dex之前，我们决定介入构建流程，在dex之前添加一个gradle任务，用来修改字节码。</p>
</li>
<li><p>用什么修改？</p>
<p>  可以使用asm这个库，由于android gradle间接依赖asm，所以我们可以在build.gradle中直接import相关类。</p>
</li>
<li><p>修改什么？</p>
<p>  当然是修改class文件，那么class文件的路径在哪里？主工程的<code>build/intermediates/exploded-aar</code>中包含了库工程aar解压后的内容，有很多jar文件，这些jar文件太过分散不好操作，由于我们使用了multidex，看一下dex任务的输入，发现是主工程的<code>build/intermediates/multi-dex/common/debug/allclasses.jar</code>文件，顾名思义，这个文件包含了所有的class文件，我们直接修改这个jar包里的class文件就可以了。</p>
</li>
</ul>
<h3 id="代码">代码</h3><p>将下面这段代码放在主工程的build.gradle就不报错了。在我们的代码中，unifyRImport任务能跑个10秒钟左右，说长不长，说短不短。</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.commons.compress.utils.IOUtils</span><br><span class="line"><span class="keyword">import</span> org.objectweb.asm.*</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.jar.JarEntry</span><br><span class="line"><span class="keyword">import</span> java.util.jar.JarFile</span><br><span class="line"><span class="keyword">import</span> java.util.jar.JarOutputStream</span><br><span class="line"><span class="keyword">import</span> java.util.zip.ZipEntry</span><br><span class="line"></span><br><span class="line">ext &#123;</span><br><span class="line">    dpPackagePrefix = <span class="string">'com/dianping/'</span></span><br><span class="line">    libDrawableClass = <span class="string">'com/dianping/nova/R\$drawable.class'</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">byte</span>[] unifyR(InputStream inputStream, String rootPackagePrefix) &#123;</span><br><span class="line">    ClassReader cr = <span class="keyword">new</span> ClassReader(inputStream);</span><br><span class="line">    ClassWriter cw = <span class="keyword">new</span> ClassWriter(cr, <span class="number">0</span>);</span><br><span class="line">    ClassVisitor cv = <span class="keyword">new</span> ClassVisitor(Opcodes.ASM4, cw) &#123;</span><br><span class="line">        @Override</span><br><span class="line">        <span class="keyword">public</span> MethodVisitor visitMethod(<span class="keyword">int</span> access, String name, String desc,</span><br><span class="line">                                         String signature, String[] exceptions) &#123;</span><br><span class="line">            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);</span><br><span class="line">            mv = <span class="keyword">new</span> MethodVisitor(Opcodes.ASM4, mv) &#123;</span><br><span class="line">                @Override</span><br><span class="line">                <span class="keyword">void</span> visitFieldInsn(<span class="keyword">int</span> opcode, String owner, String fName, String fDesc) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (owner.contains(dpPackagePrefix) &amp;&amp; owner.contains(<span class="string">"R\$"</span>) &amp;&amp; !owner.contains(rootPackagePrefix)) &#123;</span><br><span class="line">                        <span class="keyword">super</span>.visitFieldInsn(opcode, rootPackagePrefix + <span class="string">"R\$"</span> + owner.substring(owner.indexOf(<span class="string">"R\$"</span>) + <span class="number">2</span>), fName, fDesc);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">super</span>.visitFieldInsn(opcode, owner, fName, fDesc);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> mv;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;;</span><br><span class="line">    cr.accept(cv, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> cw.toByteArray();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">afterEvaluate &#123;</span><br><span class="line">    <span class="keyword">def</span> manifestFile = android.<span class="keyword">sourceSets</span>.main.manifest.srcFile</span><br><span class="line">    <span class="keyword">def</span> packageName = <span class="keyword">new</span> XmlParser().parse(manifestFile).attribute(<span class="string">'package'</span>)</span><br><span class="line">    <span class="keyword">def</span> rootPackagePrefix = packageName.replace(<span class="string">'.'</span>, <span class="string">'/'</span>) + <span class="string">'/'</span></span><br><span class="line">    <span class="keyword">println</span> packageName</span><br><span class="line">    android.applicationVariants.<span class="keyword">each</span> &#123; variant -&gt;</span><br><span class="line">        <span class="keyword">def</span> dx = tasks.findByName(<span class="string">"dex$&#123;variant.name.capitalize()&#125;"</span>)</span><br><span class="line">        <span class="keyword">def</span> unifyRImport = <span class="string">"unifyRImport$&#123;variant.name.capitalize()&#125;"</span></span><br><span class="line">        <span class="keyword">task</span>(unifyRImport) &lt;&lt; &#123;</span><br><span class="line">            Set&lt;<span class="keyword">File</span>&gt; inputFiles = dx.inputs.files.files</span><br><span class="line">            inputFiles.<span class="keyword">each</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (it.name.endsWith(<span class="string">".jar"</span>)) &#123;</span><br><span class="line">                    <span class="keyword">println</span> it</span><br><span class="line">                    JarFile jarFile = <span class="keyword">new</span> JarFile(it);</span><br><span class="line">                    Enumeration enumeration = jarFile.entries();</span><br><span class="line">                    <span class="keyword">File</span> tmpFile = <span class="keyword">new</span> <span class="keyword">File</span>(it.getParent() + <span class="keyword">File</span>.separator + <span class="string">"classes.jar.tmp"</span>);</span><br><span class="line">                    JarOutputStream jarOutputStream = <span class="keyword">new</span> JarOutputStream(<span class="keyword">new</span> FileOutputStream(tmpFile));</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                    <span class="keyword">while</span> (enumeration.hasMoreElements()) &#123;</span><br><span class="line">                        JarEntry jarEntry = (JarEntry) enumeration.nextElement();</span><br><span class="line">                        String entryName = jarEntry.getName();</span><br><span class="line">                        ZipEntry zipEntry = <span class="keyword">new</span> ZipEntry(entryName);</span><br><span class="line"></span><br><span class="line">                        InputStream inputStream = jarFile.getInputStream(jarEntry);</span><br><span class="line">                        <span class="keyword">if</span> (entryName.startsWith(dpPackagePrefix) &amp;&amp; entryName.endsWith(<span class="string">".class"</span>)) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (!entryName.contains(<span class="string">"R\$"</span>)) &#123;</span><br><span class="line">                                jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                                jarOutputStream.<span class="keyword">write</span>(unifyR(inputStream, rootPackagePrefix));</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="comment">//NovaLib中R$drawable有被反射使用，不删除</span></span><br><span class="line">                                <span class="keyword">if</span> (entryName.startsWith(rootPackagePrefix) || entryName.equals(libDrawableClass)) &#123;</span><br><span class="line">                                    jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                                    jarOutputStream.<span class="keyword">write</span>(IOUtils.toByteArray(inputStream));</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                            jarOutputStream.<span class="keyword">write</span>(IOUtils.toByteArray(inputStream));</span><br><span class="line">                        &#125;</span><br><span class="line">                        jarOutputStream.closeEntry();</span><br><span class="line">                    &#125;</span><br><span class="line">                    jarOutputStream.close();</span><br><span class="line">                    jarFile.close();</span><br><span class="line">                    tmpFile.renameTo(it);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        tasks.findByName(unifyRImport).dependsOn dx.taskDependencies.getDependencies(dx)</span><br><span class="line">        dx.dependsOn tasks.findByName(unifyRImport)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="坑">坑</h3><p>我一般不喜欢用坑这个词，感觉遇到坑更多是因为无知，但特马这的确是个坑，在mac上跑的好好的，windows跑不通了，还是报class太多的错误。我一想两个系统文件分隔符不同，不会是路径上出了问题吧，最后竟然发现是<code>tmpFile.renameTo(it);</code>这行命令没有重命名成功，Google一搜发现遇到这坑的不在少数，a重命名成b，如果b已经存在，mac的做法直接覆盖，windows就会重命名失败。所以最后在rename前面加了一句，<code>it.delete();</code>。</p>
<h2 id="dex任务增量">dex任务增量</h2><p>代码欢乐的跑了几天，有哥们提意见了，什么代码就不改，为什么点击run还要跑一两分钟？在这段时间的背后AS背地里做了什么？数百头母驴为何半夜惨叫？小卖部安全套为何屡遭黑手？女生宿舍内裤为何频频失窃？连环强奸母猪案，究竟是何人所为？老尼姑的门夜夜被敲，究竟是人是鬼？数百只小母狗意外身亡的背后又隐藏着什么？这一切的背后， 是人性的扭曲还是道德的沦丧？是性的爆发还是饥渴的无奈？唉，真是崇拜<a href="http://blog.csdn.net/aigestudio/article/details/43449123" target="_blank" rel="external">爱哥</a>。</p>
<p>我们可以在AS中看到dex任务又重新跑了一遍，主要时间就花在这上面了。之前的<a href="http://jiajixin.cn/2015/08/07/gradle-android/#增量构建">博客</a>讲过，任务增量构建要求输入和输出较上次没有区别，dex重新跑说明输入或者输出有变化，输出是多个dex文件我们没有改动，输入allclasses.jar虽然有更改，但因为源码不变，第二次运行allclasses.jar应该和上次一样的，不应该重新跑啊。比较了两次运行的allclasses.jar的md值，发现还真是不一样啊，看来问题就出在这里了。</p>
<h3 id="zip哈希">zip哈希</h3><p>关键是为什么前后两次运行allclasses.jar的哈希值不同呢？</p>
<p>话说之前向maven上打包上传aar的时候，发现代码资源都不改动，上传上去的aar哈希值竟然不同，为什么呢？一个简单的a.txt前后zip压缩两次，得到的zip文件哈希值也不同，用beyond compare看了下二进制，还真的不一样。那就去看看<a href="https://en.wikipedia.org/wiki/Zip_(file_format" target="_blank" rel="external">zip算法</a>)吧，可以看到header中有时间戳相关的东西，应该就是这导致同样的文件zip压缩后哈希值不同。</p>
<p>jar打包也是用的zip算法，因为第一次运行我们修改了allclasses.jar，导致第二次运行时，某个任务的输出发生了变化，所以会重新运行生成allclasses.jar，前后两次的allclasses.jar哈希值就发生了变化，dex任务就要重新跑了。</p>
<h3 id="增量思路">增量思路</h3><p>之前的问题，主要还是没有把allclasses.jar及时还原。因为allclasses.jar是dex的输入，所以我们需要在dex之后把allclasses.jar还原，既然需要还原，那就需要在修改allclasses.jar的时候有个备份（classes.bak）。还有个问题，每次unifyRImport任务运行时，都要重新去生成精简后的allclasses.jar，这一步可以加上缓存，根据allclasses.jar的md5值命名缓存文件（.jar.opt），如果有缓存直接复制成allclasses.jar就可以了。</p>
<h3 id="代码-1">代码</h3><blockquote>
<p>Talk is cheap. Show me the code.</p>
</blockquote>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">afterEvaluate &#123;</span><br><span class="line">    <span class="keyword">def</span> manifestFile = android.<span class="keyword">sourceSets</span>.main.manifest.srcFile</span><br><span class="line">    <span class="keyword">def</span> packageName = <span class="keyword">new</span> XmlParser().parse(manifestFile).attribute(<span class="string">'package'</span>)</span><br><span class="line">    <span class="keyword">def</span> rootPackagePrefix = packageName.replace(<span class="string">'.'</span>, <span class="string">'/'</span>) + <span class="string">'/'</span></span><br><span class="line"></span><br><span class="line">    android.applicationVariants.<span class="keyword">each</span> &#123; variant -&gt;</span><br><span class="line">        <span class="keyword">def</span> dx = tasks.findByName(<span class="string">"dex$&#123;variant.name.capitalize()&#125;"</span>)</span><br><span class="line">        Set&lt;<span class="keyword">File</span>&gt; inputFiles = dx.inputs.files.files</span><br><span class="line">        <span class="keyword">def</span> allClassesJar;</span><br><span class="line">        inputFiles.<span class="keyword">each</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (it.name.endsWith(<span class="string">".jar"</span>)) &#123;</span><br><span class="line">                allClassesJar = it;</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> (allClassesJar != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">def</span> unifyRImport = <span class="string">"unifyRImport$&#123;variant.name.capitalize()&#125;"</span></span><br><span class="line">            <span class="keyword">def</span> bakJar = <span class="keyword">new</span> <span class="keyword">File</span>(allClassesJar.getParent(), allClassesJar.name + <span class="string">".bak"</span>)</span><br><span class="line">            <span class="keyword">task</span>(unifyRImport) &lt;&lt; &#123;</span><br><span class="line">                <span class="keyword">File</span> unifyRJar = <span class="keyword">new</span> <span class="keyword">File</span>(allClassesJar.getParent(), <span class="string">"$&#123;md5(allClassesJar)&#125;.jar.opt"</span>)</span><br><span class="line">                <span class="keyword">if</span> (!unifyRJar.exists()) &#123;</span><br><span class="line">                    allClassesJar.getParentFile().<span class="keyword">eachFile</span> &#123; <span class="keyword">file</span> -&gt;</span><br><span class="line">                        <span class="keyword">if</span> (<span class="keyword">file</span>.name.endsWith(<span class="string">".jar.opt"</span>)) &#123;</span><br><span class="line">                            <span class="keyword">file</span>.<span class="keyword">delete</span>()</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    JarFile jarFile = <span class="keyword">new</span> JarFile(allClassesJar);</span><br><span class="line">                    Enumeration enumeration = jarFile.entries();</span><br><span class="line">                    JarOutputStream jarOutputStream = <span class="keyword">new</span> JarOutputStream(<span class="keyword">new</span> FileOutputStream(unifyRJar));</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                    <span class="keyword">while</span> (enumeration.hasMoreElements()) &#123;</span><br><span class="line">                        JarEntry jarEntry = (JarEntry) enumeration.nextElement();</span><br><span class="line">                        String entryName = jarEntry.getName();</span><br><span class="line">                        ZipEntry zipEntry = <span class="keyword">new</span> ZipEntry(entryName);</span><br><span class="line"></span><br><span class="line">                        InputStream inputStream = jarFile.getInputStream(jarEntry);</span><br><span class="line">                        <span class="keyword">if</span> (entryName.startsWith(dpPackagePrefix) &amp;&amp; entryName.endsWith(<span class="string">".class"</span>)) &#123;</span><br><span class="line">                            <span class="keyword">if</span> (!entryName.contains(<span class="string">"R\$"</span>)) &#123;</span><br><span class="line">                                jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                                jarOutputStream.<span class="keyword">write</span>(unifyR(inputStream, rootPackagePrefix));</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="comment">//NovaLib中R$drawable有被反射使用，不删除</span></span><br><span class="line">                                <span class="keyword">if</span> (entryName.startsWith(rootPackagePrefix) || entryName.equals(libDrawableClass)) &#123;</span><br><span class="line">                                    jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                                    jarOutputStream.<span class="keyword">write</span>(IOUtils.toByteArray(inputStream));</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            jarOutputStream.putNextEntry(zipEntry);</span><br><span class="line">                            jarOutputStream.<span class="keyword">write</span>(IOUtils.toByteArray(inputStream));</span><br><span class="line">                        &#125;</span><br><span class="line">                        jarOutputStream.closeEntry();</span><br><span class="line">                    &#125;</span><br><span class="line">                    jarOutputStream.close();</span><br><span class="line">                    jarFile.close();</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (bakJar.exists()) &#123;</span><br><span class="line">                    bakJar.<span class="keyword">delete</span>()</span><br><span class="line">                &#125;</span><br><span class="line">                allClassesJar.renameTo(bakJar)</span><br><span class="line">                copyFileUsingStream(unifyRJar, allClassesJar)</span><br><span class="line">            &#125;</span><br><span class="line">            tasks[unifyRImport].dependsOn dx.taskDependencies.getDependencies(dx)</span><br><span class="line">            dx.dependsOn tasks[unifyRImport]</span><br><span class="line"></span><br><span class="line">            <span class="comment">//还原allclasses.jar</span></span><br><span class="line">            <span class="keyword">def</span> assemble = tasks.findByName(<span class="string">"assemble$&#123;variant.name.capitalize()&#125;"</span>)</span><br><span class="line">            <span class="keyword">def</span> restoreClassesJar = <span class="string">"restore$&#123;variant.name.capitalize()&#125;"</span></span><br><span class="line">            <span class="keyword">task</span>(restoreClassesJar) &lt;&lt; &#123;</span><br><span class="line">                <span class="keyword">if</span> (bakJar.exists()) &#123;</span><br><span class="line">                    allClassesJar.<span class="keyword">delete</span>()</span><br><span class="line">                    bakJar.renameTo(allClassesJar)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            tasks[restoreClassesJar].dependsOn dx</span><br><span class="line">            assemble.dependsOn tasks[restoreClassesJar]</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>这次主要修改了afterEvaluate里面的东西，然后新加了自定义的md5和copyFileUsingStream方法，groovy都有些脚本的特性了，获取md5和复制文件还要自己撸，我也是醉了。</p>
<h2 id="dex增量">dex增量</h2><p>至此，Field 65535的问题基本上算是完美解决了。但是你会发现改了一行代码，build的时间还是很久，主要耗时的任务就是dex，这个怎么搞？</p>
<p>两种方案，<a href="https://buckbuild.com/" target="_blank" rel="external">Buck</a>和<a href="https://github.com/mmin18/LayoutCast" target="_blank" rel="external">LayoutCast</a>。Buck是facebook出品的，微信很早就用上了，但有很多规则，侵入性较强，代码改动大。 LayoutCast是我司屠大师研发的，对项目改动非常小，应该也有借鉴buck的一些思路。</p>
<p>稍微看了一下buck的思路，buck的dex粒度非常小，每个module都会打成一个dex，最后合并成一个大的dex，修改代码后，只需要重新生成代码所在的dex，然后通过adb传递到手机，动态替换该dex即可，都不需要重新生成apk，也节省了安装的时间。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>很多Android开发者都知道，同一个dex中方法数不能超过65535。为什么呢？来看一下<a href="http://blog.csdn.net/t12x3456/article/details/40837287" target="_blank" rel="extern]]>
    </summary>
    
      <category term="65535" scheme="http://jiajixin.cn/tags/65535/"/>
    
      <category term="65k" scheme="http://jiajixin.cn/tags/65k/"/>
    
      <category term="Dalvik" scheme="http://jiajixin.cn/tags/Dalvik/"/>
    
      <category term="android" scheme="http://jiajixin.cn/tags/android/"/>
    
      <category term="buck" scheme="http://jiajixin.cn/tags/buck/"/>
    
      <category term="field" scheme="http://jiajixin.cn/tags/field/"/>
    
      <category term="gradle" scheme="http://jiajixin.cn/tags/gradle/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android SDK镜像服务器搭建]]></title>
    <link href="http://jiajixin.cn/2015/10/11/android-sdk-mirror/"/>
    <id>http://jiajixin.cn/2015/10/11/android-sdk-mirror/</id>
    <published>2015-10-11T15:51:19.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>前段时间XcodeGhost闹的沸沸扬扬，意外发现迅雷下载真的是可以被污染的，详情见<a href="http://gold.xitu.io/entry/56023eabddb263b560c8d254" target="_blank" rel="external">对迅雷下载进行投毒的简单尝试</a>，也提醒了开发者不要随意从非官方渠道下载开发工具。</p>
<p>我们都知道Android SDK由于国内网络环境的原因，下载速度很慢，有很多<a href="http://www.androiddevtools.cn/" target="_blank" rel="external">国内搭建的镜像</a>，但镜像服务器下载带宽肯定有一定限制，并且内网一般也会限制下载速度。所以在内网搭建一个Android SDK镜像服务器很有必要，一方面可以保证速度，几兆每秒肯定不成问题，另一方面大家从内网下载，也可以保证安全性。貌似有的团队用FTP存储SDK，还是太Low了，用起来肯定不如镜像方便。</p>
<p>搭建Android SDK镜像服务器，有两个现成的开源解决方案，<a href="https://github.com/renfeng/android-repository" target="_blank" rel="external">苏州GDG</a>和<a href="https://github.com/opencas/mirrors" target="_blank" rel="external">中科院开源镜像小组</a>，苏州GDG牛皮吹的很响，貌似还在某个会议上发表过演讲，但估计是个人作品，东西不够完善，更新缓慢。中科院开源镜像小组开源的方案非常不错，并且也对外开放了镜像服务，一直运行良好，Github上还有定时同步和Nginx的配置信息。</p>
<p>花了一个下午的时间，搞定了内网Android SDK镜像服务器，下载速度平均5M/s，效果杠杠的。</p>
<h2 id="下载SDK">下载SDK</h2><p>直接运行<a href="https://github.com/opencas/mirrors/blob/master/script/bin/android.py" target="_blank" rel="external">android.py脚本</a>，由于SDK下载需要翻墙，所以你可能需要代理。命令行程序默认不走你的全局代理，我在家用sock5代理，在mac下用的是<a href="http://www.dreamxu.com/proxychains-ng/" target="_blank" rel="external">proxychains4</a>，公司内网有Http代理服务器，想在命令行中使用，需要设置<code>http_proxy</code>和<code>https_proxy</code>环境变量，临时使用直接在终端中输入：</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> http_proxy=http:<span class="comment">//yourproxyaddress:proxyport</span></span><br><span class="line"><span class="keyword">export</span> https_proxy=http:<span class="comment">//yourproxyaddress:proxyport</span></span><br></pre></td></tr></table></figure>
<p>细看脚本，你会发现脚本会先去下载<code>repository-*.xml</code>和<code>addons_list-*.xml</code>，xml里面是一些文件的具体下载地址，包含sdk、build_tools、support包等等，然后去下载具体的文件。</p>
<h2 id="服务器配置">服务器配置</h2><p>因为最终搭建的镜像要作为代理服务器，在SDK Manager中配置，所以需要你配置ngnix的虚拟主机。在<code>/etc/nginx/conf.d/default.conf</code>文件中，修改<code>server_name</code>字段：</p>
<figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="tag">server_name</span> <span class="tag">dl</span><span class="class">.google</span><span class="class">.com</span></span><br><span class="line">            <span class="tag">dl-ssl</span><span class="class">.google</span><span class="class">.com</span>;</span><br></pre></td></tr></table></figure>
<p>因为当SDK Manger配置你的镜像作为代理后，首先会拉取xml配置文件，比如<code>https://dl.google.com/android/repository/repository-11.xml</code>，最终会找到镜像服务器的<code>android/repository/repository-11.xml</code>文件，下载其他SDK文件也是同样，所以需要你把之前运行脚本下载的文件放到正确的目录中。</p>
<h2 id="使用">使用</h2><p>别人要使用你的镜像，需要配置SDK Manager，直接去看<a href="http://www.androiddevtools.cn/" target="_blank" rel="external">androiddevtools</a>的介绍吧。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>前段时间XcodeGhost闹的沸沸扬扬，意外发现迅雷下载真的是可以被污染的，详情见<a href="http://gold.xitu.io/entry/56023eabddb263b560c8d254" target="_blank" rel="external">对迅雷]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Android SDk" scheme="http://jiajixin.cn/tags/Android-SDk/"/>
    
      <category term="nginx" scheme="http://jiajixin.cn/tags/nginx/"/>
    
      <category term="镜像" scheme="http://jiajixin.cn/tags/%E9%95%9C%E5%83%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android Studio插件开发]]></title>
    <link href="http://jiajixin.cn/2015/10/11/android-studio-plugin-dev/"/>
    <id>http://jiajixin.cn/2015/10/11/android-studio-plugin-dev/</id>
    <published>2015-10-11T15:25:55.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>很早就想开发个Android Studio的插件了，本来想撸个清理无用资源的，但Android项目使用Gradle构建后，Lint检测出来的内容有所变化，不是很好判断要清理的资源，并且开启shrinkResources选项可以轻松的完成，所以放弃了。正好最近公司有个需求，可以做成个插件一键搞定，花了一天半的时间就搞定了。</p>
<h2 id="插件开发">插件开发</h2><p>Android Studio基于IntelliJ IDEA开发，而IntelliJ IDEA是捷克软件公司JetBrains的作品，JetBrains成立十五年，做了很多语言的IDE，硕果累累，甚至还设计了Kotlin语言。</p>
<p>Android Studio自身不具备开发插件的功能，由于Android Studio基于IntelliJ IDEA，所以可以为IntelliJ IDEA开发插件，同样可以用在Android Studio上。</p>
<p>IntelliJ IDEA插件开发的官方文档可以看<a href="http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html" target="_blank" rel="external">这里</a>，需要下载IntelliJ IDEA，在IntelliJ IDEA上开发，方便调试的话需要下载源代码。大多数插件可能添加个action，再加点图形界面就差不多了，界面就是Swing那一套，当年写了那么多Swing程序，没想到现在还用的上。IntelliJ IDEA的API很多，可能很难都去读，用到什么现查吧，大多数直接用JDK就可以了。如果有些地方不太清楚的话，可以去Github上看看一些开源的插件是怎么搞的，比着葫芦画瓢估计也够用了。</p>
<h2 id="Tips">Tips</h2><p>插件开发中遇到一些琐碎的东西搞不清楚，文档也不是很全，最终基本上也都找到解决方案，列几个Tips吧：</p>
<ul>
<li><p>Toolbar图标</p>
<p>  plugin.xml中的action可以设置icon属性，但若把图片放在普通路径下会提示找不到，看到文档说放在固定目录下，但貌似也不起作用，最终放在<code>out&gt;production&gt;project</code>下才可以，要知道这个目录下都是一些编译好的class文件，直接放在这也是醉了。</p>
</li>
<li><p>Dialog居中显示</p>
<p>  <code>setLocationRelativeTo（null）</code>可以使其屏幕居中，但如果IDE不全屏，显示的效果就不好看了，想使Dialog在IDE窗口居中显示，可以这样设置：</p>
  <figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="title">setLocationRelativeTo</span><span class="params">(WindowManager.getInstance()</span></span>.<span class="function"><span class="title">getFrame</span><span class="params">(actionEvent.getProject()</span></span>)</span><br></pre></td></tr></table></figure>
</li>
<li><p>Android SDK<br>你要用Lint，那要找到Android SDK的目录，用下面这段代码：</p>
  <figure class="highlight aspectj"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function">Sdk <span class="title">findAndroidSDK</span><span class="params">()</span> </span>&#123;</span><br><span class="line">       Sdk[] allJDKs = ProjectJdkTable.getInstance().getAllJdks();</span><br><span class="line">       <span class="keyword">for</span> (Sdk sdk : allJDKs) &#123;</span><br><span class="line">           <span class="keyword">if</span> (sdk.getSdkType().getName().toLowerCase().contains(<span class="string">"android"</span>)) &#123;</span><br><span class="line">               <span class="keyword">return</span> sdk;</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">	</span><br><span class="line">       <span class="keyword">return</span> <span class="keyword">null</span>; <span class="comment">// no Android SDK found</span></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>状态栏显示信息</p>
  <figure class="highlight cmake"><table><tr><td class="code"><pre><span class="line">StatusBar statusBar = WindowManager.getInstance().getStatusBar((<span class="keyword">Project</span>) DataKeys.<span class="keyword">PROJECT</span>.getData(actionEvent.getDataContext()));</span><br><span class="line">statusBar.setInfo(<span class="string">"要显示的内容"</span>);</span><br></pre></td></tr></table></figure>
</li>
<li><p>文件即时刷新</p>
<p>  修改文件后，不会在IDE中实时刷新，需要你调用VirtualFile的refresh方法。</p>
  <figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">	VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(<span class="keyword">new</span> <span class="keyword">File</span>(projectbuildFilePath));</span><br><span class="line">vf.refresh(<span class="keyword">true</span>, <span class="keyword">false</span>);</span><br></pre></td></tr></table></figure></li>
</ul>
]]></content>
    <summary type="html">
    <![CDATA[<p>很早就想开发个Android Studio的插件了，本来想撸个清理无用资源的，但Android项目使用Gradle构建后，Lint检测出来的内容有所变化，不是很好判断要清理的资源，并且开启shrinkResources选项可以轻松的完成，所以放弃了。正好最近公司有个需求，可]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Android Studio" scheme="http://jiajixin.cn/tags/Android-Studio/"/>
    
      <category term="IntelliJ IDEA" scheme="http://jiajixin.cn/tags/IntelliJ-IDEA/"/>
    
      <category term="Java" scheme="http://jiajixin.cn/tags/Java/"/>
    
      <category term="插件" scheme="http://jiajixin.cn/tags/%E6%8F%92%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[构建神器Gradle]]></title>
    <link href="http://jiajixin.cn/2015/08/07/gradle-android/"/>
    <id>http://jiajixin.cn/2015/08/07/gradle-android/</id>
    <published>2015-08-07T08:58:59.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p><strong>本文为CSDN约稿文章，首发地址为：<a href="http://www.csdn.net/article/2015-08-10/2825420" target="_blank" rel="external">Android项目中如何用好构建神器Gradle?</a>。如需转载，请与CSDN联系。原文错误，会在本站更新。</strong></p>
<p>最近在忙团队并行开发的事情，主要是将各个团队的代码分库，一方面可以降低耦合，为后面模块插件化做铺垫，另一方面采用二进制编译，可以加快编译速度。分库遇到了一些问题，很多都要通过Gradle脚本解决，所以稍微花时间研究了一下。</p>
<p>Gradle虽为构建神器，但感觉学习曲线比较陡峭。<a href="https://docs.gradle.org/current/userguide/userguide.html" target="_blank" rel="external">Gradle User Guide</a>内容很多，但有点太多了，多的你看不完，<a href="http://tools.android.com/tech-docs/new-build-system/user-guide" target="_blank" rel="external">Gradle Plugin User Guide</a>一篇文章主要讲了Android相关的配置，看完可能感觉马马虎虎会用，但到了修改一些构建流程的时候还是不知所措。经过一段时间的摸索，我觉得在Android项目中用好Gradle，你要做到以下三点：</p>
<ol>
<li>了解 <code>Groovy</code> 基本语法。</li>
<li>粗读 <code>Gradle User Guide</code> 和 <code>Gradle Plugin User Guide</code>。</li>
<li>实战，实战，再实战。(三遍，你懂的)</li>
</ol>
<p>涉及到的知识点和内容比较多，我不会一一讲解，本文主要会解答自己学习过程中的一些疑问，讲解一些相关概念和实战经验，过程中也会推荐一些有质量的博客文章。</p>
<h2 id="Groovy语言">Groovy语言</h2><p>Gradle基于Groovy语言，虽然接触Gradle比较久，甚至写过一点Groovy语句，但对语言本身并不了解。为什么用Groovy呢？Groovy运行在JVM上，在Java语言的基础上，借鉴了脚本语言的诸多特性，相比Java代码量更少，Groovy兼容Java，可以使用Groovy和Java混合编程，可以直接使用各种Java类库。</p>
<p>Groovy语法的学习，推荐官方文章<a href="http://www.groovy-lang.org/differences.html" target="_blank" rel="external">Differences with Java</a>和IBM developerWorks的<a href="http://www.ibm.com/developerworks/cn/education/java/j-groovy/j-groovy.html" target="_blank" rel="external">精通Groovy</a>。了解了基本语法，对读写gradle脚本都会有帮助，比如随便举下面几个例子：</p>
<ol>
<li><p>比如为何在gradle脚本中使用InputStream不用import包，而使用ZipFile需要import包？因为groovy默认import了下面的包和类，无需再import.</p>
 <figure class="highlight css"><table><tr><td class="code"><pre><span class="line"><span class="tag">java</span><span class="class">.io</span>.*</span><br><span class="line"><span class="tag">java</span><span class="class">.lang</span>.*</span><br><span class="line"><span class="tag">java</span><span class="class">.math</span><span class="class">.BigDecimal</span></span><br><span class="line"><span class="tag">java</span><span class="class">.math</span><span class="class">.BigInteger</span></span><br><span class="line"><span class="tag">java</span><span class="class">.net</span>.*</span><br><span class="line"><span class="tag">java</span><span class="class">.util</span>.*</span><br><span class="line"><span class="tag">groovy</span><span class="class">.lang</span>.*</span><br><span class="line"><span class="tag">groovy</span><span class="class">.util</span>.*</span><br></pre></td></tr></table></figure>
</li>
<li><p>经常看到${var1}的用法是怎么回事？<br>这是Groovy中的<a href="http://blog.csdn.net/hivon/article/details/2271000" target="_blank" rel="external">GString</a>，可以在双引号中直接使用，用于字符串叠加非常方便。</p>
 <figure class="highlight ruby"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">dx</span> = <span class="title">tasks</span>.<span class="title">findByName</span><span class="params">(<span class="string">"dex$&#123;variant.name.capitalize()&#125;"</span>)</span></span></span><br></pre></td></tr></table></figure>
</li>
<li><p>下面的代码你真的能看懂吗？</p>
 <figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="comment">//apply是一个方法，plugin是参数，值为'com.android.application'</span></span><br><span class="line">apply plugin: <span class="string">'com.android.application'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span><br><span class="line">*buildscript,repositories和dependencies本身是方法名。</span><br><span class="line">*后面跟的大括号部分，都是一个闭包，作为方法的参数。</span><br><span class="line">*闭包可以简单的理解为一个代码块或方法指针。</span><br><span class="line">*/</span></span><br><span class="line"><span class="keyword">buildscript</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">dependencies</span> &#123;</span><br><span class="line">        <span class="keyword">classpath</span> <span class="string">'com.android.tools.build:gradle:1.2.3'</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//groovy遍历的一种写法 each后面是闭包</span></span><br><span class="line">android.applicationVariants.<span class="keyword">each</span> &#123; variant -&gt;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="Gradle概念">Gradle概念</h2><p>下面讲几个Gradle相关的概念，几个比较重要的吧，更多的东西还是要自己去看<code>Gradle User Guide</code>。</p>
<h3 id="生命周期">生命周期</h3><p>Gradle构建系统有自己的<a href="https://docs.gradle.org/current/userguide/build_lifecycle.html" target="_blank" rel="external">生命周期</a>，初始化、配置和运行三个阶段。</p>
<ol>
<li><p>初始化阶段，会去读取根工程中setting.gradle中的include信息，决定有哪几个工程加入构建，创建project实例，比如下面有三个工程：</p>
<figure class="highlight elixir"><table><tr><td class="code"><pre><span class="line"><span class="keyword">include</span> <span class="string">':app'</span>, <span class="string">':lib1'</span>, <span class="string">':lib2'</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>配置阶段，会去执行所有工程的build.gradle脚本，配置project对象，一个对象由多个任务组成，此阶段也会去创建、配置task及相关信息。</p>
</li>
<li>运行阶段，根据gradle命令传递过来的task名称，执行相关依赖任务。</li>
</ol>
<h3 id="任务创建">任务创建</h3><p>很多文章都会告诉你，任务创建要这样：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">task</span> hello &#123;</span><br><span class="line">    <span class="keyword">doLast</span> &#123;</span><br><span class="line">        <span class="keyword">println</span> <span class="string">"hello"</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>或者用<code>&lt;&lt;</code>替换doLast，那我就很纳闷，定义个任务怎么这么麻烦，还要加什么doLast，我直接这样不行吗？</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">task</span> hello &#123;</span><br><span class="line">    <span class="keyword">println</span> <span class="string">"hello"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的这种写法，“hello” 是在gradle的配置阶段打印出来的，而前面的写法是在gradle的运行阶段打印出来的，所以怎么写要看你的需求了。</p>
<p>另外task中有一个action list，task运行时会顺序执行action list中的action，doLast或者doFirst后面跟的闭包就是一个action，doLast是把action插入到list的最后面，而doFirst是把action插入到list的最前面。</p>
<h3 id="任务依赖">任务依赖</h3><p>当我们在Android工程中执行./gradlew build的时候，会有很多任务运行，因为build任务依赖了很多任务，要先执行依赖任务才能运行当前任务。任务依赖主要使用dependsOn方法，如下所示：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">task</span> A &lt;&lt; &#123;<span class="keyword">println</span> <span class="string">'Hello from A'</span>&#125;</span><br><span class="line"><span class="keyword">task</span> B &lt;&lt; &#123;<span class="keyword">println</span> <span class="string">'Hello from B'</span>&#125;</span><br><span class="line"><span class="keyword">task</span> C &lt;&lt; &#123;<span class="keyword">println</span> <span class="string">'Hello from C'</span>&#125;</span><br><span class="line">B.dependsOn A</span><br><span class="line">C.dependsOn B</span><br></pre></td></tr></table></figure>
<p>了解更多，可以看一下侦跃翻译的<a href="http://blog.csdn.net/lzyzsd/article/details/46935405" target="_blank" rel="external">Gradle tip #3-Task顺序</a>。</p>
<h3 id="增量构建">增量构建</h3><p>你在执行gradle命令的时候，是不是经常看到有些任务后面跟着[UP-TO-DATE]，这是怎么回事？</p>
<p>在Gradle中，每一个task都有inputs和outputs，如果在执行一个Task时，如果它的输入和输出与前一次执行时没有发生变化，那么Gradle便会认为该Task是最新的，因此Gradle将不予执行，这就是增量构建的概念。</p>
<p>一个task的inputs和outputs可以是一个或多个文件，可以是文件夹，还可以是project的某个property，甚至可以是某个闭包所定义的条件。自定义task默认每次执行，但通过指定inputs和outputs，可以达到增量构建的效果。</p>
<h3 id="依赖传递">依赖传递</h3><p>Gradle默认支持传递性依赖，比如当前工程依赖包A，包A依赖包B，那么当前工程会自动依赖包B。同时，Gradle支持排除和关闭依赖性传递。</p>
<p>之前引入远程AAR，一般会这样写：</p>
<figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="title">compile</span> <span class="string">'com.somepackage:LIBRARY_NAME:1.0.0<span class="variable">@aar</span>'</span></span><br></pre></td></tr></table></figure>
<p>上面的写法会关闭依赖性传递，所以有时候可能就会出问题，为什么呢？本来以为@aar是指定下载的格式，但其实不然，远程仓库文件下载格式应该是由pom文件中<a href="http://www.infoq.com/cn/news/2011/06/xxb-maven-9-package" target="_blank" rel="external">packaging属性</a>决定的，@符号的真正作用是<a href="https://docs.gradle.org/current/userguide/dependency_management.html" target="_blank" rel="external">Artifact only notation</a>,也就是只下载文件本身，不下载依赖，相当于变相的关闭了依赖传递，可以看一下sf的<a href="http://stackoverflow.com/questions/22795455/transitive-dependencies-not-resolved-for-aar-library-using-gradle" target="_blank" rel="external">这个问题</a>，通过添加transitive=true可以解决。但其实如果远程仓库有pom文件存在，compile后面根本不需要加”@aar”，也就不会遇到这个问题了。</p>
<h2 id="Android_Gradle实战">Android Gradle实战</h2><p>下面讲讲在Android Gradle实战中遇到的一些问题和经验，感觉还是蛮多干货的。</p>
<h3 id="productFlavors">productFlavors</h3><p>这个东西基本上已经烂大街了，gradle的项目一般都会使用Product Flavor，看完美团的文章，你应该就懂了。</p>
<blockquote>
<p><a href="http://tech.meituan.com/mt-apk-adaptation.html" target="_blank" rel="external">美团Android自动化之旅—适配渠道包</a></p>
</blockquote>
<h3 id="buildTypes">buildTypes</h3><p>很多App有内测版和正式版，怎么让他们同时安装在一个手机上？同时安装在一个手机上，要求packageName不同的，用productFlavors可以解决，但可能不够优雅，alpha版本还要来个debug和release版本岂不是很蛋疼？可以用buildTypes来解决，淘宝朱鸿的<a href="http://hugozhu.myalert.info/2014/08/03/50-use-gradle-to-customize-apk-build.html" target="_blank" rel="external">文章</a>有比较详细的讲解，但有些内容可能有些过时了，需要更改脚本。</p>
<h3 id="依赖更新">依赖更新</h3><p>项目依赖的远程包如果有更新，会有提醒或者自动更新吗？</p>
<p>SNAPSHOT(changing)和+号(dynamic)版本默认24小时自动更新，通过更改resolutionStrategy可以修改检查周期。</p>
<figure class="highlight smali"><table><tr><td class="code"><pre><span class="line">configurations.all &#123;</span><br><span class="line">    //<span class="instruction"> check </span>for updates every build</span><br><span class="line">    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'</span><br><span class="line">    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'</span><br><span class="line">&#125;</span><br><span class="line">dependencies &#123;</span><br><span class="line">	compile 'com.dianping.nova.business:search:1.0.+'</span><br><span class="line">	compile 'com.dianping.nova.business:selectdish:1.0.6-SNAPSHOT'</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="上传aar到Maven仓库">上传aar到Maven仓库</h3><p>在工程的build.gradle中添加如下脚本：</p>
<figure class="highlight groovy"><table><tr><td class="code"><pre><span class="line">apply <span class="string">plugin:</span> <span class="string">'maven'</span></span><br><span class="line">uploadArchives &#123;</span><br><span class="line">    repositories &#123;</span><br><span class="line">        mavenDeployer &#123;</span><br><span class="line">            pom.groupId = GROUP_ID</span><br><span class="line">            pom.artifactId = ARTIFACT_ID</span><br><span class="line">            pom.version = VERSION</span><br><span class="line">            repository(<span class="string">url:</span> RELEASE_REPOSITORY_URL) &#123;</span><br><span class="line">                authentication(<span class="string">userName:</span> USERNAME, <span class="string">password:</span> PASSWORD)</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>在build.gradle同目录下添加gradle.properties文件，配置如下：</p>
<figure class="highlight protobuf"><table><tr><td class="code"><pre><span class="line"><span class="constant">GROUP_ID</span>=dianping.android.nova.thirdparty</span><br><span class="line"><span class="constant">ARTIFACT_ID</span>=zxing</span><br><span class="line"><span class="constant">VERSION</span>=<span class="number">1.0</span></span><br><span class="line"><span class="constant">RELEASE_REPOSITORY_URL</span>=http:<span class="comment">//mvn.dp.com/nova</span></span><br><span class="line"><span class="constant">USERNAME</span>=hello</span><br><span class="line"><span class="constant">PASSWORD</span>=hello</span><br></pre></td></tr></table></figure>
<p>gradle.properties的属性会被build.gradle读取用来上传aar，最后执行<code>./gradlew :Zxing:uploadArchives</code>即可。</p>
<p>更多配置，可参考<a href="http://blog.csdn.net/qinxiandiqi/article/details/44458707" target="_blank" rel="external">建立企业内部maven服务器并使用Android Studio发布公共项目</a>。</p>
<h3 id="取消任务">取消任务</h3><p>项目构建过程中那么多任务，有些test相关的任务可能根本不需要，可以直接关掉，在build.gradle中加入如下脚本：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line">tasks.whenTaskAdded &#123; <span class="keyword">task</span> -&gt;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">task</span>.name.contains(<span class="string">'AndroidTest'</span>)) &#123;</span><br><span class="line">        <span class="keyword">task</span>.enabled = <span class="keyword">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>tasks会获取当前project中所有的task，enabled属性控制任务开关，whenTaskAdded后面的闭包会在gradle配置阶段完成。</p>
<h3 id="加入任务">加入任务</h3><p>任务可以取消了，但还不尽兴啊，想加入任务怎么搞？前面讲了dependsOn的方法，那就拿过来用啊，但是原有任务的依赖关系你又不是很清楚，甚至任务名称都不知道，怎么搞？</p>
<p>比如我想在执行dex打包之前，加入一个hello任务，可以这么写：</p>
<figure class="highlight elixir"><table><tr><td class="code"><pre><span class="line">afterEvaluate &#123;</span><br><span class="line">    android.applicationVariants.each &#123; variant -&gt;</span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">dx</span></span> = tasks.findByName(<span class="string">"dex$&#123;variant.name.capitalize()&#125;"</span>)</span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">hello</span></span> = <span class="string">"hello$&#123;variant.name.capitalize()&#125;"</span></span><br><span class="line">        task(hello) &lt;&lt; &#123;</span><br><span class="line">			println <span class="string">"hello"</span></span><br><span class="line">        &#125;</span><br><span class="line">        tasks.findByName(hello).dependsOn dx.taskDependencies.getDependencies(dx)</span><br><span class="line">        dx.dependsOn tasks.findByName(hello)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>afterEvaluate是什么鸟？你可以理解为在配置阶段要结束，项目评估完会走到这一步。</p>
<p>variant呢？variant = productFlavors+ buildTypes，所以dex打包的任务可能就是dexCommonDebug。</p>
<p>你怎么知道dex任务的具体名称？Android Studio中的Gradle Console在执行gradle任务的时候会有输出，可以仔细观察一下。</p>
<p>hello任务定义的这么复杂干啥？我直接就叫hello不行吗?不行，each就是遍历variants，如果每个都叫hello，多个variant都一样，岂不是傻傻分不清楚，加上variant的name做后缀，才有任务的区分。</p>
<p>关键来了，dx.taskDependencies.getDependencies(dx)会获取dx任务的所有依赖，让hello任务依赖dx任务的所有依赖，再让dx任务依赖hello任务，这样就可以加入某个任务到构建流程了，是不是感觉非常灵活。</p>
<p>我突然想到，用doFirst的方式加入一个action到dx任务中，应该也可以达到上面效果。</p>
<h3 id="gradle加速">gradle加速</h3><p>gradle加速可以看看这位朋友写的<a href="http://blog.isming.me/2015/03/18/android-build-speed-up/" target="_blank" rel="external">加速Android Studio/Gradle构建</a>，我就不多嘴了。并行编译，常驻内存，还有离线模式这些思路对gradle的加速感觉还是比较有限。</p>
<p>想要更快，可以尝试下Facebook出品的<a href="https://buckbuild.com/" target="_blank" rel="external">Buck</a>，可以看一下Vine团队适配Buck的<a href="http://engineering.vine.co/post/117873038742/reducing-build-times-by-adopting-buck" target="_blank" rel="external">技术文章</a>，我们的架构师也有适配Buck，加速效果在10倍左右，但有两个缺点，不支持Windows系统，不支持远程依赖。</p>
<h3 id="任务监听">任务监听</h3><p>你想知道每个执行任务的运行时间吗？你想知道每个执行任务都是干嘛的吗？把下面这段脚本加入build.gradle中即可：</p>
<figure class="highlight aspectj"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TimingsListener</span> <span class="keyword">implements</span> <span class="title">TaskExecutionListener</span>, <span class="title">BuildListener</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Clock clock</span><br><span class="line">    <span class="keyword">private</span> timings = []</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">beforeExecute</span><span class="params">(Task task)</span> </span>&#123;</span><br><span class="line">        clock = <span class="keyword">new</span> org.gradle.util.Clock()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">afterExecute</span><span class="params">(Task task, TaskState taskState)</span> </span>&#123;</span><br><span class="line">        def ms = clock.timeInMs</span><br><span class="line">        timings.add([ms, task.path])</span><br><span class="line">        task.project.logger.warn <span class="string">"$&#123;task.path&#125; took $&#123;ms&#125;ms"</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">buildFinished</span><span class="params">(BuildResult result)</span> </span>&#123;</span><br><span class="line">        println <span class="string">"Task timings:"</span></span><br><span class="line">        <span class="keyword">for</span> (timing in timings) &#123;</span><br><span class="line">            <span class="keyword">if</span> (timing[<span class="number">0</span>] &gt;= <span class="number">50</span>) &#123;</span><br><span class="line">                printf <span class="string">"%7sms  %s\n"</span>, timing</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">buildStarted</span><span class="params">(Gradle gradle)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">projectsEvaluated</span><span class="params">(Gradle gradle)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">projectsLoaded</span><span class="params">(Gradle gradle)</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="annotation">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">settingsEvaluated</span><span class="params">(Settings settings)</span> </span>&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">gradle.addListener <span class="keyword">new</span> TimingsListener()</span><br></pre></td></tr></table></figure>
<p>上面是对每个任务计时的一个例子，想要了解每个任务的作用，你可以修改上面的脚本，打印出每个任务的inputs和outputs。比如assembleDebug那么多依赖任务，每个都是干什么的，一会compile，一会generate，有什么区别？看到每个task的输入输出，就可以大体看出它的作用。如果对assemble的每个任务监听，你会发现改一行代码build的时间主要花费在了dex上，buck牛逼的地方就是对这个地方进行了优化，大大减少了增量编译运行的时间。</p>
<h3 id="buildscript方法">buildscript方法</h3><p>Android项目中，根工程默认的build.gradle应该是这样的：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="comment">// Top-level build file where you can add configuration options common to all sub-projects/modules.</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">buildscript</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">dependencies</span> &#123;</span><br><span class="line">        <span class="keyword">classpath</span> <span class="string">'com.android.tools.build:gradle:1.2.3'</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">allprojects</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>一会一个jcenter()这是在干什么？buildscript方法的作用是配置脚本的依赖，而我们平常用的compile是配置project的依赖。repositories的意思就是需要包的时候到哥这里来找，然后你以为<code>com.android.tools.build:gradle:1.2.3</code>会从jcenter那里下载了是吧，图样图森破，不信加入下面这段脚本看看输出：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">buildscript</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">        jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">repositories</span>.<span class="keyword">each</span> &#123;</span><br><span class="line">        <span class="keyword">println</span> it.getUrl()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">dependencies</span> &#123;</span><br><span class="line">        <span class="keyword">classpath</span> <span class="string">'com.android.tools.build:gradle:1.2.3'</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>结果是这样的：</p>
<blockquote>
<p>file:/Applications/Android%20Studio.app/Contents/gradle/m2repository/<br><a href="https://jcenter.bintray.com/" target="_blank" rel="external">https://jcenter.bintray.com/</a></p>
</blockquote>
<p>我靠，仓库竟然直接在Android Studio应用内部，下面还有更爽的，我们知道有依赖传递，上面classpath 中的<code>gradle</code>依赖<code>gradle-core</code>，<code>gradle-core</code>依赖<code>lint</code>，<code>lint</code>依赖<code>lint-checks</code>，<code>lint-checks</code>最后依赖到了<code>asm</code>，并且这个根目录中的依赖配置会传到所有工程的配置文件，所以如果你要引用asm相关的类，不用设置classpath，直接import就可以了。你怎么知道前面的依赖关系的？看上面m2repository目录中对应的pom文件就可以了。</p>
<p>为什么讲到ASM呢？<a href="http://asm.ow2.org/" target="_blank" rel="external">ASM</a>又是个比较刁的东西，可以直接用来操纵Java字节码，达到动态更改class文件的效果。可以用ASM<a href="http://developer.51cto.com/art/201309/410861_all.htm" target="_blank" rel="external">面向切面编程</a>，达到解耦效果。<a href="http://tech.meituan.com/mt-android-auto-split-dex.html" target="_blank" rel="external">Android DEX自动拆包及动态加载简介</a>中提到的class依赖分析和R常量替换的脚本都可以用ASM来搞。</p>
<h3 id="引入脚本">引入脚本</h3><p>脚本写多了，都挤在一个build.gradle里也不好，人长大了总要自己出去住，那可以把部分脚本抽出去吗？当然可以，新建一个other.gradle把脚本抽离，然后在build.gradle中添加<code>apply from &#39;other.gradle&#39;</code>即可，抽出去以后你会发现本来可以直接import的asm包找不到了，怎么回事？根工程中配置的buildscript会传递到所有工程，但只会传到build.gradle脚本中，其他脚本可不管，所以你要在other.gradle中重新配置buildscript，可以在other.gradle中加入：</p>
<figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">buildscript</span> &#123;</span><br><span class="line">    <span class="keyword">repositories</span> &#123;</span><br><span class="line">       jcenter()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">dependencies</span> &#123;</span><br><span class="line">        <span class="keyword">classpath</span> <span class="string">'com.android.tools.build:gradle:1.2.3'</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="获取AndroidManifest文件">获取AndroidManifest文件</h3><p><a href="http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename" target="_blank" rel="external">ApplicationId versus PackageName</a>提到，gradle中的applicationid用来区分应用，manifest中packageName用来指定R文件包名，并且各个productFlavor 的manifest中的packageName应该一致。applicationid只是gradle脚本中的定义，其实最后生成的apk中的manifest文件的packageName还是会被applicationid替换掉。</p>
<p>那获取R文件的包名怎么搞？要获取AndroidManifest中package属性，并且这个manifest要是起始的文件，因为最终文件中的package属性会被applicationid冲掉，由于各个manifest中的package属性一样，并且非主manifest可以没有package属性，所以只有获取主manifest的package属性才是最准确的。</p>
<figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">def manifestFile = android<span class="class">.sourceSets</span><span class="class">.main</span><span class="class">.manifest</span><span class="class">.srcFile</span></span><br><span class="line">def packageName = new <span class="function"><span class="title">XmlParser</span><span class="params">()</span></span>.<span class="function"><span class="title">parse</span><span class="params">(manifestFile)</span></span>.<span class="function"><span class="title">attribute</span><span class="params">(<span class="string">'package'</span>)</span></span></span><br></pre></td></tr></table></figure>
<h3 id="无用资源">无用资源</h3><p>无用的资源就不要打包进APK了。</p>
<blockquote>
<p><a href="http://tools.android.com/tech-docs/new-build-system/resource-shrinking" target="_blank" rel="external">Resource Shrinking</a></p>
</blockquote>
<h3 id="一个Bug">一个Bug</h3><p>之前在创业公司，用<a href="https://travis-ci.org/" target="_blank" rel="external">Travis</a>做持续继承，遇到一个让我很纠结的问题。在Travis上执行构建脚本如下：</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">./gradlew clean</span><br><span class="line">./gradlew assembleXR</span><br></pre></td></tr></table></figure>
<p>最后生成的APK在运行的时候报错，提示找不到某个.so文件，解压发现APK中果然缺少某个库工程的.so文件，但在本地运行的时候却是没有问题，纠结了好久，后来研究发现Android Studio中执行Clean Project的时候，会执行generateSources的任务，把它加入构建脚本后才打包正确。最近发现，这原来是个<a href="https://code.google.com/p/android/issues/detail?id=106579&amp;thanks=106579&amp;ts=1421971822" target="_blank" rel="external">Bug</a>，并且已经在android gradle1.3被修复了。</p>
<p>匆匆忙忙间，写了很多东西。读完此文，希望你能感受到构建神器的魅力，感受到它的灵活强大，当然也希望能让你使用Gradle更加得心应手。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p><strong>本文为CSDN约稿文章，首发地址为：<a href="http://www.csdn.net/article/2015-08-10/2825420" target="_blank" rel="external">Android项目中如何用好构建神器Gradl]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Android Studio" scheme="http://jiajixin.cn/tags/Android-Studio/"/>
    
      <category term="Gradle" scheme="http://jiajixin.cn/tags/Gradle/"/>
    
      <category term="Groovy" scheme="http://jiajixin.cn/tags/Groovy/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[有态度的Slack群组]]></title>
    <link href="http://jiajixin.cn/2015/07/09/slack-android/"/>
    <id>http://jiajixin.cn/2015/07/09/slack-android/</id>
    <published>2015-07-09T05:02:14.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>我要建立一个有态度的Slack群组，服务有态度的Android开发者。</p>
<h2 id="是什么？">是什么？</h2><p>今年，机缘巧合中，加入了一个比较Geek的创业公司，团队沟通一直使用Slack这个工具。使用了三四个月，感觉非常方便，Slack有如下优点：</p>
<ul>
<li>加强版的QQ群+讨论组</li>
</ul>
<p>有不同的Channel分流不同的话题，你可以只关注自己喜欢的话题。可以方便的新建Channel，加一些人进来讨论。</p>
<ul>
<li>弱干扰</li>
</ul>
<p>QQ群是强干扰的，有新消息弹窗提示，如果设为免打扰，又怕错过重要信息。而Slack默认当别人提到你或者触发了你关注的关键词后才会弹出桌面通知。</p>
<ul>
<li>第三方集成</li>
</ul>
<p>之前在创业公司我们使用的Github，Asana，HockyApp，Travis CI等服务，都可以很方便的集成到Slack中，有代码提交、打包完成等都可以收到通知。</p>
<p>当然Slack还有很多小的特性，像语法高亮，收藏内容，修改发出去的内容，@某人，覆盖移动客户端等，针对程序员，在易用性和友好性上有很多优势。</p>
<p><img src="http://7fviov.com1.z0.glb.clouddn.com/slack.png" alt="Slack截图"></p>
<h2 id="为什么？">为什么？</h2><p>我见过很多开发者都有加一些技术QQ群，其实我是比较讨厌技术QQ群的，加进去想学点东西，却发现充满了大量的灌水内容，基本上都会进行屏蔽，食之无味，弃之又可惜。我加过Android开发的唯一一个QQ群，是@Trinea建的开源交流群，Trinea要求比较严格，禁止灌水，但依然有很多无聊的聊天内容，或者有很多小白在里面提问稍微Google一下就能解决的问题，这定然不是我想要的，所以我退了群。</p>
<p>Slack完全可以替代QQ群，现在唯一的缺点是部分资源在国内被墙，但这也在一定程度上算是个优点，提高了进入者的门槛，不会翻墙的程序员不是好程序员。</p>
<p>国内有些团队是使用Slack沟通的，但针对开发者的群组应该很少，去年接触过一个多月的iOS开发，知道@唐巧建立过一个Slack群组，给我提供了不少可以借鉴的思路，比如Channel如何划分，可以集成RSS阅读等。</p>
<h2 id="怎么搞？">怎么搞？</h2><p>为了保证群组的秩序，我们对Channel进行了如下划分：</p>
<ul>
<li><code>introduction</code> 新人入群介绍</li>
<li><code>general</code> Android开发通用话题</li>
<li><code>random</code> 大家可以在这里八卦、扯淡</li>
<li><code>troubleshoot</code> 开发问题可以在这求助</li>
<li><code>libraries</code> 讨论开源库的使用和问题</li>
<li><code>conferences</code> 讨论Android相关的会议</li>
<li><code>share</code> 分享文章、酷应用、新闻等</li>
<li><code>jobs</code> 发布招聘相关信息</li>
<li><code>blog-cn</code> 和 <code>blog-en</code> 是聚合channel，会自动聚会Android开发的文章，请不要随意在这2个channel讨论。如果有好的RSS源，欢迎@jasonross</li>
</ul>
<p>上面的Channel划为不固定，会不断优化以适应更好的交流，减少信息噪音。但作为一个有态度的Slack群组，我们还是要提高开发者进入的门槛。门槛设置如下：</p>
<ol>
<li>GitHub 所有项目Star数量超过128。</li>
<li>有独立博客，质量OK。</li>
<li>由已加入的朋友引荐。</li>
</ol>
<p>只要你对Android开发感兴趣，满足上面任意一条，我们都非常欢迎你的加入。当然对于圈子里比较活跃的人，我们会主动找上你。</p>
<p>我相信，这样的交流氛围会产生一些有价值的东西，我们也会考虑定期输出一些内容，回馈社区。</p>
<h2 id="加入方式">加入方式</h2><ol>
<li>微博私信 <a href="http://weibo.com/402028248" target="_blank" rel="external">@寒江不钓</a> / <a href="https://github.com/lzyzsd" target="_blank" rel="external">@hi大头鬼hi</a> / <a href="http://weibo.com/lightSkyStreet" target="_blank" rel="external">@light_sky</a></li>
<li>发送邮件到 jiajixin@gmail.com</li>
<li>博客下方留言</li>
</ol>
<p>通过以上任意一种方式，留下你的<strong>邮箱</strong>和<strong>个人介绍</strong>，有<a href="http://xitu.io" target="_blank" rel="external">稀土</a>帐号更好，可以看到你的社交网络，方便群组里的人更好的了解你。</p>
<p>下图是已加入的同学的截图，看看你认识几个头像：<br><img src="http://7fviov.com1.z0.glb.clouddn.com/pt2015_07_13_10_34_43.jpg" alt="你认识几个头像？"></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>我要建立一个有态度的Slack群组，服务有态度的Android开发者。</p>
<h2 id="是什么？">是什么？</h2><p>今年，机缘巧合中，加入了一个比较Geek的创业公司，团队沟通一直使用Slack这个工具。使用了三四个月，感觉非常方便，Slack有如下优点：<]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Slack" scheme="http://jiajixin.cn/tags/Slack/"/>
    
      <category term="有态度" scheme="http://jiajixin.cn/tags/%E6%9C%89%E6%80%81%E5%BA%A6/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[关于Android的Data Binding技术]]></title>
    <link href="http://jiajixin.cn/2015/05/31/Data%20Binding/"/>
    <id>http://jiajixin.cn/2015/05/31/Data Binding/</id>
    <published>2015-05-31T12:13:13.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>此次Google IO大会，给Andorid开发者带来了很多福利。我对其中的Data Binding技术最感兴趣，所以花时间研究了一下。</p>
<h2 id="Data_Binding与MVVM">Data Binding与MVVM</h2><p>Data Binding即数据绑定，在很多前端框架中都有应用，比如Google维护的AngularJS就支持View和Model的双向绑定。当数据更新，框架收到通知，视图可以自动更新，View和Model可以做到松耦合。</p>
<p>有了Data Binding库的支持，在Android中可以很方便的实现MVVM的开发模式。MVVM，是Windows社区的一种叫法，即Model-View-ViewModel。在Android中，Activity，Fragment，Dialog之类都属于View层；Model层主要负责数据相关部分，可以从数据库或者网络获取；ViewModel主要是把Model转换为View可以呈现的东西，例如把一个Date类型转化为格式化后的String。</p>
<p>ViewModel在哪里呢？在Data Binding插件的支持下，会实时生成以Layout名称命名的Binding类，比如Layout名称为activity_main，则会生成ActivityMainBinding类，数据绑定在Layout文件中XML标注，最终在ActivityMainBinding中转化为Java代码，所以ActivityMainBinding类扮演了ViewModel的角色。</p>
<p>在我看来，MVVM相对于传统的MVC，把一些业务逻辑抽离，放到ViewModel中，可以减轻Activity和Fragment的压力，同时数据绑定也大大减少了逻辑代码，另一方面对于测试来说，数据变化引起视图自动更新，可以更加方便测试。但在XML中进行数据绑定的操作，可能不利于调试。</p>
<p>在Google官方发布Data Binding库之前，已经有类似的开源项目，如<a href="http://robobinding.github.io/RoboBinding/getting_started.zh.html" target="_blank" rel="external">RoboBinding</a>，支持双向绑定等，比较成熟，Google的库目前只支持单向绑定，为Beta版本，不知道后面是不是还有很多特性改进。你会发现，由于Android的开源特性，开发者在很多方面走在了Google的前面，比如这次发布的权限控制，国内很多ROM早就有了这个功能。我在想，当Google发布新版本的时候，会不会去看看MIUI有什么特性优化呢。</p>
<h2 id="参考资料">参考资料</h2><p><a href="https://developer.android.com/tools/data-binding/guide.html" target="_blank" rel="external">Data Binding Guide</a> （官方资料）</p>
<p><a href="http://www.cnblogs.com/dxy1982/p/3793895.html" target="_blank" rel="external">Web开发的MVVM模式</a>（MVC VS. MVP VS. MVVM）</p>
<p><a href="http://objccn.io/issue-13-1/" target="_blank" rel="external">MVVM介绍</a>（iOS中MVVM的一种实现，对概念的理解有帮助）</p>
<p><a href="http://www.willowtreeapps.com/blog/mvvm-on-android-what-you-need-to-know/" target="_blank" rel="external">MVVM on Android: What You Need to Know</a> （这个博客很不错）</p>
<p><a href="http://www.willowtreeapps.com/blog/animating-android-binding-transitions/" target="_blank" rel="external">Animating Android Binding Transitions</a>（Data Binding技巧）</p>
<h2 id="Sample">Sample</h2><p>官方貌似没有Data Binding的Sample，我写了个简单的Demo，Github地址：<br><a href="https://developer.android.com/tools/data-binding/guide.html" target="_blank" rel="external">https://developer.android.com/tools/data-binding/guide.html</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<p>此次Google IO大会，给Andorid开发者带来了很多福利。我对其中的Data Binding技术最感兴趣，所以花时间研究了一下。</p>
<h2 id="Data_Binding与MVVM">Data Binding与MVVM</h2><p>Data Binding]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Data Binding" scheme="http://jiajixin.cn/tags/Data-Binding/"/>
    
      <category term="Google IO" scheme="http://jiajixin.cn/tags/Google-IO/"/>
    
      <category term="MVVM" scheme="http://jiajixin.cn/tags/MVVM/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Mac下显示桌面应用开发]]></title>
    <link href="http://jiajixin.cn/2015/02/21/mac-application-show-desktop/"/>
    <id>http://jiajixin.cn/2015/02/21/mac-application-show-desktop/</id>
    <published>2015-02-21T12:13:13.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>由于开始接触iOS开发，所以最近一直在Mac下工作。在Windows上，有个显示桌面的图标，对于有点洁癖的人来说，点击一下还我清爽，非常实用。在Mac下，⌘+F3快捷键虽然可以达到类似的效果，但触发后桌面边缘有阴影，且点击桌面文件后会重新显示之前隐藏的应用，不是很爽。知乎上有人问过Mac显示桌面的问题，应该有不少Mac用户需要这个功能，AppStore搜索了一下，真有相关应用，但需要付费，所以我就打算自己开发一款。</p>
<h2 id="技术要点">技术要点</h2><p>没有太多精力，第一个版本我只想实现两个功能：</p>
<ol>
<li>状态栏点击图标，即显示桌面，所有显示窗口最小化。</li>
<li>开机默认自启动，无需用户配置。</li>
</ol>
<h3 id="状态栏">状态栏</h3><p>首先要在状态栏显示图标,代码如下：</p>
<p><pre><br>statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];<br>[statusItem setImage:[NSImage imageNamed:@”StatusIcon”]];<br>[statusItem setHighlightMode:YES];<br>[statusItem setAction:@selector(onStatusItemClicked:)];<br>[statusItem setTarget:self];<br></pre><br><strong>注意</strong>：由于只显示状态栏图标，不显示窗口和菜单栏，所以需要在plist文件中设置<code>Application is background only</code>为<code>YES</code>。</p>
<h3 id="显示桌面">显示桌面</h3><p>如何实现显示桌面呢？<br>其实显示桌面是有快捷键的，<code>CMD+ALT+H+M</code>，按下CMD+ALT+H就会隐藏当前窗口除外的窗口，CMD+M会隐藏当前窗口，所以同时按下CMD+ALT+H+M就会达到显示桌面的效果。</p>
<p>所以现在需要做的就是模拟按键，之前做Java桌面应用开发的时候，有个Robot类非常方便，查阅了一下资料，Mac开发也有类似方法：</p>
<p><pre><br>GEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);<br>CGEventRef cmdDown = CGEventCreateKeyboardEvent(src, kVK_Command, true);<br>CGEventPost(kCGHIDEventTap, cmdDown);<br></pre><br>其中<code>kVK_Command</code>参数是指虚拟按键，对应的是<code>0x37</code>,代指Command按键。<code>kVK_Command</code>在<code>&lt;Carbon/Carbon.h&gt;</code>中枚举定义，所以需要<code>#import &lt;Carbon/Carbon.h&gt;</code>。</p>
<h3 id="开机启动">开机启动</h3><p>Mac开发实现开机启动有多种方式，比较方便的是使用<code>LSSharedFileListInsertItemURL</code>方法，腾讯的老谭有篇<a href="http://www.tanhao.me/pieces/380.html" target="_blank" rel="external">文章</a>，参考了一下，不过其中的<code>LSSharedFileListItemResolve</code>已被废弃，需要用<code>LSSharedFileListItemCopyResolvedURL</code>代替。</p>
<h2 id="成果">成果</h2><h3 id="效果图">效果图</h3><p>代码比较简单，也已在<a href="https://github.com/jasonross/ShowDeskTop" target="_blank" rel="external">Github</a>上开源，最终效果图如下:<br><img src="http://jiajixin.qiniudn.com/show_desktop.gif" alt="演示动画"></p>
<h3 id="应用下载">应用下载</h3><p><a href="http://jiajixin.cn/ShowDeskTop.app.zip">http://jiajixin.cn/ShowDeskTop.app.zip</a></p>
<h3 id="不足之处">不足之处</h3><p>时间仓促，应用有些不足之处：</p>
<ol>
<li>不会作图，所以图标不是很优雅，只能说看的过去，如果你有更好的图标，可以给我提交<a href="https://github.com/jasonross/ShowDeskTop" target="_blank" rel="external">Pull Request</a>。</li>
<li>不支持自定义快捷键和退出功能，由于状态栏图标要保持操作简单，所以不会添加菜单选项，后面可以考虑在Dock中添加。</li>
<li>不支持复制自身到<code>应用程序</code>文件夹中，后续会添加此功能。</li>
</ol>
]]></content>
    <summary type="html">
    <![CDATA[<p>由于开始接触iOS开发，所以最近一直在Mac下工作。在Windows上，有个显示桌面的图标，对于有点洁癖的人来说，点击一下还我清爽，非常实用。在Mac下，⌘+F3快捷键虽然可以达到类似的效果，但触发后桌面边缘有阴影，且点击桌面文件后会重新显示之前隐藏的应用，不是很爽。知乎上]]>
    </summary>
    
      <category term="mac开发" scheme="http://jiajixin.cn/tags/mac%E5%BC%80%E5%8F%91/"/>
    
      <category term="开机启动" scheme="http://jiajixin.cn/tags/%E5%BC%80%E6%9C%BA%E5%90%AF%E5%8A%A8/"/>
    
      <category term="快捷键" scheme="http://jiajixin.cn/tags/%E5%BF%AB%E6%8D%B7%E9%94%AE/"/>
    
      <category term="显示桌面" scheme="http://jiajixin.cn/tags/%E6%98%BE%E7%A4%BA%E6%A1%8C%E9%9D%A2/"/>
    
      <category term="状态栏" scheme="http://jiajixin.cn/tags/%E7%8A%B6%E6%80%81%E6%A0%8F/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[在路上·2014]]></title>
    <link href="http://jiajixin.cn/2015/02/04/ontheway-2014/"/>
    <id>http://jiajixin.cn/2015/02/04/ontheway-2014/</id>
    <published>2015-02-04T15:05:41.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>『风紧扯呼』，今夜的北京狂风肆虐，过两天我也要回家了。偷得浮生半日闲，终于有时间可以回首一下即将过去的2014年，今年本命年，经历了很多事情，很多也是人生中的大事。</p>
<h2 id="实习面试">实习面试</h2><p>3月初就开始准备实习面试的事情，基本上边玩边看书，陆续地投递了几份简历。当时想做Android研发的实习工作，但来学校宣讲的很多公司比较传统，所以很多都没去参加面试。</p>
<p>回首一下，主要面试了这么几家公司：</p>
<ul>
<li>上海胜略。<br>技术面试没问题，估计是HR面跪了，一个是聊天的时候谈到创业的事情，估计给人不踏实的感觉，另外听他们说是做内部产品，估计我言语中流露出了不太喜欢。</li>
<li>上海绿茶，联想旗下公司，做手机浏览器。宣讲的时候说同事很多都是百度出来的，受不了太多束缚，想做点事情，所以去了绿茶。技术面试没问题，终面和一个经理聊，都说会给offer了，后来聊到产品，夸奖了一下猎豹的视频播放功能，然后就没有然后了，真他妈小气。想做些事情的人，去了联想，还老是宣传公司朝九晚五，想想也是可笑。</li>
<li>上海英语流利说。我们导师认识他们老总，帮我推荐了一下，很快就得到了Android负责人的电话面试，技术面试还算可以，但后面也没有收到其他回复。想想主要当时有点嚣张吧，开发过几款App，觉得Android开发没有太多难度，也有点想去做Java后台开发，当时表露了这个想法，是有点图样图森破了。现在看来，这款产品还是挺不错的，他们团队应该在上海也是挺不错的了。</li>
<li>北京百度。面了两次，第一次专业不太对口，一面就跪了；第二次面试，Android相关问题回答的比较一般，虽然写过不少APP代码，但深入点的知识还是有所欠缺，再加上百度面试特别喜欢问算法题或者智力题，电话面试的情况下我的思考能力又差，所以后来也是跪了。</li>
<li>杭州阿里。三次电面通过，最后一次现场面跪了，我觉得主要是现场表现的比较一般，回答问题有点太简略了，给面试官的印象不太好。</li>
<li>上海饿了么。 4月份的时候，应该很多人还不知道这个公司，我也是之前买.me域名看权重的时候，发现了他们的网站(ele.me)。当时朋友的朋友帮我内推了一下，简单电话面试后去上海参加了现场面，前台妹子非常不热情，面试官估计是一个本科生，手上脏兮兮的，一直抖着腿，面试的问题也挺傻逼，给我的印象非常不好，最后也是跪了。</li>
<li>深圳腾讯。参加了笔试，很多都是C/C++相关的题目，结果最后连面试的机会都没有。</li>
</ul>
<p>除了这些，还简单面了一些不太对口的公司，有盛大、网易游戏等，总之面一个跪一个，朋友和导师都有点替我着急了。本科的时候，给周鸿祎发邮件怒斥他们APP做的怎么烂，得到了一个面试机会，所以我有直接找老大的习惯。于是给周鸿祎、傅盛和陈琪分别发了邮件，老周转给HR总监了，但面试安排的有点太晚了；傅盛直接没回，不知道是不是邮箱搞错了；陈琪回了邮件，但貌似Android不缺人了。几乎绝望的时候，得到一个大众点评面试的机会，5分钟电话面试就通过了，当时的情况下不想再拖了，并且还有几个同学可以一起去实习，所以就决定去大众点评，于是找实习的日子就这样结束了。</p>
<h2 id="大众点评实习">大众点评实习</h2><p>5月初，去了上海，去了大众点评实习，一去就是八个月。<br>之前没有进过互联网公司，所以在点评了解到了很多东西。敏捷开发是什么样的，每个阶段占多长时间，各种角色怎么打交道，应用大了编译时间这么长，APP开发还有这么多监控，数据mock工具挺好用的，200万crash还能打补丁修复等等，总之长了不少见识吧。<br>但在点评做的事情却不是很多，想想主要有包精简、新手引导动画、魅族适配、活动监控，然后还改过几个视角效果，8个月貌似就做了这么点事情，的确有点太悠闲了。</p>
<h2 id="网络安全">网络安全</h2><p><img src="http://jiajixin.qiniudn.com/wooyun_ld.png" alt="乌云漏洞"></p>
<p>在点评实习的前几个月，我还学了点Web安全的东西，在乌云上提交了几个漏洞。</p>
<ul>
<li>有锤子科技撞库的，后来他们的安全工程师送了我一个锤子手机的F码，再后来我在秘密上看到朋友里有人爆料老罗脾气暴躁等等，估计就是那个工程师了，因为锤子员工我只有他的手机号。</li>
<li>有豌豆荚帐号泄密的，送了我一个抱枕和clickandgrow植物盆栽，真是土豪啊。</li>
<li>有脉脉客户端任意密码重置的，送了一大箱零食，那个月我貌似胖了20斤。</li>
<li>春秋航空短信验证有问题，后来送了一个飞机模型，听说外面卖200多。</li>
<li>还有雨林木风、滴滴打车、乐蜂网、乐视网、墨迹天气的，都不知道送点东西，唉。</li>
</ul>
<p>除了这些乌云漏洞，还搞过不少账户：</p>
<ul>
<li>用ig战队月如的号打过天梯2400的局，还赢了。</li>
<li>同学不带我玩dota的时候，登上他的11帐号，他就乖了。</li>
<li>进了小米员工的支付宝，50000多余额，支付宝的认证机制当时应该是有问题的，可以被跳过，当然我没作恶。</li>
<li>搞到过几个VPN帐号，有红杏年费会员、POPvpn年费会员、貌似还有小米的VPN帐号。</li>
<li>你知道周鸿祎的美空密码是多少吗？ak4774ka，老周是有多么喜欢AK47啊。</li>
<li>不小心在代码库里发现了同事的帐号密码，你以为代码中删除了密码就解决问题了吗，git log中依然有保留啊。</li>
<li>差点忘了，手头还有几个摄像头，韩国人貌似睡觉前喜欢放段音乐入睡啊。</li>
</ul>
<p>找漏洞不是件容易的事情，更多的时候靠感觉，也就前几个月有些收获，后来我都懒得去找了。</p>
<h2 id="博客">博客</h2><p>5月份开始维护这个博客，到年底也就写了三四篇文章，产量挺低的，但每一篇都是自己的沉淀。小时候看《桃花扇传奇》，女主角李香君说过的「与众不同」四个字对我影响很深，所以这个博客我不想写千篇一律的东西，没有沉淀就不要写。</p>
<p><img src="http://jiajixin.qiniudn.com/blog_traffic_2014.png" alt="博客流量"></p>
<p>博客流量还算可以吧，八个月一万多UV，基本上每篇文章能带来两三千UV，分享一下经验吧。我的博客流量主要来源是CSDN的极客头条和fenng的StartUp News，CSDN有个审核过程，但文章优秀基本都能通过，然后写个脚本刷到前几条应该不是难事，fenng的网站流量也很大，并且无审核，作弊上头条也很容易，但用户鱼龙混杂，跳出率应该比较高。然后还有几个小的网站，伯乐头条和开发者头条，流量不多。</p>
<h2 id="产品">产品</h2><p>在点评实习的时候，还构思了一款产品，云影。在线观看视频的，是用的是百度网盘上的资源，利用百度网盘开放API的一个漏洞，可以做到网盘资源分享，一键搜索视频播放，做出来应该有不少用户，毕竟找电影资源是个很大的需求。主要的问题，一个是盗版的问题，这个倒不用太考虑，有百度网盘背锅。另一个问题就是貌似百度网盘的资源在很多地方播放速度不太理想，这个就不好解决了，毕竟视频播放流畅速度是个非常重要的体验。在点评空闲的日子，云影的代码也写了不少，采用了MaterialDesign设计风格，简单的框架搭好了，但现在挺忙的，估计后面没有时间去做这件事了。</p>
<p><img src="http://jiajixin.qiniudn.com/cm_git_log.png" alt="云影Git Log"></p>
<h2 id="校招offer">校招offer</h2><p>在点评实习期间，参加了极客马拉松比赛，获得了三等奖，然后实习表现还算优秀吧，所以转正的时候获得了Special Offer。10月份校招的时候，请了一个月假，参加了百度、360和阿里的面试，还是都跪了，所以就打算留在点评了，这边给的待遇很不错，还有点股票，唯一不太满意的就是有点太悠闲，并且点评的技术氛围谈不上很好。12月底的时候，同学帮我内推了一下百度，电面三次，去北京现场面一次，最终拿下了offer。拿到offer反而有点犹豫了，点评给的待遇高，钱倒不是什么问题，然后上海户口应该也能拿到，北京户口压根都不要想，北京的压力还更大一些。五六天的思考时间，一天一个想法，那几天问了一二十个在北京的同学朋友，他们在北京都是怎么打算的，大多数也都没有北京户口，有些人没想太多，有打算过两年去杭州的，有打算过两年回家的，大多数都感觉留不下啊。最后的时刻还是选择了百度，一方面百度的技术氛围更好一些，另一方面是在点评留下更多的是因为户口待遇，户口如果以后不打算留在上海，这些东西也没什么用，还是要看看找个哪里的妹子吧；待遇的话，我算了一下，股票也就值一二十万，并且要待够四年时间，虽然我比较看来点评的未来，但觉得自己很难在一个比较成熟的公司待四年，并且上海的互联网公司还少，过两年真不知道跳到哪里去，再说去北京四年，应该比上海四年，要混的好一些。</p>
<h2 id="百度实习">百度实习</h2><p>百度给的是iOS的offer，由于之前一直做Android开发，iOS开发经验基本为0，所以起初挺不情愿的，我对c系列的语言一直不太熟，所以也把这次机会当做是一种挑战。<br>年前来百度实习了两三周，深感压力很大。一两周的时间去熟悉代码，然后讲给同事听，并且还是在OC语言都不熟的情况下。在点评的时候，晚上6点就可以走了，在百度每天到家基本上都要10点了，前两天来的时候，看代码不知道如何下手，并且回家那么晚，甚至都不想待在这了，第二天找老大聊天，给了点建议和指导，让我受益匪浅，感觉牛逼的人都思路清晰，有自己的一套方法论。不知为啥，在公司感觉效率比较低，所以经常回家看代码到一两点，周末有时间也要去看，所以来到北京真的是感觉很累，并且北京这空气质量也真是差，坐地铁真是人挤人。<br>对比一下百度和点评吧，说一下印象深刻的几个点：</p>
<ul>
<li>百度这边架构还算不错吧，有的问题也一直在重构，来这边也学到了一些分层解耦的思想。在点评实习了8个月，我对其架构谈不上了解，主要是没有代码串讲的过程吧，想想新人代码串讲真的是个比较不错的思路，去点评开始的时候也会让你看上两三点代码，但看的怎么样根本没人在乎吧。</li>
<li>百度这边用的还是SVN，真的是感觉挺操蛋的，但是历史原因页不太好迁移，在点评用GIt还是挺爽的，SVN在Mac下的客户端免费的都没有好用的，大家用的CornerStone估计都是盗版的了。</li>
<li>百度这边有个RD自测，就是RD每天会花费一两个小时的时间去跑200多个核心测试用例，开始的时候业务不了解，可能花费时间更长。但真的挺有用的，新人可以比较快的熟悉业务，并且的确可以发现一些问题。我记得之前在点评的时候，有个页面入口比较深，总是进去第一次失败，第二次才成功，后来忘了去提这个事情了，貌似大家也一直没有发现问题，但如果有个RD自测的过程，估计很快就能发现问题了，当然QA也要每天去自测。</li>
<li>百度这边工程师文化，工程师发挥了很多作用，每周工程师都会提出一些优化点和建议，跟产品进行沟通。这其实挺好的一个思路，刚开始去点评的时候，客户端Tab还是黑灰色的，丑的一逼，当时就想提建议，但真的没有很好的渠道，直接找产品经理感觉有些怪，并且有些事情不放到台面上可能不能得到很好的处理，我现在还觉得点评个人信息下面的绿色背景太丑，给人XP桌面壁纸的感觉，还有好友去哪为啥不叫好友去哪儿。</li>
<li>百度这边每周会有一些推荐文章的整理，并且会有技术分享，每周整理出技术周刊。RD自己推荐文章，大家相互分享，在例会上简单介绍一下。在点评实习的时候，我的新浪微博收藏达到了1000多条，里面应该有不少技术文章，很多Android技术点，都有相应的文章或者视频收藏，如果有一个机会分享也是挺好的，技术氛围也会更浓厚一些。</li>
</ul>
<h2 id="总结">总结</h2><p>2014年，我在三家公司待过。几十个人的小公司，一直在摸索，创始人没有互联网背景的话很难做出东西，技术上也容易被忽悠；几千人的中型公司，更多的考虑是业务的发展；几万人的大公司，流程更加规范，更加注重技术。互联网每五年左右就会有牛逼的公司诞生，00年BAT，05年360，10年小米，争取这两年积累点技术，两年后抓住那波机会。</p>
<p>在北京工作，的确挺累的，既来之则安之吧，年轻累点也没什么，也是来之前考虑到的，只要不留遗憾就好了。</p>
<p>2015年，要更加努力，更加有计划性，要成为iOS开发高手，要多读几本技术书，要做出点工作成绩。</p>
<p>最近突然想起了《兰亭集序》里的一段话，这段话当时也是高中语文老师极力推崇的，现在看来挺有味道的:</p>
<blockquote>
<p>夫人之相与，俯仰一世。或取诸怀抱，悟言一室之内；或因寄所托，放浪形骸之外。虽趣舍万殊，静躁不同，当其欣于所遇，暂得于己，快然自足，不知老之将至；及其所之既倦，情随事迁，感慨系之矣。向之所欣，俯仰之间，已为陈迹，犹不能不以之兴怀，况修短随化，终期于尽！古人云：“死生亦大矣”，岂不痛哉！</p>
</blockquote>
]]></content>
    <summary type="html">
    <![CDATA[<p>『风紧扯呼』，今夜的北京狂风肆虐，过两天我也要回家了。偷得浮生半日闲，终于有时间可以回首一下即将过去的2014年，今年本命年，经历了很多事情，很多也是人生中的大事。</p>
<h2 id="实习面试">实习面试</h2><p>3月初就开始准备实习面试的事情，基本上边玩边看书]]>
    </summary>
    
      <category term="offer" scheme="http://jiajixin.cn/tags/offer/"/>
    
      <category term="产品" scheme="http://jiajixin.cn/tags/%E4%BA%A7%E5%93%81/"/>
    
      <category term="大众点评" scheme="http://jiajixin.cn/tags/%E5%A4%A7%E4%BC%97%E7%82%B9%E8%AF%84/"/>
    
      <category term="实习" scheme="http://jiajixin.cn/tags/%E5%AE%9E%E4%B9%A0/"/>
    
      <category term="工作" scheme="http://jiajixin.cn/tags/%E5%B7%A5%E4%BD%9C/"/>
    
      <category term="总结" scheme="http://jiajixin.cn/tags/%E6%80%BB%E7%BB%93/"/>
    
      <category term="校招" scheme="http://jiajixin.cn/tags/%E6%A0%A1%E6%8B%9B/"/>
    
      <category term="百度" scheme="http://jiajixin.cn/tags/%E7%99%BE%E5%BA%A6/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android内存泄漏研究]]></title>
    <link href="http://jiajixin.cn/2015/01/06/memory_leak/"/>
    <id>http://jiajixin.cn/2015/01/06/memory_leak/</id>
    <published>2015-01-05T16:18:44.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<h2 id="概念">概念</h2><h3 id="根搜索算法">根搜索算法</h3><p>Android虚拟机的垃圾回收采用的是<code>根搜索算法</code>。GC会从根节点（GC Roots）开始对heap进行遍历。到最后，部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾，会被GC回收掉。</p>
<p>根搜索算法相比引用计数法很好的解决了循环引用的问题。举个例子，Activity有View的引用，View也有Activity的引用，之前我还尝试去源代码里找Activity何时和View断开连接是大错特错了。当Activity finish掉之后，Activity和View的循环引用已成孤岛，不再引用到GC Roots，无需断开也会被回收掉。</p>
<h3 id="内存泄漏">内存泄漏</h3><p><code>Android内存泄漏</code>指的是进程中某些对象（垃圾对象）已经没有使用价值了，但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。无用的对象占据着内存空间，使得实际可使用内存变小，形象地说法就是内存泄漏了。</p>
<h2 id="场景">场景</h2><ul>
<li>类的静态变量持有大数据对象<br>静态变量长期维持到大数据对象的引用，阻止垃圾回收。</li>
<li>非静态内部类的静态实例<br>非静态内部类会维持一个到外部类实例的引用，如果非静态内部类的实例是静态的，就会间接长期维持着外部类的引用，阻止被回收掉。</li>
<li>资源对象未关闭<br>资源性对象如Cursor、File、Socket，应该在使用后及时关闭。未在finally中关闭，会导致异常情况下资源对象未被释放的隐患。</li>
<li>注册对象未反注册<br>未反注册会导致观察者列表里维持着对象的引用，阻止垃圾回收。</li>
<li><code>Handler</code>临时性内存泄露<br>Handler通过发送Message与主线程交互，Message发出之后是存储在MessageQueue中的，有些Message也不是马上就被处理的。在Message中存在一个 target，是Handler的一个引用，如果Message在Queue中存在的时间越长，就会导致Handler无法被回收。如果Handler是非静态的，则会导致Activity或者Service不会被回收。<br>由于AsyncTask内部也是Handler机制，同样存在内存泄漏的风险。<br>此种内存泄露，一般是临时性的。</li>
</ul>
<h2 id="预防">预防</h2><ul>
<li>不要维持到Activity的长久引用，对activity的引用应该和activity本身有相同的生命周期。</li>
<li>尽量使用<code>context-application</code>代替<code>context-activity</code></li>
<li>Activity中尽量不要使用非静态内部类，可以使用静态内部类和<code>WeakReference</code>代替。</li>
</ul>
<h2 id="检测">检测</h2><h3 id="静态检测">静态检测</h3><p>静态检测主要是检测资源未关闭的情况，Eclipse和Android Studio都可以检测出IO或者Socket未关闭的情况，然后在finally中关闭即可。</p>
<h3 id="动态监测">动态监测</h3><p> 动态检测主要是依靠<a href="https://eclipse.org/mat/" target="_blank" rel="external">MAT</a>这个工具。2011年Google IO有一个<a href="http://droidyue.com/blog/2014/11/02/note-for-google-io-memory-management-for-android-chinese-edition/" target="_blank" rel="external">主题演讲</a>，非常详细地讲解了内存泄露的检测，包含MAT工具的使用，值得一看。<br>我在某项目中使用MAT检测，发现一处内存泄漏，分享一下过程。<br>从首页到商户列表到商户详情再退回首页执行<code>Dump HPROF File</code>，查看MAT中的<code>Histogram</code>，过滤Activity后结果如下： <img src="http://jiajixin.qiniudn.com/histogram.png" alt="Histogram"><br>仍然存在<code>ShopInfoActivity</code>的实例，选中右键点击<code>Merge Shortest Paths to GC Roots</code>，结果如下： <img src="http://jiajixin.qiniudn.com/example.png" alt="Path"><br>可以看到<code>ShopDatabase</code>中维持着<code>ShopInfoActivity</code>的引用，查看源代码如下： <pre><br>       public class ShopDatabase {<br>           …<br>           private static ShopDatabase instance;<br>           public static ShopDatabase getInstance(Context context) {<br>               if (instance == null &amp;&amp; context != null) {<br>                   instance = new ShopDatabase(context);<br>               }<br>               return instance;<br>           }<br>           protected Context context;<br>           …<br>       } </pre> 很明显，静态变量<code>instance</code>长期持有<code>context</code>的引用，造成内存泄露。<br>       所以动态检测内存泄露的一个简单思路就是随意操作APP，最后返回首页，然后用MAT检测，查看是否存在Activity多于一个或者Activity不正常存在的问题。</p>
<h2 id="参考资料">参考资料</h2><ul>
<li><a href="http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html" target="_blank" rel="external">Avoiding memory leaks</a></li>
<li><a href="http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html" target="_blank" rel="external">Memory Analysis for Android Applications</a></li>
<li><a href="http://dubroy.com/memory_management_for_android_apps.pdf" target="_blank" rel="external">memory_management_for_android_apps</a></li>
<li><a href="http://droidyue.com/blog/2014/11/02/note-for-google-io-memory-management-for-android-chinese-edition/" target="_blank" rel="external">Google IO：Android内存管理主题演讲记录</a></li>
<li><a href="http://blog.csdn.net/gemmem/article/details/13017999" target="_blank" rel="external">Android内存泄漏分析及调试</a></li>
</ul>
]]></content>
    <summary type="html">
    <![CDATA[<h2 id="概念">概念</h2><h3 id="根搜索算法">根搜索算法</h3><p>Android虚拟机的垃圾回收采用的是<code>根搜索算法</code>。GC会从根节点（GC Roots）开始对heap进行遍历。到最后，部分没有直接或者间接引用到GC Roots的]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Java" scheme="http://jiajixin.cn/tags/Java/"/>
    
      <category term="MAT" scheme="http://jiajixin.cn/tags/MAT/"/>
    
      <category term="内存泄露" scheme="http://jiajixin.cn/tags/%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Android Webview Java和Javascript安全交互]]></title>
    <link href="http://jiajixin.cn/2014/09/16/webview-js-safety/"/>
    <id>http://jiajixin.cn/2014/09/16/webview-js-safety/</id>
    <published>2014-09-16T15:25:18.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>最近要对一个网页的源代码进行检测，Android Webview中没有直接获取网页源代码的接口，传统的<code>addJavascriptInterface</code>方法存在安全隐患，所以研究了一下Java和Javascript的安全交互。</p>
<h2 id="Android_Webview漏洞">Android Webview漏洞</h2><p>Android Webview有两个非常知名的漏洞:</p>
<ul>
<li>最近爆出来的UXSS漏洞，可以越过同源策略，获得任意网页的Cookie等信息，Android 4.4以下都有此问题，基本无解，只能重新编译浏览器内核解决，详情可以参考<a href="http://zhuanlan.zhihu.com/fooying/19840752" target="_blank" rel="external">最近移动安全三两事</a>，感兴趣的可以去看一下<a href="http://weibo.com/rayh4c" target="_blank" rel="external">@RAyH4c</a>劫持微博、QQ空间的视频。</li>
<li>成名已久的任意命令执行漏洞，通过<code>addJavascriptInterface</code>方法，Js可以调用Java对象方法，通过反射机制，Js可以直接获取Runtime，从而执行任意命令。Android 4.2以上，可以通过声明<code>@JavascriptInterface</code>保证安全性，4.2以下不能再调用<code>addJavascriptInterface</code>，需要另谋他法。</li>
</ul>
<h2 id="Java和Javascript安全交互">Java和Javascript安全交互</h2><p>首先要说明几点：</p>
<p>1.Android Webview中Java调用Js方法很容易，<code>loadUrl(&quot;javascript:isOk()&quot;)</code>就可以调用isOk这个Js方法，但不能直接获取Js方法的返回结果。<br>2.传统的方法中，Js获取Java信息可以采用如下方式：</p>
<pre><code><span class="class"><span class="keyword">class</span> <span class="title">JsObject</span> </span>{
      <span class="annotation">@JavascriptInterface</span>
      <span class="function"><span class="keyword">public</span> String <span class="title">toString</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="string">"injectedObject"</span>; }
   }
   webView.addJavascriptInterface(<span class="keyword">new</span> JsObject(), <span class="string">"injectedObject"</span>);
   webView.loadData(<span class="string">""</span>, <span class="string">"text/html"</span>, <span class="keyword">null</span>);
   webView.loadUrl(<span class="string">"javascript:alert(injectedObject.toString())"</span>);
</code></pre><p> Java获取Js信息（如通过Js获取网页源代码）可以这样：</p>
<pre><code><span class="keyword">import</span> android.app.<span class="type">Activity</span>;
<span class="keyword">import</span> android.graphics.<span class="type">Bitmap</span>;
<span class="keyword">import</span> android.os.<span class="type">Bundle</span>;
<span class="keyword">import</span> android.util.<span class="type">Log</span>;
<span class="keyword">import</span> android.webkit.<span class="type">WebView</span>;
<span class="keyword">import</span> android.webkit.<span class="type">WebViewClient</span>;

public <span class="class"><span class="keyword">class</span> <span class="title">HtmlSource</span> <span class="keyword"><span class="keyword">extends</span></span> <span class="title">Activity</span> {</span>
    <span class="keyword">private</span> <span class="type">WebView</span> webView;

    <span class="annotation">@Override</span>
    public void onCreate(<span class="type">Bundle</span> savedInstanceState) {
        <span class="keyword">super</span>.onCreate(savedInstanceState);
        setContentView(<span class="type">R</span>.layout.main);
        webView = (<span class="type">WebView</span>)findViewById(<span class="type">R</span>.id.webview);
        webView.getSettings().setJavaScriptEnabled(<span class="literal">true</span>);
        webView.addJavascriptInterface(<span class="keyword">new</span> <span class="type">InJavaScriptLocalObj</span>(), <span class="string">"local_obj"</span>);
        webView.setWebViewClient(<span class="keyword">new</span> <span class="type">MyWebViewClient</span>());
        webView.loadUrl(<span class="string">"http://www.cnblogs.com/hibraincol/"</span>);
    }


   <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MyWebViewClient</span> <span class="keyword"><span class="keyword">extends</span></span> <span class="title">WebViewClient</span>{</span>  
        public boolean shouldOverrideUrlLoading(<span class="type">WebView</span> view, <span class="type">String</span> url) {   
            view.loadUrl(url);   
            <span class="keyword">return</span> <span class="literal">true</span>;   
        }  
        public void onPageStarted(<span class="type">WebView</span> view, <span class="type">String</span> url, <span class="type">Bitmap</span> favicon) {
            <span class="type">Log</span>.d(<span class="string">"WebView"</span>,<span class="string">"onPageStarted"</span>);
            <span class="keyword">super</span>.onPageStarted(view, url, favicon);
        }    
        public void onPageFinished(<span class="type">WebView</span> view, <span class="type">String</span> url) {
            <span class="type">Log</span>.d(<span class="string">"WebView"</span>,<span class="string">"onPageFinished "</span>);
            view.loadUrl(<span class="string">"javascript:window.local_obj.showSource('&lt;head&gt;'+"</span> +
                <span class="string">"document.getElementsByTagName('html')[0].innerHTML+'&lt;/head&gt;');"</span>);
            <span class="keyword">super</span>.onPageFinished(view, url);
        }
    }

    <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">InJavaScriptLocalObj</span> {</span>

        public void showSource(<span class="type">String</span> html) {
            <span class="type">Log</span>.d(<span class="string">"HTML"</span>, html);
        }
    }
}
</code></pre><p>3.当网页中有超链接跳转时，将会调用WebClient的<code>shouldOverrideUrlLoading</code>方法，若设置 WebViewClient 且该方法返回 true，则说明由应用的代码处理该 url，WebView 不处理，就可以达到拦截跳转的效果。</p>
<p>明白了上面几点，我们可以总结出一个比较安全的Java和Js交互方式：</p>
<blockquote>
<p>可以借鉴Android Intent的思路，Java和Js定义一个url格式如<code>js://_</code>,Java调用Js方法，在Js方法中通过<code>window.location.href=&#39;js://_?key=value#key1=value1&#39;</code>模拟跳转，被Java的<code>shouldOverrideUrlLoading</code>捕获，函数的返回值可以放在url的参数中。（Js调用Java方法原理相同）<br>这样的交互方式是异步的，如果你想知道调用一个Js方法是否返回了值怎么办？一般Java调用Js方法是在<code>onPageFinished</code>方法中，获得Js返回值是在<code>shouldOverrideUrlLoading</code>方法中，两个方法有个共同的参数webview,所以可以首先<code>webview.setTag(false)</code>，如果捕获到返回结果，则<code>webview.setTag(true)</code>,postDelayed在很短时间比如300毫秒后，<code>webview.getTag()</code>检查是否有变化即可。</p>
</blockquote>
]]></content>
    <summary type="html">
    <![CDATA[<p>最近要对一个网页的源代码进行检测，Android Webview中没有直接获取网页源代码的接口，传统的<code>addJavascriptInterface</code>方法存在安全隐患，所以研究了一下Java和Javascript的安全交互。</p>
<h2 id="A]]>
    </summary>
    
      <category term="Android" scheme="http://jiajixin.cn/tags/Android/"/>
    
      <category term="Javascript" scheme="http://jiajixin.cn/tags/Javascript/"/>
    
      <category term="webview" scheme="http://jiajixin.cn/tags/webview/"/>
    
      <category term="安全" scheme="http://jiajixin.cn/tags/%E5%AE%89%E5%85%A8/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[我不是为了黑你，我就是认真]]></title>
    <link href="http://jiajixin.cn/2014/05/24/hack_ttt/"/>
    <id>http://jiajixin.cn/2014/05/24/hack_ttt/</id>
    <published>2014-05-23T16:01:39.000Z</published>
    <updated>2017-03-18T10:06:01.000Z</updated>
    <content type="html"><![CDATA[<p>周二早上和朋友聊天，我说锤子手机今晚发布，他说4000元有人买吗？我当时说了一句，<strong>成功很多时候是需要机遇的，但认真的去做一件事很重要</strong>。关注老罗还是比较早的吧，看过不少他的资料、语录和演讲。虽然有些吹毛求疵、不合时宜，但他对「契约精神」、「版权意识」、「公平正义」的理解和坚持超出这个社会的平均值太多太多，若在武侠世界，他必是侠客。从来没有怀疑过老罗会做不出一个好手机，那晚的发布会也没让人失望，说下对发布会印象最深的几点吧。</p>
<ul>
<li><strong>他山之石</strong> 你会看到锤子手机无论从硬件、设计和软件上，要么用了业界领先的解决方案，要么找了专业人士，要么借鉴同行产品亮点。优秀的艺术家复制，伟大的艺术家剽窃。</li>
<li><strong>信息透明</strong> 老罗把业内的一些东西掰开给你讲，给人一种透明感，这是消费者非常喜欢的。</li>
<li><strong>保险思维</strong> 智能手机怕摔，哪个厂商不了解，但却没人想到保险这种思维，风险均摊。</li>
<li><strong>就是认真</strong> 发布会最后的那句「我不是为了输赢，我就是认真」和我早上对朋友讲的那句遥相呼应，所以你可以看到我的情怀。</li>
</ul>
<h2 id="撞库是一种情怀">撞库是一种情怀</h2><p>当天晚上就注册了锤子官网账号，但却遇到账号无法登录，微博上搜索了一下，也有人遇到这种问题，可能系统上线有些仓促吧。第二天开始还是登录不上，请注意，我就顺手用Chrome的审查元素分析了一下登录过程，发现登录用的是Http Get请求，直接浏览器打开网址403，估计是请求头Refer有认证吧，立刻写了段Java代码验证了一下果然如此。</p>
<p>对撞库还没有实战经验，之前因为<a href="http://drops.wooyun.org/news/989" target="_blank" rel="external">京东密码泄露恶意炒作</a>了解了撞库这个概念，当时我就感觉自己被历史选中了，由于上次分析出的小米密码不在公司电脑上，我是第二天才去撞库的，自己也就写了十几行代码就撞出了六七个账号。<a href="http://wooyun.org/bugs/wooyun-2014-061871" target="_blank" rel="external">乌云</a>上传漏洞，选中获取邀请码（上次学校官网漏洞影响太小,不给邀请码），然后就是焦急的等待。还是很顺利的拿到了邀请码，发布的漏洞获得一群小黑客关注，可能是这群白帽不是程序员出身吧，竟然说我有耐心只是因为他们觉得撞库结果验证比较麻烦，我会告诉他们我只写了十几行代码吗。锤子科技的回复还是比较及时的，在我的追问下，后面也加入了登录IP限制，问题已经解决，我这篇文章也可以发出来了。今天收到了锤子手机优先购买码一枚，感谢锤子科技的诚意。没想到锤子刚刚起步就如此重视安全，上个月我就在微博看到，老罗在招安全人才，发布上OpenSSL捐款一展情怀，实属难能可贵。</p>
<p><img src="http://jiajixin.qiniudn.com/wooyun_smartisan.png" alt="乌云漏洞"></p>
<p>撞库这个问题的解决方案，可以参考<a href="http://www.zhihu.com/question/21555879" target="_blank" rel="external">网站被黑客扫描撞库该怎么应对防范</a>。</p>
<p>上篇文章刚分析了小米论坛泄露的密码，这篇就用到了；老罗N年前搞传销的经历，也让他在讲台上挥洒自如；正如乔布斯所说：</p>
<blockquote>
<p>Again, you can’t connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future. You have to trust in something - your gut, destiny, life, karma, whatever. This approach has never let me down, and it has made all the difference in my life.</p>
</blockquote>
<h2 id="郑重声明">郑重声明</h2><p>部门媒体报告此事有些夸大事实，不希望此事给锤子手机带来负面影响，在此郑重声明，老罗团队对待安全是非常认真的，对，他们就是认真。</p>
<p>没有人比我更爱老罗，<strong>我不是为了黑你，我就是认真</strong>。</p>
]]></content>
    <summary type="html">
    <![CDATA[<p>周二早上和朋友聊天，我说锤子手机今晚发布，他说4000元有人买吗？我当时说了一句，<strong>成功很多时候是需要机遇的，但认真的去做一件事很重要</strong>。关注老罗还是比较早的吧，看过不少他的资料、语录和演讲。虽然有些吹毛求疵、不合时宜，但他对「契约精神」、「版]]>
    </summary>
    
      <category term="Web安全" scheme="http://jiajixin.cn/tags/Web%E5%AE%89%E5%85%A8/"/>
    
      <category term="创业" scheme="http://jiajixin.cn/tags/%E5%88%9B%E4%B8%9A/"/>
    
      <category term="撞库" scheme="http://jiajixin.cn/tags/%E6%92%9E%E5%BA%93/"/>
    
      <category term="锤子科技" scheme="http://jiajixin.cn/tags/%E9%94%A4%E5%AD%90%E7%A7%91%E6%8A%80/"/>
    
  </entry>
  
</feed>