Nstd
2019-06-11T09:00:40.774Z
http://nstd.github.io/
Nstd
Hexo
Android Studio Module&页面 自定义模板
http://nstd.github.io/2019/06/10/android-studio-module-page-template/
2019-06-10T03:00:44.000Z
2019-06-11T09:00:40.774Z
<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>最近项目在搞组件化,基本改造已完成(解耦什么的真的是体力活呀!因为项目比较庞大,所以老代码决定还是放着不动,有时间有人力再搞,新的大功能以组件化的方式开发),但是创建新的组件模块要拷贝很多相似的配置和代码,鉴于IDEA强大的模板功能,决定把这块给自动化了。</p>
<h2 id="符合我们要求的自定义模板分两类:"><a href="#符合我们要求的自定义模板分两类:" class="headerlink" title="符合我们要求的自定义模板分两类:"></a>符合我们要求的自定义模板分两类:</h2><h3 id="1-Module-模板"><a href="#1-Module-模板" class="headerlink" title="1. Module 模板"></a><span id="template_module">1. Module 模板</span></h3><p>位于<code><AS安装目录>/plugins/android/lib/templates/gradle-project</code>下,创建Application或者Module,类型只有固定的几类,<code>category</code>为<code>Application</code>,<code>formfactor</code>为:</p>
<ul>
<li>Mobile : 移动端应用</li>
<li>Wear : 可穿戴设备的应用</li>
<li>Car : 车载应用</li>
<li>TV : 电视应用</li>
<li>Things : 物联网应用</li>
</ul>
<p>一开始,自定义模板的时候使用的<code>Mobile</code>,不生效,发现<code>Mobile</code>只认一个;按照JavaLibrary的写法,去掉<code>formfactor</code>,发现也只认一个;后来改成<code>Things</code>才可以。<br>Module模板在创建的时候,无法像页面模板那样,在Create Dialog中添加自定义参数,让用户选填,这个有点坑。</p>
<h3 id="2-页面模板"><a href="#2-页面模板" class="headerlink" title="2. 页面模板"></a>2. 页面模板</h3><p>位于<code>templates/activities</code>下,在module中创建特定的文件或目录,这种模板可以在Create Dialog中添加自定义的参数。</p>
<a id="more"></a>
<h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><pre><code>templateName
├--- template.xml 模板入口配置文件
├--- globals.xml 可供模板使用的全局预设变量
├--- recipe.xml 操作清单
└--- <其他文件/目录> 模板源文件
</code></pre><h3 id="1-template-xml"><a href="#1-template-xml" class="headerlink" title="1. template.xml"></a>1. template.xml</h3><p>这是当前模板的入口配置文件。下面稍微介绍下一些重要的tag和字段。</p>
<p><code><template></code> tag:</p>
<ul>
<li><code>format</code> 模板格式版本号,和templates目录中其他模板一样就好,当前是5,如果大于android plugin定义的CURRENT_FORMAT,该模板将会被跳过。</li>
<li><code>revision</code> 模板版本号,如果有重名的模板(模板文件不仅可以放在<code><AS安装目录>/plugins/android/lib/templates</code>下,还可以放在<code><用户主目录>/.android/templates</code>目录下),取版本号最高的那个,如果相等,取修改时间最新的那个</li>
<li><code>name</code>、<code>description</code>属性标明了该模板显示的名称和描述。</li>
</ul>
<p><code><category></code> tag的<code>value</code>属性,标明该模板对应的分类:</p>
<ul>
<li><code>Application</code>:创建project或者module<br> 这种模板一般放在templates/gradle-project下<br> 通过File -> New -> New Project… / New Module… 进行创建</li>
<li><code>Activity</code>:创建Activity的<br> 这种模板一般放在templates/activities下<br> 通过右击module -> New -> Activity 进行创建</li>
<li><code>Xml</code>、<code>Fragment</code>、<code>Service</code>、<code>Folder</code>、<code>Other</code>等:创建其他文件的模板<br> 这种模板一般放在templates/other下<br> 通过右击module -> New -> 选择对应的分类进行创建</li>
</ul>
<p><code><formfactor></code> tag的<code>value</code>属性,标明模板的二级分类,参见<a href="#template_module">一、/ 1.</a></p>
<p><code><thumbs></code> & <code><thumb></code> 指定该模板的图标,图标文件一般和template.xml同级</p>
<p><code><parameter></code> tag标明当前模板自定义的参数<br>如果是project & module类的模板(<code>category</code>为<code>Application</code>),这些参数都是定死的,创建模板的界面也是定死的,我们无法自定义添加。但是其他的category类型(包括自定义category),这些参数都会在UI上展现,可以让用户输入。tag中的属性简介:</p>
<ul>
<li><code>id</code>:模板中引用时用到的参数名</li>
<li><code>name</code>:参数显示的名称</li>
<li><code>type</code>:参数类型,boolean、string等</li>
<li><code>default</code>:参数的默认值</li>
</ul>
<p><code><globals></code> tag的<code>file</code>属性,指定了全局配置文件(一般为globals.xml.ftl),模板文件中可用的全局参数都可以写到这个文件中。</p>
<p><code><execute></code> tag的<code>file</code>属性,指定了模板的创建清单(一般为recipe.xml.ftl),这个文件中注明了生成哪些文件、目录,以及怎么生成。</p>
<h3 id="2-globals-xml-ftl"><a href="#2-globals-xml-ftl" class="headerlink" title="2. globals.xml.ftl"></a>2. globals.xml.ftl</h3><p>该文件定义了模板中可用的全局参数,系统预定义的参数可以参看这个<a href="https://github.com/JetBrains/android/blob/df29dd1cff3669dcf048a0a7000ead9e5d8a1e55/android/src/com/android/tools/idea/templates/TemplateMetadata.java#L40-L128" target="_blank" rel="external">链接</a>。<br><code><global></code> tag定义了一个全局参数:</p>
<ul>
<li><code>id</code>:模板中引用时用到的参数名</li>
<li><code>type</code>: 数据类型(可选)</li>
<li><code>value</code>:参数值<br>这里可以通过${name}的方式,引用系统预定义的参数,而且,系统还预定义一些方法可供使用,具体参见这个<a href="https://github.com/JetBrains/android/blob/df29dd1cff3669dcf048a0a7000ead9e5d8a1e55/android/src/com/android/tools/idea/templates/FreemarkerUtils.java#L50-L71" target="_blank" rel="external">链接</a></li>
</ul>
<h3 id="3-recipe-xml-ftl"><a href="#3-recipe-xml-ftl" class="headerlink" title="3. recipe.xml.ftl"></a>3. recipe.xml.ftl</h3><p>这个文件中通过tag操作符的方式指明了需要生成哪些文件,以及怎么生成,可用的tag如下:<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">mkdir</span> <span class="attr">at</span>=<span class="string">"somewhere"</span> /></span></div><div class="line"> 创建 somewhere 目录</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">instantiate</span> <span class="attr">from</span>=<span class="string">"A.ftl"</span> <span class="attr">to</span>=<span class="string">"...B"</span> /></span></div><div class="line"> 解析A.ftl模板并生成为工程中的...B文件</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">copy</span> <span class="attr">from</span>=<span class="string">"A"</span> <span class="attr">to</span>=<span class="string">"…B"</span> /></span></div><div class="line"> 将A文件拷贝为工程中的…B文件,纯拷贝(不做模板解析)</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">append</span> <span class="attr">from</span>=<span class="string">"A"</span> <span class="attr">to</span>=<span class="string">"…B"</span> /></span></div><div class="line"> 将A文件追加到工程中的…B文件最后(不做模板解析)</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">merge</span> <span class="attr">from</span>=<span class="string">"A.ftl"</span> <span class="attr">to</span>=<span class="string">"…B"</span> /></span></div><div class="line"> 解析A.ftl模板并合并到工程中的…B文件,只有xml和gradle文件才能用<span class="tag"><<span class="name">merge</span>></span></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">open</span> <span class="attr">file</span>=<span class="string">"…B"</span> /></span></div><div class="line"> 在AS中打开工程中的…B文件</div></pre></td></tr></table></figure></p>
<p>以下是gradle相关的tag:<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">apply</span> <span class="attr">plugin</span>=<span class="string">"…"</span> /></span></div><div class="line"> 应用某个gradle插件</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">classpath</span> <span class="attr">mavenUrl</span>=<span class="string">"…"</span> /></span></div><div class="line"> 添加某个mavenUrl到classpath</div><div class="line"></div><div class="line"><span class="tag"><<span class="name">dependency</span> <span class="attr">mavenUrl</span>=<span class="string">"…"</span> /></span></div><div class="line"> 添加某个依赖</div></pre></td></tr></table></figure></p>
<p>这个文件中也可以使用系统预定义的<a href="https://github.com/JetBrains/android/blob/df29dd1cff3669dcf048a0a7000ead9e5d8a1e55/android/src/com/android/tools/idea/templates/TemplateMetadata.java#L40-L128" target="_blank" rel="external">参数</a>、<a href="https://github.com/JetBrains/android/blob/df29dd1cff3669dcf048a0a7000ead9e5d8a1e55/android/src/com/android/tools/idea/templates/FreemarkerUtils.java#L50-L71" target="_blank" rel="external">方法</a>以及globals.xml.ftl中定义的参数、FreeMarker的语法。</p>
<h2 id="举个🌰"><a href="#举个🌰" class="headerlink" title="举个🌰"></a>举个🌰</h2><pre><code>gradle-project/DemoTemplate
├--- template.xml
├--- globals.xml
├--- recipe.xml
├--- mobile-module.png //tempalte的缩略图,从别的template里拷贝一个就好
└--- root
├--- AndroidManifest.xml.ftl
├--- build.properties.ftl
├--- settings.gradle.ftl
└--- res
└--- values
└--- strings.xml.ftl
</code></pre><h3 id="template-xml"><a href="#template-xml" class="headerlink" title="template.xml"></a>template.xml</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div></pre></td><td class="code"><pre><div class="line"><span class="php"><span class="meta"><?</span>xml version=<span class="string">"1.0"</span><span class="meta">?></span></span></div><div class="line"><span class="tag"><<span class="name">template</span></span></div><div class="line"> <span class="attr">format</span>=<span class="string">"5"</span></div><div class="line"> <span class="attr">revision</span>=<span class="string">"2"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Demo Module Template"</span></div><div class="line"> <span class="attr">description</span>=<span class="string">"Create a Demo Template Module."</span>></div><div class="line"></div><div class="line"> <span class="comment"><!-- Module 级别的模板 --></span></div><div class="line"> <span class="tag"><<span class="name">category</span> <span class="attr">value</span>=<span class="string">"Application"</span> /></span></div><div class="line"></div><div class="line"> <span class="comment"><!-- 类别为 Things --></span></div><div class="line"> <span class="tag"><<span class="name">formfactor</span> <span class="attr">value</span>=<span class="string">"Things"</span> /></span></div><div class="line"></div><div class="line"> <span class="comment"><!-- Create Dialog 上显示的预览图 --></span></div><div class="line"> <span class="tag"><<span class="name">thumbs</span>></span></div><div class="line"> <span class="tag"><<span class="name">thumb</span>></span>mobile-module.png<span class="tag"></<span class="name">thumb</span>></span></div><div class="line"> <span class="tag"></<span class="name">thumbs</span>></span></div><div class="line"></div><div class="line"> <span class="comment"><!-- 以下都是一些默认参数 --></span></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"packageName"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Package name"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"app_package|nonempty"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"com.your.myapp"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"appTitle"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Module Name"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"nonempty"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"module_"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"minApi"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Minimum API level"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"8"</span> /></div><div class="line"></div><div class="line"> <span class="comment"><!--</span></div><div class="line"> Usually the same as minApi, but when minApi is a code name this will be the corresponding</div><div class="line"> API level</div><div class="line"> --></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"minApiLevel"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Minimum API level"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"8"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"targetApi"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Target API level"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"19"</span> /></div><div class="line"></div><div class="line"> <span class="comment"><!--</span></div><div class="line"> Usually the same as targetApi, but when targeting a preview platform this is the code name instead</div><div class="line"> --></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"targetApiString"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Target API"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"19"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"buildApi"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Build API level"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"19"</span> /></div><div class="line"></div><div class="line"> <span class="comment"><!--</span></div><div class="line"> Usually the same as buildApi, but when targeting a preview platform this is the code name instead</div><div class="line"> --></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"buildApiString"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Build API level"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"string"</span></div><div class="line"> <span class="attr">constraints</span>=<span class="string">"apilevel"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"19"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">parameter</span></span></div><div class="line"> <span class="attr">id</span>=<span class="string">"makeIgnore"</span></div><div class="line"> <span class="attr">name</span>=<span class="string">"Create .gitignore file"</span></div><div class="line"> <span class="attr">type</span>=<span class="string">"boolean"</span></div><div class="line"> <span class="attr">default</span>=<span class="string">"true"</span> /></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">globals</span> <span class="attr">file</span>=<span class="string">"globals.xml.ftl"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">execute</span> <span class="attr">file</span>=<span class="string">"recipe.xml.ftl"</span> /></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">template</span>></span></div></pre></td></tr></table></figure>
<h3 id="globals-xml-ftl"><a href="#globals-xml-ftl" class="headerlink" title="globals.xml.ftl"></a>globals.xml.ftl</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="php"><span class="meta"><?</span>xml version=<span class="string">"1.0"</span><span class="meta">?></span></span></div><div class="line"><span class="tag"><<span class="name">globals</span>></span></div><div class="line"> <span class="comment"><!-- 项目根目录 --></span></div><div class="line"> <span class="tag"><<span class="name">global</span> <span class="attr">id</span>=<span class="string">"topOut"</span> <span class="attr">value</span>=<span class="string">"."</span> /></span></div><div class="line"> <span class="comment"><!-- Module根目录 --></span></div><div class="line"> <span class="tag"><<span class="name">global</span> <span class="attr">id</span>=<span class="string">"projectOut"</span> <span class="attr">value</span>=<span class="string">"."</span> /></span></div><div class="line"> <span class="tag"><<span class="name">global</span> <span class="attr">id</span>=<span class="string">"srcOut"</span> <span class="attr">value</span>=<span class="string">"${srcDir}/${slashedPackageName(packageName)}"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">global</span> <span class="attr">id</span>=<span class="string">"manifestOut"</span> <span class="attr">value</span>=<span class="string">"${manifestDir}"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">global</span> <span class="attr">id</span>=<span class="string">"resOut"</span> <span class="attr">value</span>=<span class="string">"${resDir}"</span> /></span></div><div class="line"><span class="tag"></<span class="name">globals</span>></span></div></pre></td></tr></table></figure>
<h3 id="recipe-xml-ftl"><a href="#recipe-xml-ftl" class="headerlink" title="recipe.xml.ftl"></a>recipe.xml.ftl</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line"><span class="php"><span class="meta"><?</span>xml version=<span class="string">"1.0"</span><span class="meta">?></span></span></div><div class="line"><span class="tag"><<span class="name">recipe</span>></span></div><div class="line"> <span class="comment"><!-- 在Module的根目录下,创建 libs 和 drawable 目录 --></span></div><div class="line"> <span class="tag"><<span class="name">mkdir</span> <span class="attr">at</span>=<span class="string">"${escapeXmlAttribute(projectOut)}/libs"</span> /></span></div><div class="line"> <span class="tag"><<span class="name">mkdir</span> <span class="attr">at</span>=<span class="string">"${escapeXmlAttribute(resOut)}/drawable"</span> /></span></div><div class="line"></div><div class="line"> <span class="comment"><!-- 合并 settings.gradle --></span></div><div class="line"> <span class="tag"><<span class="name">merge</span> <span class="attr">from</span>=<span class="string">"root/settings.gradle.ftl"</span></span></div><div class="line"> <span class="attr">to</span>=<span class="string">"${escapeXmlAttribute(topOut)}/settings.gradle"</span> /></div><div class="line"></div><div class="line"> <span class="comment"><!-- 根据当前template的root/build.gradle.ftl,初始化Module的build.grale --></span></div><div class="line"> <span class="tag"><<span class="name">instantiate</span> <span class="attr">from</span>=<span class="string">"root/build.gradle.ftl"</span></span></div><div class="line"> <span class="attr">to</span>=<span class="string">"${escapeXmlAttribute(projectOut)}/build.gradle"</span> /></div><div class="line"></div><div class="line"> <span class="comment"><!-- 初始化AndroidManifest.xml --></span></div><div class="line"> <span class="tag"><<span class="name">instantiate</span> <span class="attr">from</span>=<span class="string">"root/AndroidManifest.xml.ftl"</span></span></div><div class="line"> <span class="attr">to</span>=<span class="string">"${escapeXmlAttribute(manifestOut)}/AndroidManifest.xml"</span> /></div><div class="line"> <span class="comment"><!-- 初始化strings.xml --></span></div><div class="line"> <span class="tag"><<span class="name">instantiate</span> <span class="attr">from</span>=<span class="string">"root/res/values/strings.xml.ftl"</span></span></div><div class="line"> <span class="attr">to</span>=<span class="string">"${escapeXmlAttribute(resOut)}/values/strings.xml"</span> /></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">#if</span> !<span class="attr">createActivity</span>></span></div><div class="line"> <span class="tag"><<span class="name">mkdir</span> <span class="attr">at</span>=<span class="string">"${escapeXmlAttribute(srcOut)}"</span> /></span></div><div class="line"><span class="tag"></<span class="name">#if</span>></span></div><div class="line"></div><div class="line"></div><div class="line"><span class="tag"><<span class="name">#if</span> <span class="attr">makeIgnore</span>></span></div><div class="line"> <span class="tag"><<span class="name">copy</span> <span class="attr">from</span>=<span class="string">"root://gradle-projects/common/gitignore"</span></span></div><div class="line"> <span class="attr">to</span>=<span class="string">"${escapeXmlAttribute(projectOut)}/.gitignore"</span> /></div><div class="line"><span class="tag"></<span class="name">#if</span>></span></div><div class="line"></div><div class="line"> <span class="tag"><<span class="name">#include</span> "<span class="attr">root:</span>//<span class="attr">gradle-projects</span>/<span class="attr">common</span>/<span class="attr">proguard_recipe.xml.ftl</span>"/></span></div><div class="line"></div><div class="line"><span class="tag"></<span class="name">recipe</span>></span></div></pre></td></tr></table></figure>
<h3 id="root-AndroidManifest-xml-ftl"><a href="#root-AndroidManifest-xml-ftl" class="headerlink" title="root/AndroidManifest.xml.ftl"></a>root/AndroidManifest.xml.ftl</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">manifest</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line"> <span class="attr">package</span>=<span class="string">"${packageName}"</span> /></div></pre></td></tr></table></figure>
<h3 id="root-build-gradle-ftl"><a href="#root-build-gradle-ftl" class="headerlink" title="root/build.gradle.ftl"></a>root/build.gradle.ftl</h3><figure class="highlight"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line">apply plugin: 'com.android.library'</div><div class="line"></div><div class="line">android {</div><div class="line"> compileSdkVersion <#if buildApiString?matches("^\\d+$")>${buildApiString}<#else>'${buildApiString}'</#if></div><div class="line"> <#if explicitBuildToolsVersion!false>buildToolsVersion "${buildToolsVersion}"</#if></div><div class="line"></div><div class="line"> defaultConfig {</div><div class="line"> minSdkVersion <#if minApi?matches("^\\d+$")>${minApi}<#else>'${minApi}'</#if></div><div class="line"> targetSdkVersion <#if targetApiString?matches("^\\d+$")>${targetApiString}<#else>'${targetApiString}'</#if></div><div class="line"> versionCode 1</div><div class="line"> versionName "1.0"</div><div class="line"> }</div><div class="line"></div><div class="line"> buildTypes {</div><div class="line"> release {</div><div class="line"> minifyEnabled false</div><div class="line"> proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="root-settings-gradle-ftl"><a href="#root-settings-gradle-ftl" class="headerlink" title="root/settings.gradle.ftl"></a>root/settings.gradle.ftl</h3><figure class="highlight gradle"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">include</span> <span class="string">':${projectName}'</span></div></pre></td></tr></table></figure>
<h3 id="root-res-values-string-xml-ftl"><a href="#root-res-values-string-xml-ftl" class="headerlink" title="root/res/values/string.xml.ftl"></a>root/res/values/string.xml.ftl</h3><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag"><<span class="name">resources</span>></span></div><div class="line"> <span class="tag"><<span class="name">string</span> <span class="attr">name</span>=<span class="string">"app_name"</span>></span>${escapeXmlString(appTitle)}<span class="tag"></<span class="name">string</span>></span></div><div class="line"><span class="tag"></<span class="name">resources</span>></span></div></pre></td></tr></table></figure>
<p><strong> 设置好以后,重启Studio,然后 <code>File -> New —> New Module..</code> 应该就能弹出创建的对话框了 </strong></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://github.com/JetBrains/android/tree/df29dd1cff3669dcf048a0a7000ead9e5d8a1e55/android/src/com/android/tools/idea/templates" target="_blank" rel="external">JetBrains/android plugin</a></li>
<li><a href="http://jessyan.me/templates/" target="_blank" rel="external">效率提升百分之四十,AS模板也太好用了吧 - JessYan</a></li>
</ul>
<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>最近项目在搞组件化,基本改造已完成(解耦什么的真的是体力活呀!因为项目比较庞大,所以老代码决定还是放着不动,有时间有人力再搞,新的大功能以组件化的方式开发),但是创建新的组件模块要拷贝很多相似的配置和代码,鉴于IDEA强大的模板功能,决定把这块给自动化了。</p>
<h2 id="符合我们要求的自定义模板分两类:"><a href="#符合我们要求的自定义模板分两类:" class="headerlink" title="符合我们要求的自定义模板分两类:"></a>符合我们要求的自定义模板分两类:</h2><h3 id="1-Module-模板"><a href="#1-Module-模板" class="headerlink" title="1. Module 模板"></a><span id='template_module'>1. Module 模板</span></h3><p>位于<code><AS安装目录>/plugins/android/lib/templates/gradle-project</code>下,创建Application或者Module,类型只有固定的几类,<code>category</code>为<code>Application</code>,<code>formfactor</code>为:</p>
<ul>
<li>Mobile : 移动端应用</li>
<li>Wear : 可穿戴设备的应用</li>
<li>Car : 车载应用</li>
<li>TV : 电视应用</li>
<li>Things : 物联网应用</li>
</ul>
<p>一开始,自定义模板的时候使用的<code>Mobile</code>,不生效,发现<code>Mobile</code>只认一个;按照JavaLibrary的写法,去掉<code>formfactor</code>,发现也只认一个;后来改成<code>Things</code>才可以。<br>Module模板在创建的时候,无法像页面模板那样,在Create Dialog中添加自定义参数,让用户选填,这个有点坑。</p>
<h3 id="2-页面模板"><a href="#2-页面模板" class="headerlink" title="2. 页面模板"></a>2. 页面模板</h3><p>位于<code>templates/activities</code>下,在module中创建特定的文件或目录,这种模板可以在Create Dialog中添加自定义的参数。</p>
发送异常堆栈到企业微信
http://nstd.github.io/2018/04/12/analysis-ios-crash/
2018-04-12T03:54:29.000Z
2018-04-12T06:54:58.000Z
<h2 id="What-amp-Why"><a href="#What-amp-Why" class="headerlink" title="What & Why"></a>What & Why</h2><p>最近项目收尾有空闲时间,于是想完善一下工具链。接入了DebugDrawer,加了不少小功能功能,还蛮欣喜的。<br>后来想想流程上的一些细节,想到了测试在反馈崩溃bug的时候,我们经常要拿他们的测试机过来,连上手机以后,根据日志里的崩溃信息来定位问题。<br>这个步骤有点繁琐,于是决定写个小工具,debug版本测试的时候,如果遇到了崩溃,直接把异常堆栈发到我们的聊天软件(企业微信)可好?<br>看了下企业微信有提供<a href="https://work.weixin.qq.com/api/doc#10167" target="_blank" rel="external">发送消息的api</a>,只要把异常信息通过内网服务器转发到企业微信就好了。</p>
<h3 id="大致流程"><a href="#大致流程" class="headerlink" title="大致流程"></a>大致流程</h3><ul>
<li>app崩溃</li>
<li>捕获异常</li>
<li>重启应用</li>
<li>(弹窗提示是否发送崩溃堆栈到服务器)发送异常堆栈、账号、等信息到服务器</li>
<li>服务器转发异常到企业微信</li>
</ul>
<p><a href="#server_code">#服务器端转发代码见附录</a></p>
<h3 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h3><p>android这边,debug代码默认是不混淆的,所以从异常堆栈可以直接看出出错的代码位置,但是和ios那边沟通了一下,发现他们的异常堆栈需要用符号表解析一下才可用。<br>他们也有个类似mapping的文件叫dSYM(dSYM其实是个目录,实际的mapping文件为:dSYM/Contents/Resources/DWARF/${appName}),需要用xcrun atos把异常堆栈解析成可读的代码。<br>因为我们的编译机就是mac系统,所以自带xcrun,写个脚本转一下就好了。</p>
<a id="more"></a>
<h2 id="How"><a href="#How" class="headerlink" title="How"></a>How</h2><p>解析ios异常堆栈的代码</p>
<h3 id="analysisIOSCrash-sh"><a href="#analysisIOSCrash-sh" class="headerlink" title="analysisIOSCrash.sh"></a>analysisIOSCrash.sh</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#!/bin/bash </span></div><div class="line"></div><div class="line"><span class="comment">#############################</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment"># usage: ./analysisIOSCrash.sh crashFile dSYMKey appName arch outputAll [resultFile]</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment"># crashFile: 保存异常堆栈的文件</span></div><div class="line"><span class="comment"># dSYMKey: dSYM目录,这里是用build号标识的,dSYM目录存储路径为:当前脚本路径/dSYM/$appName/$dSYMKey</span></div><div class="line"><span class="comment"># appName: app bundle名</span></div><div class="line"><span class="comment"># arch: 异常发生的机器cpu架构</span></div><div class="line"><span class="comment"># outputAll:</span></div><div class="line"><span class="comment"># 0: 只输出appName对应的转换后的异常信息</span></div><div class="line"><span class="comment"># 1: 输出转换后的整个异常栈</span></div><div class="line"><span class="comment"># resultFile: 将结果保存到$resultFile,如果没有这个参数则直接输出到控制台,可选参数</span></div><div class="line"><span class="comment">#############################</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [ <span class="variable">$#</span> <span class="_">-lt</span> 5 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span> <span class="string">"param num not fit"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line">crashFile=<span class="variable">$1</span></div><div class="line">dSYMKey=<span class="variable">$2</span></div><div class="line">dSYM_DIR=`<span class="built_in">pwd</span>`/dSYM</div><div class="line">appName=<span class="variable">$3</span></div><div class="line">dSYMFile=<span class="variable">$dSYM_DIR</span>/<span class="variable">$appName</span>/<span class="variable">$dSYMKey</span></div><div class="line">arch=<span class="variable">$4</span></div><div class="line">outputAll=<span class="variable">$5</span></div><div class="line">resultFile=`<span class="built_in">pwd</span>`/`date +%s`<span class="string">".tmp"</span></div><div class="line">keepResult=0</div><div class="line"></div><div class="line"><span class="keyword">if</span> [ <span class="variable">$#</span> == 6 ]; <span class="keyword">then</span></div><div class="line"> resultFile=<span class="variable">$6</span></div><div class="line"> keepResult=1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [ ! <span class="_">-d</span> <span class="string">"<span class="variable">$dSYMFile</span>"</span> ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span> <span class="string">"not found dSYMFile: <span class="variable">$dSYMFile</span>"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [ ! <span class="_">-f</span> <span class="string">"<span class="variable">$crashFile</span>"</span> ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span> <span class="string">"not found crashFile: <span class="variable">$crashFile</span>"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [ <span class="variable">$outputAll</span> == 1 ]; <span class="keyword">then</span></div><div class="line"> awk -v dSYM=<span class="string">"<span class="variable">$dSYMFile</span>"</span> -v appName=<span class="variable">$appName</span> -v arch=<span class="variable">$arch</span> <span class="string">'{if ($0 ~ /'</span><span class="variable">$appName</span><span class="string">'[\t ]*0x[0123456789abcdefABCDEF]+[\t ]+'</span><span class="variable">$appName</span><span class="string">'/) {value=int($3) - int($6); addr="0x"sprintf("%lx", value); dSYM2=dSYM"/Contents/Resources/DWARF/"appName; "xcrun atos -arch "arch" -o \""dSYM2"\" -l "addr" "$3"" | getline result; printf("\t%-4d%-35s %s %s\n", $1, $2, $3, result);} else {print $0;}}'</span> <span class="string">"<span class="variable">$crashFile</span>"</span> > <span class="variable">$resultFile</span> 2>&1</div><div class="line"><span class="keyword">else</span></div><div class="line"> awk -v dSYM=<span class="variable">$dSYMFile</span> -v appName=<span class="variable">$appName</span> -v arch=<span class="variable">$arch</span> <span class="string">'BEGIN{opLine=1;}{if($0 ~ /^\(/){print "("; opLine=0;} if($0 ~ /^\)/){opLine=1;} if ($0 ~ /'</span><span class="variable">$appName</span><span class="string">'[\t ]*0x[0123456789abcdefABCDEF]+[\t ]+'</span><span class="variable">$appName</span><span class="string">'/) {value=int($3) - int($6); addr="0x"sprintf("%lx", value); dSYM2=dSYM"/Contents/Resources/DWARF/"appName; "xcrun atos -arch "arch" -o \""dSYM2"\" -l "addr" "$3"" | getline result; printf("\t%-4d%-35s %s %s\n", $1, $2, $3, result);} else {if(opLine == 1) {print $0;}}}'</span> <span class="variable">$crashFile</span> > <span class="variable">$resultFile</span> 2>&1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line">cat <span class="variable">$resultFile</span></div><div class="line"></div><div class="line"><span class="keyword">if</span> [ <span class="variable">$keepResult</span> == 0 ]; <span class="keyword">then</span></div><div class="line"> rm <span class="variable">$resultFile</span></div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="built_in">exit</span> 0</div></pre></td></tr></table></figure>
<h3 id="服务器端转发代码"><a href="#服务器端转发代码" class="headerlink" title="服务器端转发代码"></a><span id="server_code">服务器端转发代码</span></h3><p>使用的时候需要替换 <code>cropid</code>、<code>cropsecret</code>、<code>agentid</code>这几个参数<br><figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div><div class="line">109</div><div class="line">110</div><div class="line">111</div><div class="line">112</div><div class="line">113</div><div class="line">114</div><div class="line">115</div><div class="line">116</div><div class="line">117</div><div class="line">118</div><div class="line">119</div><div class="line">120</div><div class="line">121</div><div class="line">122</div><div class="line">123</div><div class="line">124</div><div class="line">125</div><div class="line">126</div><div class="line">127</div><div class="line">128</div><div class="line">129</div><div class="line">130</div><div class="line">131</div><div class="line">132</div><div class="line">133</div><div class="line">134</div><div class="line">135</div><div class="line">136</div><div class="line">137</div><div class="line">138</div><div class="line">139</div><div class="line">140</div><div class="line">141</div><div class="line">142</div><div class="line">143</div><div class="line">144</div><div class="line">145</div><div class="line">146</div><div class="line">147</div><div class="line">148</div><div class="line">149</div><div class="line">150</div><div class="line">151</div><div class="line">152</div><div class="line">153</div></pre></td><td class="code"><pre><div class="line"><span class="meta"><?php</span></div><div class="line"></div><div class="line"><span class="comment">/****************************</span></div><div class="line"> *</div><div class="line"> * 参数:</div><div class="line"> * p: ios/android</div><div class="line"> * app: app名称</div><div class="line"> * msg: 额外消息</div><div class="line"> * stack: 异常堆栈</div><div class="line"> * archive: cpu架构(ios额外参数)</div><div class="line"> * dSYMId:dSYM文件名,这里用的是build号(ios额外参数)</div><div class="line"> * </div><div class="line"> ****************************/</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">getToken</span><span class="params">()</span> </span>{</div><div class="line"> $url = <span class="string">"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${这里填入cropId}&corpsecret=${这里填入应用的corpsecret}"</span>;</div><div class="line"> $result = json_decode(file_get_contents($url));</div><div class="line"></div><div class="line"> <span class="comment">//TODO 如果高频率请求token,后面可能会收不到消息,这两个参数存数据库</span></div><div class="line"> <span class="comment">//$result->access_token</span></div><div class="line"> <span class="comment">//$result->expires_in</span></div><div class="line"></div><div class="line"> <span class="keyword">return</span> $result->access_token;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">http_post_data</span><span class="params">($url, $data)</span> </span>{</div><div class="line"> $ch = curl_init($url);</div><div class="line"> curl_setopt($ch, CURLOPT_POST, <span class="keyword">true</span>);</div><div class="line"> curl_setopt($ch, CURLOPT_POSTFIELDS, $data);</div><div class="line"> curl_setopt($ch, CURLOPT_HTTPHEADER, <span class="keyword">array</span>(</div><div class="line"> <span class="string">'Content-Type: application/json; charset=utf-8'</span>,</div><div class="line"> <span class="string">'Content-Length: '</span> . strlen($data)</div><div class="line"> ));</div><div class="line"></div><div class="line"> ob_start();</div><div class="line"> $return_content = curl_exec($ch);</div><div class="line"> curl_close($ch);</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(!<span class="keyword">empty</span>($return_content)) {</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> $result = json_decode($return_content);</div><div class="line"> $code = $result->errcode;</div><div class="line"> <span class="keyword">if</span>($code == <span class="number">0</span>) {</div><div class="line"> <span class="keyword">return</span> <span class="string">'success'</span>;</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> $result->errmsg;</div><div class="line"> }</div><div class="line"> } <span class="keyword">catch</span> (<span class="keyword">Exception</span> $e) {</div><div class="line"> <span class="keyword">return</span> $e->getMessage();</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">return</span> <span class="string">'failed!!'</span>;</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendMsg</span><span class="params">($totag, $msg)</span> </span>{</div><div class="line"> $url = <span class="string">"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="</span> . getToken();</div><div class="line"> $target_msg = <span class="keyword">array</span>(</div><div class="line"> <span class="string">'totag'</span> => $totag,</div><div class="line"> <span class="string">'agentid'</span> => <span class="string">'${这里填入公司的agentId}'</span>,</div><div class="line"> <span class="string">'msgtype'</span> => <span class="string">'text'</span>,</div><div class="line"> <span class="string">'text'</span> => <span class="keyword">array</span>(</div><div class="line"> <span class="string">'content'</span> => $msg</div><div class="line"> )</div><div class="line"> );</div><div class="line"> $encode_msg = json_encode($target_msg);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> http_post_data($url, $encode_msg);</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="comment">//检查参数</span></div><div class="line"><span class="keyword">if</span>(!(array_key_exists(<span class="string">'p'</span>, $_POST)</div><div class="line"> && array_key_exists(<span class="string">'app'</span>, $_POST)</div><div class="line"> && array_key_exists(<span class="string">'msg'</span>, $_POST)</div><div class="line"> && array_key_exists(<span class="string">'stack'</span>, $_POST))) {</div><div class="line"> <span class="keyword">echo</span> <span class="string">"need param!"</span>;</div><div class="line"> <span class="keyword">return</span> ;</div><div class="line">}</div><div class="line"></div><div class="line">$platform = $_POST[<span class="string">'p'</span>];</div><div class="line">$app = $_POST[<span class="string">'app'</span>];</div><div class="line">$msg = $_POST[<span class="string">'msg'</span>];</div><div class="line">$stack = $_POST[<span class="string">'stack'</span>];</div><div class="line">$archive = <span class="string">''</span>;</div><div class="line">$dSYMId = <span class="string">''</span>;</div><div class="line"></div><div class="line"><span class="keyword">if</span>(<span class="keyword">empty</span>($platform) || <span class="keyword">empty</span>($app) || <span class="keyword">empty</span>($msg) || <span class="keyword">empty</span>($stack)) {</div><div class="line"> <span class="keyword">echo</span> <span class="string">"empty param!"</span>;</div><div class="line"> <span class="keyword">return</span> ;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">if</span>($platform != <span class="string">'android'</span> && $platform != <span class="string">'ios'</span>) {</div><div class="line"> <span class="keyword">echo</span> <span class="string">"invalidate param app"</span>;</div><div class="line"> <span class="keyword">return</span> ;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">if</span>($platform == <span class="string">'ios'</span>) {</div><div class="line"> <span class="keyword">if</span>(!array_key_exists(<span class="string">'archive'</span>, $_POST) || !array_key_exists(<span class="string">'dSYMId'</span>, $_POST)) {</div><div class="line"> <span class="keyword">echo</span> <span class="string">'need param!'</span>;</div><div class="line"> }</div><div class="line"></div><div class="line"> $archive = $_POST[<span class="string">'archive'</span>];</div><div class="line"> $dSYMId = $_POST[<span class="string">'dSYMId'</span>];</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(<span class="keyword">empty</span>($archive) || <span class="keyword">empty</span>($dSYMId)) {</div><div class="line"> <span class="keyword">echo</span> <span class="string">"empty param!"</span>;</div><div class="line"> <span class="keyword">return</span> ;</div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"> $crash_dir = <span class="string">"crash"</span>;</div><div class="line"> <span class="keyword">if</span>(!is_dir($crash_dir)) {</div><div class="line"> mkdir($crash_dir, <span class="number">0777</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"></div><div class="line"> <span class="comment">//将错误堆栈写入临时文件</span></div><div class="line"> $crashFileName = $crash_dir . <span class="string">"/"</span>. time() . <span class="string">".txt"</span>;</div><div class="line"> $resultFileName = $crash_dir . <span class="string">"/"</span> . time() . <span class="string">"-result.txt"</span>;</div><div class="line"> $crashFile = fopen($crashFileName, <span class="string">"w"</span>);</div><div class="line"> fwrite($crashFile, $stack);</div><div class="line"> fclose($crashFile);</div><div class="line"></div><div class="line"> $dSYMFile = <span class="string">""</span> . $dSYMId . <span class="string">".dSYM"</span>;</div><div class="line"> $showAll = strlen($msg . $stack) > <span class="number">2000</span> ? <span class="number">0</span> : <span class="number">1</span>;</div><div class="line"></div><div class="line"> $cmd = <span class="string">"./analysisIOSCrash.sh "</span> . $crashFileName . <span class="string">" "</span> . $dSYMFile . <span class="string">" "</span> . $app . <span class="string">" "</span> . $archive . <span class="string">" "</span> . $showAll . <span class="string">" "</span> . $resultFileName;</div><div class="line"></div><div class="line"> exec($cmd, $dSYM_result, $r_code);</div><div class="line"></div><div class="line"></div><div class="line"> <span class="keyword">if</span>($r_code == <span class="number">0</span>) {</div><div class="line"> $dSYM_result = file_get_contents($resultFileName);</div><div class="line"> <span class="keyword">if</span>(!<span class="keyword">empty</span>($dSYM_result)) {</div><div class="line"> $stack = $dSYM_result;</div><div class="line"> }</div><div class="line"> } <span class="keyword">else</span> {</div><div class="line"> <span class="keyword">echo</span> <span class="string">"error: "</span>;</div><div class="line"> var_dump($dSYM_result);</div><div class="line"> $stack = <span class="string">"\n!!!!!!!!!!!!!!! ERROR:\n "</span> . $dSYM_result[<span class="number">0</span>] . <span class="string">"\n!!!!!!!!!!!!!!!\n\n"</span> . $stack;</div><div class="line"> }</div><div class="line"></div><div class="line"> unlink($crashFileName);</div><div class="line"> unlink($resultFileName);</div><div class="line">}</div><div class="line"></div><div class="line"> $totag = $platform == <span class="string">'android'</span> ? <span class="string">'1'</span> : <span class="string">'2'</span>;</div><div class="line"> $msg = <span class="string">"应用: "</span> . $app . <span class="string">"\n"</span> . $msg . <span class="string">"\n"</span> . $stack;</div><div class="line"></div><div class="line"> <span class="keyword">echo</span> sendMsg($totag, $msg);</div><div class="line"></div><div class="line"><span class="meta">?></span></div></pre></td></tr></table></figure></p>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>程序员需要学会更多偷懒的姿势😁</p>
<h2 id="What-amp-Why"><a href="#What-amp-Why" class="headerlink" title="What & Why"></a>What & Why</h2><p>最近项目收尾有空闲时间,于是想完善一下工具链。接入了DebugDrawer,加了不少小功能功能,还蛮欣喜的。<br>后来想想流程上的一些细节,想到了测试在反馈崩溃bug的时候,我们经常要拿他们的测试机过来,连上手机以后,根据日志里的崩溃信息来定位问题。<br>这个步骤有点繁琐,于是决定写个小工具,debug版本测试的时候,如果遇到了崩溃,直接把异常堆栈发到我们的聊天软件(企业微信)可好?<br>看了下企业微信有提供<a href="https://work.weixin.qq.com/api/doc#10167">发送消息的api</a>,只要把异常信息通过内网服务器转发到企业微信就好了。</p>
<h3 id="大致流程"><a href="#大致流程" class="headerlink" title="大致流程"></a>大致流程</h3><ul>
<li>app崩溃</li>
<li>捕获异常</li>
<li>重启应用</li>
<li>(弹窗提示是否发送崩溃堆栈到服务器)发送异常堆栈、账号、等信息到服务器</li>
<li>服务器转发异常到企业微信</li>
</ul>
<p><a href="#server_code">#服务器端转发代码见附录</a></p>
<h3 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h3><p>android这边,debug代码默认是不混淆的,所以从异常堆栈可以直接看出出错的代码位置,但是和ios那边沟通了一下,发现他们的异常堆栈需要用符号表解析一下才可用。<br>他们也有个类似mapping的文件叫dSYM(dSYM其实是个目录,实际的mapping文件为:dSYM/Contents/Resources/DWARF/${appName}),需要用xcrun atos把异常堆栈解析成可读的代码。<br>因为我们的编译机就是mac系统,所以自带xcrun,写个脚本转一下就好了。</p>
搭建一个本地jFrog Artifactory并上传库文件
http://nstd.github.io/2017/03/08/set-up-a-local-jfrog-artifactory/
2017-03-08T02:38:12.000Z
2017-03-08T08:23:26.000Z
<h1 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h1><p>前段时间,公司想搞hybrid开发,老司机觉得web里的url跳转挺方便的,稍稍调研了下,就自己撸了一个AndroidRouter库。<br>差不多市面上该有的功能都有:</p>
<ul>
<li>根据URL跳转到Activity</li>
<li>通过Helper跳转到Activity</li>
<li>同一个Activity可以映射多个URL</li>
<li>跳转Activity的时候可以通过URL参数将变量的值注入到Activity中(支持基本类型和实现序列化接口的类)</li>
<li>通过H5打开Native页面</li>
<li>统一解决savedInstanceState的注入与读取</li>
</ul>
<p>用着也还挺舒服的,他就想推广到其他项目里,但是每个工程重新拷贝一份Library代码,对代码管理和版本管理什么的都不太方便,于是就有了今天这出:<br>搭建一个本地的maven仓库,用gradle的compile进行引用,再方便不过了。<br>在网上看的时候,都说jFrog Artifactory比较好用,就随大流啦。</p>
<a id="more"></a>
<h1 id="本文使用环境"><a href="#本文使用环境" class="headerlink" title="本文使用环境"></a>本文使用环境</h1><table>
<thead>
<tr>
<th>item</th>
<th>info</th>
</tr>
</thead>
<tbody>
<tr>
<td>系统</td>
<td>osx 10.11.6</td>
</tr>
<tr>
<td>artifactory版本</td>
<td>5.1.0</td>
</tr>
</tbody>
</table>
<h1 id="安装-amp-配置"><a href="#安装-amp-配置" class="headerlink" title="安装 & 配置"></a>安装 & 配置</h1><p><a href="https://www.jfrog.com/open-source/" target="_blank" rel="external">官网</a>有两种方式进行安装:</p>
<ol>
<li>下载安装包(安装包里有各个平台的安装文件)</li>
<li>用docker</li>
</ol>
<p>因为对docker不是很熟,而且安装包安装方式听说也挺简单的,就着最简原则,就用第一种方式安装了。</p>
<h2 id="预安装"><a href="#预安装" class="headerlink" title="预安装"></a>预安装</h2><p>jFrog Artifactory得用java8(自带的tomcat需要),如果没有安装或者安装了多个版本的java需要进行切换(mac下可以用jenv),请自行解决:)</p>
<h2 id="安装jFrog-Artifactory"><a href="#安装jFrog-Artifactory" class="headerlink" title="安装jFrog Artifactory"></a>安装jFrog Artifactory</h2><p>从<a href="https://www.jfrog.com/open-source/" target="_blank" rel="external">这里</a>下载zip包,解压并进入bin文件夹,执行对应的安装程序:</p>
<ul>
<li>mac/linux: <code>artifactory.sh</code></li>
<li>windows: <code>artifactory.bat</code></li>
</ul>
<p>安装过程挺快的,完成以后,浏览器里输入:<code>http://localhost:8081/artifactory</code><br>首先会让你设置admin等信息,然后就可以用了<br>我默认创建了一个gradle repository,生成了4个子Repository:</p>
<ul>
<li>gradle-dev</li>
<li>gradle-release</li>
<li>gradle-dev-local</li>
<li>gradle-release-local (<strong>我们内部库默认上传到这里</strong>)</li>
</ul>
<p>直到这里都还挺顺利的,随后就要入一个自己挖的大坑了!</p>
<h1 id="library中配置上传信息"><a href="#library中配置上传信息" class="headerlink" title="library中配置上传信息"></a>library中配置上传信息</h1><h2 id="项目构成"><a href="#项目构成" class="headerlink" title="项目构成"></a>项目构成</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">android_router //workspace</div><div class="line"> app //测试工程</div><div class="line"> annotation-lib //通用注解部分,它被其他3部分所引用</div><div class="line"> api-lib //Router and Helper,被app引用</div><div class="line"> compiler-lib //生成帮助类的apt库,被app引用</div></pre></td></tr></table></figure>
<h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>一开始是按<a href="http://www.voidcn.com/blog/zzulp/article/p-5979375.html" target="_blank" rel="external">这篇教程</a>配置的<br>虽然能正常生成和上传,但是因为有依赖工程(annotation-lib),引用的时候报错:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">android_router:annotation-lib:unspecified</div></pre></td></tr></table></figure></p>
<p>试了半天,怀疑是<code>api-lib</code>和<code>compiler-lib</code>中引用的<code>annotation-lib</code>库是以project的形式引用,而不是以jar方式<br>于是就想能不能把pom.xml中的依赖重写一下,google一下还真有,所以就有了以下的配置。</p>
<h3 id="修改android-router下的build-gradle"><a href="#修改android-router下的build-gradle" class="headerlink" title="修改android_router下的build.gradle"></a>修改<code>android_router</code>下的<code>build.gradle</code></h3><p>添加本地仓库<br><figure class="highlight gradle"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">buildscript</span> {</div><div class="line"> <span class="keyword">dependencies</span> {</div><div class="line"> <span class="keyword">classpath</span> <span class="string">"org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1"</span></div><div class="line"> }</div><div class="line">}</div><div class="line"><span class="keyword">allprojects</span> {</div><div class="line"> <span class="keyword">repositories</span> {</div><div class="line"> maven { url <span class="string">"http://server_address:8081/artifactory/gradle-release-local"</span> }</div><div class="line"> <span class="comment">//...</span></div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h3 id="修改annotation-lib下的build-gradle"><a href="#修改annotation-lib下的build-gradle" class="headerlink" title="修改annotation-lib下的build.gradle"></a>修改<code>annotation-lib</code>下的<code>build.gradle</code></h3><figure class="highlight gradle"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div></pre></td><td class="code"><pre><div class="line">apply plugin: <span class="string">'com.jfrog.artifactory'</span></div><div class="line">apply plugin: <span class="string">'maven-publish'</span></div><div class="line"></div><div class="line"><span class="keyword">def</span> MAVEN_LOCAL_PATH =<span class="string">'http://server_address:8081/artifactory'</span></div><div class="line"><span class="keyword">def</span> ARTIFACT_ID = <span class="string">'annotation'</span></div><div class="line"><span class="keyword">def</span> VERSION_NAME = <span class="string">'1.0.0'</span></div><div class="line"><span class="keyword">def</span> GROUP_ID = <span class="string">'com.router'</span></div><div class="line"></div><div class="line">publishing {</div><div class="line"> publications {</div><div class="line"> aar(MavenPublication) {</div><div class="line"> groupId GROUP_ID</div><div class="line"> version = VERSION_NAME</div><div class="line"> artifactId ARTIFACT_ID</div><div class="line"></div><div class="line"> <span class="comment">//----- !!!为了别的项目能读到当前的配置!!! -----</span></div><div class="line"> <span class="keyword">project</span>.ext.<span class="keyword">group</span> = GROUP_ID</div><div class="line"> <span class="keyword">project</span>.ext.version = VERSION_NAME</div><div class="line"> <span class="keyword">project</span>.ext.artifactId = ARTIFACT_ID</div><div class="line"></div><div class="line"> <span class="comment">// Tell maven to prepare the generated "*.aar" file for publishing</span></div><div class="line"> artifact(<span class="string">"$buildDir/libs/${project.getName()}.jar"</span>)</div><div class="line"></div><div class="line"> pom.withXml {</div><div class="line"> <span class="keyword">def</span> <span class="keyword">dependencies</span> = asNode().appendNode(<span class="string">'dependencies'</span>)</div><div class="line"> <span class="keyword">configurations</span>.<span class="keyword">compile</span>.allDependencies.<span class="keyword">each</span>{</div><div class="line"> <span class="comment">// 如果有compile fileTree(),group会为空,需要去除</span></div><div class="line"> <span class="keyword">if</span>(it.<span class="keyword">group</span> != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">def</span> dependency = <span class="keyword">dependencies</span>.appendNode(<span class="string">'dependency'</span>)</div><div class="line"> dependency.appendNode(<span class="string">'groupId'</span>, it.<span class="keyword">group</span>)</div><div class="line"> dependency.appendNode(<span class="string">'artifactId'</span>, it.name)</div><div class="line"> dependency.appendNode(<span class="string">'version'</span>, it.version)</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">artifactory {</div><div class="line"> contextUrl = MAVEN_LOCAL_PATH</div><div class="line"> publish {</div><div class="line"> repository {</div><div class="line"> <span class="comment">// The Artifactory repository key to publish to</span></div><div class="line"> repoKey = <span class="string">'gradle-release-local'</span></div><div class="line"></div><div class="line"> username = artifactory_username</div><div class="line"> password = artifactory_password</div><div class="line"> }</div><div class="line"> defaults {</div><div class="line"> <span class="comment">// Tell the Artifactory Plugin which artifacts should be published to Artifactory.</span></div><div class="line"> publications(<span class="string">'aar'</span>)</div><div class="line"> publishArtifacts = <span class="keyword">true</span></div><div class="line"></div><div class="line"> <span class="comment">// Properties to be attached to the published artifacts.</span></div><div class="line"> properties = [<span class="string">'qa.level'</span>: <span class="string">'basic'</span>, <span class="string">'dev.team'</span>: <span class="string">'core'</span>]</div><div class="line"> <span class="comment">// Publish generated POM files to Artifactory (true by default)</span></div><div class="line"> publishPom = <span class="keyword">true</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li><strong>说明</strong><br><code>artifactory_username</code>和<code>artifactory_password</code>定义在gradle.properties中,下同</li>
</ul>
<h3 id="修改api-lib下的build-gradle"><a href="#修改api-lib下的build-gradle" class="headerlink" title="修改api-lib下的build.gradle"></a>修改<code>api-lib</code>下的<code>build.gradle</code></h3><figure class="highlight gradle"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div></pre></td><td class="code"><pre><div class="line">apply plugin: <span class="string">'com.jfrog.artifactory'</span></div><div class="line">apply plugin: <span class="string">'maven-publish'</span></div><div class="line"></div><div class="line"><span class="keyword">def</span> MAVEN_LOCAL_PATH =<span class="string">'http://server_address:8081/artifactory'</span></div><div class="line"><span class="keyword">def</span> ARTIFACT_ID = <span class="string">'api'</span></div><div class="line"><span class="keyword">def</span> VERSION_NAME = <span class="string">'1.0.0'</span></div><div class="line"><span class="keyword">def</span> GROUP_ID = <span class="string">'com.router'</span></div><div class="line"></div><div class="line">publishing {</div><div class="line"> publications {</div><div class="line"> aar(MavenPublication) {</div><div class="line"> groupId GROUP_ID</div><div class="line"> version = VERSION_NAME</div><div class="line"> artifactId ARTIFACT_ID</div><div class="line"></div><div class="line"> <span class="comment">// Tell maven to prepare the generated "*.aar" file for publishing</span></div><div class="line"> artifact(<span class="string">"$buildDir/outputs/aar/${project.getName()}-release.aar"</span>)</div><div class="line"></div><div class="line"> pom.withXml {</div><div class="line"> <span class="keyword">def</span> <span class="keyword">dependencies</span> = asNode().appendNode(<span class="string">'dependencies'</span>)</div><div class="line"> <span class="keyword">configurations</span>.<span class="keyword">compile</span>.allDependencies.<span class="keyword">each</span>{</div><div class="line"> <span class="keyword">def</span> depGroup = it.<span class="keyword">group</span></div><div class="line"> <span class="keyword">def</span> depName = it.name</div><div class="line"> <span class="keyword">def</span> depVersion = it.version</div><div class="line"></div><div class="line"> <span class="comment">//如果当前库是annotation-lib,重写依赖</span></div><div class="line"> <span class="keyword">if</span>(it.name == <span class="string">"annotation-lib"</span>) {</div><div class="line"> <span class="keyword">for</span> (proj in <span class="keyword">project</span>.parent.<span class="keyword">subprojects</span>) {</div><div class="line"> <span class="keyword">if</span>(proj.name == <span class="string">"annotation-lib"</span>) {</div><div class="line"> <span class="comment">//这些信息是在annotation-lib的build.gradle中定义的</span></div><div class="line"> depGroup = proj.ext.<span class="keyword">group</span></div><div class="line"> depName = proj.ext.artifactId</div><div class="line"> depVersion = proj.ext.version</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(it.<span class="keyword">group</span> != <span class="keyword">null</span>) {</div><div class="line"> <span class="keyword">def</span> dependency = <span class="keyword">dependencies</span>.appendNode(<span class="string">'dependency'</span>)</div><div class="line"> dependency.appendNode(<span class="string">'groupId'</span>, depGroup)</div><div class="line"> dependency.appendNode(<span class="string">'artifactId'</span>, depName)</div><div class="line"> dependency.appendNode(<span class="string">'version'</span>, depVersion)</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line">artifactory {</div><div class="line"> contextUrl = MAVEN_LOCAL_PATH</div><div class="line"> publish {</div><div class="line"> repository {</div><div class="line"> <span class="comment">// The Artifactory repository key to publish to</span></div><div class="line"> repoKey = <span class="string">'gradle-release-local'</span></div><div class="line"></div><div class="line"> username = artifactory_username</div><div class="line"> password = artifactory_password</div><div class="line"> }</div><div class="line"> defaults {</div><div class="line"> <span class="comment">// Tell the Artifactory Plugin which artifacts should be published to Artifactory.</span></div><div class="line"> publications(<span class="string">'aar'</span>)</div><div class="line"> publishArtifacts = <span class="keyword">true</span></div><div class="line"></div><div class="line"> <span class="comment">// Properties to be attached to the published artifacts.</span></div><div class="line"> properties = [<span class="string">'qa.level'</span>: <span class="string">'basic'</span>, <span class="string">'dev.team'</span>: <span class="string">'core'</span>]</div><div class="line"> <span class="comment">// Publish generated POM files to Artifactory (true by default)</span></div><div class="line"> publishPom = <span class="keyword">true</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">dependencies</span> {</div><div class="line"> <span class="keyword">compile</span> <span class="keyword">fileTree</span>(<span class="keyword">include</span>: [<span class="string">'*.jar'</span>], dir: <span class="string">'libs'</span>)</div><div class="line"> <span class="keyword">compile</span> <span class="string">'com.android.support:appcompat-v7:22.2.1'</span></div><div class="line"> <span class="keyword">compile</span> <span class="keyword">project</span>(<span class="string">':annotation-lib'</span>)</div><div class="line">}</div></pre></td></tr></table></figure>
<ul>
<li>compiler-lib的<code>build.gradle</code>配置同上</li>
</ul>
<h3 id="实测"><a href="#实测" class="headerlink" title="实测"></a>实测</h3><p>配置好以后,怀着激动的心情敲下如下代码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">./gradlew clean assembleRelease</div><div class="line">./gradlew artifactoryPublish</div></pre></td></tr></table></figure></p>
<p>编译上传都成功!!!(≧▽≦)/</p>
<p>然后放到其他项目里一试…..一…..试……😱,什么鬼…还是那个错:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">android_router:annotation-lib:unspecified</div></pre></td></tr></table></figure></p>
<p>尝试了小半个下午都没解决(有时候想不通的时候,真该停下来,也许反而能增加不少效率!)<br>第二天突然开窍,会不会是缓存的问题(AndroidStudio的dataBinding抽风的时候clean再重启就好了),试了下,更改了一下上传的lib版本号,果然可以了!</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>做事的时候能静下心好好做了,但是思维漏洞有时候还是难以填补,需要更多的经验。</p>
<h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="http://www.jenv.be/" target="_blank" rel="external">jEnv官网</a><br><a href="http://boxingp.github.io/blog/2015/01/25/manage-multiple-versions-of-java-on-os-x/" target="_blank" rel="external">在OS X中使用jEnv管理多个Java版本</a><br><a href="https://github.com/hehonghui/android-tech-frontier/blob/master/issue-26/30%E5%88%86%E9%92%9F%E6%90%AD%E5%BB%BA%E4%B8%80%E4%B8%AAandroid%E7%9A%84%E7%A7%81%E6%9C%89Maven%E4%BB%93%E5%BA%93.md" target="_blank" rel="external">30分钟搭建一个android的私有Maven仓库</a><br><a href="https://github.com/JeroenMols/ArtifactoryExample/blob/master/AwesomeAdvancedLibrary/awesomeadvancedlibrary/build.gradle" target="_blank" rel="external">ArtifactoryExample</a><br><a href="http://stackoverflow.com/questions/32998716/maven-publish-cannot-not-apply-withxml-on-pom-file-no-signature-of-method" target="_blank" rel="external">Maven-publish cannot not apply withXML on pom file (No signature of method)</a><br><a href="https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project.properties" target="_blank" rel="external">Gradle Prject Properties</a></p>
<h1 id="PS"><a href="#PS" class="headerlink" title="PS"></a>PS</h1><p>今天妇女节,下午所有女生放假,啥时候也给男生放一个啊</p>
<h1 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h1><p>前段时间,公司想搞hybrid开发,老司机觉得web里的url跳转挺方便的,稍稍调研了下,就自己撸了一个AndroidRouter库。<br>差不多市面上该有的功能都有:</p>
<ul>
<li>根据URL跳转到Activity</li>
<li>通过Helper跳转到Activity</li>
<li>同一个Activity可以映射多个URL</li>
<li>跳转Activity的时候可以通过URL参数将变量的值注入到Activity中(支持基本类型和实现序列化接口的类)</li>
<li>通过H5打开Native页面</li>
<li>统一解决savedInstanceState的注入与读取</li>
</ul>
<p>用着也还挺舒服的,他就想推广到其他项目里,但是每个工程重新拷贝一份Library代码,对代码管理和版本管理什么的都不太方便,于是就有了今天这出:<br>搭建一个本地的maven仓库,用gradle的compile进行引用,再方便不过了。<br>在网上看的时候,都说jFrog Artifactory比较好用,就随大流啦。</p>
common-lang3.4 简摘
http://nstd.github.io/2017/03/03/common-lang3-package/
2017-03-03T09:45:35.000Z
2017-03-08T08:19:46.000Z
<h2 id="ArraysUtils"><a href="#ArraysUtils" class="headerlink" title="ArraysUtils:"></a>ArraysUtils:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//这个类的很多方法都有做数组为空的判断</span></div><div class="line"></div><div class="line"><span class="comment">//数组转字符串,便于打印日志</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">toString</span><span class="params">(Object array)</span></span>;</div><div class="line"><span class="comment">//数组转Map,Entry数组或者n行2列的数组才行</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Map<Object, Object> <span class="title">toMap</span><span class="params">(Object[] array)</span></span>;</div><div class="line"><span class="comment">//如果数组为null,返回一个空数组,否则返回本身</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> ..[] nullToEmpty(..[] array);</div><div class="line"><span class="comment">//取数组的一个前闭后开的区间</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> ..[] subarray(..[] array, <span class="keyword">int</span> startIndexInclusive, <span class="keyword">int</span> endIndexExclusive);</div><div class="line"><span class="comment">//数组是否等长,一个为null,一个为空也算等长</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isSameLength</span><span class="params">(..[] array1, ..[] array2)</span></span>;</div><div class="line"><span class="comment">//包装类型和原始类型互换</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span>[] toPrimitive(Integer[] array);</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span>[] toPrimitive(Integer[] array, <span class="keyword">int</span> valueForNull);</div><div class="line"><span class="comment">//原始类型和包装类型互换</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> Integer[] toObject(in[] array);</div><div class="line"><span class="comment">//返回array1和array2合并后的结果,并不改变原始数组</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> T[] addAll(T[] array1, T.. array2);</div></pre></td></tr></table></figure>
<ul>
<li>array强转成object,然后通过getClass().getComponentType()可以获取到数组的实际类型,可以由此做些统一的逻辑处理</li>
</ul>
<a id="more"></a>
<h2 id="StringUtils"><a href="#StringUtils" class="headerlink" title="StringUtils:"></a>StringUtils:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//这个类的很多方法都有做字符串为空的判断</span></div><div class="line"></div><div class="line"><span class="comment">//字符串是否为空</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isEmpty</span><span class="params">(CharSequence cs)</span></span>;</div><div class="line"><span class="comment">//字符串是否非空</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isNotEmpty</span><span class="params">(CharSequence cs)</span></span>;</div><div class="line"><span class="comment">//去前后空格,null安全</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">trim</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//如果是null或者空串都返回null,否则原样返回</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">trimToNull</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//如果是null或者空串都返回空串,否则原样返回</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">trimToEmpty</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//从str的开头和结尾中删除stripChars中出现的字符</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">strip</span><span class="params">(String str, String stripChars)</span></span>;</div><div class="line"><span class="comment">//字符串判等,cs1、cs2可为null</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(CharSequence cs1, CharSequence cs2)</span></span>;</div><div class="line"><span class="comment">//待查找目标在原始字符串中出现的位置,处理了null串的问题</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexOf</span><span class="params">(…)</span></span>;</div><div class="line"><span class="comment">//同上,反向查找</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">lastIndexOf</span><span class="params">(…)</span></span>;</div><div class="line"><span class="comment">//待查串在目标串中出现第ordinal次的位置</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">ordincalIndexOf</span><span class="params">(CharSequence str, CharSequence searchStr, <span class="keyword">int</span> ordinal)</span></span>;</div><div class="line"><span class="comment">//同上,反向查找</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">lastOrdinalIndexOf</span><span class="params">(…)</span></span>;</div><div class="line"><span class="comment">//子串截取</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substring</span><span class="params">(String str, <span class="keyword">int</span> star)</span></span>;</div><div class="line"><span class="comment">//截取左边</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">left</span><span class="params">(String str, <span class="keyword">int</span> len)</span></span>;</div><div class="line"><span class="comment">//截取右边</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">right</span><span class="params">(String str, <span class="keyword">int</span> len)</span></span>;</div><div class="line"><span class="comment">//从pos开始截取len长度</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">mid</span><span class="params">(String str, <span class="keyword">int</span> pos, <span class="keyword">int</span> len)</span></span>;</div><div class="line"><span class="comment">//截取第一次出现separator之前的字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substringBefore</span><span class="params">(String str, String separator)</span></span>;</div><div class="line"><span class="comment">//截取第一次出现separator之后的字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substringAfter</span><span class="params">(String str, String separator)</span></span>;</div><div class="line"><span class="comment">//截取最后一次出现separator之前的字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substringBeforeLast</span><span class="params">(String str, String separator)</span></span>;</div><div class="line"><span class="comment">//截取最后一次出现separator之后的字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substringAfterLast</span><span class="params">(String str, String separator)</span></span>;</div><div class="line"><span class="comment">//截取第一次和第二次出现tag之间的字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">substringBetween</span><span class="params">(String str, String tag)</span></span>;</div><div class="line"><span class="comment">//截取所有open和close串之间的字符串</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> String[] substringBetween(String str, String open, String close);</div><div class="line"><span class="comment">//分割字符串</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> String[] split(String str, String separatorChars);</div><div class="line"><span class="comment">//连接字符串</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <T> <span class="function">String <span class="title">join</span><span class="params">(T… elements)</span></span>;</div><div class="line"><span class="comment">//使用separator连接字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">join</span><span class="params">(Object[] array, <span class="keyword">char</span> separator)</span></span>;</div><div class="line"><span class="comment">//删除以特定字符串开头的字符串头,如果没有就不删除</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">removeStart</span><span class="params">(String str, String remove)</span></span>;</div><div class="line"><span class="comment">//删除以特定字符串结束的字符串接诶,如果没有就不删除</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">removeEnd</span><span class="params">(String str, String remove)</span></span>;</div><div class="line"><span class="comment">//如果短于size,则用padChar在左侧自动补齐,如果是null,还是返回null</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">leftPad</span><span class="params">(String str, <span class="keyword">int</span> size, <span class="keyword">char</span> padChar)</span></span>;</div><div class="line"><span class="comment">//同上,右侧自动补齐</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">rightPad</span><span class="params">(String str, <span class="keyword">int</span> size, <span class="keyword">char</span> padChar)</span></span>;</div><div class="line"><span class="comment">//短于size,则在两侧用空格自动补齐</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">center</span><span class="params">(String str, <span class="keyword">int</span> size)</span></span>;</div><div class="line"><span class="comment">//使首字母大写</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">capitalize</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//使字母反向大小写</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">swapCase</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//字符串翻转</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">reverse</span><span class="params">(String str)</span></span>;</div><div class="line"><span class="comment">//以separator进行翻转</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">reverseDelimited</span><span class="params">(String str, <span class="keyword">char</span> separatorChar)</span></span>;</div><div class="line"><span class="comment">//超过maxWidth时,maxWidth长度的最后三位用…表示,如果maxWidth小于4,会报错(null和空串原样返回)</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">abbreviate</span><span class="params">(String str, <span class="keyword">int</span> maxWidth)</span></span>;</div><div class="line"><span class="comment">//查找两个字符串的第一个不同之处的位置</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">indexOfDifference</span><span class="params">(CharSequence cs1, CharSequence cs2)</span></span>;</div><div class="line"><span class="comment">//获取两个串的相同前缀</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getCommonPrefix</span><span class="params">(String.. strs)</span></span>;</div><div class="line"><span class="comment">//求两个串的编辑距离</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">getLevenshteinDistance</span><span class="params">(CharSequence s, CharSequence t)</span></span>;</div><div class="line"><span class="comment">//用wrapWith字符将str包裹起来(null和空串原样返回)</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">wrap</span><span class="params">(String str, <span class="keyword">char</span> wrapWith)</span></span>;</div></pre></td></tr></table></figure>
<h2 id="RandomStringUtils"><a href="#RandomStringUtils" class="headerlink" title="RandomStringUtils:"></a>RandomStringUtils:</h2><pre><code>生成随机的字符串
</code></pre><h2 id="RandomUtils"><a href="#RandomUtils" class="headerlink" title="RandomUtils:"></a>RandomUtils:</h2><pre><code>生成某一范围内的随机数
</code></pre><h2 id="Pair"><a href="#Pair" class="headerlink" title="Pair:"></a>Pair:</h2><pre><code>二元组
</code></pre><h2 id="Triple"><a href="#Triple" class="headerlink" title="Triple:"></a>Triple:</h2><pre><code>三元组 //如果要return两个或者三个数据,可以用这两个类封一下,而不需要额外定义类
</code></pre><h2 id="DateUtils"><a href="#DateUtils" class="headerlink" title="DateUtils:"></a>DateUtils:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//增加年份,还有Months、Days等</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">addYears</span><span class="params">(Date date, <span class="keyword">int</span> amount)</span></span>;</div><div class="line"><span class="comment">//设置年份,还有Months、Days等</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">setYears</span><span class="params">(Date date, <span class="keyword">int</span> amount)</span></span>;</div><div class="line"><span class="comment">//Date转Calendar</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Calendar <span class="title">toCalendar</span><span class="params">(Date date)</span></span>;</div><div class="line"></div><div class="line"><span class="comment">//根据field截取时间,field可以是年(Calendar.YEAR)月日时分秒</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">truncate</span><span class="params">(<span class="keyword">final</span> Date date, <span class="keyword">final</span> <span class="keyword">int</span> field)</span></span>;</div><div class="line"><span class="comment">//根据field四舍五入时间</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">round</span><span class="params">(<span class="keyword">final</span> Date date, <span class="keyword">final</span> <span class="keyword">int</span> field)</span></span>;</div><div class="line"><span class="comment">//根据field上取整时间</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Date <span class="title">ceiling</span><span class="params">(<span class="keyword">final</span> Date date, <span class="keyword">final</span> <span class="keyword">int</span> field)</span></span>;</div></pre></td></tr></table></figure>
<h2 id="StopWatch"><a href="#StopWatch" class="headerlink" title="StopWatch:"></a>StopWatch:</h2><pre><code>用于计时的一个工具类
</code></pre><h2 id="ExceptionUtil"><a href="#ExceptionUtil" class="headerlink" title="ExceptionUtil:"></a>ExceptionUtil:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//从Throwable中获取错误堆栈字符串</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getStackTrace</span><span class="params">(Throwable throwable)</span></span>;</div><div class="line"><span class="comment">//获取错误堆栈数组</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> Throwable[] getThrowables(Throwable throwable);</div><div class="line"><span class="comment">//获取错误堆栈列表</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> List<Throwable> <span class="title">getThrowableList</span><span class="params">(Throwable throwable)</span></span>;</div><div class="line"><span class="comment">//获取根错误</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Throwable <span class="title">getRootCause</span><span class="params">(Throwable throwable)</span></span>;</div></pre></td></tr></table></figure>
<h2 id="ClassUtils"><a href="#ClassUtils" class="headerlink" title="ClassUtils:"></a>ClassUtils:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//根据实例获取短类名</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getShortClassName</span><span class="params">(Object object, String valueIfNull)</span></span>;</div><div class="line"><span class="comment">//根据类获取短类名</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getShortClassName</span><span class="params">(Class<?> cls)</span></span>;</div><div class="line"><span class="comment">//获取简单类名</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getSimpleName</span><span class="params">(Class<?> cls)</span></span>;</div><div class="line"><span class="comment">//获取包名</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getPackageName</span><span class="params">(Class<?> cls)</span></span>;</div><div class="line"><span class="comment">//获取所有的父类</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> List<Class<?>> getAllSuperclasses(Class<?> cls);</div><div class="line"><span class="comment">//获取所有的接口</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> List<Class<?>> getAllInterfaces(Class<?> cls);</div><div class="line"><span class="comment">//判断是否可以转型</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="title">isAssignable</span><span class="params">(Class<?>[] classArray, Class… toClassArray)</span></span>;</div></pre></td></tr></table></figure>
<h2 id="org-apache-commons-lang3-reflect下的一些包:"><a href="#org-apache-commons-lang3-reflect下的一些包:" class="headerlink" title="org.apache.commons.lang3.reflect下的一些包:"></a>org.apache.commons.lang3.reflect下的一些包:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//使用这些工具类,会使反射操作比较方便</span></div><div class="line">ConstructorUtils</div><div class="line">FiledUtils</div><div class="line">InheritanceUtils</div><div class="line">MemberUtils</div><div class="line">MethodUtils</div><div class="line">Typed</div><div class="line">TypeLiteral</div><div class="line">TypeUtils</div></pre></td></tr></table></figure>
<h2 id="ArraysUtils"><a href="#ArraysUtils" class="headerlink" title="ArraysUtils:"></a>ArraysUtils:</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//这个类的很多方法都有做数组为空的判断</span></div><div class="line"></div><div class="line"><span class="comment">//数组转字符串,便于打印日志</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">toString</span><span class="params">(Object array)</span></span>;</div><div class="line"><span class="comment">//数组转Map,Entry数组或者n行2列的数组才行</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Map<Object, Object> <span class="title">toMap</span><span class="params">(Object[] array)</span></span>;</div><div class="line"><span class="comment">//如果数组为null,返回一个空数组,否则返回本身</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> ..[] nullToEmpty(..[] array);</div><div class="line"><span class="comment">//取数组的一个前闭后开的区间</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> ..[] subarray(..[] array, <span class="keyword">int</span> startIndexInclusive, <span class="keyword">int</span> endIndexExclusive);</div><div class="line"><span class="comment">//数组是否等长,一个为null,一个为空也算等长</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isSameLength</span><span class="params">(..[] array1, ..[] array2)</span></span>;</div><div class="line"><span class="comment">//包装类型和原始类型互换</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span>[] toPrimitive(Integer[] array);</div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span>[] toPrimitive(Integer[] array, <span class="keyword">int</span> valueForNull);</div><div class="line"><span class="comment">//原始类型和包装类型互换</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> Integer[] toObject(in[] array);</div><div class="line"><span class="comment">//返回array1和array2合并后的结果,并不改变原始数组</span></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> T[] addAll(T[] array1, T.. array2);</div></pre></td></tr></table></figure>
<ul>
<li>array强转成object,然后通过getClass().getComponentType()可以获取到数组的实际类型,可以由此做些统一的逻辑处理</li>
</ul>
CodeFights Challenges Cipher_Zeroes
http://nstd.github.io/2016/06/04/CodeFights-Challenges-Cipher-Zeroes/
2016-06-04T10:17:51.000Z
2017-03-03T09:39:34.000Z
<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="- 写在前面"></a>- 写在前面</h2><p>最近在玩CodeFights(一个进行在线编程PK的网站),这个网站蛮有意思的。相比于ACM的题目,这里的可能简单一点,不过做了几场感觉学到不少(因为可以看别人代码啊!!!)。主要玩法有两种:</p>
<ol>
<li>CodeWriting:根据题意,完成一个实现相应功能的函数(代码长度越短【空格、回车、注释不记长度】、耗时越少排名越高)</li>
<li>Bugfix:给出一段有bug的代码,根据题意修复代码</li>
</ol>
<p>为了记录和总结,以后如果有看到好的解法、思路都会在博客中分享出来!</p>
<h2 id="题目-Cipher-Zeroes"><a href="#题目-Cipher-Zeroes" class="headerlink" title="- 题目 Cipher_Zeroes"></a>- 题目 <a href="https://codefights.com/challenge/x9TcPgiFRmgN22W44/main" target="_blank" rel="external">Cipher_Zeroes</a></h2><h3 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h3><p>假如有如下前提:</p>
<ul>
<li>0, 6, 9 这三个数中有一个可见的0</li>
<li>8 有两个可见的0</li>
<li>其他数字没有可见的0</li>
</ul>
<p>给定一串数字N,假设M是N中可见0的个数,如果M是大于0的偶数,返回M-1的二进制形式;如果M是奇数,返回M+1的二进制形式;否则返回0。</p>
<p>补全如下函数(java 版):<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String N)</span> </span>{</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></p>
<h3 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h3><blockquote>
<p>N = “565”<br>Cipher_Zeroes(N) = 10</p>
<p>解释:<br>565 中只有6有一个可见0,即:M<sub>1</sub> = 1<br>M1是奇数,所以结果要加1,M<sub>2</sub> = 2<br>最终结果为:2<sub>10</sub> = 10<sub>2</sub></p>
</blockquote>
<a id="more"></a>
<h2 id="分析"><a href="#分析" class="headerlink" title="- 分析"></a>- 分析</h2><h3 id="我的思路"><a href="#我的思路" class="headerlink" title="我的思路"></a>我的思路</h3><ol>
<li>根据前提统计可见0的个数</li>
<li>判奇偶</li>
<li>用系统函数Integer.parseInt(Integer.toBinaryString(s));返回结果。</li>
</ol>
<p>为了使代码尽量短,判断0、6、9的时候不能用 d == 0 || d == 6 || d == 9 的形式(16个字符),因为都是3的倍数,可以用模3的形式判断 d % 3 == 0 && d != 3 (12个字符)</p>
<p>代码如下(176个有效字符,为了方便,以后将省略中文,直接标数字):<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">int</span> s, d; <span class="comment">//0-7字符</span></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String N)</span> </span>{</div><div class="line"> <span class="keyword">for</span>(<span class="keyword">char</span> c : N.toCharArray()) { <span class="comment">//1-67字符</span></div><div class="line"> d = c - <span class="string">'0'</span>;</div><div class="line"> s += (d % <span class="number">3</span> == <span class="number">0</span> && d != <span class="number">3</span> ? <span class="number">1</span> : (d == <span class="number">8</span> ? <span class="number">2</span> : <span class="number">0</span>));</div><div class="line"> }</div><div class="line"> s += (s % <span class="number">2</span> == <span class="number">1</span> ? <span class="number">1</span> : (s > <span class="number">0</span> ? -<span class="number">1</span> : <span class="number">0</span>)); <span class="comment">//2-25字符</span></div><div class="line"> <span class="keyword">return</span> Integer.parseInt(Integer.toBinaryString(s)); <span class="comment">//3-50字符</span></div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>当时已经有上百人提交答案了,而从solutions board看到最短代码才124个字符,差了1/3啊!离Challenges结束还有近1个小时,但是又想不到更好的解法,有点抓狂。在漫长地等待之后,满怀着期待与憧憬终于可以看到别人的思路了,简直比洞房花烛夜等待揭开新娘的面纱还要焦急(/‵Д′)/~ ╧╧</p>
<p><strong>看了前3页的答案之后,有种脑洞被打开的感觉!!!</strong></p>
<h3 id="别人的思路"><a href="#别人的思路" class="headerlink" title="别人的思路"></a>别人的思路</h3><p>别人普遍使用的是<em>查表法</em>,不过有三种查表的方法,真他妈神奇!</p>
<ol>
<li><p>方法一(144)</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//from: sameerkhan2k</span></div><div class="line"><span class="keyword">int</span> c, r, d = <span class="number">1</span>; <span class="comment">//0-11</span></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String n)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i: n.getBytes()) <span class="comment">//1-54</span></div><div class="line"> c += <span class="string">"1000001021"</span>.charAt(i - <span class="number">48</span>) - <span class="number">48</span>;</div><div class="line"></div><div class="line"> <span class="keyword">for</span> ( c += c % <span class="number">2</span> < <span class="number">1</span>? -<span class="number">1</span>: <span class="number">1</span>; c > <span class="number">0</span>; c /= <span class="number">2</span>, d *= <span class="number">10</span>) <span class="comment">//2、3-44</span></div><div class="line"> r += d * (c & <span class="number">1</span>);</div><div class="line"></div><div class="line"> <span class="keyword">return</span> r;</div><div class="line">}</div></pre></td></tr></table></figure>
<p> 第一步,这是最直观的字符串查表法。</p>
<blockquote>
<p>0 ==> “1”<br> 1 ==> “0”<br> 2 ==> “0”<br> :<br> 6 ==> “1”<br> 7 ==> “0”<br> 8 ==> “2”<br> 9 ==> “1”</p>
</blockquote>
<p> 第二步和第三步合并了,没用系统函数而是手动计算的。</p>
</li>
<li><p>方法二(129)</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">//from: todayhumor</span></div><div class="line"><span class="keyword">int</span> h;</div><div class="line">Integer I; <span class="comment">//0-14</span></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String N)</span> </span>{</div><div class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> e:N.getBytes()) <span class="comment">//1-42</span></div><div class="line"> h += <span class="number">397313</span> >> e*<span class="number">2</span>-<span class="number">96</span> & <span class="number">3</span>;</div><div class="line"> <span class="keyword">return</span> I.decode(I.toString(h><span class="number">0</span>?h+h%<span class="number">2</span>*<span class="number">2</span> - <span class="number">1</span>:<span class="number">0</span>,<span class="number">2</span>)); <span class="comment">//2、3-46</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p> 对于 397313 这个神奇的网站,哦不,神奇的数字,大家可能一头雾水,我们先把Magic Number 397313 转成二进制以后看看:1100001000000000001。<br> 光看这个数其实还看不出什么,咱往后再瞧瞧。<br> e是每个字符的int值,从’0’到’9’对应48到57,随便代入两个值到 += 后面的表达式试试:</p>
<blockquote>
<ul>
<li><p>当 e = 48 (‘0’)时<br>e * 2 - 96 = 0<br>1 10 00 01 00 00 00 00 00 01<sub>2</sub> & 3<sub>10</sub> = 1</p>
</li>
<li><p>当 e = 49 (‘1’)时<br>e * 2 - 96 = 2<br>1 10 00 01 00 00 00 00 00 <span style="color:#E0E0E0;">01</span><sub>2</sub> & 3<sub>10</sub> = 0</p>
</li>
<li><p>当 e = 56 (‘8’)时<br>e * 2 - 96 = 16<br>1 10 <span style="color:#E0E0E0;">00 01 00 00 00 00 00 01</span><sub>2</sub> & 3<sub>10</sub> = 2</p>
</li>
<li><p>当 e = 57 (‘9’)时<br>e * 2 - 96 = 18<br>1 <span style="color:#E0E0E0;">10 00 01 00 00 00 00 00 01</span><sub>2</sub> & 3<sub>10</sub> = 1</p>
</li>
</ul>
</blockquote>
<p> 恍然大悟,这是神奇的二进制查表法!!<br> 1100001000000000001 从右往左每两位组成表中的一个值,通过右移操作,移到表中的指定位置,用3<sub>10</sub> => 11<sub>2</sub>这个mask取出对应位置的值。不过用二进制表的时候,有个字段长度的限制,最多只能用于64位的表(long)。</p>
<p> 第二步的奇数+1,偶数-1实现的也蛮有意思的:</p>
<blockquote>
<p>h + h % 2 * 2 - 1</p>
</blockquote>
<p> 才知道,Integer.decode()方法能将字符串型的数字转为数字。</p>
</li>
<li><p>方法三(124)</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"> <span class="comment">//from: zerael</span></div><div class="line"> <span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String N)</span> </span>{</div><div class="line"> Integer r = N.replaceAll(<span class="string">"(8)|[^690]"</span>, <span class="string">"$1$1"</span>).length()+<span class="number">1</span>^<span class="number">1</span>; <span class="comment">//0、1-56</span></div><div class="line"> <span class="keyword">return</span> r.valueOf(r.toString(r><span class="number">0</span> ? r-<span class="number">1</span> : <span class="number">0</span>, <span class="number">2</span>)); <span class="comment">//2、3-41</span></div><div class="line">}</div></pre></td></tr></table></figure>
<p> 这人竟然用正则替换,弄了一个转换表出来!WTF!<br> 这个正则替换的意思是:<br> 当前字符如果是8,将其替换为88(\$1是使用第一个捕获的分组进行替换,也就是小括号中的8);当前字符如果不是6、9、0(也就是1、2、3、4、5、7),将其替换为空(因为[^690]没有被包在分组中,所以\$1不起效);其他(6、9、0)维持不变。</p>
<blockquote>
<p>举两个例子:</p>
<p>“565”.replaceAll(“(8)|[^690]”, “\$1\$1”).length() + 1 ^ 1<br>==> “6”.length() + 1 ^ 1<br>==> 1 + 1 ^ 1<br>==> 3</p>
<p>“8200”.replaceAll(“(8)|[^690]”, “\$1\$1”).length() + 1 ^ 1<br>==> “8800”.length() + 1 ^ 1<br>==> 4 + 1 ^ 1<br>==> 4</p>
</blockquote>
<p> 第一行,其实是把第一步和第二步的一半合并了。<br> 第二步的奇数+1,偶数-1实现方式:</p>
<blockquote>
<p>(n + 1 ^ 1) - 1</p>
</blockquote>
<p> 才知道,Integer的toString()方法能指定转换的基数。</p>
</li>
</ol>
<h2 id="写在最后"><a href="#写在最后" class="headerlink" title="-写在最后"></a>-写在最后</h2><p>有趣!!!</p>
<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="- 写在前面"></a>- 写在前面</h2><p>最近在玩CodeFights(一个进行在线编程PK的网站),这个网站蛮有意思的。相比于ACM的题目,这里的可能简单一点,不过做了几场感觉学到不少(因为可以看别人代码啊!!!)。主要玩法有两种:</p>
<ol>
<li>CodeWriting:根据题意,完成一个实现相应功能的函数(代码长度越短【空格、回车、注释不记长度】、耗时越少排名越高)</li>
<li>Bugfix:给出一段有bug的代码,根据题意修复代码</li>
</ol>
<p>为了记录和总结,以后如果有看到好的解法、思路都会在博客中分享出来!</p>
<h2 id="题目-Cipher-Zeroes"><a href="#题目-Cipher-Zeroes" class="headerlink" title="- 题目 Cipher_Zeroes"></a>- 题目 <a href="https://codefights.com/challenge/x9TcPgiFRmgN22W44/main">Cipher_Zeroes</a></h2><h3 id="题意"><a href="#题意" class="headerlink" title="题意"></a>题意</h3><p>假如有如下前提:</p>
<ul>
<li>0, 6, 9 这三个数中有一个可见的0</li>
<li>8 有两个可见的0</li>
<li>其他数字没有可见的0</li>
</ul>
<p>给定一串数字N,假设M是N中可见0的个数,如果M是大于0的偶数,返回M-1的二进制形式;如果M是奇数,返回M+1的二进制形式;否则返回0。</p>
<p>补全如下函数(java 版):<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">Cipher_Zeroes</span><span class="params">(String N)</span> </span>{</div><div class="line"></div><div class="line">}</div></pre></td></tr></table></figure></p>
<h3 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h3><blockquote>
<p>N = “565”<br>Cipher_Zeroes(N) = 10</p>
<p>解释:<br>565 中只有6有一个可见0,即:M<sub>1</sub> = 1<br>M1是奇数,所以结果要加1,M<sub>2</sub> = 2<br>最终结果为:2<sub>10</sub> = 10<sub>2</sub></p>
</blockquote>
Android webview 之onPause暂停audio播放
http://nstd.github.io/2016/05/28/webview-pause-audio/
2016-05-28T02:44:31.000Z
2019-06-11T06:39:25.863Z
<h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>最近,公司里的老司机在帮测试测app的时候,发现一个bug:android端,用webview打开一个活动页,点击活动页中的音乐进行播放,退出webview之后音乐仍在继续播放。</p>
<p>这个bug之前在浏览技术文章的时候有见过,不过当时只是了解了大致的原因,在脑子里做了个问题的索引,并没有索引解决办法。</p>
<p>不过大致方向有了,就和老司机说“这个bug我来解”</p>
<p><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-2628941275.gif" alt="英雄气概.gif"><br><a id="more"></a></p>
<h2 id="Why"><a href="#Why" class="headerlink" title="Why"></a>Why</h2><p>网上说这是Android的一个bug!</p>
<h2 id="HOW"><a href="#HOW" class="headerlink" title="HOW"></a>HOW</h2><p>网上解决方法有三种</p>
<h3 id="1-在webview所在activity-onPause的时候,调用webView的onPause"><a href="#1-在webview所在activity-onPause的时候,调用webView的onPause" class="headerlink" title="1.在webview所在activity onPause的时候,调用webView的onPause"></a>1.在webview所在activity onPause的时候,调用webView的onPause</h3><p>不过SDK 11之前是不能直接调用webView的onPause,所以此处可以用版本号判断加反射来解决,如果觉得判断版本号麻烦,也可直接用反射。(测试无效/(ㄒoㄒ)/~~)</p>
<h3 id="2-使用pauseTimers-和resumeTimers"><a href="#2-使用pauseTimers-和resumeTimers" class="headerlink" title="2.使用pauseTimers()和resumeTimers()"></a>2.使用pauseTimers()和resumeTimers()</h3><p>在activity onPause的时候调用webView.pauseTimers(), onResume的时候调用webView.resumeTimers(),暂停/启动本app中所有webview的layout解析和js执行。(测试还是无效 (╯°Д°)╯︵ ┻━┻ )</p>
<h3 id="3-退出的时候载入一个空白的网页"><a href="#3-退出的时候载入一个空白的网页" class="headerlink" title="3.退出的时候载入一个空白的网页"></a>3.退出的时候载入一个空白的网页</h3><p>这样退出时有效的,但是按Home键(回到主屏)或者电源键(关闭主屏)还是无效。(ಥ_ಥ)</p>
<p>以下代码来自网络:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">callHiddenWebViewMethod</span><span class="params">(String name)</span></span></div><div class="line">{</div><div class="line"> <span class="keyword">if</span> (mWebView != <span class="keyword">null</span>)</div><div class="line"> {</div><div class="line"> <span class="keyword">try</span></div><div class="line"> {</div><div class="line"> Method method = WebView.class.getMethod(name);</div><div class="line"> method.invoke(mWebView);</div><div class="line"> }</div><div class="line"> <span class="keyword">catch</span> (NoSuchMethodException e)</div><div class="line"> {</div><div class="line"> Log.e(<span class="string">"No such method: "</span> + name, e.toString());</div><div class="line"> }</div><div class="line"> <span class="keyword">catch</span> (IllegalAccessException e)</div><div class="line"> {</div><div class="line"> Log.e(<span class="string">"Illegal Access: "</span> + name, e.toString());</div><div class="line"> }</div><div class="line"> <span class="keyword">catch</span> (InvocationTargetException e)</div><div class="line"> {</div><div class="line"> Log.e(<span class="string">"Invocation Target Exception: "</span> + name, e.toString());</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onPause</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onPause();</div><div class="line"></div><div class="line"> mWebView.pauseTimers();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (isFinishing())</div><div class="line"> {</div><div class="line"> mWebView.loadUrl(<span class="string">"about:blank"</span>);</div><div class="line"> setContentView(<span class="keyword">new</span> FrameLayout(<span class="keyword">this</span>));</div><div class="line"> }</div><div class="line"> callHiddenWebViewMethod(<span class="string">"onPause"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onResume();</div><div class="line"> mWebView.resumeTimers();</div><div class="line"> callHiddenWebViewMethod(<span class="string">"onResume"</span>);</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>本着 <em><strong>山不过来,我就过去</strong></em> 的思想,想了一会儿(当然不是一直在想这句话-。-),发现js可以暂停audio的播放,而webview又可以执行js代码,豁然开朗啊!<br>在onPause的时候通过jsInterface来判断网页中是否有audio?是否在播放?在播放就暂停,onResume的时候再恢复。</p>
<p>以下是核心代码:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div><div class="line">70</div><div class="line">71</div><div class="line">72</div><div class="line">73</div><div class="line">74</div><div class="line">75</div><div class="line">76</div><div class="line">77</div><div class="line">78</div><div class="line">79</div><div class="line">80</div><div class="line">81</div><div class="line">82</div><div class="line">83</div><div class="line">84</div><div class="line">85</div><div class="line">86</div><div class="line">87</div><div class="line">88</div><div class="line">89</div><div class="line">90</div><div class="line">91</div><div class="line">92</div><div class="line">93</div><div class="line">94</div><div class="line">95</div><div class="line">96</div><div class="line">97</div><div class="line">98</div><div class="line">99</div><div class="line">100</div><div class="line">101</div><div class="line">102</div><div class="line">103</div><div class="line">104</div><div class="line">105</div><div class="line">106</div><div class="line">107</div><div class="line">108</div><div class="line">109</div><div class="line">110</div><div class="line">111</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onCreate(savedInstanceState);</div><div class="line"> setContentView(R.layout.web_activity);</div><div class="line"></div><div class="line"> <span class="comment">//...</span></div><div class="line"></div><div class="line"> mWebView.getSettings().setJavaScriptEnabled(<span class="keyword">true</span>);</div><div class="line"> mWebView.setWebViewClient(<span class="keyword">new</span> WebViewClient() {</div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPageStarted</span><span class="params">(WebView view, String url, Bitmap favicon)</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onPageStarted(view, url, favicon);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onPageFinished</span><span class="params">(WebView view, String url)</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onPageFinished(view, url);</div><div class="line"></div><div class="line"> mWebView.loadUrl(</div><div class="line"> <span class="string">"javascript:var radioTags = document.getElementsByTagName('audio');"</span> + </div><div class="line"> <span class="string">" var isHtmlAudioPaused = false;"</span> +</div><div class="line"> <span class="string">"if(radioTags.length > 0) {"</span> + </div><div class="line"> <span class="string">"try{window.appJs.log('has audio'); window.appJs.hasAudio();}catch(err){}"</span> + </div><div class="line"> <span class="string">"}"</span></div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@Override</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onReceivedError</span><span class="params">(WebView view, <span class="keyword">int</span> errorCode,</span></span></div><div class="line"> String description, String failingUrl) {</div><div class="line"> <span class="keyword">super</span>.onReceivedError(view, errorCode, description, failingUrl);</div><div class="line"> Toast.makeText(WebActivity.<span class="keyword">this</span>, description,</div><div class="line"> Toast.LENGTH_SHORT).show();</div><div class="line"> }</div><div class="line"></div><div class="line"> });</div><div class="line"></div><div class="line"> mWebView.addJavascriptInterface(<span class="keyword">new</span> HookInterface(<span class="keyword">this</span>), <span class="string">"appJs"</span>);</div><div class="line"></div><div class="line"> mWebView.loadUrl(mLoadUrl);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onPause</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onPause();</div><div class="line"></div><div class="line"> <span class="keyword">if</span>(isHasAudio) {</div><div class="line"> <span class="comment">//这里只对页面中只有一个音频的情况做了处理,如果有多个音频需要遍历整个数组记录状态</span></div><div class="line"> mWebView.loadUrl(</div><div class="line"> <span class="string">"javascript:audioEty = document.getElementsByTagName('audio')[0]; "</span> +</div><div class="line"> <span class="string">"isHtmlAudioPaused = audioEty.paused;"</span> +</div><div class="line"> <span class="string">"if(!audioEty.paused) {"</span> +</div><div class="line"> <span class="string">"try{window.appJs.log('to pause audio'); audioEty.pause();}catch(err){}"</span> +</div><div class="line"> <span class="string">"}"</span></div><div class="line"> );</div><div class="line"> }</div><div class="line"></div><div class="line"> mWebView.pauseTimers();</div><div class="line"></div><div class="line"> <span class="keyword">if</span> (isFinishing())</div><div class="line"> {</div><div class="line"> mWebView.loadUrl(<span class="string">"about:blank"</span>);</div><div class="line"> setContentView(<span class="keyword">new</span> FrameLayout(<span class="keyword">this</span>));</div><div class="line"> }</div><div class="line"> callHiddenWebViewMethod(<span class="string">"onPause"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onResume();</div><div class="line"> mWebView.resumeTimers();</div><div class="line"> <span class="keyword">if</span>(isHasAudio) {</div><div class="line"> mWebView.loadUrl(</div><div class="line"> <span class="string">"javascript:audioEty = document.getElementsByTagName('audio')[0]; "</span> +</div><div class="line"> <span class="string">"if(!isHtmlAudioPaused && audioEty.paused) {"</span> +</div><div class="line"> <span class="string">"try{window.appJs.log('to resume audio'); audioEty.play();}catch(err){}"</span> +</div><div class="line"> <span class="string">"} else {"</span> + </div><div class="line"> <span class="string">"window.appJs.log('audio not paused');"</span> + </div><div class="line"> <span class="string">"}"</span></div><div class="line"> );</div><div class="line"> }</div><div class="line"> callHiddenWebViewMethod(<span class="string">"onResume"</span>);</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HookInterface</span> </span>{</div><div class="line"></div><div class="line"> <span class="keyword">private</span> Context mContext;</div><div class="line"></div><div class="line"> <span class="comment">/** Instantiate the interface and set the context */</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="title">HookInterface</span><span class="params">(Context c)</span> </span>{</div><div class="line"> mContext = c;</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@JavascriptInterface</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">hasAudio</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">//现在只在加载完html之后做了一次是否有音频的检测</span></div><div class="line"> <span class="comment">//但如果有动态生成的audio Tag,最好不依赖isHasAudio变量</span></div><div class="line"> <span class="comment">//而是每次进入onPause和onResume的时候都重新检测下</span></div><div class="line"> isHasAudio = <span class="keyword">true</span>;</div><div class="line"> Log.d(<span class="string">"TAG"</span>, <span class="string">"this webview has audio"</span>);</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="meta">@JavascriptInterface</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">log</span><span class="params">(String msg)</span> </span>{</div><div class="line"> <span class="keyword">if</span>(msg != <span class="keyword">null</span>) {</div><div class="line"> Log.i(<span class="string">"TAG"</span>, <span class="string">"from webview log: "</span> + msg);</div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h2 id="PS"><a href="#PS" class="headerlink" title="PS"></a>PS</h2><p>以上只处理了单个audio播放的情况,如果有多个audio,需要遍历所有audio进行判断以及注意状态的缓存</p>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="http://www.eoeandroid.com/thread-91052-1-1.html" target="_blank" rel="external">WebView 完全退出flash的方法,停止声音的播放</a><br><a href="http://stackoverflow.com/questions/5946698/how-to-stop-youtube-video-playing-in-android-webview" target="_blank" rel="external">How to stop youtube video playing in Android webview?</a><br><a href="https://code.google.com/p/android/issues/detail?id=10282" target="_blank" rel="external">Issue 10282: Public API for WebView.onPause and WebView.onResume</a></p>
<h2 id="What"><a href="#What" class="headerlink" title="What"></a>What</h2><p>最近,公司里的老司机在帮测试测app的时候,发现一个bug:android端,用webview打开一个活动页,点击活动页中的音乐进行播放,退出webview之后音乐仍在继续播放。</p>
<p>这个bug之前在浏览技术文章的时候有见过,不过当时只是了解了大致的原因,在脑子里做了个问题的索引,并没有索引解决办法。</p>
<p>不过大致方向有了,就和老司机说“这个bug我来解”</p>
<p><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-2628941275.gif" alt="英雄气概.gif"><br>
Git+Jenkins之自动构建
http://nstd.github.io/2016/03/23/integrate-git-and-jenkins-auto-build/
2016-03-23T04:01:23.000Z
2019-06-11T06:38:38.815Z
<h2 id="一、起因"><a href="#一、起因" class="headerlink" title="一、起因"></a>一、起因</h2><p>公司的服务端维护一份错误码文件:errorCode.json,同时客户端也需要根据这样一份错误码对用户进行提示,错误码Sample如下:<br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"200"</span>: {</div><div class="line"> <span class="attr">"0"</span>: {<span class="attr">"msg"</span>: <span class="string">"成功"</span>}</div><div class="line"> },</div><div class="line"> <span class="attr">"300"</span>: {</div><div class="line"> <span class="attr">"100"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误1"</span>},</div><div class="line"> <span class="attr">"101"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误2"</span>}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>平时都是服务器的同学在群里说 <strong>“加了xxx协议,更新了错误码 -。-”</strong> 客户端的同学才会去errorCode.json所在的项目(项目计划)更新项目,然后复制出来放到自己的应用项目中。</p>
<p>这么操作,一来可能会让错误码更新不及时;二来这份文件返回的msg对用户并不友好,有时候需要我们手动设置对应的错误提示,代码中设置并不是一个好的选择,所以我们打算改造这份错误码,添加一些自定义的消息,如果存在自定义消息则使用自定义消息(这些不在本篇文章的讨论范围),改造后的json如下:<br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"200"</span>: {</div><div class="line"> <span class="attr">"0"</span>: {<span class="attr">"msg"</span>: <span class="string">"成功"</span>}</div><div class="line"> },</div><div class="line"> <span class="attr">"300"</span>: {</div><div class="line"> <span class="attr">"100"</span>: {</div><div class="line"> <span class="attr">"msg"</span>: <span class="string">"错误1"</span>,</div><div class="line"> <span class="attr">"cmsg"</span>: <span class="string">"这是给用户看的消息"</span></div><div class="line"> },</div><div class="line"> <span class="attr">"101"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误2"</span>}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>然后就出现了这么一个需求:</p>
<blockquote>
<p><strong>自动更新我们项目中的errorCode.json文件,同时要把我们的自定义消息合并进去。</strong></p>
</blockquote>
<p>后来想想正好用git + jenkins hooks的方式:当有更新push到GitLab(我们内部使用的git管理平台)的时候,调用jenkins的project hook去自动执行相应的job,这个job是个shell脚本,有三步操作:</p>
<ol>
<li>update git project</li>
<li>merge errorCode.json(此处是使用java做的,怎么方便怎么弄)</li>
<li>push to git project<a id="more"></a>
</li>
</ol>
<h2 id="二、经过"><a href="#二、经过" class="headerlink" title="二、经过"></a>二、经过</h2><h3 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h3><p><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgit_jenkins_plugin.png" alt="安装git插件"><br>进入【插件管理】界面安装以下插件:</p>
<blockquote>
<p><a href="https://wiki.jenkins-ci.org/display/JENKINS/Git+Client+Plugin" target="_blank" rel="external">Git client plugin</a><br><a href="https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin" target="_blank" rel="external">Git plugin</a><br><a href="https://wiki.jenkins-ci.org/display/JENKINS/GitLab+Plugin" target="_blank" rel="external">GitLab Plugin</a><br><a href="https://wiki.jenkins-ci.org/display/JENKINS/AnsiColor+Plugin" target="_blank" rel="external">AnsiColor</a>(可选)这个插件可以让Jenkins的控制台输出的log带有颜色(就和linux控制台那样)</p>
</blockquote>
<h3 id="配置本地Git环境和Jenkins"><a href="#配置本地Git环境和Jenkins" class="headerlink" title="配置本地Git环境和Jenkins"></a>配置本地Git环境和Jenkins</h3><h4 id="配置Git"><a href="#配置Git" class="headerlink" title="配置Git"></a>配置Git</h4><ol>
<li><p>先在Jenkins所在机器 <a href="https://git-scm.com/download/" target="_blank" rel="external">安装Git</a><br>安装好以后,添加git用户:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">git config --global user.name <span class="string">"jenkins"</span></div><div class="line">git config --global user.email <span class="string">"jenkins@your-company.com"</span></div></pre></td></tr></table></figure>
<p>PS:<strong>GitLab中需要有一个对应的jenkins账号,用来拉取代码</strong></p>
</li>
<li><p>生成本地账号的ssh秘钥<br>在命令行下(window下,使用Git Bash)进入主目录,执行以下命令:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">ssh-keygen -t rsa -C <span class="string">"jenkins@your-company.com"</span></div><div class="line">cat ~/.ssh/id_rsa.pub</div></pre></td></tr></table></figure>
<p>然后复制出公钥,添加到GitLab对应账号(jenkins)的SSH Keys中,这个公钥是为了让jenkins在拉取代码的时候有相应权限</p>
</li>
<li><p>生成jenkins的ssh秘钥<br>在命令行下进入 <strong>jenkins的安装目录\.ssh</strong>,执行以下命令:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">ssh-keygen -t rsa</div><div class="line">cat id_rsa.pub</div></pre></td></tr></table></figure>
<p>然后复制出公钥,添加到GitLab对应账号(jenkins)的SSH keys中,这个公钥是为了让jenkins执行shell脚本时,如果shell脚本中有git命令,使其有拉取、提交代码的权限</p>
</li>
</ol>
<h4 id="配置Jenkins"><a href="#配置Jenkins" class="headerlink" title="配置Jenkins"></a>配置Jenkins</h4><ol>
<li><p>在Jenkins中配置Git<br>进入【<strong>系统管理</strong>】->【<strong>系统设置</strong>】</p>
<ul>
<li><p>设置使用的Git命令行<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgit_config_1.png" alt="设置Git"></p>
</li>
<li><p>配置Git账号<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgit_config_2.png" alt="配置Git账号"></p>
</li>
<li><p>配置Gitlab信息<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgit_config_3.png" alt="配置Gitlab"></p>
</li>
<li><p>API Token获取方式<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgitlab_api_token.png" alt="API Token获取方式"></p>
</li>
</ul>
</li>
<li><p>在Jenkins中配置Git账号<br><span id="config-git-credentials">进入</span>【<strong>Credentials</strong>】->【<strong>Global credentials(unrestricted</strong>】->【<strong>Add Credentials</strong>】<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgit_credentials.png" alt="配置Git账号"></p>
</li>
</ol>
<h3 id="添加Job"><a href="#添加Job" class="headerlink" title="添加Job"></a>添加Job</h3><p>新建一个【<strong>构建一个自由风格的软件项目</strong>】的Job</p>
<ul>
<li><p>配置这个Job所用的Git项目信息<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fjenkins_job_git.png" alt="Job Git Info"><br>这里的<strong>Credentials</strong>就是在<a href="#config-git-credentials">配置Git账号</a>中设置的账号;<font color="red">如果不配置Git项目信息,Git Push之后这个Job不会自动执行</font></p>
</li>
<li><p>配置触发器<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fjenkins_job_trigger.png" alt="Job Trigger"><br>这里的圈出来的URL,需要配置到Gitlab对应项目的Web Hooks中</p>
</li>
<li><p>配置errorCode所在项目的Web Hooks<br>【<strong>Gitlab</strong>】->【<strong>选择项目</strong>】->【<strong>Settings</strong>】->【<strong>Web Hooks</strong>】<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fgitlab_web_hooks.png" alt="Gitlab Web Hooks"><br>设置只有在收到push消息时才自动执行jenkins的job</p>
</li>
<li><p>配置构建环境(可选)<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fjenkins_job_xterm_env.png" alt="Job Xterm Evn"><br>shell脚本中如果会输出带颜色的文字,可以设置这个,使Jenkins的Web Console也输出相同的颜色</p>
</li>
<li><p>配置执行的脚本并保存<br><img src="http://nstd-blog.oss-cn-hangzhou.aliyuncs.com/blog-post%2Fjenkins_job_execute_shell.png" alt="Job Execute Shell"></p>
</li>
</ul>
<h3 id="update-sh脚本"><a href="#update-sh脚本" class="headerlink" title="update.sh脚本"></a>update.sh脚本</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div><div class="line">57</div><div class="line">58</div><div class="line">59</div><div class="line">60</div><div class="line">61</div><div class="line">62</div><div class="line">63</div><div class="line">64</div><div class="line">65</div><div class="line">66</div><div class="line">67</div><div class="line">68</div><div class="line">69</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#!/bin/bash</span></div><div class="line"></div><div class="line"><span class="comment">###################################################################</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment"># 目录结构:</span></div><div class="line"><span class="comment"># .../ErrorCode</span></div><div class="line"><span class="comment"># tools/</span></div><div class="line"><span class="comment"># ECode.jar //用java写的转换json工具</span></div><div class="line"><span class="comment"># color.sh</span></div><div class="line"><span class="comment"># config.properties //ECode.jar用的配置文件,配置json文件路径什么的</span></div><div class="line"><span class="comment"># update.sh</span></div><div class="line"><span class="comment"># plan_project/</span></div><div class="line"><span class="comment"># ....../clientErrorCode.json</span></div><div class="line"><span class="comment"># android_application_project/</span></div><div class="line"><span class="comment"># ....../clientErrorCode.json</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment"># 注意:</span></div><div class="line"><span class="comment"># 需要把 ErrorCode/下的所有文件的所有者设置为jenkins,</span></div><div class="line"><span class="comment"># 否则jenkins执行这个脚本的时候,会因为缺乏读写权限导致更新、提交git失败</span></div><div class="line"><span class="comment">#</span></div><div class="line"><span class="comment">###################################################################</span></div><div class="line"></div><div class="line"><span class="built_in">source</span> /Users/compile/ErrorCode/tools/color.sh</div><div class="line"></div><div class="line"><span class="built_in">cd</span> /Users/compile/ErrorCode/</div><div class="line"></div><div class="line"><span class="built_in">cd</span> tools</div><div class="line"></div><div class="line"><span class="built_in">echo</span>Blue <span class="string">"update plan_project.."</span></div><div class="line"><span class="built_in">cd</span> ../plan_project</div><div class="line">git pull origin master</div><div class="line"></div><div class="line"><span class="keyword">if</span> [ $? <span class="_">-ne</span> 0 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span>Error <span class="string">"update plan_project(clientErrorCode.json) from git failed"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="built_in">echo</span>Blue <span class="string">"update android_application_project.."</span></div><div class="line"><span class="built_in">cd</span> ../android_application_project</div><div class="line">git checkout develop</div><div class="line">git pull origin develop</div><div class="line"></div><div class="line"><span class="keyword">if</span> [ $? <span class="_">-ne</span> 0 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span>Error <span class="string">"update android_application_project from git failed"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div><div class="line"></div><div class="line"><span class="built_in">cd</span> ../tools</div><div class="line">java -jar ECode.jar</div><div class="line"></div><div class="line"><span class="keyword">if</span> [ $? <span class="_">-eq</span> 0 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">cd</span> ../android_application_project</div><div class="line"> git commit -am <span class="string">"update clientErrorCode.json by jenkins auto script"</span></div><div class="line"> <span class="keyword">if</span> [ $? <span class="_">-ne</span> 0 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span>Error <span class="string">"commit android_application_project failed"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"> <span class="keyword">fi</span></div><div class="line"> git push origin develop</div><div class="line"> <span class="keyword">if</span> [ $? <span class="_">-ne</span> 0 ]; <span class="keyword">then</span></div><div class="line"> <span class="built_in">echo</span>Error <span class="string">"push android_application_project failed"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"> <span class="keyword">fi</span></div><div class="line"></div><div class="line"> <span class="built_in">exit</span> 0</div><div class="line"><span class="keyword">else</span></div><div class="line"> <span class="built_in">echo</span>Error <span class="string">"merge clientErrorCod.json failed"</span></div><div class="line"> <span class="built_in">exit</span> 1</div><div class="line"><span class="keyword">fi</span></div></pre></td></tr></table></figure>
<p><a href="https://github.com/Nstd/Shell/blob/master/color.sh" target="_blank" rel="external">[color.sh] on Github</a></p>
<h3 id="ECode-jar"><a href="#ECode-jar" class="headerlink" title="ECode.jar"></a>ECode.jar</h3><p><a href="https://github.com/Nstd/ErrorCodeTransformer" target="_blank" rel="external">[ECode.jar] on Github</a></p>
<h2 id="三、结果"><a href="#三、结果" class="headerlink" title="三、结果"></a>三、结果</h2><p>一劳永逸,快哉。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="http://jingyan.baidu.com/article/ff42efa904b4d7c19e220282.html" target="_blank" rel="external">百度经验:打包可执行jar</a><br><a href="http://jingyan.baidu.com/article/219f4bf7d0ef87de442d3820.html" target="_blank" rel="external">百度经验:Eclipse jar打包</a><br><a href="http://www.cnblogs.com/adolfmc/archive/2012/10/07/2713562.html" target="_blank" rel="external">CNBlog:可执行jar引用其他jar</a></p>
<h2 id="一、起因"><a href="#一、起因" class="headerlink" title="一、起因"></a>一、起因</h2><p>公司的服务端维护一份错误码文件:errorCode.json,同时客户端也需要根据这样一份错误码对用户进行提示,错误码Sample如下:<br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"200"</span>: {</div><div class="line"> <span class="attr">"0"</span>: {<span class="attr">"msg"</span>: <span class="string">"成功"</span>}</div><div class="line"> },</div><div class="line"> <span class="attr">"300"</span>: {</div><div class="line"> <span class="attr">"100"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误1"</span>},</div><div class="line"> <span class="attr">"101"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误2"</span>}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>平时都是服务器的同学在群里说 <strong>“加了xxx协议,更新了错误码 -。-”</strong> 客户端的同学才会去errorCode.json所在的项目(项目计划)更新项目,然后复制出来放到自己的应用项目中。</p>
<p>这么操作,一来可能会让错误码更新不及时;二来这份文件返回的msg对用户并不友好,有时候需要我们手动设置对应的错误提示,代码中设置并不是一个好的选择,所以我们打算改造这份错误码,添加一些自定义的消息,如果存在自定义消息则使用自定义消息(这些不在本篇文章的讨论范围),改造后的json如下:<br><figure class="highlight json"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line">{</div><div class="line"> <span class="attr">"200"</span>: {</div><div class="line"> <span class="attr">"0"</span>: {<span class="attr">"msg"</span>: <span class="string">"成功"</span>}</div><div class="line"> },</div><div class="line"> <span class="attr">"300"</span>: {</div><div class="line"> <span class="attr">"100"</span>: {</div><div class="line"> <span class="attr">"msg"</span>: <span class="string">"错误1"</span>,</div><div class="line"> <span class="attr">"cmsg"</span>: <span class="string">"这是给用户看的消息"</span></div><div class="line"> },</div><div class="line"> <span class="attr">"101"</span>: {<span class="attr">"msg"</span>: <span class="string">"错误2"</span>}</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>然后就出现了这么一个需求:</p>
<blockquote>
<p><strong>自动更新我们项目中的errorCode.json文件,同时要把我们的自定义消息合并进去。</strong></p>
</blockquote>
<p>后来想想正好用git + jenkins hooks的方式:当有更新push到GitLab(我们内部使用的git管理平台)的时候,调用jenkins的project hook去自动执行相应的job,这个job是个shell脚本,有三步操作:</p>
<ol>
<li>update git project</li>
<li>merge errorCode.json(此处是使用java做的,怎么方便怎么弄)</li>
<li>push to git project
Stetho
http://nstd.github.io/2016/03/15/Stetho/
2016-03-15T13:27:57.000Z
2017-03-03T09:22:42.000Z
<h2 id="什么是Stetho"><a href="#什么是Stetho" class="headerlink" title="什么是Stetho"></a>什么是Stetho</h2><h3 id="Stetho是Facebook出的一款Android调试工具"><a href="#Stetho是Facebook出的一款Android调试工具" class="headerlink" title="Stetho是Facebook出的一款Android调试工具"></a>Stetho是Facebook出的一款Android调试工具</h3><p>我们可以用它在Chrome DevTools下查看应用的布局、网络请求、Sqlite、SharedPreference并能通过js来调试代码(使用js进行调试的时候FB只做了初步封装,使用起来会比较麻烦,需要自己进一步封装)</p>
<h3 id="来点截图(取自官网)"><a href="#来点截图(取自官网)" class="headerlink" title="来点截图(取自官网)"></a>来点截图(取自<a href="http://facebook.github.io/stetho/" target="_blank" rel="external">官网</a>)</h3><h4 id="Chrome-DevTools"><a href="#Chrome-DevTools" class="headerlink" title="Chrome DevTools"></a>Chrome DevTools</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-discovery.png" alt="Chrome DevTools"></p>
<h4 id="查看网络请求"><a href="#查看网络请求" class="headerlink" title="查看网络请求"></a>查看网络请求</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-network.png" alt="Network Inspection"></p>
<h4 id="数据库(可读写)"><a href="#数据库(可读写)" class="headerlink" title="数据库(可读写)"></a>数据库(可读写)</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-sqlite.png" alt="Database Inspection"></p>
<h4 id="查看布局(双向查看)"><a href="#查看布局(双向查看)" class="headerlink" title="查看布局(双向查看)"></a>查看布局(双向查看)</h4><ol>
<li>用鼠标放在Element面板中的xml节点上,会在应用中高亮对应的布局</li>
<li>点击Element左边的放大镜,然后点击应用中的布局,会自动跳转到Element面板中的xml节点<br><img src="http://facebook.github.io/stetho/static/images/inspector-elements.png" alt="View Hierarchy"></li>
</ol>
<h4 id="Javascript-Console"><a href="#Javascript-Console" class="headerlink" title="Javascript Console"></a>Javascript Console</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-js.png" alt="Javascript Console"><br><a id="more"></a></p>
<h2 id="在代码中配置stetho"><a href="#在代码中配置stetho" class="headerlink" title="在代码中配置stetho"></a>在代码中配置stetho</h2><h3 id="build-gradle"><a href="#build-gradle" class="headerlink" title="build.gradle"></a>build.gradle</h3><figure class="highlight gradle"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line">android {</div><div class="line"> <span class="comment">// 如果编译的时候出现OutOfMemory,添加以下配置</span></div><div class="line"> dexOptions {</div><div class="line"> incremental <span class="keyword">true</span></div><div class="line"> javaMaxHeapSize <span class="string">"4g"</span></div><div class="line"> }</div><div class="line"></div><div class="line"> buildTypes {</div><div class="line"> debug {</div><div class="line"> <span class="comment">// 如果因为加了stetho导致DexIndexOverflowException,需要添加以下配置</span></div><div class="line"> multiDexEnabled <span class="keyword">true</span></div><div class="line"> }</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="keyword">dependencies</span> {</div><div class="line"> <span class="comment">// 只在debug版中加入stetho</span></div><div class="line"> debugCompile <span class="string">'com.facebook.stetho:stetho:1.3.1'</span></div><div class="line"> debugCompile <span class="string">'com.facebook.stetho:stetho-js-rhino:1.3.1'</span> </div><div class="line"> <span class="comment">//debugCompile 'com.facebook.stetho:stetho-urlconnection:1.3.1'</span></div><div class="line"> </div><div class="line"> <span class="comment">// 如果因为加了stetho导致DexIndexOverflowException,需要添加以下配置</span></div><div class="line"> debugCompile <span class="string">'com.android.support:multidex:1.0.1'</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="YourApplication-java"><a href="#YourApplication-java" class="headerlink" title="YourApplication.java"></a>YourApplication.java</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 当前界面显示的Activity,供Console调用</span></div><div class="line"><span class="keyword">private</span> Activity mAct = <span class="keyword">null</span>;</div><div class="line"></div><div class="line"><span class="comment">// 方便从Console中通过变量名获取mAct对象</span></div><div class="line"><span class="function"><span class="keyword">public</span> Activity <span class="title">getmAct</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> mAct;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 从java代码中获取、设置mAct</span></div><div class="line"><span class="function"><span class="keyword">public</span> Activity <span class="title">getCurrentActivity</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> mAct;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setCurrentActivity</span><span class="params">(Activity act)</span> </span>{</div><div class="line"> <span class="keyword">this</span>.mAct = act;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">super</span>.onCreate();</div><div class="line"> DebugUtils.init(context);</div><div class="line"> </div><div class="line"> <span class="comment">//...</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="BaseActivity-java"><a href="#BaseActivity-java" class="headerlink" title="BaseActivity.java"></a>BaseActivity.java</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div></pre></td><td class="code"><pre><div class="line"><span class="comment">/*******************************************************</span></div><div class="line"> * 使用的Activity尽量继承自BaseActivity,以减少配置代码</div><div class="line"> *******************************************************/</div><div class="line"></div><div class="line"><span class="comment">// 可以在Console中通过context.mAct.getField()的方式获取私有变量的值</span></div><div class="line"><span class="comment">// 把获取的逻辑封装到DebugUtils中,以解除不必要的麻烦</span></div><div class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">getField</span><span class="params">(String name)</span> </span>{</div><div class="line"> <span class="keyword">return</span> DebugUtils.getActField(<span class="keyword">this</span>, name);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// 取消引用</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">clearReferences</span><span class="params">()</span> </span>{</div><div class="line"> YourApplication app = ((YourApplication)getApplicationContext());</div><div class="line"> Activity currActivity = app.getCurrentActivity();</div><div class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.equals(currActivity)) {</div><div class="line"> app.setCurrentActivity(<span class="keyword">null</span>);</div><div class="line"> }</div><div class="line">}</div><div class="line"> </div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onResume</span><span class="params">()</span> </span>{</div><div class="line"> <span class="comment">// ...</span></div><div class="line"> ((YourApplication)getApplicationContext()).setCurrentActivity(<span class="keyword">this</span>);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onPause</span><span class="params">()</span> </span>{</div><div class="line"> clearReferences();</div><div class="line"> <span class="comment">// ...</span></div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onDestroy</span><span class="params">()</span> </span>{</div><div class="line"> clearReferences();</div><div class="line"> <span class="comment">// ...</span></div><div class="line">}</div></pre></td></tr></table></figure>
<h3 id="DebugUtils-java"><a href="#DebugUtils-java" class="headerlink" title="DebugUtils.java"></a>DebugUtils.java</h3><p>因为不想把stetho添加到release中,所以需要通过gradle的buildType特性,对debug版和release版做不同处理</p>
<h4 id="debug的配置"><a href="#debug的配置" class="headerlink" title="debug的配置"></a>debug的配置</h4><p>在src目录下添加debug/java目录,并在java目录中添加相应的package(比如,DebugUtils将添加到com.yourname.tools.util中,那么就在debug/java目录中新建com.yourname.tools.util包,并在util包下新建DebugUtils.java文件)<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> com.yourname.tools.util;</div><div class="line"></div><div class="line"><span class="keyword">import</span> android.app.Activity;</div><div class="line"><span class="keyword">import</span> android.content.Context;</div><div class="line"><span class="keyword">import</span> android.support.multidex.MultiDex;</div><div class="line"></div><div class="line"><span class="keyword">import</span> com.facebook.stetho.Stetho;</div><div class="line"></div><div class="line"><span class="keyword">import</span> java.lang.reflect.Field;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * Created by Nstd on 2016/3/14.</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DebugUtils</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(<span class="keyword">final</span> Context context)</span> </span>{</div><div class="line"> <span class="comment">// 如果因为加了stetho导致DexIndexOverflowException,需要添加以下配置</span></div><div class="line"> MultiDex.install(context);</div><div class="line"></div><div class="line"> <span class="comment">// 以下是Stetho配置</span></div><div class="line"> Stetho.initialize(</div><div class="line"> Stetho.newInitializerBuilder(context)</div><div class="line"> .enableDumpapp(</div><div class="line"> Stetho.defaultDumperPluginsProvider(context))</div><div class="line"> .enableWebKitInspector(</div><div class="line"> Stetho.defaultInspectorModulesProvider(context))</div><div class="line"> .build());</div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="comment">// 通过反射获取activity中的私有变量</span></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">getActField</span><span class="params">(Activity activity, String fieldName)</span> </span>{</div><div class="line"> <span class="keyword">try</span> {</div><div class="line"> Field f = activity.getClass().getDeclaredField(fieldName);</div><div class="line"> f.setAccessible(<span class="keyword">true</span>);</div><div class="line"> <span class="keyword">return</span> f.get(activity);</div><div class="line"> } <span class="keyword">catch</span> (NoSuchFieldException e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> } <span class="keyword">catch</span> (IllegalAccessException e) {</div><div class="line"> e.printStackTrace();</div><div class="line"> }</div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h4 id="release的配置"><a href="#release的配置" class="headerlink" title="release的配置"></a>release的配置</h4><p>在src目录下添加release/java目录,并在java目录中添加相应的package(文件名同上,但是里面的函数都是空实现)<br><figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">package</span> com.haizitong.minhang.Tool.util;</div><div class="line"></div><div class="line"><span class="keyword">import</span> android.content.Context;</div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line"> * Created by Nstd on 2016/3/14.</div><div class="line"> */</div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DebugUtils</span> </span>{</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(Context context)</span> </span>{</div><div class="line"> <span class="comment">// release version: empty code</span></div><div class="line"> }</div><div class="line"></div><div class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title">getActField</span><span class="params">(Activity activity, String fieldName)</span> </span>{</div><div class="line"> <span class="comment">//release version: return null</span></div><div class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<h4 id="其他buildType的配置(参照debug或release)"><a href="#其他buildType的配置(参照debug或release)" class="headerlink" title="其他buildType的配置(参照debug或release)"></a>其他buildType的配置(参照debug或release)</h4><h4 id="PS:网络配置"><a href="#PS:网络配置" class="headerlink" title="PS:网络配置"></a>PS:网络配置</h4><p>公司现在用的是HttpUrlConnection,Stetho配置这个的时候侵入性比较强,暂时也用不到,所以不做配置<br>如果需要HttpUrlConnection的配置,可以参看官网Demo的<a href="https://github.com/facebook/stetho/blob/master/stetho-sample/src/main/java/com/facebook/stetho/sample/Networker.java" target="_blank" rel="external">配置</a><br>如果用的是okhttp,可以参看官网的这个<a href="http://facebook.github.io/stetho/#integrations" target="_blank" rel="external">配置</a></p>
<h2 id="编译运行应用"><a href="#编译运行应用" class="headerlink" title="编译运行应用"></a>编译运行应用</h2><h2 id="启动dev控制台"><a href="#启动dev控制台" class="headerlink" title="启动dev控制台"></a>启动dev控制台</h2><p>在Chrome的地址栏中输入:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">chrome://inspect</div></pre></td></tr></table></figure></p>
<p>启动以后,在页面上应该会出现对应的包名、应用名称,选择需要调试的应用,点击 <font style="color:rgb(0, 194, 255);">inspect </font>,开始调试</p>
<h3 id="PS:一直加载刷新不出来"><a href="#PS:一直加载刷新不出来" class="headerlink" title="PS:一直加载刷新不出来"></a>PS:一直加载刷新不出来</h3><p>如果启动后一直是加载状态,需要使用VPN翻墙,成功加载一次以后就不需要了</p>
<h2 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h2><h3 id="Elements面板"><a href="#Elements面板" class="headerlink" title="Elements面板"></a>Elements面板</h3><ol>
<li>可以查看当前Activity的布局;</li>
<li>Elements显示的xml中,YourApplication是根节点,直属子节点是对应的Activity,如果跳转到新的Activity,新的Activity会堆叠到旧Activity之上,就像栈一样;</li>
<li>在Elements的xml节点上移动鼠标,会在手机屏幕上高亮显示对应的区域;</li>
<li>点击Elements旁边的放大镜(变成蓝色),然后点击屏幕,随后xml会自动定位到你点击的节点;</li>
<li>在Styles面板中可以看到布局元素的属性值.</li>
<li></li>
</ol>
<ul>
<li>bug?<br>查看不了Dialog的布局信息</li>
</ul>
<h3 id="Resource面板"><a href="#Resource面板" class="headerlink" title="Resource面板"></a>Resource面板</h3><ol>
<li>Web SQL:中可以查看Sqlite的信息<ul>
<li>点击数据库名称,可以输入sql语句直接操作数据库</li>
<li>点击表名,可以查看该表的所有数据</li>
</ul>
</li>
<li>Local Storage:中可以查看SharedPreferences的信息</li>
</ol>
<h3 id="Console面板"><a href="#Console面板" class="headerlink" title="Console面板"></a>Console面板</h3><ol>
<li>context相当于Application对象</li>
<li>如果加入了之前的封装(DebugUtils等)<ul>
<li>通过context.mAct获得当前windows中的Activity</li>
<li>通过context.mAct.公有属性名,可以获取mAct中public属性变量的值</li>
<li>通过context.mAct.getField(fieldName)可以获取到这个Activity中的私有变量值(当然也可以获取public的)</li>
</ul>
</li>
<li>如果获取的是一个对象,eg:context.mAct.mTvMessage(mTvMessage是个TextView),可能不能直接操作该对象的方法,需要使用importPackage(android.widget),引入该对象所在的包(将包内的所有java对象引入到jsRuntime中);或者使用importClass(android.widget.TextView),只引入TextView对象,此时才能使用context.mAct.mTvMessage.getText()方法</li>
</ol>
<h2 id="PS:其他"><a href="#PS:其他" class="headerlink" title="PS:其他"></a>PS:其他</h2><h3 id="代码"><a href="#代码" class="headerlink" title="- 代码"></a>- 代码</h3><p><a href="https://github.com/Nstd/AndroidTools" target="_blank" rel="external">前往GitHub</a></p>
<h3 id="Android-Studio"><a href="#Android-Studio" class="headerlink" title="- Android Studio"></a>- Android Studio</h3><p>如果觉得Android Studio卡,可以尝试修改Studio的配置,增加其使用的内存大小<br>window下:<br>studio安装目录\bin\sudio64.exe.vmoptions:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">-Xmx4096m</div><div class="line">-XX:MaxPermSize=1024m</div></pre></td></tr></table></figure></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="http://facebook.github.io/stetho" target="_blank" rel="external">官网文档</a></li>
<li><a href="https://github.com/facebook/stetho" target="_blank" rel="external">官方Github-stetho</a></li>
<li><a href="https://github.com/facebook/stetho/tree/master/stetho-js-rhino" target="_blank" rel="external">官方Github-stetho-js-rhino</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java" title="Javascript Console中可用的相关知识" target="_blank" rel="external">Scripting Java</a></li>
</ol>
<h2 id="什么是Stetho"><a href="#什么是Stetho" class="headerlink" title="什么是Stetho"></a>什么是Stetho</h2><h3 id="Stetho是Facebook出的一款Android调试工具"><a href="#Stetho是Facebook出的一款Android调试工具" class="headerlink" title="Stetho是Facebook出的一款Android调试工具"></a>Stetho是Facebook出的一款Android调试工具</h3><p>我们可以用它在Chrome DevTools下查看应用的布局、网络请求、Sqlite、SharedPreference并能通过js来调试代码(使用js进行调试的时候FB只做了初步封装,使用起来会比较麻烦,需要自己进一步封装)</p>
<h3 id="来点截图(取自官网)"><a href="#来点截图(取自官网)" class="headerlink" title="来点截图(取自官网)"></a>来点截图(取自<a href="http://facebook.github.io/stetho/">官网</a>)</h3><h4 id="Chrome-DevTools"><a href="#Chrome-DevTools" class="headerlink" title="Chrome DevTools"></a>Chrome DevTools</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-discovery.png" alt="Chrome DevTools"></p>
<h4 id="查看网络请求"><a href="#查看网络请求" class="headerlink" title="查看网络请求"></a>查看网络请求</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-network.png" alt="Network Inspection"></p>
<h4 id="数据库(可读写)"><a href="#数据库(可读写)" class="headerlink" title="数据库(可读写)"></a>数据库(可读写)</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-sqlite.png" alt="Database Inspection"></p>
<h4 id="查看布局(双向查看)"><a href="#查看布局(双向查看)" class="headerlink" title="查看布局(双向查看)"></a>查看布局(双向查看)</h4><ol>
<li>用鼠标放在Element面板中的xml节点上,会在应用中高亮对应的布局</li>
<li>点击Element左边的放大镜,然后点击应用中的布局,会自动跳转到Element面板中的xml节点<br><img src="http://facebook.github.io/stetho/static/images/inspector-elements.png" alt="View Hierarchy"></li>
</ol>
<h4 id="Javascript-Console"><a href="#Javascript-Console" class="headerlink" title="Javascript Console"></a>Javascript Console</h4><p><img src="http://facebook.github.io/stetho/static/images/inspector-js.png" alt="Javascript Console"><br>
关于学习
http://nstd.github.io/2016/02/14/about-learning/
2016-02-14T13:29:53.000Z
2017-03-03T09:22:42.000Z
<h2 id="What-amp-Why"><a href="#What-amp-Why" class="headerlink" title="What & Why"></a>What & Why</h2><p>我们对这个世界的依恋或迷恋,是我们活下去的动力,而这种迷恋或多或少会以不同的方式成瘾,不理解的人唾之恨之,唯独只有自己知道个中缘由,所以我们需要一套坚固的自我价值体系。<br>然而任何体系都不是完善的,假如不辩证地持守自我体系,迟早会把自己逼入钻牛角尖的境地,时常更新完善这个体系,才能让我们更好地面对自己以及他人。为了完善它,放下羞怯、要面子以及其他会阻碍自己获得充实感的心理因素,自觉或者不自觉地去学习、吸收。<br><a id="more"></a></p>
<h2 id="Pre-How"><a href="#Pre-How" class="headerlink" title="Pre-How"></a>Pre-How</h2><h5 id="1-健康"><a href="#1-健康" class="headerlink" title="1. 健康"></a>1. 健康</h5><p>健康是做任何一样事情的前提,先学会管理自己的健康(身体和精神)</p>
<ol>
<li>适量的运动</li>
<li>充足的休息(规律作息)</li>
<li>每天做一件能让自己开心的事(莫当任务做,是为了真正让自己开心起来)</li>
</ol>
<h5 id="2-注意力"><a href="#2-注意力" class="headerlink" title="2. 注意力"></a>2. 注意力</h5><p>集中注意力在当前要做的事上,排除干扰(如果出现干扰,可以尝试快速记下再稍后去做)</p>
<h5 id="3-甜头"><a href="#3-甜头" class="headerlink" title="3. 甜头"></a>3. 甜头</h5><p>不管学习什么都要先成功学会一样东西,这种成功的经验会帮助我们能有更多的信心去成功地学下一样东西</p>
<h5 id="4-坚持"><a href="#4-坚持" class="headerlink" title="4. 坚持"></a>4. 坚持</h5><p>如果不能自发地坚持,不用再往下看了。。</p>
<h2 id="What-amp-Why"><a href="#What-amp-Why" class="headerlink" title="What & Why"></a>What & Why</h2><p>我们对这个世界的依恋或迷恋,是我们活下去的动力,而这种迷恋或多或少会以不同的方式成瘾,不理解的人唾之恨之,唯独只有自己知道个中缘由,所以我们需要一套坚固的自我价值体系。<br>然而任何体系都不是完善的,假如不辩证地持守自我体系,迟早会把自己逼入钻牛角尖的境地,时常更新完善这个体系,才能让我们更好地面对自己以及他人。为了完善它,放下羞怯、要面子以及其他会阻碍自己获得充实感的心理因素,自觉或者不自觉地去学习、吸收。<br>
CSS布局之postion、float、z-index
http://nstd.github.io/2016/01/30/CSS-layout-postion-float-z-index/
2016-01-30T08:48:50.000Z
2017-03-03T09:22:42.000Z
<h2 id="position(属性)"><a href="#position(属性)" class="headerlink" title="position(属性)"></a>position(属性)</h2><ul>
<li><p><strong><em>static</em></strong><br>如果没有指定position,浏览器默认position为static,元素处于文档流中,浏览器根据标签来识别到底是块元素还是行内元素。指定的top、right、bottom、left、z-index皆无效,文档流后面的节点会遮盖前面的节点。</p>
<a id="more"></a>
</li>
<li><p><strong><em>fixed</em></strong><br>固定位置,根据TRBL确定位置,可以实现类似常驻侧边栏或者固定header、footer的效果。</p>
</li>
<li><p><strong><em>relative</em></strong><br>相对于原有位置,根据TRBL进行偏移(原有位置所在空间仍会保留)。</p>
</li>
<li><p><strong><em>absolute</em></strong><br>相对于position为非static属性的祖先节点,进行偏移,如果祖先中没有非static节点,则相对于body进行偏移。 absolute与relative的区别在于:<br>a) absolute元素的原点(top、left)在非static祖先节点的原点上<br>b) relative元素的原点(top、left)是在自己没有设置position: relative;(其实就是自己为static)时的原点上</p>
</li>
<li><p><strong>inherit</strong><br>继承父节点的position(实际的position布局方式只有四种:static、fixed、relative、absolute)。</p>
</li>
</ul>
<h2 id="float(常见问题)"><a href="#float(常见问题)" class="headerlink" title="float(常见问题)"></a>float(常见问题)</h2><ol>
<li><p><strong>浮动标签没有被父标签包裹</strong><br>在父标签中设置overflow: auto; (IE6中还需要在父标签中设置zoom: 1;)</p>
</li>
<li><p><strong>清除浮动</strong><br>在需要清除浮动的标签上使用css的clear属性(left、right、both)</p>
</li>
</ol>
<h2 id="z-index"><a href="#z-index" class="headerlink" title="z-index"></a>z-index</h2><ol>
<li>非static元素会覆盖在static元素之上</li>
<li>z-index只对非static元素有效</li>
<li>如果没有指定z-index,按节点出现顺序,后面的节点会覆盖在前面的节点之上</li>
<li>同级元素,设置的z-index值越大越在上面(z-index>=0的元素,在没设z-index的元素之上;z-index<0的元素,在没设z-index元素之下)</li>
<li>优先响应同级父元素的z-index值,子元素的z-index值比较只在父元素内有效</li>
</ol>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="http://zh.learnlayout.com/position.html" target="_blank" rel="external">position & float</a></li>
<li><a href="http://www.neoease.com/css-z-index-property-and-layering-tree/comment-page-1/" target="_blank" rel="external">z-index</a></li>
</ol>
<h2 id="position(属性)"><a href="#position(属性)" class="headerlink" title="position(属性)"></a>position(属性)</h2><ul>
<li><p><strong><em>static</em></strong><br>如果没有指定position,浏览器默认position为static,元素处于文档流中,浏览器根据标签来识别到底是块元素还是行内元素。指定的top、right、bottom、left、z-index皆无效,文档流后面的节点会遮盖前面的节点。</p>
Hello World
http://nstd.github.io/2016/01/30/hello-world/
2016-01-30T08:48:00.000Z
2017-03-03T09:22:42.000Z
<p>Welcome to <a href="http://hexo.io/" target="_blank" rel="external">Hexo</a>! This is your very first post. Check <a href="http://hexo.io/docs/" target="_blank" rel="external">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="http://hexo.io/docs/troubleshooting.html" target="_blank" rel="external">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="external">GitHub</a>.<br><a id="more"></a></p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo new <span class="string">"My New Post"</span></div></pre></td></tr></table></figure>
<p>More info: <a href="http://hexo.io/docs/writing.html" target="_blank" rel="external">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo server</div></pre></td></tr></table></figure>
<p>More info: <a href="http://hexo.io/docs/server.html" target="_blank" rel="external">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo generate</div></pre></td></tr></table></figure>
<p>More info: <a href="http://hexo.io/docs/generating.html" target="_blank" rel="external">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo deploy</div></pre></td></tr></table></figure>
<p>More info: <a href="http://hexo.io/docs/deployment.html" target="_blank" rel="external">Deployment</a></p>
<p>Welcome to <a href="http://hexo.io/">Hexo</a>! This is your very first post. Check <a href="http://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="http://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues">GitHub</a>.<br>