<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[邵明博]]></title>
  <link href="http://mingbo.de/feed" rel="self"/>
  <link href="http://mingbo.de/"/>
  <updated>2015-03-22T13:50:18+08:00</updated>
  <id>http://mingbo.de/</id>
  <author>
    <name><![CDATA[邵明博]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[《富爸爸，穷爸爸》读书笔记]]></title>
    <link href="http://mingbo.de/blog/2015/03/22/note-of-rich-dad-poor-dad/"/>
    <updated>2015-03-22T13:44:00+08:00</updated>
    <id>http://mingbo.de/blog/2015/03/22/note-of-rich-dad-poor-dad</id>
    <content type="html"><![CDATA[<p>最近在整理Evernote，看到当年读这本书的一些笔记。</p>

<ul>
<li>富人买入资产，穷人只有支出，中产买入他们以为是资产的负债</li>
<li>资产的类别：

<ul>
<li>不需要我到场就能运作的业务</li>
<li>股票、基金、债券</li>
<li>可以产生收入的房地产</li>
<li>其他可以产生收入且市场流通性好的东西</li>
</ul>
</li>
<li>找到一个超现实的理由：想要与不想要的结合体</li>
<li>每天做出自己的选择：使用我们的时间、金钱以及头脑里的知识来实现目标</li>
<li>掌握一种模式，然后再学习一种新的模式：并不要求你学太多，（因为你学到的时候，这个东西基本上就已经过时了)而在于你<strong>学得有多快</strong>！</li>
<li>自律，可以增强上述所有功能</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Alpha的秘密]]></title>
    <link href="http://mingbo.de/blog/2015/02/02/the-secret-of-alpha/"/>
    <updated>2015-02-02T21:08:00+08:00</updated>
    <id>http://mingbo.de/blog/2015/02/02/the-secret-of-alpha</id>
    <content type="html"><![CDATA[<p>Google 最近搞了个<a href="https://plus.google.com/u/1/communities/116342551728637785407">Android Performance Patterns</a>， 挺好的，光这个名字，就已经给整个社区很好的定位了。其实<strong>性能优化</strong>这档子事吧，android 社区从来就有。只不过，从前都是只言片语，大伙也只能各自摸着石头过河。这一次Google不但在G+上搞了这个讨论区，还以“Patterns”的形式推出了<a href="https://www.youtube.com/playlist?list=PLOU2XLYxmsIKEOXh5TwZEv89aofHzNCiu">一套指导视频</a>，从渲染、内存、电池三大方面剖析了性能优化的方法、工具以及一些小的技巧。这一系列的动作，引发了各界不小的反应，我还看到有国内的开发者把这套视频翻译为&#8221;<a href="http://hukai.me/android-performance-patterns/">android性能优化典范</a>&ldquo;，着实给力呢。</p>

<p>既然有了这么好的氛围，这个博客也打算凑凑热闹，帮忙翻译、整理一些和性能优化有关的Tips。你可能不敢相信，一个简单的优化步骤能带来多大的性能提升？！让我们先看一张图：</p>

<p><img class="center" src="http://mingbo.de/images/201502/alpha_contrast.png"></p>

<p>如果你已经看过<a href="https://www.youtube.com/watch?v=VzYkVL1n4M8&amp;list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&amp;index=4">Profile GPU Rendering</a> 的介绍，一定会被这幅对比图惊艳到。仅仅是一个<code>alpha</code> 属性的设置，两者的渲染性能差别如此之大。据<a href="https://plus.google.com/113735310430199015092">Roamn Nurik</a> 介绍<code>view</code>的alpha属性在合成之前需要放到屏幕外的缓冲区中作额外的渲染工作。但，如果只对<code>textcolor</code>作<code>alpha</code>属性的设置，<strong>渲染性能立刻提升了10倍</strong>。</p>

<p>与之类似，在<code>TextView</code>中使用<code>background set</code>,<code>Spannable text</code>以及让文字被选中，都会加重渲染的负担。了解渲染负担并合理的避免，应该才是性能优化的王道。</p>

<h2>参考资料</h2>

<ul>
<li><a href="https://plus.google.com/u/1/+AndroidDevelopers/posts/3p3dHBszKvo">improving performance by switching from view alpha to text color alpha</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[class文件从硬盘到内存的心路历程]]></title>
    <link href="http://mingbo.de/blog/2015/01/28/java-class-load-road-map/"/>
    <updated>2015-01-28T21:25:00+08:00</updated>
    <id>http://mingbo.de/blog/2015/01/28/java-class-load-road-map</id>
    <content type="html"><![CDATA[<h2>0x00 加载</h2>

<ul>
<li><strong>类的加载</strong>本质上是由类加载器将字节码转换为<code>java.lang.Class</code>对象的过程</li>
<li>除了引导类加载器之外，所有的类加载器都有一个父类加载器</li>
<li>每一层的类加载器在收到加载请求之后，会将请求委派给父加载器执行；只有父加载器无法完成加载请求的时候，子加载器才尝试自己加载</li>
<li>这种双亲委托模型一方面避免了类的重复加载，另一方面还能防止Java 核心API<strong>被子加载器替换</strong>带来的隐患</li>
</ul>


<h2>0x01 链接</h2>

<ul>
<li>链接分为三步：验证、准备和解析。其本质上是将字节码合入JVM运行时的过程。</li>
<li><strong>验证阶段</strong>主要是确认字节码结构的合法性，一旦验证失败，JVM会抛出<code>java.lang.VerifyError</code>异常</li>
<li><strong>准备阶段</strong>主要是将Java类的静态域初始化为默认值（并不执行任何代码）</li>
<li><strong>解析阶段</strong>主要是确保所有引用的类能被正确的找到，这通常会导致引用类被加载</li>
</ul>


<h2>0x02 初始化</h2>

<p>该阶段的任务主要是初始化静态域，以及执行静态块代码。一个类被初始化之前，其父类会被先初始化。关于这一点，有2个需要注意的地方：</p>

<ul>
<li>通过Java反射API也可能造成类的初始化</li>
<li>当访问一个类的静态域时，只有真正声明这个域的类才会被初始化。则，考虑如下代码：</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">class</span> <span class="nc">Base</span> <span class="o">{</span>
</span><span class='line'>   <span class="kd">static</span> <span class="kt">int</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">100</span><span class="o">;</span>
</span><span class='line'>   <span class="kd">static</span> <span class="o">{</span>
</span><span class='line'>      <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&quot;我会被输出.&quot;</span><span class="o">);</span>
</span><span class='line'>   <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">class</span> <span class="nc">A</span> <span class="kd">extends</span> <span class="n">Base</span> <span class="o">{</span>
</span><span class='line'>   <span class="kd">static</span> <span class="o">{</span>
</span><span class='line'>      <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&quot;我不会被输出&quot;</span><span class="o">);</span>
</span><span class='line'>   <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span>
</span><span class='line'>   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>      <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">A</span><span class="o">.</span><span class="na">value</span><span class="o">);</span>
</span><span class='line'>   <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>如上所述，上述代码的运行结果为：</p>

<p><img class="center" src="http://mingbo.de/images/201501/code_result.png"></p>

<h2>0x03 参考资料</h2>

<ul>
<li><a href="http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html">深入探讨Java 类加载器</a></li>
<li><a href="http://computerdragon.blog.51cto.com/6235984/1223354">Java虚拟机类加载机制浅谈</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[定位Native异常]]></title>
    <link href="http://mingbo.de/blog/2015/01/26/how-to-locate-the-native-exception/"/>
    <updated>2015-01-26T21:08:00+08:00</updated>
    <id>http://mingbo.de/blog/2015/01/26/how-to-locate-the-native-exception</id>
    <content type="html"><![CDATA[<h2>0x00 ndk-stack</h2>

<p>Google从<code>NDK r6</code>开始提供这个方便的工具，帮助开发者定位Native 异常。和<code>ndk-build</code>一样，这个命令行工具被放在<code>NDK</code>安装目录下。作为Debug 工具，其操作方法也十分简单：只需在参数里指定包含符号表的<code>so</code>文件即可。</p>

<p>如果需要实时获取异常信息，我们可以直接运行：<code>adb shell logcat | ndk-stack -sym $PROJECT_DIR/obj/local/armeabi</code>；有的时候日志文件并不一定能直接看到，该工具也支持“异步”分析。这里我们首先运行：<code>adb shell logcat &gt; android.log</code>来模拟“异步”获取日志。 在获得日志文件之后，我们拿它喂给命令行工具：<code>ndk-stack -sym $PROJECT_DIR/obj/local/armeabi -dump android.log</code>。</p>

<p>就这样，天书一般的日志文件瞬间变得友好可读了。</p>

<h2>0x01 addr2line以及objdump</h2>

<p>对于有经验的程序员来说，可能更熟悉这2款工具。方法类似，结果相同，所以这里就不再赘述了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[蛋疼的BUG：binder事务异常]]></title>
    <link href="http://mingbo.de/blog/2014/11/11/failed-binder-transaction/"/>
    <updated>2014-11-11T15:15:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/11/11/failed-binder-transaction</id>
    <content type="html"><![CDATA[<p>一句log&#8221;<strong>!!! FAILED BINDER TRANSACTION !!!</strong>&ldquo;，伴随着App的崩溃，留下的是程序员深深的思考。定位到android 的源码，此log 出自<code>android_util_Binder.cpp</code>。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class='c++'><span class='line'><span class="kt">void</span> <span class="n">signalExceptionForError</span><span class="p">(</span><span class="n">JNIEnv</span><span class="o">*</span> <span class="n">env</span><span class="p">,</span> <span class="n">jobject</span> <span class="n">obj</span><span class="p">,</span> <span class="n">status_t</span> <span class="n">err</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">canThrowRemoteException</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="k">switch</span> <span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="p">...</span>
</span><span class='line'>        <span class="k">case</span> <span class="nl">FAILED_TRANSACTION:</span>
</span><span class='line'>            <span class="n">ALOGE</span><span class="p">(</span><span class="s">&quot;!!! FAILED BINDER TRANSACTION !!!&quot;</span><span class="p">);</span>
</span><span class='line'>            <span class="c1">// TransactionTooLargeException is a checked exception, only throw from certain methods.</span>
</span><span class='line'>            <span class="c1">// FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION</span>
</span><span class='line'>            <span class="c1">//        but it is not the only one.  The Binder driver can return BR_FAILED_REPLY</span>
</span><span class='line'>            <span class="c1">//        for other reasons also, such as if the transaction is malformed or</span>
</span><span class='line'>            <span class="c1">//        refers to an FD that has been closed.  We should change the driver</span>
</span><span class='line'>            <span class="c1">//        to enable us to distinguish these cases in the future.</span>
</span><span class='line'>            <span class="n">jniThrowException</span><span class="p">(</span><span class="n">env</span><span class="p">,</span> <span class="n">canThrowRemoteException</span><span class="o">?</span> <span class="s">&quot;android/os/TransactionTooLargeException&quot;</span><span class="o">:</span> <span class="s">&quot;java/lang/RuntimeException&quot;</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span><span class='line'>            <span class="k">break</span><span class="p">;</span>
</span><span class='line'>        <span class="p">...</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>很明显，按照注释的描述，我们遇到的问题就转换为了<code>TransactionTooLargeException</code>引起的程序崩溃了。去<a href="http://developer.android.com/reference/android/os/TransactionTooLargeException.html">官网</a>一查，它给出的说明更清晰：远程调用的时候，参数和返回值都以Parcel 的形式保存在<strong>Binder 事务的缓存对象</strong>中。如果参数或返回值过大，则会抛出<code>TransactionTooLargeException</code>异常。</p>

<p>按照这个提示，我跟到驱动层的<code>binder.c</code>的<a href="https://android.googlesource.com/kernel/common.git/+/android-2.6.39/drivers/staging/android/binder.c">代码</a>看了一下<code>binder_transaction</code>以及<code>binder_alloc_buf</code>方法，证实了前面注释代码所言非虚。另外，官方文档提到了一个1M 大小的buffer空间的限制，我翻了几个版本的代码来看，发现其实这个数值并没有写死。buffer 应该是不大于2M而不是它说的1M，但正如文档所说，由于binder 被所有跨进程通讯所共享，即使你的parcel 不大，但是使用binder 的人很多，也有可能造成上述异常。所以，这里解决问题的方法，应该是尽可能的传递小一点的parcel或者使用其他数据通讯方案。是的，说的就是你，不要用binder 传那么多那么大的高清图片了！</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[android内存泄露及分析工具]]></title>
    <link href="http://mingbo.de/blog/2014/10/24/avoiding-android-memory-leaks/"/>
    <updated>2014-10-24T14:42:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/10/24/avoiding-android-memory-leaks</id>
    <content type="html"><![CDATA[<p>话说，任何开发环境，想要取得上乘的性能，内存优化是一个不可避免的话题。虽然android 开发过程中，有<code>GC</code>陪伴，但这并不意味着你可以完全忽略内存的分配与释放。这是因为，不恰当的代码仍然会导致内存泄露，以至于<code>GC</code>也无力回天。</p>

<h3>内存限制</h3>

<p>为了维持多任务的运行环境，android 给每个运行的应用规定了一个内存上限（因不同设备的物理内存大小而变化，常见的有16M~48M不等）。每当你的应用企图在触碰上限的情况下分配内存，就会引发<code>OutOfMemoryError</code>。</p>

<h3>内存泄露</h3>

<p>虚拟机中的内存泄露基本上就一条：代码残留了对象，让<code>GC</code>无法回收。也许这个情况一时半会不会翻起大浪，但也可能在下一秒，就触碰<strong>内存限制</strong>，导致程序崩溃。所以，内存泄露的BUG通常都非常隐蔽、随机、让人头大。</p>

<p>常见的泄露有以下几种：</p>

<ul>
<li><code>Cursor</code>没有关闭</li>
<li><code>registerReceiver</code>之后没有成对的<code>unregisterReceiver</code></li>
<li>I/O Stream没有关闭</li>
<li><code>Context</code>泄露</li>
</ul>


<p>前面3种情况，没啥好说的，在代码层面稍加注意即可避免。最后一种情况可能有些变种需要注意，这里使用2个例子尝试着描述一下。首先借用Romain Guy的例子：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">private</span> <span class="kd">static</span> <span class="n">Drawable</span> <span class="n">sBackground</span><span class="o">;</span>
</span><span class='line'>
</span><span class='line'><span class="nd">@Override</span>
</span><span class='line'><span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="n">Bundle</span> <span class="n">state</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>  <span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">state</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">TextView</span> <span class="n">label</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TextView</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
</span><span class='line'>  <span class="n">label</span><span class="o">.</span><span class="na">setText</span><span class="o">(</span><span class="s">&quot;Leaks are bad&quot;</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="o">(</span><span class="n">sBackground</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>    <span class="n">sBackground</span> <span class="o">=</span> <span class="n">getDrawable</span><span class="o">(</span><span class="n">R</span><span class="o">.</span><span class="na">drawable</span><span class="o">.</span><span class="na">large_bitmap</span><span class="o">);</span>
</span><span class='line'>  <span class="o">}</span>
</span><span class='line'>  <span class="n">label</span><span class="o">.</span><span class="na">setBackgroundDrawable</span><span class="o">(</span><span class="n">sBackground</span><span class="o">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">setContentView</span><span class="o">(</span><span class="n">label</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>万恶的静态变量出现了，引用关系<code>Drawable</code>&ndash;> <code>TextView</code>&ndash;> <code>Activity</code>导致整个Activity 无法被回收。此外，经常被讨论的Webview 泄露，情况也是类似的。解决方法可以参考这个<a href="http://stackoverflow.com/questions/3130654/memory-leak-in-webview">帖子</a>。这里要谈的另外一种<code>Context</code>泄露是由于非静态内部类引用导致的:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DemoActivity</span> <span class="kd">extends</span> <span class="n">Activity</span> <span class="o">{</span>
</span><span class='line'>    <span class="kd">class</span> <span class="nc">LeakRUN</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="o">{</span>
</span><span class='line'>
</span><span class='line'>        <span class="nd">@Override</span>
</span><span class='line'>        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
</span><span class='line'>            <span class="k">try</span> <span class="o">{</span>
</span><span class='line'>                <span class="n">TimeUnit</span><span class="o">.</span><span class="na">DAYS</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">365</span><span class="o">);</span>
</span><span class='line'>            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">InterruptedException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>            <span class="o">}</span>
</span><span class='line'>        <span class="o">}</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>正确的处理这种泄露应当仿造<code>ViewRoot</code>中的内部类<code>W</code>那样使用<a href="http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.1_r2/android/view/ViewRoot.java#ViewRoot.W">弱引用</a>。这么做的好处是，一来不会出现空指针，二来不会导致内存泄露。</p>

<h2>内存工具</h2>

<p>尽管上面罗列了种种常见的泄露情况，但实际开发中遇到的现象总是千奇百怪的，解决泄露的方法也会因情况而异。所以，这里更重要的是需要了解如何使用工具来发现应用内存是如何泄露的。</p>

<h3>Logcat</h3>

<p>越简单的方法，往往越直接有效。Logcat 提供实时的虚拟机日志，每当垃圾回收发生的时候，我们会看到这样的日志<code>D/dalvikvm: &lt;GC_Reason&gt; &lt;Amount_freed&gt;, &lt;Heap_stats&gt;, &lt;External_memory_stats&gt;, &lt;Pause_time&gt;</code>。其中，垃圾回收的原因、回收内存的大小这些并不是现在关注的重点，我们的兴趣点应该放在<strong>堆的状态</strong>上。这个状态通常会提供两个数值，“空闲内存的比例”以及“内存占用的比例”。如果后者在每次垃圾回收之后，持续上升，<strong>那么几乎就可以断定出现内存泄露了</strong>。</p>

<h3>Monitor</h3>

<p><strong>Monitor</strong>是一个工具大集合，开发者可以从<code>Android studio/ Eclipse</code>直接找到它的入口，调试人员也可以直接从目录<code>&lt;sdk&gt;/tools/</code>下找到它。比起Logcat 被动的查看log, <code>Heap Update</code>提供了可视化的内存数据界面以及垃圾回收的操作接口。连续的在观察数据、回收内存以及应用交互的过程中往返，可以帮助我们定位<strong>“哪些操作”引起了内存激增</strong>。<code>Allocation Tracker</code>则提供了更为精确的<strong>代码级定位</strong>。</p>

<p><img class="center" src="http://mingbo.de/images/201410/monitor-vmheap.png"></p>

<h3>adb</h3>

<p>adb 命令行工具提供的内存查询接口则更为灵活，信息也更加丰富。一行命令<code>adb shell dumpsys meminfo &lt;package_name&gt;</code>即可完成操作。有精力的开发者完全可以依此定制一款自己的工具来帮助内存泄露的定位。一般来说，我们可以关注<code>Pss Total</code>以及<code>Private Dirty</code>这两项数据来掌握内存信息，而<code>Objects</code>栏目的数据可以帮助引导我们更直观的发现泄漏的情况。</p>

<p><img class="center" src="http://mingbo.de/images/201410/dumpsys_meminfo.png"></p>

<h3>Heap Dump</h3>

<p>最有效的工具，肯定需要最后出场。在Monitor 中选择<code>Dump HPROF file</code>，可以导出整个设备的虚拟机内存信息。由于该文件并不是传统的java 虚拟机内存文件，因此需要使用android sdk 中的<code>hprof-conv</code>工具对其进行转换。对于转换后的文件，可以使用<a href="http://www.eclipse.org/mat/downloads.php">Eclipse Memory Analyzer Tool (MAT)</a>进行分析。该工具提供两种不同的视图（<code>Histogram</code>和<code>Dominator tree</code>）分别从不同的视角帮助你分析内存。值得一提的是，如果需要更为精确的导出时机的话，还可以在项目的代码中进行控制。</p>

<p>以上工具的具体使用方法，可以参考以下链接的文献，这里就不再赘述了。</p>

<h2>参考资料</h2>

<ul>
<li><a href="http://www.littleeye.co/blog/2013/06/11/android-memory-management-understanding-app-pss/">Android Memory Management: Understanding App PSS</a></li>
<li><a href="http://www.youtube.com/watch?v=_CruQY55HOk">Memory management for Android Apps</a></li>
<li><a href="http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html">Avoiding memory leaks</a></li>
<li><a href="http://developer.android.com/training/articles/memory.html">Managing your app&rsquo;s memory</a></li>
<li><a href="https://developer.android.com/tools/debugging/debugging-memory.html">Investigating Your RAM Usage</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[警惕libgl1-mesa-glx:i386]]></title>
    <link href="http://mingbo.de/blog/2014/10/19/preparing-build-android/"/>
    <updated>2014-10-19T15:13:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/10/19/preparing-build-android</id>
    <content type="html"><![CDATA[<p>编译android工程应该来说没啥技术含量，按照官方的guide line一步一步的做，基本上都还OK。但，之所以想写个日志，主要是因为被这个工程的编译环境搞得有点抓狂了。起初是在<code>osx</code>上折腾，之后发现各种依赖关系搞起来太费神，就开始了转投传说中最稳定、顺畅的ubuntu 12.04了。万万没有想到，在虚拟机上跑了4个小时，最后给我看这个：</p>

<p><img class="center" src="http://mingbo.de/images/201410/built_android_failed.png"></p>

<p>刚查到<a href="http://blog.csdn.net/g_r_u_b/article/details/8644745">解决方案</a>的时候，打算给虚拟机更大的内存跑编译任务，于是重启了。重启之后，蛋疼的卡在了logo界面。一顿排查，发现原来是在准备编译环境的时候出了问题。</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>sudo apt-get install git gnupg flex bison gperf build-essential <span class="se">\</span>
</span><span class='line'>  zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev <span class="se">\</span>
</span><span class='line'>  libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 <span class="se">\</span>
</span><span class='line'>  libgl1-mesa-dev g++-multilib mingw32 tofrodos <span class="se">\</span>
</span><span class='line'>  python-markdown libxml2-utils xsltproc zlib1g-dev:i386
</span></code></pre></td></tr></table></div></figure>


<h3>症结所在</h3>

<p>Ubuntu 12.04.5 上执行<code>sudo apt-get install libgl1-mesa-glx:i386</code>时，会提示你安装<code>libgl1-mesa-dri:i386</code>。如果按照这个建议执行，你会发现安装程序会帮你删除一批不兼容的软件，其中包括<code>xorg</code>，最终导致你无法进入桌面。</p>

<p><img class="center" src="http://mingbo.de/images/201410/remove_xorg_when_install_lib.png"></p>

<h3>解决办法</h3>

<p>将<code>libgl1-mesa-glx:i386</code>替换为<code>libgl1-mesa-glx-lts-&lt;release&gt;:i386</code>，其中release 根据ubuntu的版本可以是<code>Quantal</code>、<code>Raring</code>、<code>Saucy</code>以及<code>Trusty</code>。我自己安装的是<code>libgl1-mesa-glx-lts-Trusty:i386</code>。至此，该问题完美解决。</p>

<h2>参考资料</h2>

<ul>
<li><a href="https://wiki.ubuntu.com/Kernel/LTSEnablementStack">LTS Enablement Stacks</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[android数据库升级tips]]></title>
    <link href="http://mingbo.de/blog/2014/10/16/a-strategy-for-upgrade-database-in-android/"/>
    <updated>2014-10-16T22:18:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/10/16/a-strategy-for-upgrade-database-in-android</id>
    <content type="html"><![CDATA[<h3>唯一不变的是变化</h3>

<p>App 版本迭代的过程中，一定会遇到sqlite 数据库需要升级的情况。一个简单粗暴的解决方法，是不顾用户的数据，直接去重建各个数据表。显然，今天要分享的并不是这个粗暴的方法。</p>

<h3>常见的困惑</h3>

<p><code>SQLiteOpenHelper</code>提供了一个处理数据库升级的API<code>onUpgrade(final SQLiteDatabase db, int from, final int to)</code>。从语义上来看，升级策略的描述应该有一个起始点<code>from</code>，也还应该有一个终点<code>to</code>。但由于起始点的不确定性，致使这里的代码分支会略显复杂。老实讲，在写这篇日志之前，我都没有把握温柔细致的处理好这件事。</p>

<h3>灵感来源于android源码</h3>

<p><code>DownloadManager</code>也使用数据库存储下载状态。其子类<code>DatabaseHelper</code>将升级函数降维，把原本复杂的升级过程，分解为多个原子的升级过程，而这个原子过程是简单、确定的。直接上代码吧：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="nd">@Override</span>
</span><span class='line'> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onUpgrade</span><span class="o">(</span><span class="kd">final</span> <span class="n">SQLiteDatabase</span> <span class="n">db</span><span class="o">,</span> <span class="kt">int</span> <span class="n">oldV</span><span class="o">,</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">newV</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>    <span class="o">...</span>
</span><span class='line'>    <span class="c1">//循环体内，每次只升一个版本</span>
</span><span class='line'>    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">version</span> <span class="o">=</span> <span class="n">oldV</span> <span class="o">+</span> <span class="mi">1</span><span class="o">;</span> <span class="n">version</span> <span class="o">&lt;=</span> <span class="n">newV</span><span class="o">;</span> <span class="n">version</span><span class="o">++)</span> <span class="o">{</span>
</span><span class='line'>                <span class="n">upgradeTo</span><span class="o">(</span><span class="n">db</span><span class="o">,</span> <span class="n">version</span><span class="o">);</span>
</span><span class='line'>    <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span><span class='line'>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='java'><span class='line'><span class="c1">//Upgrade database from (version - 1) to version.</span>
</span><span class='line'><span class="kd">private</span> <span class="kt">void</span> <span class="nf">upgradeTo</span><span class="o">(</span><span class="n">SQLiteDatabase</span> <span class="n">db</span><span class="o">,</span> <span class="kt">int</span> <span class="n">version</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>    <span class="k">switch</span> <span class="o">(</span><span class="n">version</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>        <span class="k">case</span> <span class="mi">100</span><span class="o">:</span>
</span><span class='line'>            <span class="n">createDownloadsTable</span><span class="o">(</span><span class="n">db</span><span class="o">);</span>
</span><span class='line'>            <span class="k">break</span><span class="o">;</span>
</span><span class='line'>        <span class="k">case</span> <span class="mi">101</span><span class="o">:</span>
</span><span class='line'>            <span class="n">createHeadersTable</span><span class="o">(</span><span class="n">db</span><span class="o">);</span>
</span><span class='line'>            <span class="k">break</span><span class="o">;</span>
</span><span class='line'>        <span class="k">case</span> <span class="mi">102</span><span class="o">:</span>
</span><span class='line'>            <span class="n">addColumn</span><span class="o">(</span><span class="n">db</span><span class="o">,</span> <span class="n">DB_TABLE</span><span class="o">,</span> <span class="n">Downloads</span><span class="o">.</span><span class="na">Impl</span><span class="o">.</span><span class="na">COLUMN_IS_PUBLIC_API</span><span class="o">,</span>
</span><span class='line'>                              <span class="s">&quot;INTEGER NOT NULL DEFAULT 0&quot;</span><span class="o">);</span>
</span><span class='line'>            <span class="k">break</span><span class="o">;</span>
</span><span class='line'>        <span class="k">case</span> <span class="mi">105</span><span class="o">:</span>
</span><span class='line'>            <span class="n">fillNullValues</span><span class="o">(</span><span class="n">db</span><span class="o">);</span>
</span><span class='line'>            <span class="k">break</span><span class="o">;</span>
</span><span class='line'>        <span class="o">...</span>
</span><span class='line'>        <span class="k">default</span><span class="o">:</span>
</span><span class='line'>            <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="s">&quot;Don&#39;t know how to upgrade to &quot;</span> <span class="o">+</span> <span class="n">version</span><span class="o">);</span>
</span><span class='line'>        <span class="o">}</span>
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>以上代码不仅逻辑清晰，而且还能很好的复用。比如在<code>onCreate</code>函数里需要初始化数，就可以直接调用<code>upgradeTo(db, CURRENT_VERSION)</code>.这样一来，无论是从哪个版本升级上来，都能平滑的处理版本问题了。</p>

<h2>参考资料</h2>

<ul>
<li><a href="https://android.googlesource.com/platform/packages/providers/DownloadProvider/+/master/src/com/android/providers/downloads/DownloadProvider.java#228">DownloadProvider</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[谈谈android屏幕适配]]></title>
    <link href="http://mingbo.de/blog/2014/10/08/supporting-multiple-screens/"/>
    <updated>2014-10-08T17:32:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/10/08/supporting-multiple-screens</id>
    <content type="html"><![CDATA[<h2>痛并快乐着</h2>

<p><a href="http://opensignal.com/reports/2014/android-fragmentation/">&ldquo;碎片化&rdquo;</a>像一个魔咒，从android 平台问世那天至今，伴随其成长。客观的说，这样的<strong>多样性</strong>的确帮助android 俘获了数以亿计的用户，但却给该平台下的设计师和开发者带来了不小的挑战。学会适应这样的生态环境，不仅需要更加广泛的知识还需要更加高超的技巧。不过话说回来，在满足各式各样的用户的过程中，你将成为一个更好的设计师或开发者。不得不说，这是一个值得追求的挑战！</p>

<h2>目标</h2>

<p>想要收获挑战的胜利果实，必须要有一个明确的目标。本文并不想纠结于品牌、乃至OS 版本的碎片化等问题，那需要更多的篇幅和精力去探讨。这里只想专注于屏幕适配这一问题进行集中的讨论。</p>

<p>从<a href="http://pan.baidu.com/s/1qWHNVVe">OpenSignal这份报告</a>来看，2014年支持android 的设备就有18796种，而设备屏幕的种类也是千奇百怪的。理想状态下，为每一种目标设备设计一套视觉资源，似乎就不存在适配的问题。但，没有一个设计师会愿意接受这种方案（<em>工作量太大了好吗？</em>）。就算有“勇士”接受了这样的设计任务，应用安装包也将变得臃肿不堪（<em>apk太大了好吗？</em>）。</p>

<p><img class="center" src="http://mingbo.de/images/201410/screen_fragmentaion_2014.png"></p>

<p>这样看来，屏幕适配的目标就清晰了许多：尽可能设计<strong>可重用</strong>的视觉资源，让<strong>目标设备</strong>良好呈现。这通常需要视觉人员以及程序猿的通力配合，才能达到预期的效果，<strong>任何一方的懈怠，都会导致功亏一篑</strong>。</p>

<h2>怎么搞</h2>

<p>设计的好不好，决定了适配程度的优劣。这里谈到的设计与视觉效果关系不大，而主要是<strong>适配设计</strong>。从以往的经验来看，设计师们习惯了用<strong>视觉语言</strong>去描绘设计，而程序猿们则习惯了用代码去实现设计。所以适配问题只有在视觉走查这个阶段才会让双方感到蛋疼。</p>

<h3>明确需要支持的范围</h3>

<p>那么多屏幕，可能有很多并不是我们的目标用户（比如TV屏），将有限的精力瞄准该做的事从来不会有错。国内的一些第三方<a href="http://www.umindex.com/">市场报告</a>能够给我们这方面的“灵感”，另外Google 也会定时更新在全球范围内的<a href="https://developer.android.com/about/dashboards/index.html">统计报告</a>(需要翻墙)。</p>

<h3>使用模糊定位和相对布局</h3>

<p>通常来讲，设计师们直接给出的标注往往会让android 程序猿们头疼。这是因为，设计稿通常绑定了一个静态的设备分辨率，这就导致其标注出来的数字具有相当的局限性——<em>只对一种分辨率有效！</em>。换句话讲，在适配屏幕的过程中，程序猿们<strong>并不希望看到一些硬编码的数值标注，因为这会使得这份设计过渡依赖当前这个分辨率</strong>，而使得适配难上加难了。</p>

<p>好在Android 平台为我们提供了强大的适配语言：使用<code>wrap_content</code>以及<code>match_parent</code>来描述单个控件的大小，还有超级强大的<code>RelativeLayout</code>来描述多个控件的组合&hellip;&hellip;系统在运行时，会通过动态计算摆放它们的位置。用自然语言说，<strong>最好</strong>不要告诉程序员具体大小，而是告诉他们一些个相对数值。让我们看个例子：</p>

<p><img class="center" src="http://mingbo.de/images/201410/tagging.png"></p>

<p>“先战一把”的按钮并不需要知道自身要有多长多宽(使用<code>match_parent</code>)，而只要知道自己和周围的距离，就能摆放恰当了; 再看看应用图标的摆放甚至都不需要知道左右的相对距离（使用<code>wrap_content</code>），只要给出类似“水平居中”这样的模糊定位，即可良好呈现。</p>

<p>通常一个设计稿，如果从上到下，从左到右（反过来也行），能够让每个控件尽量使用模糊定位和相对布局，那么适配各种屏幕，简直就是轻而易举。不过，有些刁巧的设计并不适用于这类描述。那么这个时候，可能就需要产品经理去权衡展示效果和适配力度之间的问题了。</p>

<h3>使用资源限定符</h3>

<p>细心的读者可能已经发现，上面的方法虽然能够尽可能的规避具体控件的大小，但是“漏网之鱼”以及一些<strong>相对距离还是硬编码</strong>来的啊！虽然他们只是边边角角，但是为了视觉呈现的效果，还是可以使用资源限定符来进行适配的。简单的说，资源限定符可以让设备在程序运行的时候，自动选择与设备最匹配的资源。不过，在正式介绍资源限定符之前，恐怕还得介绍一些个相关概念：</p>

<ul>
<li>屏幕密度：一言以蔽之，同样的物理尺寸，密度大的屏幕通常显示更为细腻。这是因为该屏幕<strong>单位面积内</strong>可以用来显示的“点”更多。所以，屏幕密度的单位是dpi(dot per inch，每英寸点)就很好理解了。这里还需要强调的是，屏幕尺寸和屏幕密度是2个独立的概念。一些个山寨手机虽然屏幕大，但显示效果却不尽如人意。这是因为山寨屏的单位面积内可用来显示的“点”太少，导致图像细节在显示过程中丢失。android 平台目前将屏幕密度分为6个等级：low（ldpi~120dpi）, medium（mdpi~160dpi）, high(hdpi~240dpi), extra-high(xhdpi~320dpi), extra-extra-high(xxhdpi~480dpi), and extra-extra-extra-high(xxxhdpi~640dpi)。</li>
<li>密度独立的像素(dp, Density-independent pixel): 是一个虚拟的度量单位，与屏幕密度无关。试想，在没有dp 的年代，直接使用像素去描述布局和位置。那么，1个像素在<code>ldpi</code>的屏幕占1/120, 而在<code>xxdpi</code>的屏幕上只占1/480。那么同样的视觉设计在4寸山寨屏与4寸视网膜屏的显示会相差3倍！dp 的出现完美的解决了这个问题：系统会根据当前设备密度的需要，透明的缩放尺寸。</li>
</ul>


<p>也许你已经看出，我们的适配都寄希望于<strong>系统的自动缩放</strong>了——嗯，我想你已经领悟到了精髓。现在继续来讲前面的“硬编码”问题。所以，即使是硬编码，我们也不能直接使用<code>px</code>来描述，而应该使用<code>dp</code>。那关屏幕密度什么事呢？关资源限定符什么事呢？</p>

<p>可以说，除代码外的所有<strong>静态资源</strong>，全部都分布在不同类型的工程目录下，而不同类型的目录<strong>还细分了各种属性</strong>，这些属性帮助系统在运行时能够根据设备自身的配置从相应的目录中动态的加载这些静态资源，以达到适配的效果。android 资源文件夹使用<code>-</code>来分割限定符。拿图片文件来说，<code>drawable-hdpi</code>文件夹所存放的图片适用于高密度(high density)的设备屏幕，而<code>drawable-xhdpi</code>则适用于超高密度(etra high density)的设备屏幕。下图所示的<code>ic_launcher.png</code>图片重复出现在了三个文件夹里。当请求名为 ic_launcher 的图片时，系统运行时会根据设备配置自动选择适应的图片。这能让我们根据不同屏幕尺寸最优化图片的显示，但是重复存储的图片势必造成浪费资源。</p>

<p><img class="center" src="http://mingbo.de/images/201410/res-qualifiers.png"></p>

<p>动态加载静态资源的过程，可能会遇到“找不到最合适资源”的情况。这个时候，系统会拿其他目录中<strong>同名的资源进行缩放处理</strong>。举个例子，系统需要加载<code>drawable-xhdpi</code>下的<code>ic_launcher.png</code>文件，但发现找不到！这个时候找遍了资源目录却发现只有<code>drawable-hdpi</code>下有<code>ic_launcher.png</code>。系统会毫不犹豫的加载，并将其放大1.3倍。设计师看到这里可能会有点激动，“放大？那图片资源会不会失真？”没错，这个担心很合理。所以，通常的做法是保证最高密度文件夹下有相应的文件，那么系统在动态加载的时候只会处理缩小的过程。</p>

<p>事实上，利用上面这个特点，我们在设计输出时，应该直接瞄准那些高密度规格的设备，比如<code>1920 * 1080</code>。一次输出后，填满<code>drawable-xxhdpi</code>文件夹。这样直接可以一次性适配的分辨率就有<code>640 * 360</code>、<code>1280 * 720</code>、<code>960 * 540</code>。</p>

<p>同理，前面谈到的硬编码的相对数值（静态常量资源），应该合理的分布在不同文件夹下的dimens.xml文件里。对，这里可能就需要视觉人员给出多套标注，以达到最大限度的复用！</p>

<p><img class="center" src="http://mingbo.de/images/201410/use-size-qualifiers.png"></p>

<p>你以为资源限定符就这样介绍清楚了？图样图森破！不过<a href="https://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch">这段文字</a>应该能够让你对动态加载静态资源有一个清晰的认识。想要了解更多，请访问developer.android.com 进行系统的学习。(*^__^*)</p>

<h3>使用9-patch</h3>

<p>那么多屏幕要适配，要是图片本身就有自适应的能力该多好啊！9 patch bitmap 将赋予你这种魔力。这是一种图片格式，允许设计人员定义图片能够被拉伸的区域，<strong>通常被用在背景性质的图片中</strong>。这样带来的好处是：一方面给背景显示带来了灵活性（可拉伸），另一方面还大大降低了图片的“重量”。官方提供了简便的<a href="http://developer.android.com/tools/help/draw9patch.html">制作工具</a>（需翻墙）, 网络上也有大量的文章介绍，这里就不再赘述了。显然，这类资源如果得到复用，那么必能极大地减小应用安装包的大小。另外，各种按钮、背景的图片资源如果能在设计时就考虑复用的话，那也必是极好的。</p>

<h2>唠叨</h2>

<p>android 平台提供的适配工具，我想先介绍到这里。本文没有提到“为特殊的屏幕类型提供不同的布局”这类细节，是因为觉得其工作原理和上述内容类似，当然太懒也是一个不容掩饰的原因。一句话总结，无非是<strong>提供可替换的静态资源，让系统自身去操心如何适配</strong>罢了。</p>

<h2>参考文献</h2>

<ul>
<li><a href="https://developer.android.com/training/multiscreen/screensizes.html">Supporting Different Screen Sizes</a></li>
<li><a href="https://developer.android.com/guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
<li><a href="https://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch">How Android Finds the Best-matching Resource</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[TCPDump for android]]></title>
    <link href="http://mingbo.de/blog/2014/10/04/tcpdump-for-android/"/>
    <updated>2014-10-04T23:41:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/10/04/tcpdump-for-android</id>
    <content type="html"><![CDATA[<h3>抓包要有效率</h3>

<p>前段时间一直在做一款社交类答题的App。深有体会的是，不管是前期研究竞品，还是后期和服务端联调，抓包调试都是必不可缺少的环节，而且抓包的效率深深的影响着开发的效率。</p>

<p>由于REST API 的盛行，已经有大量现成的工具可以方便的进行抓包，比如大名鼎鼎的<a href="http://www.telerik.com/fiddler">Fiddler</a>， 以及免费且跨平台的<a href="https://github.com/AlloyTeam/Rythem">Rythem</a>。应用开发者只需要简单的<a href="http://docs.telerik.com/fiddler/configure-fiddler/tasks/ConfigureForAndroid">设置代理</a>，抓包信息即可尽收眼底。而非REST API的抓包则相对来说<a href="http://www.kandroid.org/online-pdk/guide/tcpdump.html">就麻烦了许多</a>，每抓一次包既要碰电脑敲命令，又要弄手机进行测试操作，很是忧桑。因此，为了提升效率，本猿就想到基于<a href="http://www.tcpdump.org/">tcpdump</a> 重新定制一个工具。想要的效果也不复杂，一个全局的悬浮窗，给出抓包控制按钮即可。这样调试起来，就不用来回切换那么麻烦了。</p>

<h3>编译tcpdump</h3>

<p>首先的工作当然是编译一个可用的tcpdump。虽然Android NDK 提供了方便的交叉编译工具，但是编译过程还是有<a href="https://github.com/chatch/tcpdump-android/blob/master/build-tcpdump">不少的坑</a>。比如android 不支持<code>setprotoent</code>以及<code>endprotoent</code>等。详细的编译过程，这里就不做讨论了。这里提供一个arm 版本的<a href="http://pan.baidu.com/s/1vpMFG">tcpdump for android 下载</a>。（基于tcpdump-4.6.2.tar.gz，libpcap-1.6.2.tar.gz）</p>

<h3>开发工具</h3>

<p>整个工具只有一个界面，一个全局窗口。代码在放在<a href="https://github.com/shaomingbo/EasyDump">Github 上</a>，当然你也可以直接从<a href="https://play.google.com/store/apps/details?id=de.mingbo.easydump">Google play下载</a>、使用。噢，前两天看到一个<a href="https://community.emc.com/people/Zhang%2CJiawen">妹纸</a>发了一张图和抓包相关，而且还不错，这里也mark 一下。</p>

<p><img class="center" src="http://mingbo.de/images/tcpdump_analyce.jpg"></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[深圳动物园一日游]]></title>
    <link href="http://mingbo.de/blog/2014/04/13/one-day-at-shenzhen-zoo/"/>
    <updated>2014-04-13T19:46:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/04/13/one-day-at-shenzhen-zoo</id>
    <content type="html"><![CDATA[<p>天气不错，心情也还不错。约上小伙伴，去了一趟动物园。</p>

<p><img src="https://farm4.staticflickr.com/3744/13818594364_e3c8702b88_c.jpg" alt="被关着的老虎" /></p>

<hr />

<p><img src="https://farm8.staticflickr.com/7225/13818281645_b9dd04eb91_c.jpg" alt="小猴子" /></p>

<p>更多图片，查看我的<a href="https://www.flickr.com/photos/22714894@N05/sets/72157643886838455/">Flickr相册</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[使用Dropbox搭建私人git仓库]]></title>
    <link href="http://mingbo.de/blog/2014/01/02/shi-yong-dropboxda-jian-si-ren-gitcang-ku/"/>
    <updated>2014-01-02T13:59:00+08:00</updated>
    <id>http://mingbo.de/blog/2014/01/02/shi-yong-dropboxda-jian-si-ren-gitcang-ku</id>
    <content type="html"><![CDATA[<h3>Why this?</h3>

<ul>
<li>有私人项目(不限于代码以及设计方案)</li>
<li>该项目（暂时）不公开</li>
<li>需要<strong>版本控制</strong></li>
<li>虽然github 是一个不错的选择，但就项目目前状况而言premium 账号显得并不划算</li>
<li>项目备份以及研发环境的迁移</li>
<li>and so on</li>
</ul>


<h3>How</h3>

<ul>
<li>就像stage area是working directory与本地仓库的缓存，本地仓库是working directory与中心仓库之间的缓存</li>
<li>我们把dropbox看成是中心仓库，那么working directory与中心仓库之间的缓存就是本地仓库了</li>
<li>建立本地仓库<code>git init &lt;path to your project&gt;</code></li>
<li>构建stage area<code>git add .</code></li>
<li>初始化提交<code>git commit -m 'repo init'</code></li>
<li>创建dropbox 中心仓库<code>git init --bare ~/Dropbox/git/center.git</code></li>
<li>建立远程连接<code>git remote add dropbox ~/Dropbox/git/center.git</code></li>
<li>备份本地仓库<code>git push dropbox master</code></li>
<li>拉取中心仓库<code>git pull dropbox master</code></li>
<li>若开发环境发生了迁移，比如从公司换到了家里，<code>git clone ~/Dropbox/git/center.git</code></li>
</ul>


<h3>写在最后</h3>

<ul>
<li>好的云存储服务有很多，而dropbox是客户端最完备的一个。本文介绍的方法显然不限于dropbox一家。</li>
<li><code>~/Dropbox/</code>是dropbox安装后的默认路径，不同的云存储的本地默认地址会不同</li>
<li>git子目录是我自己创建的，所以<code>center.git</code>也可以根据需求命名</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[git 常用命令复习]]></title>
    <link href="http://mingbo.de/blog/2013/12/23/git-revise/"/>
    <updated>2013-12-23T11:13:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/12/23/git-revise</id>
    <content type="html"><![CDATA[<p>最近打算对<a href="http://mingbo.de/soulsaunter">帝傲狮</a>进行重构，觉得应该是时候启用版本控制工具来管理代码了。想当初为了学习这个，我还专门看了几天的<a href="http://book.douban.com/subject/5311565/">《Git版本控制管理》</a>，可是长期在公司使用clearcase，现在我连它长什么样都记不起来了-_&ndash;# 。悔恨当初做的笔记太渣，现在重新把书啃一遍那基本上又不太现实。好在这两年网上积累了好多有关的优秀资源，以下便是我这两天通过学习&amp;使用这些网络资源而整理的<strong>git 常用命令</strong>的使用说明</p>

<h3>git clone</h3>

<ol>
<li>clone 操作将会复制一个已经存在的git仓库</li>
<li>clone 操作会自动的创建一个名叫<code>origin</code>的远程连接指向原始的仓库</li>
<li>This makes collaborating with Git fundamentally different than with SVN</li>
</ol>


<h3>git config</h3>

<ol>
<li><code>git config user.name</code>设置用户名</li>
<li><code>git config user.email</code>设置电子邮箱</li>
<li>默认情况下是对当前仓库进行设置，加上<code>--global</code>标签之后，则为全局设置</li>
</ol>


<h3>git add</h3>

<ol>
<li>直到运行<code>git commit</code>之前，<code>git add</code>并没有直接影响仓库</li>
<li><code>git add &lt;file&gt;</code>stage 单个文件的改变</li>
<li><code>git add &lt;directory&gt;</code>stage 整个目录的改变</li>
<li><code>git add -p</code>交互式的add</li>
</ol>


<h3>git commit</h3>

<ol>
<li>将处于stage 状态的该表保存到仓库</li>
<li><code>git commit -a</code>会将工作区的所有改变保存到仓库</li>
<li> 除非你真的准备好了，否则git 不会强迫你与中心仓库记性交互</li>
<li> 就像stage 空间是工作目录与代码仓库之间的缓存一样，本地仓库是开发者的代码贡献与中心仓库之间的缓存</li>
<li> <code>git commit --amend</code>修改之前的commit。这将导致当前的stage区间与前一个commit的状态融合。</li>
<li> 和<code>git reset</code>一样，<code>git commit --amend</code>不应该发生在发布后的<code>&lt;commit&gt;</code>上。</li>
<li> <code>--no-edit</code>可以使<code>git commit --amend</code>使用上一次commit的注释</li>
</ol>


<h3>git status</h3>

<p>用来显示工作区的状态</p>

<h3>git log</h3>

<ol>
<li>该命令被用来显示commit 历史</li>
<li><code>git log</code>将使用默认格式显示，通常会输出多于一屏的内容。使用空格翻页，使用<code>q</code>退出。</li>
<li><code>git log -n &lt;limit&gt;</code>限制输出的条数</li>
<li><code>git log -oneline</code>高度抽象概括提交情况</li>
<li><code>git log --stat</code>同时呈现相关文件、相关行的信息</li>
<li><code>git log --author=&lt;pattern&gt;</code>搜索指定作者的commit。pattern可以是包含正则表达式的字符串</li>
<li><code>git log &lt;since&gt;..&lt;until&gt;</code>搜索两者之间的commit。since、until既可以是commitID,也可以是branchName或者是其他的<a href="http://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html">reference</a></li>
<li><code>git log &lt;file&gt;</code>查看指定文件的提交情况</li>
<li><code>git log --graph --decorate ﻿--oneline</code>&mdash;graph 顾名思义，用来图形化提交状态；——decorate用来显示分支或者便签的名称；</li>
</ol>


<h3>git checkout</h3>

<ol>
<li>该命令有三种功能：检出file，检出commit，检出branch</li>
<li><code>git checkout &lt;existing-branch&gt;</code>更新本地仓库与<code>&lt;exsiting-branch&gt;</code>匹配，之后的操作都将记录到该分支上。检出分支后，不必担心之前的最新代码会遭到污染，任何改变如果没有<code>commit</code>，只存在于stage状态</li>
<li><code>git checkout &lt;commit&gt;</code>检出commit。会使得工作目录的所有代码变成该commit的状态(<code>&lt;commit&gt;</code>可以为commit hash或者是tag)。检出一个旧的commit，并不会造成代码丢失，开发状态将会变为<code>detached HEAD</code>。值得注意的是，开发应当发生在真正的分支上，否则代码融合的时候会遇到不小的麻烦。</li>
<li><code>git checkout &lt;commit&gt; &lt;file&gt;</code>检出文件。和之前的检出不同，这种检出会影响到当前工程，使得该文件的状态变为<strong>Change to be committed</strong>,若希望还原回主线状态，应使用<code>git checkout HEAD &lt;file&gt;</code></li>
<li><code>git checkout -b &lt;new-branch&gt;</code>创建分支，并切换到该分支去</li>
<li><code>git checkout -b &lt;new-branch&gt; &lt;exsiting-branch&gt;</code>与上条一样，不过创建的分支是基于&lt;existing-branch>，而不是当前分支</li>
</ol>


<h3>git revert</h3>

<ol>
<li>作为undo 操作存在</li>
<li>并没有删除任何commit 结点，而是将之前的结点append 到HEAD</li>
<li>比reset 更安全</li>
</ol>


<h3>git reset</h3>

<ol>
<li>是一个可能影响仓库安全的命令</li>
<li>和<code>git checkout</code>一样，根据目的不同，有多种用法</li>
<li><code>git reset &lt;file&gt;</code>清除该文件的stage状态，保留工作区间的文件状态</li>
<li><code>git reset</code>将stage恢复到最近一次提交，保留工作区间的文件状态</li>
<li><code>git reset --hard</code>将stage和工作区间的文件都恢复到最近一次提交</li>
<li><code>git reset &lt;commit&gt;</code>将stage恢复到<commit>的状态，保留工作区间的状态，以便re-commit的时候有一个更干净的stage</li>
<li><code>git reset --hard &lt;commit&gt;</code>将stage和工作区间的文件恢复到<commit>状态</li>
<li>需要注意的是：如果<commit>之后的仓库被发布过，那么你不应该使用<code>git reset --hard &lt;commit&gt;</code>，因为有可能其他开发者对这个<code>&lt;commit&gt;</code>及之后的代码有依赖</li>
</ol>


<h3>git clean</h3>

<ol>
<li>该命令通常和<code>git reset --hard</code>一起使用</li>
<li>主要目的是用来删除工作区间里没有被追踪的文件</li>
<li><code>git clean -n</code>并不会真正执行清除，而是告诉你将会清除哪里文件</li>
<li><code>git clean -f</code>将会清理当前文件夹下的未被跟踪的文件，但并不会清理文件夹以及<code>.gitignore</code>中提及的文件</li>
<li><code>git clean -df</code>清理当前文件夹下的未被跟踪的文件和目录</li>
<li><code>git clean -xf</code>清理当前文件夹下的未被跟踪的文件以及那些git 通常忽略的文件</li>
</ol>


<h3>git branch</h3>

<ol>
<li>每一个branch都可以看做是独立的开发流水线，是上述所有操作组合形成的抽象引用</li>
<li><code>git branch</code>支持创建，罗列，删除以及重命名操作，通常需要配合<code>git checkout</code>进行分支切换，配合<code>git merge</code>进行代码融合</li>
<li>分支不仅可以使开发任务并行，还可以保证主线代码永远处于可用状态</li>
<li><code>git branch</code>罗列当前仓库中所有的分支，带上<code>-r</code>可以罗列远程分支</li>
<li><code>git branch &lt;branch-name&gt;</code>创建分支，但并没有切换过去</li>
<li><code>git branch -d &lt;branch-name&gt;</code>如果代码已经merge回主线，则安全的删除该分支，否则给予警告，且不执行删除操作</li>
<li><code>git branch -D &lt;branch-name&gt;</code>强行删除分支</li>
<li><code>git branch -m &lt;branch-name&gt;</code>修改当前分支的名字</li>
</ol>


<h3>git merge</h3>

<ol>
<li>通常来说merge 有两种算法来执行：<code>fast-foward merge</code>以及<code>3-way merge</code></li>
<li>当前分支与目标分支之间是<strong>linear path</strong>，则执行<code>fast-forwad merge</code>，如图1所示，git 只需要将当前分支的HEAD移过来即可（不走回头路）</li>
<li>如果当前分支与目标分支之间有分叉,如图2所示，则执行<code>3-way merge</code>。</li>
<li>git 使用<code>edit/stage/commit</code>的工作流程来解决merge 冲突：使用edit来解决冲突，使用add 来闭合修改，使用commit 来完成merge</li>
</ol>


<h3>git rebase</h3>

<ol>
<li><code>git rebase &lt;base&gt;</code>将当前分支rebase为<code>&lt;base&gt;</code>，其中<code>&lt;base&gt;</code>可以为commit、tag或者是分支名称</li>
<li>使用rebase主要目的是保持项目仓库的linear状态，以便分支融合的时候可以使用<code>fast-forward merge</code></li>
<li>和<code>git reset</code>一样，不要对以发布的仓库部分执行rebase</li>
<li><code>git rebase -i</code>以交互的形式进行rebase</li>
</ol>


<h3>git remote</h3>

<ol>
<li>该命令可以让你创建、查看、删除与其他仓库的连接，这些连接更像是一个个书签</li>
<li><code>git remote</code>呈现你的所有连接</li>
<li><code>git remote -v</code>和上一条命令一样，但是要多显示URL</li>
<li><code>git remote add &lt;name&gt; &lt;url&gt;</code>创建一个连接，在此之后，可以直接使用<code>&lt;name&gt;</code>来访问这个连接了</li>
<li><code>git remote rm &lt;name&gt;</code>删除这个连接</li>
<li><code>git remote rename &lt;old-name&gt; &lt;new-name&gt;</code>修改连接名称</li>
</ol>


<h3>git fetch</h3>

<ol>
<li><code>git fetch &lt;remote&gt;</code>将该连接的所有分支全部导入本地</li>
<li><code>git fetch &lt;remote&gt; &lt;branch&gt;</code>仅导入该连接的特定分支</li>
<li>我们可以checkout 远程分支，但此刻会处于一个HEAD detached状态；可以想象这些远程分支为只读的，我们能做的是review他们的代码以及<strong>merge</strong>到我们的本地分支上来</li>
</ol>


<h3>git pull</h3>

<ol>
<li><code>git pull &lt;remote&gt;</code>将该连接的当前分支导入本地，并进行融合操作</li>
<li><code>git pull --rebase &lt;remote&gt;</code>和上述操作效果一样，但使用rebase而不是融合</li>
<li><code>git pull</code>与SVN的<code>update</code>类似，作用都是保持本地代码最新</li>
<li><code>--rebase</code>可以避免不要的merge操作</li>
<li>使用<code>git config --global branch.autosetuprebase always</code>将pull的默认动作设置为rebase</li>
</ol>


<h3>git push</h3>

<ol>
<li><code>git push &lt;remote&gt; &lt;branch&gt;</code>将<branch>推送到远程仓库去。如果本地仓库和远程仓库之间的代码不能执行<code>fast-forward merge</code>，则git 会拒绝该次推送</li>
<li><code>git push &lt;remote&gt; --force</code>和上一条效果一样，只不过会强制执行</li>
<li><code>git push &lt;remote&gt; --tags</code>将本地标签全部推送到远程仓库去</li>
</ol>


<h2>参考资料</h2>

<ul>
<li><a href="https://www.atlassian.com/git/tutorial/">Git Tutorials</a></li>
<li><a href="http://pcottle.github.io/learnGitBranching/">Learn git branching</a></li>
</ul>


<h2>附图</h2>

<ul>
<li>图1 a fast-forward merge</li>
</ul>


<p><img src="http://mingbo.de/images/20131223/git-revise-merge-fast-forward.png" alt="fast-forward merge 示意图" /></p>

<ul>
<li>图2 a 3-way merge</li>
</ul>


<p><img src="http://mingbo.de/images/20131223/git-revise-merge-three-way.png" alt="3-way merge 示意图" /></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[android无线调试]]></title>
    <link href="http://mingbo.de/blog/2013/12/03/debug-over-wifi/"/>
    <updated>2013-12-03T20:04:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/12/03/debug-over-wifi</id>
    <content type="html"><![CDATA[<p>使用无线调试对于我来讲，只有一个理由：爱惜手机电池。长时间的调试过程，使得我们的爱机必须通过USB 与电脑相连。长此以往，手机电池就会变得非常不经用。</p>

<p>很早之前，以为只有获得ROOT 权限的手机才能开启这个功能，直到有一天被我发现statckoverflow 上的一个<a href="http://stackoverflow.com/questions/2604727/how-can-i-connect-to-android-with-adb-over-tcp">帖子</a>。（之所以会有这个错觉，是因为看到Google Play上提供的相关APP 都有root权限的声明）。那么，这里我就分别总结一下，不同情况下该如何使用<strong>无线adb</strong></p>

<h2>如果手机拥有ROOT 权限</h2>

<p>在shell 里执行以下命令：</p>

<figure class='code'><figcaption><span>开启adb无线调试</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>su
</span><span class='line'>setprop service.adb.tcp.port 5555
</span><span class='line'>stop adbd
</span><span class='line'>start adbd
</span></code></pre></td></tr></table></div></figure>


<p>这也是大部分完成无线调试APP的核心基础：在应用内使用Process 对象来执行这些命令。其中<code>start/stop adbd</code>，你可以通过手动开启/关闭调试模式来完成同样的目的。如果希望回到usb 模式，则应该将<code>tcp.port</code>修改回-1。</p>

<h2>如果手机没有ROOT 权限</h2>

<ul>
<li>事情并没有因为少一个权限而变得麻烦</li>
<li>首先将手机通过USB连接到PC</li>
<li>在命令行中执行<code>adb tcpip 5555</code>，即可开启无线adb了</li>
<li>想要恢复有线adb时，在保持手机与PC连接的前提下，命令行中执行<code>adb usb</code>即可</li>
</ul>


<h2>连接与断开手机</h2>

<ul>
<li>开启无线adb后，想要通过WIFI连接手机，请先确保电脑和手机在同一网段</li>
<li>连接：adb connect android-device-ip</li>
<li>终止：adb disconnect</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[进程与线程]]></title>
    <link href="http://mingbo.de/blog/2013/11/17/processes-nand-threads-notes/"/>
    <updated>2013-11-17T15:57:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/11/17/processes-nand-threads-notes</id>
    <content type="html"><![CDATA[<p>Android 组件具有天生的跨进程特性，因此，android 应用开发者通常是不需要关注进程概念的。但这也往往导致我们忽视了进程的一些细节问题。这里，就目前的认识做一些小结。</p>

<p>Android 是以<strong>进程对用户的重要性</strong>为依据来管理进程的。其重要性分为5个层级，重要性越低的进程越容易被系统干掉。</p>

<ul>
<li>Foreground process</li>
<li>Visible process</li>
<li>Service process</li>
<li>Background process</li>
<li>Empty process</li>
</ul>


<h2>Foreground process</h2>

<p>优先级最高，即使在系统内存吃紧的情况下不到万不得已，是不会杀这类进程的。如果这类进程被杀，通常是因为用户界面卡得不能动弹而被迫执行的。这类进程所包含的组件通常满足以下特征：</p>

<pre><code>1.  activity 正在与用户进行交互
2.  service 绑定的activity 正在与用户交互
3.  service 执行了`startForeground()`
4.  service 正在执行生命周期回调函数
5.  BroadcastReceiver 正在执行`onReceived()`
</code></pre>

<h2>Visible process</h2>

<p>优先级次之，系统在保证Foreground process 的前提下，保证这类进程的运行。这类进程虽不直接与用户交互，但用户能在界面上看到与之相关的组件。这些组件通常会有以下特征：</p>

<pre><code>1.  activity 在Paused 状态，处于可视但不可交互的状态
2.  service 绑定的activity 处于paused 状态
</code></pre>

<h2>Service process</h2>

<p>虽然没有与用户直接交互，但它们所做做的事情与用户息息相关。所以系统会按照优先级，在保留上述进程的前提下，尽可能的保留此类进程。不符合上面描述的Service 进程都属于Service Process。</p>

<h2>Background process</h2>

<p>随时会被系统回收。为了提升用户体验，系统使用LRU 缓存维护这类进程，使得用户最近使用的进程推迟回收。这类进程通常只持有不可视组件，如paused 状态下的activity。</p>

<h2>Empty process</h2>

<p>这类进程的存在仅仅只是为了让启动进程看起来更快一些，也因此更容易被系统回收。</p>

<h2>android:multiprocess 属性</h2>

<p>默认情况下，该设置为false，表示该组件会在定义组件的应用进程中运行。当第三方应用调用该组件时，会有2个进程启动：第三方应用以及定义组件的应用。</p>

<p>如果该属性设置为true，表示允许该组件“嫁”出去：当第三方应用调用该组件时，会直接在该进程中构造组件。这么做会让组件在整个系统中的数量增加，但与调用者之间的交互更为紧密。值得一提的是，在实际开发中，这么做需要考虑并发的复杂性。</p>

<h2>零散的知识点</h2>

<ul>
<li>一个进程如果包含多个组件，那么，该进程取组件中优先级最高的作为自己的优先级</li>
<li>一个进程的优先级会因为其他进程的依赖而提升</li>
<li>由于service 比后台进程优先级高，一些长时间操作在service 中运行更有安全感</li>
<li>IBinder、 ContentProvider 的方法通常要注意线程安全</li>
</ul>


<h2>参考资料</h2>

<ul>
<li><a href="https://developer.android.com/guide/components/processes-and-threads.html">Processes and Threads</a></li>
<li><a href="https://developer.android.com/guide/topics/manifest/activity-element.html#multi">activity属性</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[视图的绘制]]></title>
    <link href="http://mingbo.de/blog/2013/11/08/how-android-draws-views/"/>
    <updated>2013-11-08T14:48:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/11/08/how-android-draws-views</id>
    <content type="html"><![CDATA[<p>在<a href="http://mingbo.de/blog/2013/11/04/listview-re-getview/">《Listview 子控件重复加载》</a>中谈到了<code>onMeasure()</code>方法，谈到了layout，但更细致的内容没有提到。这里找了一下相关资料来补充：</p>

<ul>
<li>绘制过程是一个控件树遍历的过程，从根控件开始</li>
<li><code>ViewGroup</code>负责通知它的子视图，<code>View</code>负责自绘，按照顺序，父视图先于子视图绘制</li>
<li>绘制实际上是2个自顶向下的过程：measure 和layout。</li>
<li>经过measure 之后，每个<code>View</code>都会保存自己的尺寸，而在layout 过程中父视图会使用这些尺寸来摆放子视图</li>
<li><code>View</code>对象的高宽会受到父视图的限制，以保证整个视图的正常显示</li>
<li>父视图会<strong>多次调用子视图的measure方法</strong>。比如：父视图会先计算不受约束的情况下，子视图的大小；如果子视图过大或者过小，父视图都会指定一个具体的值</li>
<li>Measure 过程还会涉及<code>ViewGroup.LayoutParams</code>以及<code>MeasureSpec</code>的设置</li>
<li><code>ViewGroup.LayoutParams</code>被View 对象用来告诉其父控件，自己想如何被计算以及摆放，最基本的是指定长宽：

<ol>
<li>MATCH_PARENT, which means the View wants to be as big as its parent (minus padding)</li>
<li>WRAP_CONTENT, which means that the View wants to be just big enough to enclose its content (plus padding).</li>
<li>具体数值</li>
</ol>
</li>
<li>MeasureSpec 指定一种计算模式：

<ol>
<li>UNSPECIFIED: This is used by a parent to determine the desired dimension of a child View.</li>
<li>EXACTLY: This is used by the parent to impose an exact size on the child.</li>
<li>AT MOST: This is used by the parent to impose a maximum size on the child.</li>
</ol>
</li>
</ul>


<h2>参考资料</h2>

<ul>
<li><a href="https://developer.android.com/guide/topics/ui/how-android-draws.html">How Android Draws Views</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Listview 子控件重复加载]]></title>
    <link href="http://mingbo.de/blog/2013/11/04/listview-re-getview/"/>
    <updated>2013-11-04T20:45:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/11/04/listview-re-getview</id>
    <content type="html"><![CDATA[<p>这是一个常见的listview 资源文件的写法。</p>

<figure class='code'><figcaption><span>listview资源文件 </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'>    <span class="nt">&lt;ListView</span>
</span><span class='line'>        <span class="na">android:layout_width=</span><span class="s">&quot;match_parent&quot;</span>
</span><span class='line'>        <span class="na">android:layout_height=</span><span class="s">&quot;wrap_content&quot;</span>
</span><span class='line'>        <span class="na">android:id=</span><span class="s">&quot;@+id/listView&quot;</span>
</span><span class='line'>    <span class="nt">/&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>注意到<code>android:layout_height</code> 被设置为<code>wrap_content</code>，这似乎很合理。但如果我们留心的话可以发现，与这个listview 关联的adapter对象的<code>getView()</code>方法被<strong>重复调用了好几遍</strong>!这对于那些依赖listview 展示大量数据的应用来说，绝对是性能打击。</p>

<h2>解决方案：</h2>

<p>将这个属性设置为<code>match_parent</code> 或者固定的数值</p>

<h2>原因：</h2>

<p>通过分析<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/widget/AbsListView.java">AbsListView</a> 以及<a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/widget/ListView.java">Listview</a>的代码，发现答案应该在<code>onMeasure()</code>方法里：由于<code>wrap_content</code> 并没有指定一个固定值，<strong>系统需要通过尝试layout来满足<code>wrap_content</code></strong>。每次尝试都会调用<code>onMeasure()</code>,<code>layoutChildren()</code>,使得<code>getView()</code>被重复调用</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Service 心经]]></title>
    <link href="http://mingbo.de/blog/2013/11/04/services-note/"/>
    <updated>2013-11-04T15:21:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/11/04/services-note</id>
    <content type="html"><![CDATA[<p>Service 作为一个“看不见”的android 组件，天然的就应该为Android 程序去处理那些费事费力的苦活。但即使这样，作为四大组件之一的它依然运行在主线程上。也就是说，那些理所当然的“负担”如果<strong>直接运行在Service 上必然会影响程序的UI 性能</strong>。因此，我们通常需要与之对应的后台线程来完成工作。</p>

<p>与其他组件一样，要想Service 正常运行，就必须在<code>AndroidManifest.xml</code> 中对其进行声明。如果Service 作为一个开放组件（允许外部程序调用），则还需要设置Intent Filter 来支持隐式调用；反之，如果它只在程序内运行，则可省去Filter 设置，更妥当的做法是将<code>export</code>属性设置为false。</p>

<p>总的来说，Service 有两种形式：<strong>命令启动类(Started)</strong> 与 <strong>绑定类(Bound)</strong>。两种形式之间并不总是非此既彼的关系，同一个应用的同一个Service 可以同时满足这两种形式，而这一点完全取决于应用逻辑。</p>

<h2>命令启动类（started）的Service</h2>

<ul>
<li>一旦被启动，如果没有其他组件调用<code>stopService()</code>而且自己也没有调用<code>stopSelf(int)</code>，那么它将会一直运行下去（除非系统内存吃紧）</li>
<li><code>onStartCommand()</code>有三类返回值，分别代表Service 在执行完该函数之后，如遭遇系统Kill 的不同策略：

<ol>
<li><code>START_NOT_STICKY</code>，除非有新的命令，不然不会重新构建Service</li>
<li><code>START_STICKY</code>，不死Service。如果系统资源允许，会重新构建Service并调用<code>startCommand()</code>，但并不会重新传递最后一次发过来的intent。（适用于不care 命令，而care 启没启动的操作，比如恢复音乐播放）</li>
<li><code>START_REDELIVER_INTENT</code>, 对intent 负责到底。在系统资源允许的情况下，重新构建service并将最后一次发送的intent 通过参数传递给<code>startCommand()</code>。（适用于care 命令，比如恢复下载某个文件）</li>
</ol>
</li>
<li>和绑定类服务不同，如果希望得到交互结果，则必须使用Broardcast传递</li>
<li><code>startCommand()</code> 会有并发的情况，但<code>stopService()</code> 只会执行一次。所以在具体应用场景中，我们需要考虑命令Service们能不能、该不该被中途停止。如果得到的答案是否定的，那么我们应该尝试使用<code>stopSelf(int)</code>来管理service，其参数为希望停止的service ID。</li>
</ul>


<h2>简化的命令服务IntentServie</h2>

<p>如果你不希望Service 处理并发请求，那么IntentService 是不二的选择。之所以这么说，是你几乎只需要三步就可以实现一个为你完成后台操作的单线程服务：1，继承IntetnService；2，覆盖<code>onHandleIntent()</code>；3，在构造函数中初始化父类。那么IntentService 会帮你做到：
&ndash;   创建一个后台线程
&ndash;   创建一个消息队列来执行任务
&ndash;   当没有任务的时候自动停止</p>

<p>如果希望自己覆盖某些Service 的方法，<strong>那么一定要记得调用super</strong>，否则会导致一些意想不到的结果出现。</p>

<h2>绑定类(Bound)服务</h2>

<ul>
<li><code>IBinder</code> 表示客户端与服务端交互的接口</li>
<li>三种提供Binder 接口的方法：

<ol>
<li>直接继承Binder 类，提供API 给客户端调用。这些API 既可以由Binder 子类来实现，也可以通过API 返回该服务或其他服务的实例，由服务本身提供的方法去完成调用；<strong>这种方法适用与私有服务</strong>。</li>
<li>使用Messenger返回binder。该方法的本质是AIDL，提供了一种线程安全的跨进程通讯方案。<strong>这种方法适用于服务与调用组件不在同一个进程的情况</strong>。详见<code>参考代码</code></li>
<li>如果服务不但跨进程，而且还希望处理并发请求，则应该使用<code>AIDL</code>来实现</li>
</ol>
</li>
<li>多个客户端可以绑定同一个服务，但<code>onBind()</code> 只会在第一个客户端绑定的时候被调用。之后绑定的客户端虽然会获得同样的binder，但已经不会再调用<code>onBind()</code>了</li>
<li>四大组件中除了<code>Broardcast Receiver</code>之外都可以绑定服务</li>
<li><code>onServiceDisconnected()</code>只会在服务崩溃的时候调用，<strong>而并不会在unbind 的时候被调用</strong></li>
<li>bind 应与unbind 成对出现，服务与其绑定的客户端组件共存亡。如果交互工作完全结束的话，可以尽早的结束绑定，而不一定要等到组件消亡(比如某浪sso 接口那样)</li>
<li>bind 与unbind 不应该出现在<code>onResume()</code> 以及<code>onPause()</code>里，这样会使得服务的绑定与解绑操作过于频繁</li>
<li>绑定类服务又恰巧被调用了<code>startService()</code>，则只有同时满足以下条件，服务才会终止：1，没有客户端组件绑定该服务；2，服务调用了<code>stopService()</code> 或者<code>stopSelf(int)</code></li>
<li>如下图所示，只有当<code>unBind()</code>返回true 的时候，下次绑定的时候，系统才会调用<code>onRebind()</code>，否则还会继续调用<code>onBind()</code></li>
<li>所有对象均为跨进程引用计数</li>
<li>如果服务连接丢失的话，容易引发<code>DeadObjectException</code></li>
</ul>


<p><img src="http://mingbo.de/images/20131104/service_binding_tree_lifecycle.png" alt="服务的生命周期" /></p>

<h2>前台服务</h2>

<ul>
<li>前台服务能够极大的提升不被杀掉的概率</li>
<li>前台服务必须有与之对应<strong>ON-GONING</strong>通知显示</li>
<li><code>stopForeground()</code>并不会停止服务；而停止服务后，通知自然消亡</li>
</ul>


<h2>AIDL</h2>

<ul>
<li>Stub 继承自Binder 还实现了aidl 中的接口，除此之外还提供类似<code>asInterface(IBinder)</code>的辅助方法</li>
<li>AIDL 接口调用是直接方法调用，不应该想当然的认为它们在哪个线程中执行。如果是本地进程调用，那么接口会和调用者使用同一个线程执行；如果是远程调用，会由服务所在的进程的线程池派发一个线程来执行；如果使用oneway修饰，在远程调用中是非阻塞的，而在本地调用中依然是同步的。</li>
<li>AIDL 支持java 的基础类型、<code>String</code>、<code>Charsequence</code>、<code>List</code>、<code>Map</code>，除此之外的类型，都需要实现Parcebal接口，且必须使用import来声明，即使它们在同一个包</li>
<li>调用不保证会执行，所以从一开始设计的时候就应该考虑线程安全</li>
<li>为了避免ANR，应该将调用转移到单独的线程中执行</li>
<li>抛出的异常不会被调用者捕获（跨进程异常是不可取的）</li>
</ul>


<h2>参考资料</h2>

<ul>
<li><a href="https://developer.android.com/guide/components/services.html">Services</a></li>
<li><a href="https://developer.android.com/guide/components/bound-services.html">Bound Services</a></li>
<li><a href="https://developer.android.com/guide/components/aidl.html">AIDL</a></li>
</ul>


<h2>参考代码</h2>

<ul>
<li><a href="https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.java">MessengerService.java</a></li>
<li><a href="https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java">MessengerServiceActivities.java</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Activity 心经]]></title>
    <link href="http://mingbo.de/blog/2013/11/03/activity-note/"/>
    <updated>2013-11-03T11:33:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/11/03/activity-note</id>
    <content type="html"><![CDATA[<p>Android 四大组件中，Activty 最为常见。这里对Activity 的相关知识进行梳理，以供日后查阅。</p>

<h2>生命周期</h2>

<p><img src="http://mingbo.de/images/20131103/basic-lifecycle.png" alt="生命周期" /></p>

<ul>
<li>六种状态构成了生命周期金字塔</li>
<li>位于顶端的Resumed 状态有着与用户交互的特权</li>
<li>金字塔的第二层对于用户来说可视或部分可视</li>
<li>金字塔的第三层虽然存在于系统内存，但对于用户来说不可视</li>
<li>从箭头的指向来看，<strong>Resumed 与Paused 之间的转换最为活跃</strong>，为了保证UI流畅，这里应尽可能的放一些轻巧的代码</li>
<li>从Activity 对象的角度来讲，只有<strong>三种状态持续存在</strong>：Resumed，Paused，Stopped</li>
<li>Activity 的Paused 状态对于自身而言是被前台的Activity(或窗口）遮挡了一部分，对于用户来讲这个Activity 只有部分可见。<strong>在这种状态下，Activity 对象依然停留在内存里，也同时被windows manager 引用着</strong>；Activity 的Stopped状态与Paused 状态类似，<strong>除了</strong>更彻底的不可见<strong>之外</strong>，此时此刻这个Activity已经不被windows manager 引用了</li>
<li>当Activity 的Paused 或Stopped状态遭遇系统内存吃紧：系统要么温柔的调用<code>finish()</code> 逐个清理Activity，要么粗暴的结束掉该对象所在的进程</li>
</ul>


<h2>资源管理</h2>

<ul>
<li>鉴于<strong>内存吃紧</strong>的不可预见，我们应该在<code>onStop()</code>中释放掉那些容易造成内存泄漏的资源</li>
<li>关于<code>onPause()</code>方法更具体的建议

<pre><code>1.  停止动画等消耗CPU 的操作
2.  注销广播监听器、传感器监听器等影响电池寿命的资源
3.  仅当用户需要的情况下，提交未保存的状态。为了保证界面的平滑切换，应避免在这里调用CPU-Intensive 的操作（如，写数据库），而应将其移至之后的stop方法中
</code></pre></li>
<li> <code>onSaveInstanceState()</code>默认的会保存Activity 的视图结构，如Checkbox 是否被选中，Edittext 中的编辑文字，但前提是为每个控件都配备有一个独特的ID</li>
<li> 当且仅当有数据需要恢复的时候，<code>onRestoreInstanceState()</code>才被系统调用，且一般在<code>onStart()</code>之后</li>
<li> 可以使用<code>finish()</code>以及<code>finishActivity(int requestCode)</code>来关闭Activity，但除非你确定必须这么做，<strong>不然最好将这项工作交由系统来完成</strong></li>
<li> 一般情况下，如果一个应用退到后台超过30分钟，系统会清理掉该堆栈中的acitivties。但如果<code>android:alwaysRetainTaskState</code>属性被设置为true，用户重返应用的时候总会获得上次Activity的状态。这一点对于浏览器应用相当有用</li>
<li> 如果属性<code>android:noHistory</code>被设置为true，那么当用户从这个activity 离开的时候，就会自动调用<code>finish()</code>。因此，无法从任务堆栈中找回该activity 的历史轨迹了</li>
<li> 一般来讲，<code>onDestroy()</code> 的执行应该在<code>onPause()</code>或者<code>onStop()</code>之后。但如果，你在<code>onCreate()</code>中直接调用<code>finish()</code>，则系统会直接跳过他们调用<code>onDestroy()</code>的</li>
</ul>


<h2>运行模式</h2>

<ul>
<li>Task 是由一组为了完成某项工作的Activities 组成，它们<strong>不受</strong>进程或应用的约束，体现的是Activity 之间<strong>相互调用和执行</strong>的关系</li>
<li>Activity 的运行模式有四种:standard，singleTop，singleTask，singleInstance。除了Standard 之外的三种模式对于activity均有不同程度的复用</li>
<li>standard 与singleTop 类似，在程序运行的过程中，这两种模式的activity 会有多个实例出现在一个或多个Task 中的任意位置。但二者之间有着细微的差别：singleTop 模式下的activty 如果在目标task 的顶部，对于新收到的intent会使用<code>onNewIntent()</code>回调来响应；其他情况下和standard 模式一样，对于每一个新收到的intent 都对应一个新的实例。</li>
<li>singleTask 与singleInstance 类似，都只能出现在一个task stack 内，且始终呆在stack 的根部，对于整个设备来说它们只有一个实例。除此之外，它们都使用<code>onNewIntent()</code> 来响应创建后收到的intent。但两者之间有着细微的差别：singleTask 允许引入新的activity 来组建task，而singleInstance 永远自成一派。很显然，singleInstance适合内存消耗较多的界面，如播放器、浏览器等。</li>
</ul>


<h2>其他</h2>

<ul>
<li>如果项目中没有一个Activity 被设置为<code>MAIN action</code> 或者<code>LAUNCHER category</code>，那么桌面就不会显示相应的图标</li>
<li>程序运行过程中，如遇到配置变动，则会引起activity 重启，这是系统自动适配的行为。</li>
<li>重启后，其数据需要得到重新填充。如果数据量较小，则可以依赖<code>onSaveInstanceState()</code>以及<code>onRestoreInstanceState()</code>这两个回调接口；反之，则需要特殊对待：要么规避系统的重启行为，自己来做适配工作；要么在配置变更的时候通过接口<code>onRetainNonConfigurationInstance()</code>来保存数据对象，待到重启之后使用<code>getLastNonConfigurationInstance()</code>来恢复相关数据</li>
</ul>


<hr />

<h2>参考资料</h2>

<ul>
<li><a href="https://developer.android.com/training/basics/activity-lifecycle/starting.html">Starting an Activity</a></li>
<li><a href="https://developer.android.com/training/basics/activity-lifecycle/pausing.html">Pausing and Resuming an Activity</a></li>
<li><a href="https://developer.android.com/training/basics/activity-lifecycle/stopping.html">Stopping and Restarting an Activity</a></li>
<li><a href="https://developer.android.com/training/basics/activity-lifecycle/recreating.html">Recreating an Activity</a></li>
<li><a href="https://developer.android.com/guide/components/activities.html">Activities</a></li>
<li><a href="https://developer.android.com/guide/topics/manifest/activity-element.html">android:launchMode</a></li>
<li><a href="https://developer.android.com/guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[廉价的吐槽]]></title>
    <link href="http://mingbo.de/blog/2013/04/14/comment-is-cheap/"/>
    <updated>2013-04-14T19:44:00+08:00</updated>
    <id>http://mingbo.de/blog/2013/04/14/comment-is-cheap</id>
    <content type="html"><![CDATA[<p>这几年，你有没有发现“赞美”变成了一件奢侈的事情？尤其当你面对一群挑剔的听众，你的赞美往往会被人扭曲：“你拿了人家什么好处，这样帮他说话？”、“推销？你被洗脑了吧！”、“这你都没见过？有什么好大惊小怪的。”…诸如此类。不晓得从什么时候开始，开口赞美，也变得越来越有压力了。</p>

<p>赞美的起点似乎总比被赞美的事物要低，反而吐槽更容易让你居高临下，优越感一览无遗。不管人家说什么，你跟着去赞美，容易被人说成是拍马，而吐槽的言行至少不会显得你太低端（哈哈，说这是批判精神）。这种不用思考的“安全反应”，使得越来越多的人，习惯了吐槽。当跟风赞美变成弱智、“五毛”之后，跟风吐槽就显得有节操，有品位了。任何事情，你都可以表现得不屑一顾，“安全的”凌驾于其之上。只是，这种廉价的破烂习惯，让我们的社会关系变得越来越冷漠了。</p>

<p>其实，生活中并不缺乏温情的画面，讲一个刚刚发生的故事。昨天晚上，我习惯性的去了趟沃尔玛。轮到我结账的时候，为了不让后面排队的人等太久，我手忙脚乱的把货品往买来的2个袋子里塞。收银员打断了我的动作，说：你这些速冻食品压在最底下，回家的时候肯定就烂掉了。我愣了一下，结果，她很自然的接手了剩下来的操作。只用了一个袋子，就将所有货品麻利的装了进去，还照顾到了各种货品的运输属性（易碎性、温度、空间等），合理的安排了他们在装袋时的位置。很快的，我缓过神来，没有说话，但竖起了大拇指。</p>

<p>同样是排队结账，吐槽就显得太过廉价了。你永远可以调侃中国人多，甚至责怪超市收银出口太少，但绝大多数的情况下，你都无法避免排队等候。而这种廉价的吐槽行为，只会让自己愤怒，说不定还会影响他人，人与人之间就在消费吐槽时，变得淡了下来。</p>
]]></content>
  </entry>
  
</feed>
