<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>笨马</title>
    <description>当当当
</description>
    <link>http://yourdomain.com/</link>
    <atom:link href="http://yourdomain.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 25 Oct 2022 15:27:02 +0000</pubDate>
    <lastBuildDate>Tue, 25 Oct 2022 15:27:02 +0000</lastBuildDate>
    <generator>Jekyll v3.9.2</generator>
    
      <item>
        <title>Android Activity Task launch mode</title>
        <description>&lt;h1 id=&quot;android-activity-task-launch-mode&quot;&gt;Android Activity Task launch mode&lt;/h1&gt;

&lt;p&gt;基本上，task / task stack / back stack / 任务栈 / 回退栈 说的是一回事。&lt;/p&gt;

&lt;h2 id=&quot;进程&quot;&gt;进程&lt;/h2&gt;

&lt;p&gt;在默认情况下，一个应用程序的所有组件运行在同一个进程中。&lt;/p&gt;

&lt;p&gt;在 manifest 中用 process 属性指定组件所运行的进程的名字，同一个应用程序的不同组件可以运行在不同的进程中。&lt;/p&gt;

&lt;h2 id=&quot;task-stack&quot;&gt;Task stack&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/am/ActivityStack.java&quot;&gt;https://github.com/aosp-mirror/platform_frameworks_base/blob/master/services/core/java/com/android/server/am/ActivityStack.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;参考：Android ActivityStack 那片文章&lt;/p&gt;

&lt;p&gt;在 Activity 中可以用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTaskId()&lt;/code&gt; 获取当前 Activity 所在的 task 的 ID&lt;/p&gt;

&lt;p&gt;Task 就是用户为了完成某个功能而执行的一系列 Activity 序列。栈结构，栈顶的 Activity 就是屏幕正在展示的 Activity&lt;/p&gt;

&lt;p&gt;Task 中的 Activity 不一定属于同一个应用，Activity 可能会多次实例化（即使 Activity 来自不同的 Task）&lt;/p&gt;

&lt;p&gt;当用户启动应用时，该应用的 Task 将出现在前台。 如果应用不存在 Task（应用最近未曾使用），则会创建一个新 Task，并且该应用的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;主Activity&lt;/code&gt; 加入这个栈中。&lt;/p&gt;

&lt;p&gt;启动一个 Activity 会把这个 Activity 加入栈顶，之前的栈顶 Activity 会保存状态。用户按 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;返回&lt;/code&gt; 键会把栈顶的 Activity 出栈并 destroy，之前的栈顶 Activity 会恢复状态。&lt;/p&gt;

&lt;p&gt;当用户开始新任务（比如点击&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;主页&lt;/code&gt;键转到主屏幕），整个栈移动到后台。 栈中的所有 Activity 全部停止。&lt;/p&gt;

&lt;p&gt;当系统资源紧张时，可能将非栈顶 Activity 销毁，在非栈顶 Activity 又变成栈顶 Activity 时重建。此时需要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onSaveInstanceState()&lt;/code&gt; 保存状态，以便在重建时恢复状态&lt;/p&gt;

&lt;p&gt;默认情况下，如果一个应用在后台呆的太久，系统就会对该应用的 Task 进行清理，除了根 Activity，其他 Activity 都会被清理出栈。&lt;/p&gt;

&lt;h2 id=&quot;taskaffinity&quot;&gt;taskAffinity&lt;/h2&gt;

&lt;p&gt;taskAffinity 可以简单理解成 Task 的名称。默认的 taskAffinity 是 包名。可以给 Activity 设置不同的 taskAffinity。&lt;/p&gt;

&lt;p&gt;Activity 的 taskAffinity 可以设置成一个空字符串，表明这个 Activity 不属于任何 Task。&lt;/p&gt;

&lt;p&gt;不同的 APP 的 Activity 也可以有相同的 taskAffinity。他们会进入同一个 Task 中，尽管这些 Activity 不在一个应用进程中。&lt;/p&gt;

&lt;h2 id=&quot;launch-mode&quot;&gt;launch mode&lt;/h2&gt;

&lt;p&gt;launch mode 定义 Activity 的新实例 与 当前 Task 如何关联&lt;/p&gt;

&lt;p&gt;可以在 manifest 中定义，也可以在 startActivity 的 Intent 中定义&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;standard （默认）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不管 Task 里有没有，都创建一个新的 Activity 放在栈顶，谁启动的这个 Activity，这个 Activity 实例就在谁的 Task 中。一个 Task 可以拥有同一个 Activity 的多个实例。&lt;/p&gt;

&lt;p&gt;当非 Activity 的 Context 以 standard 去启动一个 Activity 时，会报错，因为非 Activity 的 Context 没有 Task 。如果想让非 Activity 的 Context 去启动 Activity ，需要让 Activity 设置一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_NEW_TASK&lt;/code&gt; 标记，这样就会创建一个新 Task ，此时这个 Activity 的 launch mode 实际上是 singleTask 。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;singleTop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果要启动的 Activity 正好在堆栈顶部，那就直接用它，而不去创建新的实例（会调用 onNewIntent ，而 onCreate 和 onStart 不会被调用），如果没在栈顶，则行为与 standard 一样。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;singleTask&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此 Acticity 的实例只有一个，只能存在于指定的 Task 中。&lt;/p&gt;

&lt;p&gt;如果没有相同 taskAffinity 的 Task ，就创建这个 taskAffinity 的新的 Task ，并创建 Activity 实例，加入这个新栈的栈顶。&lt;/p&gt;

&lt;p&gt;如果有相同 taskAffinity 的 Task ，没有 Activity 实例，则新建实例，加入这个栈的栈顶。&lt;/p&gt;

&lt;p&gt;如果有相同 taskAffinity 的 Task ，并且其中已经存在相应的 Activity 实例，会把位于这个 Activity 实例上面的 Activity 全部结束掉，让这个 Activity 实例位于栈顶。并调用 onNewIntent ，而 onCreate  不会被调用。有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_CLEAR_TOP&lt;/code&gt;的效果。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;singleInstance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个 Activity 在整个系统中只会存在于一个它专有的 Task ，这个 Task 里只有这一个 Activity 实例。只要存在这个栈，就存在 Activity 实例，反之亦然。
它的意义是只存在一个实例，单独放在一个 Task 栈里给别的 Task 栈&lt;strong&gt;共享&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;启动这个 Activity 时，如果系统不存在这个 Task ，则创建一个栈，并创建这个 Activity 实例加入栈中。&lt;/p&gt;

&lt;p&gt;如果存在这个 Task ，则直接跳转到这个栈中，调用栈里这个 Activity 实例的 onNewIntent 。&lt;/p&gt;

&lt;p&gt;被这个 singleInstance 的 Activity 启动的任何 Activity 都会运行在其他 Task 中，被启动的 Activity 在启动时，行为与 singleTask 模式一样。&lt;/p&gt;

&lt;p&gt;onActivityResult 将会失去作用，它的 resultCode 会直接返回 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity.RESULT_CANCELED&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;allowtaskreparenting&quot;&gt;allowTaskReparenting&lt;/h2&gt;

&lt;p&gt;如果 allowTaskReparenting 设置为 true，现在这个 Activity 的实例已经存在于一个 Task  T1 中，此时，如果另一个 Task  T2 启动，并且 T2 的 taskAffinity 跟这个 Activity 的值相同，那么这个 Activity 实例会从 T1 移动到 T2 中。&lt;/p&gt;

&lt;p&gt;如果这个属性没有设置，那么其对应的 &lt;application&gt; 元素的 allowTaskReparenting 属性值就会应用到这个 Activity 上。它的默认值是false。&lt;/application&gt;&lt;/p&gt;

&lt;p&gt;应用场景：
短信应用里，短信详情 Activity 中，用户点击了一个网页地址超链接，就会启动浏览器应用的 网页浏览 Activity 。此时的 Task  T1 是 短信详情 Activity - 网页浏览 Activity 。
如果这个网页浏览 Activity  的 allowTaskReparenting 设置为 true。在用户回到主屏幕然后再次启动 浏览器应用时，会将 T1 中的 网页浏览 Activity 转移到当前新的 Task 中 并打开。&lt;/p&gt;

&lt;h2 id=&quot;alwaysretaintaskstate&quot;&gt;alwaysRetainTaskState&lt;/h2&gt;

&lt;p&gt;默认情况下，如果一个应用在后台呆的太久，系统就会对该应用的 Task 进行清理，除了根 Activity，其他 Activity 都会被清理出栈，但是如果在根 Activity 中设置了 alwaysRetainTaskState 为 true 之后，就不会清理。用户再次打开时，仍然可以看到上一次操作的界面。 
这个根 Activity 的 launchMode 如果是 singleTask 或者 singleInstance 会导致无效。&lt;/p&gt;

&lt;p&gt;应用场景：
浏览器打开了很多标签页，每次打开浏览器都保存了这些标签页的打开状态。&lt;/p&gt;

&lt;h2 id=&quot;cleartaskonlaunch&quot;&gt;clearTaskOnLaunch&lt;/h2&gt;

&lt;p&gt;当应用进入后台后，然后用户再次打开时。如果根 Activity 的 clearTaskOnLaunch 为 true，则 Task 中除了根 Activity 之外所有的 Activity 都会被清理出栈。（被清理出栈的 Activity 中如果有 allowTaskReparenting 设置了 true 的，会被转移到 taskAffinity 相同的 Task 中）&lt;/p&gt;

&lt;h2 id=&quot;finishontasklaunch&quot;&gt;finishOnTaskLaunch&lt;/h2&gt;

&lt;p&gt;** 理论上 ** 当应用进入后台后，然后用户再次打开时。如果 Task 中某个 Activity 的 finishOnTaskLaunch 为 true，则把这个 Activity 从 Task 中清理出栈。&lt;/p&gt;

&lt;h2 id=&quot;相关的-intent-flag&quot;&gt;相关的 Intent FLAG&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_NEW_TASK&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;与 launchMode 是 singleTask 时，行为类似。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_SINGLE_TOP&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;与 launchMode 是 singleTop 时，行为相同。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_CLEAR_TOP&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果指定 Activity 的 launchMode 为默认，也就是没有设置，则是 standard ，则会销毁 Task 里此 Activity 实例上方包括自身的所有 Activity 实例，并在其位置启动一个新实例，以便处理传入的 Intent。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_CLEAR_TOP&lt;/code&gt; 通常与 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FLAG_ACTIVITY_NEW_TASK&lt;/code&gt; 结合使用，这样可以找到其他任务中的现有 Activity，并将其放入可从中响应 Intent 的位置。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;官方文档中任务和返回栈，其中 singleTask 部分讲的一泡稀。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.android.com/guide/components/tasks-and-back-stack&quot;&gt;https://developer.android.com/guide/components/tasks-and-back-stack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/luoshengyang/article/details/6714543&quot;&gt;https://blog.csdn.net/luoshengyang/article/details/6714543&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 09 May 2018 15:52:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/05/09/Android_task_launchmode.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/05/09/Android_task_launchmode.html</guid>
        
        <category>develop</category>
        
        <category>android</category>
        
        
      </item>
    
      <item>
        <title>Java 中的 Enum 枚举</title>
        <description>&lt;h1 id=&quot;java-中的-enum-枚举&quot;&gt;Java 中的 Enum 枚举&lt;/h1&gt;

&lt;p&gt;一个典型的使用：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;使用关键字 enum 创建 枚举（Day） 在编译后 本质上 也是一个类，该类继承自&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java.lang.Enum&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;每一个枚举项（MONDAY, TUESDAY…），都会 new 一个 Day 类型的实例。并且是 public static final 的。&lt;/p&gt;

&lt;p&gt;所以 Enum 类型所使用的内存，肯定是比一个静态 int 常量多，所以在 Android 开发中建议直接使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@XXXDef&lt;/code&gt; 注解&lt;/p&gt;

&lt;h2 id=&quot;方法&quot;&gt;方法&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ordinal()&lt;/code&gt; 方法，该方法获取的是枚举变量在枚举类中声明的顺序。从 0 开始。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compareTo(E o)&lt;/code&gt;  Enum实现了Comparable接口。比较枚举的大小，内部实现是根据每个枚举的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ordinal&lt;/code&gt;值大小进行比较的。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name()&lt;/code&gt; 与&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toString()&lt;/code&gt;几乎是等同的，都是输出变量的字符串形式。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valueOf(Class&amp;lt;T&amp;gt; enumType, String name)&lt;/code&gt; 方法则是根据枚举类的 Class 对象和枚举名称获取枚举常量，注意该方法是静态的&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valueOf(String name)&lt;/code&gt; 方法最终还是会调用 Enum 类的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valueOf(Class&amp;lt;T&amp;gt; enumType, String name)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values()&lt;/code&gt; 方法的作用就是获取枚举类中的所有变量，并作为数组返回&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values()&lt;/code&gt;方法和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valueOf(String name)&lt;/code&gt;方法是编译器生成的 static 方法，注意这里有两个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valueOf&lt;/code&gt; 方法&lt;/p&gt;

&lt;h2 id=&quot;几个特性&quot;&gt;几个特性&lt;/h2&gt;

&lt;p&gt;enum 可以实现接口&lt;/p&gt;

&lt;p&gt;两个枚举常量可以用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;比较&lt;/p&gt;

&lt;p&gt;可用于控制 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;switch&lt;/code&gt; 语句&lt;/p&gt;

&lt;h2 id=&quot;添加方法与自定义构造函数&quot;&gt;添加方法与自定义构造函数&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public enum Day {
    MONDAY(&quot;星期一&quot;),
    TUESDAY(&quot;星期二&quot;),
    WEDNESDAY(&quot;星期三&quot;),
    THURSDAY(&quot;星期四&quot;),
    FRIDAY(&quot;星期五&quot;),
    SATURDAY(&quot;星期六&quot;),
    SUNDAY(&quot;星期日&quot;);  // 注意这里这个分号

    private String desc; 

    private Day(String desc){
        this.desc=desc;
    }

    public String getDesc(){
        return desc;
    }

    public static void main(String[] args){
        for (Day day:Day.values()) {
            System.out.println(&quot;name:&quot;+day.name()+ &quot;,desc:&quot;+day.getDesc());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;抽象方法&quot;&gt;抽象方法&lt;/h2&gt;

&lt;p&gt;enum 类的实例可以有类似多态的特性，但是也只是实例，不能作为方法的参数的类型。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public enum Direction {
    LEFT{
        @Override
        public String getInfo() {
            return &quot;LEFT&quot;;
        }
    },
    RIGHT{
        @Override
        public String getInfo() {
            return &quot;RIGHT&quot;;
        }
    }
    ;

    public abstract String getInfo();

    public static void main(String[] args){
        System.out.println(&quot;1:&quot;+Direction.LEFT.getInfo());
        System.out.println(&quot;2:&quot;+Direction.RIGHT.getInfo());
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Mon, 07 May 2018 15:40:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/05/07/java_enum.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/05/07/java_enum.html</guid>
        
        <category>develop</category>
        
        <category>java</category>
        
        
      </item>
    
      <item>
        <title>HashMap</title>
        <description>&lt;h1 id=&quot;hashmap&quot;&gt;HashMap&lt;/h1&gt;

&lt;h2 id=&quot;哈希表--散列表&quot;&gt;哈希表 / 散列表&lt;/h2&gt;

&lt;p&gt;哈希表可以理解为数组的扩展或者关联数组，数组使用数字下标来寻址，如果关键字的范围较小且是数字的话，可以直接使用数组来完成哈希表，但是空间利用率极低。同时键也可能不是数字，所以人们使用一种映射函数(哈希函数)来将关键字映射到特定的域中。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;键(key) 又称为关键字。唯一的标示要存储的数据，可以是数据本身或者数据的一部分。&lt;/li&gt;
  &lt;li&gt;槽(slot/bucket) 哈希表中用于保存数据的一个单元，也就是数据真正存放的容器。&lt;/li&gt;
  &lt;li&gt;哈希函数(hash function)：将键(key)映射(map)到数据应该存放的槽(slot)所在位置的函数。&lt;/li&gt;
  &lt;li&gt;哈希冲突(hash collision)：哈希函数将两个不同的键映射到同一个索引的情况。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;与其他数据结构比较：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;数组：寻址容易，插入和删除困难&lt;/li&gt;
  &lt;li&gt;链表：寻址困难，插入和删除容易&lt;/li&gt;
  &lt;li&gt;与二叉树不同，哈希表中存储的数据是无序的，对于查找任意数据高效。二叉树查找某一范围的一些数据比较高效。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;理想情况下，哈希表的插入和查找操作的时间复杂度均为O(1)，任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值，然后在常量时间内定位到一个桶。&lt;/p&gt;

&lt;p&gt;可以通过使用不同的哈希函数和冲突解决方案在时间和空间性能上做取舍。&lt;/p&gt;

&lt;p&gt;使用哈希表的插入主要有如下两个步骤：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;使用哈希函数将被查找的键转换为数组的索引。这个过程是将长度不同的字符串映射为固定长度的整数值。根据键的类型和哈希表的大小选择不同的哈希函数。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;处理哈希冲突。因为键是所有任意长度的字符串，数量是无穷的，而哈希值长度固定的常数，数量是有限的，同时根据生日悖论，必然有不同的键映射到同一个哈希值，这就产生了哈希冲突。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;处理哈希冲突，通常有两类方法：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Open Hashing 
比如拉链法，是哈希桶（Bucket Hashing）的&lt;strong&gt;一种&lt;/strong&gt;。碰到哈希冲突后，用链表去延展。所以有链表带来的优缺点。（因为开辟了新空间，所以叫 Open Hashing）。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Closed Hashing 
比如线性探测法。哈希冲突后，并不会在本身之外开拓新的空间，而是继续顺延下去某个位置来存放，（因为依旧在同一个密闭的空间，所以叫 Closed Hashing），至于 Open Addressing 的意思是，相对于那种通过链表来开拓新空间，它是在本身地址上，另外找个位置。所以叫开地址。
常用的开地址法有探测（Probing），&lt;strong&gt;某些&lt;/strong&gt;哈希桶（Bucket Hashing）等。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;hashmap-1&quot;&gt;HashMap&lt;/h2&gt;

&lt;p&gt;Java 的 HashMap 也是一个哈希表。先分配（allocate）一定的内存位置，然后向这些位置插入数据。使用 Open Hashing 拉链法解决哈希冲突。&lt;/p&gt;

&lt;p&gt;储存位置的确定：
把 key 经过 hash 之后得到的值，取余（真正的实现是使用的位操作），得到一个在长度范围内的下标。&lt;/p&gt;

&lt;p&gt;HashMap 非线程安全，需要线程安全的话，使用 ConcurrentHashMap 。
LinkedHashMap 是 HashMap的一个子类，是带插入顺序的 HashMap。&lt;/p&gt;

&lt;p&gt;构造方法：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap
HashMap()
// 构造一个带指定初始容量和默认负载因子 (0.75) 的空 HashMap
HashMap(int initialCapacity)
// 构造一个带指定初始容量和加载因子的空 HashMap
HashMap(int initialCapacity, float loadFactor) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;底层实现是数组，数组的每一项都是一条链表 （&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K,V&amp;gt;&lt;/code&gt;中的 next ）（ UPDATE：Java8 中使用链表+红黑树）。其中参数 initialCapacity 就代表了该数组的长度。（实际 capacity 是 initialCapacity 向上取最接近的一个 「2的幂的数」）
容量表示哈希表中桶的数量，初始容量是创建哈希表时的容量，&lt;/p&gt;

&lt;p&gt;负载因子是哈希表在扩容前可以达到多满的一种尺度（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;threshold = (int)(capacity * loadFactor);&lt;/code&gt;），它衡量的是一个哈希表的空间的使用程度，负载因子越大表示哈希表的装填程度越高，反之愈小。&lt;/p&gt;

&lt;p&gt;对于使用链表法的哈希表来说，查找一个元素的平均时间是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(1+a)&lt;/code&gt;，
因此如果负载因子越大，对空间的利用更充分，元素多了，哈希冲突的可能性变大，链表变长，后果是查找效率的降低；
如果负载因子太小，那么链表的数据少，对空间造成严重浪费。&lt;/p&gt;

&lt;p&gt;系统默认负载因子为 0.75，一般情况下我们是无需修改的。&lt;/p&gt;

&lt;h2 id=&quot;插入-put&quot;&gt;插入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;put&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;先 hash 元素的 key 计算 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash()&lt;/code&gt; ，再根据数组长度取余 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexFor(hash, table.length)&lt;/code&gt;，得到数组下标，&lt;/p&gt;

&lt;p&gt;然后通过数组下标得到链表的第一个元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K, V&amp;gt;&lt;/code&gt;，同时开始通过 next 进行迭代。&lt;/p&gt;

&lt;p&gt;判断每个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K, V&amp;gt;&lt;/code&gt;的 hash 值（注意不是 key 的 hash 值）是否与当前要插入的这个元素 hash 值一样，如果一样，则覆盖，并返回旧值。&lt;/p&gt;

&lt;p&gt;迭代完后如果还没有返回，则说明是新元素，那么把该元素插队存在链头。&lt;/p&gt;

&lt;h2 id=&quot;扩容-resize&quot;&gt;扩容 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resize&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;当达到负载因子设定的数量后，会创建新的数组代替已有数组。新数据长度是旧数组的 2 倍。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resize(2 * table.length);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这个过程需要将所有元素重新 hash 计算，插入新的数组中，开销较大。（实际的实现中有优化）&lt;/p&gt;

&lt;h2 id=&quot;取值-get&quot;&gt;取值 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;先 hash 元素的 key 计算 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash()&lt;/code&gt; ，再根据数组长度取余 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexFor(hash, table.length)&lt;/code&gt;，得到数组下标，&lt;/p&gt;

&lt;p&gt;然后通过数组下标得到链表的第一个元素&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K, V&amp;gt;&lt;/code&gt;，同时开始通过 next 进行迭代。&lt;/p&gt;

&lt;p&gt;判断每个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K, V&amp;gt;&lt;/code&gt; 的 key 是否与当前要获取的这个元素 key 一样，如果一样，则返回这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entry&amp;lt;K, V&amp;gt;&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;arraymap&quot;&gt;ArrayMap&lt;/h2&gt;

&lt;p&gt;Andoid 提供的 ArrayMap 的特性是减少内存使用。牺牲一些插入和删除的性能。比 HashMap 占用的内存少很多。比较适合 1000 以内的元素个数使用。&lt;/p&gt;

&lt;p&gt;ArrayMap 内部使用两个数组。一个记录 key hash 后的顺序列表，另一个按照 key 的顺序记录 key-value 值。&lt;/p&gt;

&lt;h2 id=&quot;sparsearray&quot;&gt;SparseArray&lt;/h2&gt;

&lt;p&gt;Andoid 提供的这些稀疏数组，因为指定了数据类型，所以没有 HashMap 那样的自动装箱过程。&lt;/p&gt;

</description>
        <pubDate>Mon, 07 May 2018 15:40:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/05/07/hashmap.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/05/07/hashmap.html</guid>
        
        <category>develop</category>
        
        <category>java</category>
        
        <category>android</category>
        
        
      </item>
    
      <item>
        <title>Android 内存泄漏</title>
        <description>&lt;h1 id=&quot;android-内存泄漏&quot;&gt;Android 内存泄漏&lt;/h1&gt;

&lt;h2 id=&quot;形成的原因&quot;&gt;形成的原因&lt;/h2&gt;

&lt;p&gt;内存分配：&lt;/p&gt;

&lt;p&gt;静态存储区：主要存放 static 数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好，并且在程序整个运行期间都存在。&lt;/p&gt;

&lt;p&gt;栈：局部变量 的 基本数据类型 和 引用 存储于栈中。当方法被执行时，方法体内的局部变量都在栈上创建，生命周期随方法而结束。因为栈内存的分配效率很高，但是分配的内存容量有限。&lt;/p&gt;

&lt;p&gt;堆：通常是在程序运行时 new 出来的内存。类成员变量 全部 存储在堆中（包括基本数据类型，引用和引用的对象实体）。这部分内存在不使用时将会由 GC 来负责回收。&lt;/p&gt;

&lt;p&gt;虚拟机的垃圾处理机制：&lt;/p&gt;

&lt;p&gt;没有直接或者间接被 GC Roots（通常是线程的 main） 引用的对象，会被 GC 回收掉。Java 内存泄漏指的是进程中某些对象（垃圾对象）已经没有使用价值了，但是它们却直接或间接被 gc roots 引用导致无法被 GC 回收。&lt;/p&gt;

&lt;p&gt;所以，&lt;strong&gt;生命周期不同 的 对象&lt;/strong&gt;是重灾区。&lt;/p&gt;

&lt;h2 id=&quot;常见情况和避免方式&quot;&gt;常见情况和避免方式&lt;/h2&gt;

&lt;h3 id=&quot;持有a实例的b的生命周期-超过了-a实例的生命周期导致a实例无法回收&quot;&gt;持有A实例的B的生命周期 超过了 A实例的生命周期，导致A实例无法回收。&lt;/h3&gt;

&lt;p&gt;导致生命周期不一致的原因：静态，线程的异步，作用范围（ 比如用作缓存的集合数据类型，引用着里面保存的图片 ）等&lt;/p&gt;

&lt;h4 id=&quot;静态实例-所在的类&quot;&gt;静态实例 所在的类&lt;/h4&gt;

&lt;p&gt;这个静态实例的类如果不是静态的，那么这个静态实例所在的类将无法回收
非静态的内部类，包括匿名内部类
某些单例模式&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Demo {
    static Inner sInnerInstance = null;

    public Demo() {
        if (sInnerInstance == null) {
            sInnerInstance= new Demo();
        }
    }

    class Inner {
        void doSomething() {
            System.out.print(&quot;dosh&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;非静态内部类默认会持有外部类的引用。&lt;/p&gt;

&lt;p&gt;Demo 的构造方法创建了一个该非静态内部类的静态实例，该实例的生命周期和应用的一样长，该静态实例一直会持有 Demo 的引用，导致 Demo 无法正常回收。&lt;/p&gt;

&lt;p&gt;解决：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class OutterClass {

    static class InnerClass {
        private final WeakReference&amp;lt;OutterClass&amp;gt; mOutterClassInstance;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;集合&quot;&gt;集合&lt;/h4&gt;

&lt;p&gt;集合类如果仅仅有添加元素的代码，而没有相应的删除机制，集合类和这个元素的生命周期不一致（比如集合是当前类的静态属性，全局性的 map 或 final 一直指向它)，会导致内存泄漏。比如通过 HashMap 做缓存时就需要注意。&lt;/p&gt;

&lt;h4 id=&quot;线程&quot;&gt;线程&lt;/h4&gt;

&lt;p&gt;线程是典型的生命周期不同的情况，需要特别注意。&lt;/p&gt;

&lt;h2 id=&quot;android-的常见情况和避免方式&quot;&gt;Android 的常见情况和避免方式&lt;/h2&gt;

&lt;p&gt;如果泄漏对象越攒越多，没有办法被回收，最终会导致 OutOfMemory，使得 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system_process&lt;/code&gt; 进程挂掉。&lt;/p&gt;

&lt;h3 id=&quot;activity&quot;&gt;Activity&lt;/h3&gt;

&lt;p&gt;想要避免 context 相关的内存泄漏，需要注意以下几点：&lt;/p&gt;

&lt;p&gt;不要对 activity 的 context 长期引用(一个 activity 的引用的生存周期应该和activity的生命周期相同)
注意 View、 Drawable 等容易持有 Activity 的引用&lt;/p&gt;

&lt;p&gt;如果一个 acitivity 的非静态内部类的生命周期不受控制，那么避免使用它；正确的方法是使用一个静态的内部类，并且对它的外部类有一 WeakReference，就像在 ViewRootImpl 中内部类 W 所做的那样。&lt;/p&gt;

&lt;p&gt;持有 activity 的线程对象 mThread，确保 activity 在 destroy 后，线程已经终止，可以这样做：在 onDestroy 时调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mThread.join();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;针对整个应用生命周期的情况使用关于 application 的 context 来替代和 activity 相关的 context&lt;/p&gt;

&lt;p&gt;View 会保持 Activity 的引用，Activity 同时还和其他内部对象也有可能保持引用关系。&lt;/p&gt;

&lt;p&gt;一般通过不断改变屏幕方向，使 Activity 不断重建，可以直观的观察泄漏。&lt;/p&gt;

&lt;h3 id=&quot;handler&quot;&gt;Handler&lt;/h3&gt;

&lt;p&gt;Handler 通过发送 Message 与其他线程交互，Message 发出之后是存储在目标线程的 MessageQueue 中的，Message 并不保证马上被处理，可能会驻留比较久的时间。在 Message 类中的成员变量 target，它强引用了 handler 实例，如果 Message 在 Queue 中一直存在，就会导致handler 实例无法被回收，如果 handler 对应的类是非静态内部类 ，则会导致外部类实例（ Activity 或者 Service ）不会被回收，这就造成了外部类实例的泄露。&lt;/p&gt;

&lt;p&gt;正确处理 Handler 等之类的内部类，应该将自己的 Handler 定义为静态内部类，并且在类中增加一个成员变量，用来弱引用外部类实例。在 Activity 的 Destroy 或者 Stop 时使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;removeCallbacks&lt;/code&gt; 移除消息队列 MessageQueue 中的消息。&lt;/p&gt;

&lt;p&gt;使用 HandlerThread 时，因为 HandlerThread 的 run 方法是一个无限循环，它不会自己结束，线程的生命周期超过了 activity 生命周期。应该在 onDestroy 时将线程停止掉：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mThread.getLooper().quit();&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;广播接收器注册观察者系统事件的监听器&quot;&gt;广播接收器、注册观察者、系统事件的监听器&lt;/h3&gt;

&lt;p&gt;假设我们希望在锁屏界面（LockScreen）中，监听系统中的信号强度，则可以在 LockScreen 中定义一个 PhoneStateListener 的对象，同时将它注册到 TelephonyManager 服务中。&lt;/p&gt;

&lt;p&gt;对于 LockScreen 对象，当需要显示锁屏界面的时候就会创建一个 LockScreen 对象，而当锁屏界面消失的时候 LockScreen 对象就会被释放掉。&lt;/p&gt;

&lt;p&gt;但是如果在释放 LockScreen 对象的时候，没有取消之前注册的 PhoneStateListener 对象，则会导致LockScreen 无法被 GC 回收。内存泄了漏了。&lt;/p&gt;

&lt;p&gt;虽然有些系统程序，它本身好像是可以自动取消注册的，但还是最好明确的手动取消注册。&lt;/p&gt;

&lt;h3 id=&quot;资源源对象-file--cursor&quot;&gt;资源源对象 File / Cursor&lt;/h3&gt;

&lt;p&gt;资源性对象比如（Cursor，File文件等）往往都用了一些缓冲，我们在不使用的时候，应该及时关闭它们，并置为 null。以便它们的缓冲及时回收内存。&lt;/p&gt;

&lt;p&gt;它们的缓冲不仅存在于 Java 虚拟机内（GC 可以回收），还存在于 Java 虚拟机外（一些缓存逻辑，需要手动关闭）。如果我们仅仅是把它的引用设置为 null，而不关闭它们，往往会造成内存泄露。&lt;/p&gt;

&lt;p&gt;有些资源性对象，比如 SQLiteCursor（在析构函数&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;finalize()&lt;/code&gt;如果我们没有关闭它，它自己会调&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;close()&lt;/code&gt;关闭)，所以就算没有手动关闭，系统在会在 GC 时也会关闭它，但是这样的效率太低了。&lt;/p&gt;

&lt;h2 id=&quot;android-的调试&quot;&gt;Android 的调试&lt;/h2&gt;

&lt;p&gt;神器 LeakCanary
&lt;a href=&quot;https://github.com/square/leakcanary&quot;&gt;https://github.com/square/leakcanary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MAT(Memory Analyzer Tool)&lt;/p&gt;

&lt;p&gt;Android Profiler
类似 MAT，可以在 Android studio 中直接操作，可以直接 dump 分析类的数量&lt;/p&gt;

&lt;p&gt;正常的操作，在正常gc后，Allocated 的内存会保持在一个平稳的水平。如果 Allocated 值在每次GC后不会有明显的回落，随着操作次数的增多 Allocated 的值会越来越大，说明代码中存在没有释放对象引用的情况。&lt;/p&gt;

</description>
        <pubDate>Mon, 07 May 2018 15:40:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/05/07/android_memory_leak.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/05/07/android_memory_leak.html</guid>
        
        <category>develop</category>
        
        <category>java</category>
        
        <category>android</category>
        
        
      </item>
    
      <item>
        <title>Android 应用开发性能优化提纲</title>
        <description>&lt;h1 id=&quot;已发布android-应用开发性能优化提纲&quot;&gt;「已发布」Android 应用开发性能优化提纲&lt;/h1&gt;

&lt;p&gt;** 要进行优化，请先确定主要次要瓶颈是什么！ **&lt;/p&gt;

&lt;p&gt;【UPDATE】Android Studio 3.2 之后，Android Device Monitor 已经被 Android Studio 内置的功能取代。文中提到的一些工具，做了响应的更新。如果依旧要使用Android Device Monitor，可以在 SDK 目录下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;android-sdk/tools/&lt;/code&gt;找到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;monitor&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;布局和绘制&quot;&gt;布局和绘制&lt;/h2&gt;

&lt;p&gt;布局和绘制如果有问题会直接造成 UI 卡顿，甚至 ANR&lt;/p&gt;

&lt;h3 id=&quot;布局&quot;&gt;布局&lt;/h3&gt;

&lt;p&gt;布局会出现的主要问题是层级过多和布局重叠。以及界面业务逻辑的实现混乱，非必要逻辑，以及重复的 inflate。&lt;/p&gt;

&lt;p&gt;避免布局重叠（过度绘制）。使用开发中选项的调试 GPU 过度绘制功能检查&lt;/p&gt;

&lt;p&gt;使用 merge 减少层级，include 减少布局内重复内容，viewstub 懒加载，注意 include 可能导致层级增加，最好搭配 merge 达到少增加一个层级的效果。&lt;/p&gt;

&lt;p&gt;不使用 AbsoluteLayout，除非 APP 永远只适配一种机型。&lt;/p&gt;

&lt;p&gt;避免布局层级过多过于复杂，&lt;/p&gt;

&lt;p&gt;&lt;del&gt;使用 Hierarchy Viewer  检查试图层级。&lt;/del&gt;
如果使用 Android studio 3.1 以上的版本，直接使用自带的 Layout Inspector 工具。&lt;/p&gt;

&lt;h3 id=&quot;绘制&quot;&gt;绘制&lt;/h3&gt;

&lt;p&gt;不要在 UI 线程中做耗时操作，比如绘制操作太多，IO 或者计算负载过重的操作。比如同一时间动画执行的次数过多。&lt;/p&gt;

&lt;p&gt;使用开发者选项中的 GPU 呈现模式分析，选择 在屏幕上显示为条形图，动态的观察问题出现的时机。还可以使用 GPU Monitor 配合严格模式，会在主线程上做耗时操作时，闪烁屏幕。&lt;/p&gt;

&lt;p&gt;控件 measure 的时候避免多次测量，draw 的时候避免过多的计算量或者内存开销，比如大量循环，频繁 new 对象创建内存&lt;/p&gt;

&lt;h3 id=&quot;ui-资源&quot;&gt;UI 资源&lt;/h3&gt;

&lt;p&gt;尽量使用 xml 定义的图片&lt;/p&gt;

&lt;p&gt;使用 dp 作为单位，不使用 px 为单位，除非特殊情况，比如一条分隔线之类的&lt;/p&gt;

&lt;p&gt;尽量使用一套资源进行多数的适配&lt;/p&gt;

&lt;p&gt;使用 9patch&lt;/p&gt;

&lt;h2 id=&quot;内存&quot;&gt;内存&lt;/h2&gt;

&lt;h3 id=&quot;内存泄漏&quot;&gt;内存泄漏&lt;/h3&gt;

&lt;p&gt;参考内存泄漏那篇文章&lt;/p&gt;

&lt;h3 id=&quot;自动装箱和集合类&quot;&gt;自动装箱和集合类&lt;/h3&gt;

&lt;p&gt;自动装箱的过程会 new 一个原始类型对应的对象，这个过程不但使用更多的内存，并且多了 new 的调用。会造成一定的内存浪费。&lt;/p&gt;

&lt;p&gt;各种泛型集合中常用到对原始类型自动装箱。&lt;/p&gt;

&lt;p&gt;选择合适的集合数据类型和结构。在合适的情况下，使用 SparseArray 替代 HashMap ，可以避免自动装箱行为，使用 ArrayMap 比 HashMap 更节约内存。&lt;/p&gt;

&lt;h3 id=&quot;bitmap-使用&quot;&gt;Bitmap 使用&lt;/h3&gt;

&lt;h4 id=&quot;及时销毁&quot;&gt;及时销毁&lt;/h4&gt;

&lt;p&gt;虽然，系统能够确认 Bitmap 分配的内存最终会被销毁，但是由于它占用的内存过多，所以很可能会超过Java 堆的限制。因此，在用完 Bitmap 时，要及时的 recycle ，给虚拟机信号：该图片可以释放了。&lt;/p&gt;

&lt;h4 id=&quot;设置合适的采样率&quot;&gt;设置合适的采样率&lt;/h4&gt;

&lt;p&gt;当要显示的区域很小，没必要将图片原尺寸加载出来，而只需要加载一个缩小过的图片，这时候可以设置一定的采样率，则可以大大减少占用的内存。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;private ImageView iv;    
BitmapFactory.Options options = new BitmapFactory.Options();    
options.inSampleSize = 2; //图片宽高都为原来的二分之一，即图片为原来的四分之一    
Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); iv.setImageBitmap(bitmap);   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;设置合适的像素格式&quot;&gt;设置合适的像素格式&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bitmap.Config&lt;/code&gt; 用来描述图片的像素是怎么被存储的：
ARGB_8888: 每个像素4字节. 共32位，默认设置。
RGB_565:共16位，2字节，只存储RGB值。
Alpha_8: 只保存透明度，共8位，1字节。
ARGB_4444: 共16位，2字节。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeFile(&quot;/foo.png&quot;,options);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;使用用软引用softrefrence&quot;&gt;使用用软引用（SoftRefrence）&lt;/h4&gt;

&lt;p&gt;有时，在使用 Bitmap 后没有保留对它的引用，因此就无法调用 Recycle 函数。这种情况可以使用软引用，可以使 Bitmap 在内存不足时得到有效的释放。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SoftReference&amp;lt;Bitmap&amp;gt; bitmap_ref = new SoftReference&amp;lt;Bitmap&amp;gt;(BitmapFactory.decodeStream(inputstream));   
if (bitmap_ref .get() != null) {
  bitmap_ref.get().recycle();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;图片缓存&quot;&gt;图片缓存&lt;/h4&gt;

&lt;p&gt;通常在不同的层级都会设置缓存，比如内存缓存，文件系统缓存。&lt;/p&gt;

&lt;p&gt;LRU 算法：设置缓存图片最大数量，当图片数量超过最大值，则删除使用较少的图片，使用次数同样少的就删除更早使用的。&lt;/p&gt;

&lt;p&gt;FTU 算法：设置图片的缓存时限，从最后一次使用算起，当达到时限即删除&lt;/p&gt;

&lt;p&gt;FMU 算法：设置固定大小的缓存空间，当达到空间限制后删除最大尺寸的图片&lt;/p&gt;

&lt;h3 id=&quot;其他媒体文件&quot;&gt;其他媒体文件&lt;/h3&gt;

&lt;p&gt;比如视频，音频，因为处理通常会使用专门的库，需要针对性的考察。&lt;/p&gt;

&lt;h2 id=&quot;性能优化&quot;&gt;性能优化&lt;/h2&gt;

&lt;p&gt;全局使用的功能使用单例模式&lt;/p&gt;

&lt;p&gt;缓存网络请求，大文件&lt;/p&gt;

&lt;p&gt;同步改异步&lt;/p&gt;

&lt;p&gt;线程池&lt;/p&gt;

&lt;h2 id=&quot;网络优化&quot;&gt;网络优化&lt;/h2&gt;

&lt;p&gt;避免频繁的网络请求，使用线程池管理异步网络请求&lt;/p&gt;

&lt;p&gt;图片进行缓存&lt;/p&gt;

&lt;p&gt;大文件上传或下载使用断点续传&lt;/p&gt;

&lt;p&gt;非 UI 交互数据的请求，尽量集中在一个时刻处理&lt;/p&gt;

&lt;p&gt;服务器端减少重定向次数，服务器端 API 响应时间尽量不超过 100ms，CDN 缓存静态资源，APP 客户端自带一份服务器 ip，使用 ip 直连，省去 dns 时间&lt;/p&gt;

&lt;p&gt;HTTP 请求添加 http time out&lt;/p&gt;

&lt;p&gt;合理使用 HTTP 的 connection 的 keep－alive&lt;/p&gt;

&lt;p&gt;使用 HTTP 缓存（ HTTP 头信息中的 Cache－Control 和 expires 确定是否缓存请求结果）&lt;/p&gt;

&lt;p&gt;HTTP 开启 gzip 压缩&lt;/p&gt;

&lt;p&gt;API 使用数据小的数据形式，如 json&lt;/p&gt;

&lt;h2 id=&quot;数据库优化&quot;&gt;数据库优化&lt;/h2&gt;

&lt;p&gt;使用索引&lt;/p&gt;

&lt;p&gt;返回的结果集字段尽量少&lt;/p&gt;

&lt;p&gt;SQL 语句的拼接使用 StringBuilder&lt;/p&gt;

&lt;p&gt;execSQL 执行原始 SQL 语句的效率更高，在封装与效率之间选择一个平衡点&lt;/p&gt;

&lt;p&gt;一次性修改或插入多个数据，使用 SQLite 事务，适合文件存储的尽量使用文件存储&lt;/p&gt;

&lt;h2 id=&quot;java-代码优化&quot;&gt;Java 代码优化&lt;/h2&gt;

&lt;p&gt;牺牲类型安全和使用方便性，常量使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static final&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@XXXDef&lt;/code&gt; 注解，非必要情况不使用 Enum。&lt;/p&gt;

&lt;p&gt;减少不必要的全局变量&lt;/p&gt;

&lt;p&gt;不必使用内部 getter 和 setter&lt;/p&gt;

&lt;p&gt;选择合适的 Java 引用方式&lt;/p&gt;

&lt;p&gt;某些 Java 对象注意手动回收，比如 XmlPullParserFactory、BitmapFactory、Matcher、各种流的操作和数据库的关闭，不使用的 Bitmap 对象及时 recycle，并且赋值为 null&lt;/p&gt;

&lt;p&gt;算法优化，复杂算法用 c 完成使用 jni 调用。&lt;/p&gt;

&lt;p&gt;使用 &lt;del&gt;traceview&lt;/del&gt; Android Studio 的 CPU profiler 功能 来分析调用过程，定位卡顿问题出现的位置。&lt;/p&gt;

&lt;p&gt;在主线程上的耗时操作过多会导致 ANR。碰到 ANR 时，系统会在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/data/anr&lt;/code&gt;目录下创建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traces.txt&lt;/code&gt; 文件。取出这个文件&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adb pull /data/anr/traces.txt&lt;/code&gt;，通过分析这个文件来定位 ANR 发生的地方。&lt;/p&gt;

&lt;h2 id=&quot;业务逻辑优化&quot;&gt;业务逻辑优化&lt;/h2&gt;

&lt;p&gt;减少不必要的业务流程，比如无意义的启动画面，过早请求登录用户的信息等。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;一些调试工具：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://juejin.im/entry/563ae1b560b216575c53c3d6&quot;&gt;https://juejin.im/entry/563ae1b560b216575c53c3d6&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.android.google.cn/studio/profile/systrace.html&quot;&gt;https://developer.android.google.cn/studio/profile/systrace.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.android.google.cn/reference/android/view/FrameMetrics.html&quot;&gt;https://developer.android.google.cn/reference/android/view/FrameMetrics.html&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 07 May 2018 15:40:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/05/07/android_app_performance_optimization.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/05/07/android_app_performance_optimization.html</guid>
        
        <category>develop</category>
        
        <category>android</category>
        
        
      </item>
    
      <item>
        <title>Android TouchEvent 触摸事件传递</title>
        <description>&lt;h1 id=&quot;android-touchevent-触摸事件传递&quot;&gt;Android TouchEvent 触摸事件传递&lt;/h1&gt;

&lt;h2 id=&quot;activity&quot;&gt;Activity&lt;/h2&gt;

&lt;p&gt;事件由 Activity 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTouchEvent()&lt;/code&gt; 开始，将事件传递给当前 Activity 的根ViewGroup：mDecorView，事件开始自上而下进行传递，直至被消费。&lt;/p&gt;

&lt;p&gt;Activity 里 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTouchEvent(MotionEvent ev)&lt;/code&gt; 被调用，
会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getWindow().superDispatchTouchEvent(ev)&lt;/code&gt;，
PhoneWindow 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;superDispatchTouchEvent(ev)&lt;/code&gt; 调用的是 DecorView 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;superDispatchTouchEvent(event)&lt;/code&gt; 
再调用的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super.dispatchTouchEvent(event);&lt;/code&gt;DecorView 的父类是 FrameLayout，FrameLayout 没有重写 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTouchEvent&lt;/code&gt; 方法，所以事件开始交由 ViewGroup 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTouchEvent&lt;/code&gt; 开始分发&lt;/p&gt;

&lt;p&gt;如果 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getWindow().superDispatchTouchEvent(ev)&lt;/code&gt; 返回 false，也就意味着整个分发过程没有人消费，则会直接调用 Activity 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent(ev)&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;viewgroup-中的-dispatchtouchevent&quot;&gt;ViewGroup 中的 dispatchTouchEvent&lt;/h2&gt;

&lt;p&gt;这里的代码分析使用的是 Android 1.6 版本，之后的版本有不少改动，完善了多指触控等功能，但是行为逻辑都是一样的。
&lt;a href=&quot;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/ViewGroup.java&quot;&gt;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/ViewGroup.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这里调度事件在 View 层级中的传递。&lt;/p&gt;

&lt;p&gt;ViewGroup 的 onInterceptTouchEvent 直接返回了 false，即默认是不拦截事件的。这个 onInterceptTouchEvent 是自定义 View 时，是否要在当前 View 对当前一串滑动操作进行处理。通常就写在这里。
可以通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestDisallowInterceptTouchEvent(boolean disallowIntercept)&lt;/code&gt; 设置是否拦截事件。
通常处理上下层滑动冲突时，下层 View 想要处理滑动事件，就会根据滑动的情况判断来调用 上层 ViewGroup 的 requestDisallowInterceptTouchEvent 来获得当前滑动。如果是上层 ViewGroup 想要处理滑动事件，则是使用 onInterceptTouchEvent 。&lt;/p&gt;

&lt;p&gt;这段伪代码就把传递过程解释了大部分，可以多看几眼。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 1. ACTION_DOWN 时，判断是否有 target
if (action == MotionEvent.ACTION_DOWN) {
    // disallowIntercept 为 true （下层 View 不允许在这里 Intercept）
    //  或者 onInterceptTouchEvent 返回 false 时。（此处调用了onInterceptTouchEvent）
    if (disallowIntercept || !onInterceptTouchEvent(ev)) {
        // 向下层传递。循环下层 View ，找到触摸点**击中**的下层 View 
        for(){
            if (frame.contains(scrolledXInt, scrolledYInt)) {
                // 如果这个下层 View 的 dispatchTouchEvent 返回 true，则把它赋值给 mMotionTarget，同时这个 ViewGroup 中的 dispatchTouchEvent 返回true。（此处调用了下层 View 的dispatchTouchEvent）
                if (child.dispatchTouchEvent(ev)) {
                    mMotionTarget = child;
                    return true;
                }
            }
        }
    }
}
// 2. 如果 mMotionTarget 为空，这里有两种情况，一种是中断，一种是没找到合适的下层 View
// 那么调用  super.dispatchTouchEvent(ev)，也就是 View 的 dispatchTouchEvent(ev)，也就是由 ViewGroup 自身来处理事件。
final View target = mMotionTarget;
if (target == null) {
    return super.dispatchTouchEvent(ev);
} 
// 3. 如果 onInterceptTouchEvent 返回了 true
if (!disallowIntercept &amp;amp;&amp;amp; onInterceptTouchEvent(ev)) {
           // 那么给 target 发去一个 CANCEL 的事件。这里调用 target.dispatchTouchEvent(ev) CANCEL 事件。
            ev.setAction(MotionEvent.ACTION_CANCEL);
            if (!target.dispatchTouchEvent(ev)) {
            
            }
            // 当前事件已经改成 CANCEL 发给 target。这里把 target 设置为 null，这个同一串事件的下一个事件开始，在运行到 「2」 时，调用 `return super.dispatchTouchEvent(ev);`
            mMotionTarget = null;
            // 这里返回 true。依旧由这个 ViewGroup 处理事件。
            return true;
}
// 4. 在一串事件的结束时，重置 target
if (isUpOrCancel) {
    mMotionTarget = null;
}
// 5. 将事件转换成 目标 View 的坐标后 ，调用 target.dispatchTouchEvent(ev); 并返回
return target.dispatchTouchEvent(ev);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;首先在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTION_DOWN&lt;/code&gt; 事件时，先 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onInterceptTouchEvent&lt;/code&gt; ，不中断的话，迭代下层 view 寻找 target 。target 的条件是 点击区域在这个 child view 的范围内，同时这个 child 的 dispatchTouchEvent(ev) 返回 true。说明这个下层 View 想要消费这一串事件，则设置这个 child 为 target，注意这里形成了上下层 view 的递归，并且当前 ViewGroup 的 dispatchTouchEvent 返回 true 。事件就经过一层一层的 ViewGroup 返回 true，到达此处。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果没找到 target，则调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super.dispatchTouchEvent(ev);&lt;/code&gt; 也就是 ViewGroup 自身处理事件。这里有两种情况，一种是此时是 ACTION_DOWN 事件，没找到 target ，一种是此时不是 ACTION_DOWN 事件，之前找到了 target，但是此时是后续的比如 ACTION_MOVE 事件时，之前的 ACTION_MOVE 事件时，中断了。
如果这里也返回 false ，只有此时是 ACTION_DOWN 事件，也就是确定 target 时，会起作用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;到这里时，肯定有 target ，则先判断 onInterceptTouchEvent ，中断的话则  把当前时间修改成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTION_CANCEL&lt;/code&gt;事件，把这个取消事件给 target，然后把 target 设置为 null，但是让当前 ViewGroup 的 dispatchTouchEvent 依然返回 true 。这样一来，这一串事件的后续事件再进入当前 dispatchTouchEvent 方法后，在第 2 步，由于 target 是 null，会调用自身的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super.dispatchTouchEvent(ev);&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;到这里时，有 target ，不中断。如果是一个一串事件的结束事件，那么把 target 设置为 null。重置 target。（等待下一次 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ACTION_DOWN&lt;/code&gt;）&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;到这里时，有 target ，不中断，不是一串事件的结束，正常向 target 传递事件并返回。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return target.dispatchTouchEvent(ev);&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;view-中的-dispatchtouchevent&quot;&gt;View 中的 dispatchTouchEvent&lt;/h2&gt;

&lt;p&gt;这里决定事件在 View 内部由哪个方法处理。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTouchEvent(MotionEvent event)&lt;/code&gt;
如果有 OnTouchListener ，同时当前 View 处于可用状态&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(mViewFlags &amp;amp; ENABLED_MASK) == ENABLED&lt;/code&gt;的话，就执行 OnTouchListener 的 onTouch，如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mOnTouchListener.onTouch(this, event)==true&lt;/code&gt; ，View 的 dispatchTouchEvent 在这里直接返回，返回值是 true。
没有 OnTouchListener 的话就执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent(MotionEvent event)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null &amp;amp;&amp;amp; (mViewFlags &amp;amp; ENABLED_MASK) == ENABLED &amp;amp;&amp;amp; mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;OnTouchListener 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouch()&lt;/code&gt;
用户使用setOnTouchListener设置的。
如果它的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouch()&lt;/code&gt; 返回 true，则 View 本身的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent()&lt;/code&gt;不会被调用。如果返回 false，则 View 本身的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent()&lt;/code&gt;会被调用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以，OnTouchListener 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouch()&lt;/code&gt; 优先级比 View 本身的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent()&lt;/code&gt; 要高。&lt;/p&gt;

&lt;p&gt;而用户通过 setOnClickListener 设置的 OnClickListener 中的 onClick，是在 View 本身的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent()&lt;/code&gt;中判断执行的，因此，用户设置的 onTouch 事件会优先于 onClick 事件。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onTouchEvent(MotionEvent event)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/View.java#L4097&quot;&gt;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/View.java#L4097&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 View 类中，会通过 MotionEvent 的事件类型（ ACTION_UP ）和各项条件判断去调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;performClick()&lt;/code&gt; 继而调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mOnClickListener.onClick(this);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;View 的 onTouchEvent 默认返回 true。也就是如果事件流到 View 的 onTouchEvent 中，默认会消费这个事件。
如果当前View 是不可点击的（ clickable 和 longClickable 都是 false，longClickable 默认是 false，clickable 不同控件的值不同），那么 View 的 onTouchEvent 默认返回 false。&lt;/p&gt;

&lt;h2 id=&quot;framework-中包含的工具&quot;&gt;framework 中包含的工具&lt;/h2&gt;

&lt;p&gt;ViewConfigration 中：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Slop&lt;/li&gt;
  &lt;li&gt;ScaleSlop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;处理触摸事件的相关工具：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;GestureDetector&lt;/li&gt;
  &lt;li&gt;ScaleGestureDetector&lt;/li&gt;
  &lt;li&gt;VelocityTracker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;控制某位置的触摸事件流向：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;TouchDelegate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;处理滚动的计算：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Scroller&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/devunwired/custom-touch-examples&quot;&gt;https://github.com/devunwired/custom-touch-examples&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Fri, 27 Apr 2018 12:16:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/04/27/android_touch_event_dispatch.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/04/27/android_touch_event_dispatch.html</guid>
        
        <category>develop</category>
        
        <category>android</category>
        
        <category>touch</category>
        
        <category>event</category>
        
        
      </item>
    
      <item>
        <title>Android View 测量布局绘制过程</title>
        <description>&lt;h2 id=&quot;开始&quot;&gt;开始&lt;/h2&gt;

&lt;p&gt;每个 Activity 会创建一个 PhoneWindow 对象，是 Activity 和整个 View 系统交互的接口。&lt;/p&gt;

&lt;p&gt;每个 Window 都通过一个 ViewRootImpl 与一个 VIew 关联，对于 Activity来说，ViewRootImpl 是连接 WindowManager 和 DecorView 的纽带。&lt;/p&gt;

&lt;p&gt;绘制的入口是由 ViewRootImpl 的 performTraversals 方法去依次调用 DecorView 的 measure，layout，draw 方法。&lt;/p&gt;

&lt;p&gt;在调用 DecorView 的 measure 时，会传入 MeasureSpec 参数。这个参数是由屏幕宽高，和WindowManager.LayoutParams 得来的。&lt;/p&gt;

&lt;p&gt;FYI：ViewGroup 是一个抽象类，所以不能直接 new 对象，所以在 xml 布局文件中不能直接使用 ViewGroup。&lt;/p&gt;

&lt;p&gt;把大象装冰箱，总共分三步走。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/View.java#L7690&quot;&gt;https://github.com/aosp-mirror/platform_frameworks_base/blob/donut-release/core/java/android/view/View.java#L7690&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;measure&quot;&gt;Measure&lt;/h2&gt;

&lt;p&gt;View 的 measure 主要作用就是调用自己的 onMeasure。它接收 MeasureSpec 类型的参数。另外就是维护一些 View 本身的状态变量。&lt;/p&gt;

&lt;p&gt;View 的 measure 是 final，所以 View 的子类（比如 ViewGroup / 自定义控件）是不能 override 这个方法的。所以 ViewGroup 没有实现 measure 方法，也就是说，它使用的是父类 View 的 measure 。&lt;/p&gt;

&lt;p&gt;在完成 measure 之后，可以使用 getMeasureWidth() 和 getMeasureHeight() 获得经过 measure 之后的控件尺寸。&lt;/p&gt;

&lt;h3 id=&quot;onmeasure&quot;&gt;onMeasure&lt;/h3&gt;

&lt;p&gt;onMeasure 的功能是测量自身的尺寸。&lt;/p&gt;

&lt;p&gt;测量完成后调用 setMeasuredDimension 保存。&lt;/p&gt;

&lt;p&gt;View 类中有一个的 onMeasure 实现。抽象类 ViewGroup 中没有 onMeasure 的实现，所以默认的 onMeasure 也是父类 View 的 onMeasure 。&lt;/p&gt;

&lt;p&gt;通常，自定义的 View 和 ViewGroup 都需要根据自身业务逻辑实现自己的 onMeasure。&lt;/p&gt;

&lt;h3 id=&quot;viewonmeasure&quot;&gt;View.onMeasure&lt;/h3&gt;

&lt;p&gt;View 类中默认 onMeasure 方法，会测量自身的尺寸，并调用自身的 setMeasuredDimension 保存尺寸。（因为不是 ViewGroup ，所以只测量自身，不涉及下层 View ）&lt;/p&gt;

&lt;p&gt;其中调用 getDefaultSize 时，因为 View 本身没有业务逻辑，所以会将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WRAP_CONTENT&lt;/code&gt; 的情况像 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MATCH_PARENTS&lt;/code&gt; 一样填满父控件，所以自定义 View 如果需要支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WRAP_CONTENT&lt;/code&gt; ，需要重写 onMeasure方法。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;getSuggestedMinimumWidth 功能是计算「内容」尺寸，在没有业务逻辑的 View 类中，计算方法是：根据有没有背景，背景最小尺寸，View 的 minWidth 来获取。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;protected int getSuggestedMinimumWidth () {
    return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth());
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;getDefaultSize ，功能是根据 getSuggestedMinimumWidth 得到的「内容」尺寸，再根据上层 View 对当前 View 的限制 measureSpec，得到最终尺寸。&lt;/p&gt;

&lt;p&gt;计算方式是：当前 View 的 MeasureSpec 的 mode 是 UNSPECIFIED 时，使用「内容」尺寸，如果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AT_MOST&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXACTLY&lt;/code&gt;，使用 MeasureSpec 的尺寸（这里因为没有业务逻辑的 View，不区分 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AT_MOST&lt;/code&gt; 或者 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXACTLY&lt;/code&gt;，所以布局中的 View ，设置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrap_content&lt;/code&gt; 并不起作用）。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static int getDefaultSize (int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec. getMode(measureSpec);
    int specSize = MeasureSpec. getSize(measureSpec);
    switch (specMode) {
    case MeasureSpec. UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec. AT_MOST:
    case MeasureSpec. EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;在自定义 View 时，根据当前 View 的「内容」尺寸和上层 View 对当前 View 的限制（MeasureSpec），得到一个符合限制的尺寸这一步。可以使用 View 类的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolveSize()&lt;/code&gt;。这个方法的业务逻辑就是正常逻辑。&lt;/p&gt;

&lt;h3 id=&quot;viewgrouponmeasure&quot;&gt;ViewGroup.onMeasure&lt;/h3&gt;

&lt;p&gt;ViewGroup &lt;strong&gt;实现类&lt;/strong&gt;的 onMeasure 的实现方式，基本上，分两大步🚶🚶：遍历下层 View，计算自身。
遍历下层 View 也分两大步：计算 MeasureSpec，调用下层 View 的 measure 形成递归。&lt;/p&gt;

&lt;p&gt;遍历所有下层 View：
遍历到每个下层 View 时，先计算它的 MeasureSpec，（计算可以使用 ViewGroup 的 measureChild 调用 getChildMeasureSpec 方法）&lt;/p&gt;

&lt;p&gt;然后把得到的 MeasureSpec 作为参数调用下层 View 的 measure，形成递归。&lt;/p&gt;

&lt;p&gt;如果这个下层 View 是 ViewGroup 子类，则继续遍历它的下层 View，如果是个&lt;strong&gt;底层&lt;/strong&gt; View，则根据这个底层 View 的自身业务逻辑测量尺寸，这里会有几个参数，一个是包含了上层 ViewGroup 尺寸限制的 MeasureSpec，一个是根据自身业务计算得到到「内容」尺寸，选择一个合适的尺寸保存（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setMeasuredDimension &lt;/code&gt;）。View 的默认实现是：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;遍历结束时，或者遍历过程中，随着下层 View 的逐渐测量完毕（当前 ViewGroup &lt;strong&gt;实现类&lt;/strong&gt;的「内容」尺寸），根据当前 ViewGroup &lt;strong&gt;实现类&lt;/strong&gt;自身业务逻辑，得出当前 ViewGroup &lt;strong&gt;实现类&lt;/strong&gt; 自身的尺寸并保存。&lt;/p&gt;

&lt;p&gt;一个 View ，margin 的计算，由它上一层的 ViewGroup 负责，padding 的计算，算到「内容」尺寸中。&lt;/p&gt;

&lt;p&gt;ViewGroup 提供了几个通用的测量下层 View 相关的方法：getChildMeasureSpec ， measureChild，measureChildWithMargins，measureChildren 。&lt;/p&gt;

&lt;h3 id=&quot;measurespec&quot;&gt;MeasureSpec&lt;/h3&gt;

&lt;p&gt;一个从上层视图角度考虑的，对当前 View 的尺寸限制。有两个数据，一个 mode ， 一个 size。&lt;/p&gt;

&lt;p&gt;整型（32位）将 size 和 mode 打包成一个 int 型，其中高两位是 mode(-1 代表的是EXACTLY，-2 是AT_MOST)，后面 30 位存的 size。&lt;/p&gt;

&lt;p&gt;一个 View 的 MeasureSpec 是由上层 View 的 MeasureSpec 加上自身的 LayoutParams 来得到的 mode，由具体业务逻辑（比如当前 ViewGroup 剩余的空间）来得到 size。参考 getChildMeasureSpec 方法。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MeasureSpec.makeMeasureSpec()&lt;/code&gt; 制作一个 MeasureSpec 。&lt;/p&gt;

&lt;p&gt;MeasureSpec 一共有三种 mode&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;EXACTLY 当前 View 应直接使用上层 View 给定的限制尺寸&lt;/li&gt;
  &lt;li&gt;AT_MOST 当前 View 可以是 上层 View 给定限制尺寸 以内的任意尺寸&lt;/li&gt;
  &lt;li&gt;UPSPECIFIED 上层 View 对当前 View 没有任何限制，当前 View 可以使用任意尺寸。这种模式一般是 Android 系统内部使用，或者 ListView 和 ScrollView 等滑动控件。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;layoutparams&quot;&gt;LayoutParams&lt;/h3&gt;

&lt;p&gt;当前 View 给上层 ViewGroup 提供的布局需求。&lt;/p&gt;

&lt;p&gt;在 xml 布局中使用的以 “layout_” 开头的属性都是布局属性。&lt;/p&gt;

&lt;p&gt;在View中有一个 mLayoutParams 的变量用来保存这个View的所有布局属性。&lt;/p&gt;

&lt;p&gt;ViewGroup 中有两个内部类 LayoutParams 和 MarginLayoutParams，它们只有最基础的功能。&lt;/p&gt;

&lt;p&gt;LayoutParams 只有 width / height 两个功能属性。MarginLayoutParams 是 LayoutParams 的子类，是添加了 Margin 相关属性的 LayoutParams。
如果需要其他的功能，需要继承 LayoutParams&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;generateLayoutParams (AttributeSet attrs)&lt;/code&gt; 方法作用是将 xml 布局中的 AttributeSet 参数，转化成 LayoutParams。
自定义控件时，自定义了 LayoutParams 属性时，需要 override 这几方法。&lt;/p&gt;

&lt;h3 id=&quot;getchildmeasurespec&quot;&gt;getChildMeasureSpec&lt;/h3&gt;

&lt;p&gt;ViewGroup 中的 getChildMeasureSpec：（measureChild 和 measureChildWithMargins 都会调用这个方法）
这个方法是按照 child 在这个维度上&lt;strong&gt;独享&lt;/strong&gt;上层 ViewGroup 的逻辑来计算的。如果你的 ViewGroup 并不是这种逻辑，比如一个一个排列的摆放逻辑，那么定义一个变量记录在当前 child 时，上层 ViewGroup 还剩多少尺寸。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;// spec：上层 ViewGroup 的 MeasureSpec
// padding：上层 ViewGroup 的 Padding + 当前 View 的 Margin
// childDimension：当前 View 的 LayoutParams 的 lp.width 或者 lp.height（ 可以是wrap_content、match_parent、一个精确值)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    int size = Math.max(0, specSize - padding);
    int resultSize = 0;
    int resultMode = 0;
    switch (specMode) {
        case MeasureSpec.EXACTLY:
            if (childDimension &amp;gt;= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            }
            else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;  
                resultMode = MeasureSpec.EXACTLY; 
            }
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.AT_MOST:
            if (childDimension &amp;gt;= 0) {
                resultSize = childDimension; 
                resultMode = MeasureSpec.EXACTLY;  
            }
            else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;  
                resultMode = MeasureSpec.AT_MOST;  
            }
            break;
        case MeasureSpec.UNSPECIFIED:
            if (childDimension &amp;gt;= 0) {
                resultSize = childDimension;  
                resultMode = MeasureSpec.EXACTLY; 
            }
            else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED;  
            }
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;  
                resultMode = MeasureSpec.UNSPECIFIED; 
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;measurechild--measurechildwithmargins---measurechildren&quot;&gt;measureChild / measureChildWithMargins  / measureChildren&lt;/h3&gt;

&lt;p&gt;measureChild 是对 child 的宽高调用 getChildMeasureSpec，然后调用 child.measure。&lt;/p&gt;

&lt;p&gt;measureChildWithMargins 与 measureChild 唯一的区别是调用 getChildMeasureSpec 时，第二个参数加上了 child 的 lp.margin。&lt;/p&gt;

&lt;p&gt;measureChildren 是遍历当前 ViewGroup 所有 child，并调用 measureChild。&lt;/p&gt;

&lt;h2 id=&quot;layout&quot;&gt;Layout&lt;/h2&gt;

&lt;p&gt;摆放 View 的位置&lt;/p&gt;

&lt;p&gt;与 Measure 过程不同的是，layout 调用时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout&lt;/code&gt; 中先 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setFrame&lt;/code&gt; 确定当前 View 的位置，再 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onLayout&lt;/code&gt; 遍历下层 View 并确定位置。&lt;/p&gt;

&lt;p&gt;在某些复杂情况下，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT&lt;/code&gt;，在 layout 过程中，会再次进行 onMeasure。&lt;/p&gt;

&lt;h3 id=&quot;layout-1&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;layout 确定 View 自身的位置。&lt;/p&gt;

&lt;p&gt;View 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt; 的参数 l, t, r, b 分别表示当前 View 相对于上层 View 的左、上、右、下的坐标。这个坐标，通常是在上一步测量当前 View 的尺寸时，上层 View 通常也能得到，保存起来，以便这里使用。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt; 方法主要功能是：&lt;/p&gt;

&lt;p&gt;先调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setFrame()&lt;/code&gt; 设置当前 View 位置，&lt;/p&gt;

&lt;p&gt;View 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setFrame()&lt;/code&gt; 主要功能是：将新旧位置进行对比，只要不完全相同，那么保存新的位置，保存局部渲染可能使用的位置信息。 changed 变量设置为 true，调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onSizeChanged()&lt;/code&gt; 方法。同时如果 View 处于可见状态，那么调用 invalidate 来重新绘制，最后返回 changed 的值。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setFrame()&lt;/code&gt; 返回一个 boolean，代表位置是否变化，如果有变化，则调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onLayout()&lt;/code&gt; ，此时如果有 LayoutChangeListener，依次调用其 onLayoutChange 方法。&lt;/p&gt;

&lt;p&gt;ViewGroup 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt; 主要功能是：处理 ViewGroup 增加和删除下层 View 的 LayoutTransition 动画效果，如果未添加动画，或者动画此刻并未运行，那么调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super.layout(l, t, r, b)&lt;/code&gt;，也就是 View 的 layout，否则等待动画完成时后调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestLayout()&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;View 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt; 是可以被覆写的。
ViewGroup 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt; 是 final 的，不能被覆写。&lt;/p&gt;

&lt;p&gt;在完成 layout 之后，可以使用 getWidth 和 getHeight，获得正常尺寸，这个尺寸是通过计算摆放完的控件坐标得来。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getWidth = right - left&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&quot;onlayout&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onLayout&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;onLayout 调度下层 View 确定位置。&lt;/p&gt;

&lt;p&gt;onLayout 的参数跟 layout 的参数一样。 l, t, r, b 分别表示 当前 View 相对于上层 View 的左、上、右、下的坐标。&lt;/p&gt;

&lt;p&gt;View 不会再有下层 View，所以 View 的 onLayout 是空实现，ViewGroup 中的  onLayout 是抽象方法。所以 onLayout 肯定是 有具体的功能的 ViewGroup 子类实现。通常需要做这些事：&lt;/p&gt;

&lt;p&gt;根据这个 ViewGroup 子类的摆放逻辑，当前 ViewGroup 剩余空间，onMeasure 过程中得到的下层 View 的相关尺寸，LayoutParams 的 margin，gravity 等，计算出每个下层 View 应处的左上右下位置（可能已经在 onMeasure 时已经一起测量出来，并且保存在某个变量中），最终调用每个下层 View 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout()&lt;/code&gt;，形成递归。&lt;/p&gt;

&lt;h2 id=&quot;drawcanvas&quot;&gt;draw(canvas)&lt;/h2&gt;

&lt;p&gt;draw 方法主要功能是：按顺序调用当前 View 各个「景深」层面的绘制。&lt;/p&gt;

&lt;p&gt;View 中的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw()&lt;/code&gt; 方法不是 final 的，可以被覆写。&lt;/p&gt;

&lt;p&gt;ViewGroup 没有&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw()&lt;/code&gt;实现。它使用的是父类 View 的 draw 。&lt;/p&gt;

&lt;p&gt;View 中的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw()&lt;/code&gt; 的过程，源码注释已经很清楚了：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/*
  * Draw traversal performs several drawing steps which must be executed
  * in the appropriate order:
  *
  * 1. Draw the background
 skip step 2 &amp;amp; 5 if possible (common case)

  * 2. If necessary, save the canvas' layers to prepare for fading
  * 3. Draw view's content
  * 4. Draw children
  * 5. If necessary, draw the fading edges and restore layers
  * 6. Draw decorations (scrollbars for instance)
  */
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;主要就是调用了下面几个方法：（对应上面注释的序号）&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drawBackground()&lt;/code&gt; 背景(不能覆写)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDraw()&lt;/code&gt; 当前 View&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchDraw()&lt;/code&gt; 下层 View&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDrawForeground()&lt;/code&gt; 滑动边缘渐变提示和滚动条，前景。如果覆写，需要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minSdk&amp;gt;=23&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;不同于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;measure&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;layout&lt;/code&gt; ，遍历下层 View 这个步骤，不是在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDraw&lt;/code&gt; 中，而是在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw&lt;/code&gt; 中。并且有专门的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchDraw &lt;/code&gt; 方法遍历下层 View 。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;ondraw&quot;&gt;onDraw&lt;/h3&gt;

&lt;p&gt;实现当前 View 自身内容的绘制（包括 padding 的处理）。&lt;/p&gt;

&lt;p&gt;View 和 ViewGroup 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDraw(canvas)&lt;/code&gt; 都是空实现。&lt;/p&gt;

&lt;p&gt;ViewGroup 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw()&lt;/code&gt; 默认不会调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDraw()&lt;/code&gt; 方法。因为正常来说，ViewGroup 是一个 View 容器，自身不会有具体画面。&lt;/p&gt;

&lt;p&gt;如果需要回调 onDraw() 方法，在构造函数中调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setWillNotDraw(false)&lt;/code&gt; 即可。（但是，如果你继承的是比如 ScrollView 这种 ViewGroup ，它已经调用过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setWillNotDraw(false)&lt;/code&gt; 了）&lt;/p&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw()&lt;/code&gt; 过程中，某些情况下，比如只是前景状态改变，系统会做相应优化，跳过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onDraw()&lt;/code&gt; 。&lt;/p&gt;

&lt;h3 id=&quot;dispatchdraw&quot;&gt;dispatchDraw&lt;/h3&gt;

&lt;p&gt;View 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchDraw()&lt;/code&gt; 方法是一个空实现&lt;/p&gt;

&lt;p&gt;ViewGroup 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchDraw()&lt;/code&gt; 主要功能就是遍历下层 View，计算下层 View 的 canvas 剪切区（剪切区的大小正是由 layout 过程决定的，位置取决于滚动值以及当前的动画等），并调用它们的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw(canvas)&lt;/code&gt; 方法。&lt;/p&gt;

&lt;p&gt;这个实现基本能满足大部分需求，如果需要自定义下层 View 的绘制，则需要覆写这个方法。通常会在这个自定义的方法中，根据自定义的逻辑，在合适的位置调用默认过程 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;super.dispatchDraw(canvas);&lt;/code&gt; ，省时省力。&lt;/p&gt;

&lt;h2 id=&quot;requestlayout&quot;&gt;requestLayout&lt;/h2&gt;

&lt;p&gt;requestLayout 意味着视图的大小已经改变，整个 View 树将会重新测量，重新布局，可能会重新绘制。&lt;/p&gt;

&lt;p&gt;在 requestLayout 方法中，首先先判断当前 View 树是否正在布局流程，接着为当前 View 设置标记位，该标记位的作用就是标记了当前的 View 是需要进行重新布局的，接着调用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mParent.requestLayout&lt;/code&gt; 方法，向上层 ViewGroup 请求 layout，即调用上层 ViewGroup 的requestLayout 方法，为上层 ViewGroup 添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PFLAG_FORCE_LAYOUT&lt;/code&gt; 标记位，而上层 ViewGroup 又会调用它的上层 ViewGroup 的 requestLayout 方法，形成 requestLayout 事件层层向上传递，直到最上层的 DecorView，而 DecorView 又会传递给 ViewRootImpl，也即是当前 View 的 requestLayout 事件，最终会被 ViewRootImpl 接收并处理。&lt;/p&gt;

&lt;p&gt;在 ViewRootImpl 中，重写了requestLayout 方法，我们看看这个方法，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewRootImpl#requestLayout&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;在这里，调用了scheduleTraversals 方法，这个方法是一个异步方法，最终会调用到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewRootImpl#performTraversals&lt;/code&gt;方法，在这里会调用 measure、layout、draw 这三大流程。&lt;/p&gt;

&lt;p&gt;在 measure 时，最开始就会判断一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PFLAG_FORCE_LAYOUT&lt;/code&gt; 标记位，如果有这个标记，那么就会进行测量流程，调用 onMeasure，最后为标记位设置为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PFLAG_LAYOUT_REQUIRED&lt;/code&gt;，这个标记位的作用就是在 layout 流程中，如果当前 View 设置了该标记位，则会进行布局流程。&lt;/p&gt;

&lt;p&gt;有很多控件比如 TextView，做了一些会导致尺寸修改的操作时，例如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setPadding()&lt;/code&gt; ， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setTypeface()&lt;/code&gt; ， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setCompoundDrawables()&lt;/code&gt;等。在调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;requestLayout()&lt;/code&gt; 之后，会再调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidate()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;因为 requestLayout 本身不会导致绘制过程，只是有些控件内部已经调用了 invalidate 来响应 layout 更改。&lt;/p&gt;

&lt;p&gt;因此，为了确保重新布局会导致重画，那么你应该在 requestLayout 配一个 invalidate。&lt;/p&gt;

&lt;h2 id=&quot;invalidate&quot;&gt;invalidate&lt;/h2&gt;

&lt;p&gt;invalidate 请求把 View 重新 draw 一下。 重绘不会同步发生。 相反，它会将当前 View 区域标记为无效（dirty），以便在下一个渲染周期中重绘。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalidate()&lt;/code&gt; 重绘这个 View，需要注意的是在开启了硬件加速时（API 14之后包括 14，硬件加速默认是开启的），绘制流程会与正常流程有不同，可能会跳过 某些流程。&lt;/p&gt;

&lt;p&gt;下面分析调用流程：&lt;/p&gt;

&lt;p&gt;invalidate 最终都会调用 invalidateInternal 方法，在这个方法内部，进行了一系列的判断，判断当前 View 是否需要重绘，接着为当前 View 设置标记位，并调用上层 ViewGroup 的 invalidateChild 方法，把需要重绘的区域传递给上层 ViewGroup。&lt;/p&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewGroup#invalidateChild&lt;/code&gt; 中，先设置当前视图的标记位，接着有一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do…while…&lt;/code&gt;循环，该循环的作用是不断向上递归上层 ViewGroup。当父容器不是 ViewRootImpl 的时候，调用的是 ViewGroup 的 invalidateChildInParent 方法，这个方法会返回当前 View 的上层 ViewGroup。&lt;/p&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewGroup#invalidateChildInParent&lt;/code&gt; 中，调用 offset 方法，把之前传来的下层 View 的 dirty 区域的坐标转化为当前 ViewGroup 中的坐标，接着调用 union 方法，把下层 View 的  dirty 区域与当前 ViewGroup 区域求并集，也就是 dirty 区域加上了当前 ViewGroup 的。最后返回当前 ViewGroup 的上层 ViewGroup，以便进行下一次循环。&lt;/p&gt;

&lt;p&gt;在上面 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ViewGroup#invalidateChild&lt;/code&gt; 所说的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do…while…&lt;/code&gt;循环中，由于不断调用上层 ViewGroup 的方法，到最后会调用到 ViewRootImpl 的 invalidateChildInParent 方法，在这里，也进行了 offset 和 union 然后把dirty区域的信息保存在 mDirty 中，最后会调用 scheduleTraversals 方法，在这里会对整个 View 树调用 measure、layout、draw 这三大流程。由于没有添加 measure 和 layout 的标记位，因此 measure、layout 流程不会执行，而是直接从 draw 流程开始。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invadite()&lt;/code&gt; 必须在主线程中调用。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postInvalidate()&lt;/code&gt; 只有视图被添加到窗口的时候才会继续执行，也就是 attachInfo 不为 null 的时候。内部是由 Handler 的消息机制实现的，所以在任何线程都可以调用，但实时性没有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invadite()&lt;/code&gt; 强。一般保险起见，会使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postInvalidate()&lt;/code&gt; 来刷新界面。&lt;/p&gt;
</description>
        <pubDate>Fri, 27 Apr 2018 12:02:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/04/27/android_view_measure_layout_draw.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/04/27/android_view_measure_layout_draw.html</guid>
        
        <category>develop</category>
        
        <category>android</category>
        
        <category>view</category>
        
        <category>measure</category>
        
        <category>layout</category>
        
        <category>draw</category>
        
        
      </item>
    
      <item>
        <title>Android 库的打包和发布</title>
        <description>&lt;h1 id=&quot;已发布android-库的打包和发布&quot;&gt;「已发布」Android 库的打包和发布&lt;/h1&gt;

&lt;p&gt;一个库项目的构建过程与一般项目是一样的。&lt;/p&gt;

&lt;p&gt;如果只需要一个 aar 文件的话，只需要在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./gradlew assembleRelease&lt;/code&gt;之后，在项目的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/outputs/aar&lt;/code&gt; 目录下就会生成 aar 文件。然后在使用这个库的的项目里添加这个依赖就可以了&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;repositories {
    flatDir {
        dirs 'libs' //this way we can find the .aar file in libs folder
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dependencies {
    compile(name:'test', ext:'aar')
}·
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果需要上传到比如 jcenter 这类中央仓库。除了打包，还需要上传到 jcenter 的仓库中。&lt;/p&gt;

&lt;p&gt;打包成 maven 仓库支持的格式，需要使用 maven，Android 项目可以通过 gradle 调用 maven，但是 maven 默认并不支持 Android 的库，所以需要使用这个：&lt;a href=&quot;https://github.com/dcendents/android-maven-gradle-plugin&quot;&gt;https://github.com/dcendents/android-maven-gradle-plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果配置好这个插件之后，执行一下 build，就可以在项目的 build 目录下生成 JavaDoc 和 sourceJAR 等文件了。&lt;/p&gt;

&lt;p&gt;上传到 jcenter ，可以直接在 bintray 的网站上传上面的到的文件，也可以使用 bintray 自己出的这个插件：&lt;a href=&quot;https://github.com/bintray/gradle-bintray-plugin&quot;&gt;https://github.com/bintray/gradle-bintray-plugin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;打包上传的 gradle 脚本我放在 github gist 中了：&lt;a href=&quot;https://gist.github.com/foolhorse/2175dc920e43e4abc27f72e792352fad&quot;&gt;https://gist.github.com/foolhorse/2175dc920e43e4abc27f72e792352fad&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;使用时，在根项目的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle&lt;/code&gt; 中添加&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dependencies {
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0'
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在库项目的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle&lt;/code&gt; 中添加&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apply from: 'https://gist.githubusercontent.com/foolhorse/2175dc920e43e4abc27f72e792352fad/raw/1bfcb2b0d6fbbbb0169672f5df79f887a95fb3e8/gradle-bintray-push.gradle'

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在库项目的目录下的给项目新建一个 gradle.properties ，里面放上各个需要的属性，比如：&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Project
PROJECT_NAME=lightframeview
PROJECT_PACKAGE_NAME=me.machao.lightframeview
PROJECT_DESCRIPTION=LightFrameView is able to provide a light frame animation by wrapping it to a normal View.

PROJECT_VERSION_NAME=1.0.0
PROJECT_VERSION_DESC=

PROJECT_SITE=https://github.com/foolhorse/AndroidLightFrameView
PROJECT_GIT_URL=https://github.com/foolhorse/AndroidLightFrameView.git
PROJECT_ISSUE_URL=https://github.com/foolhorse/AndroidLightFrameView/issues

PROJECT_LICENCE_NAME=Apache-2.0
PROJECT_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt

# Developer
DEVELOPER_NAME=machao
DELELOPER_EMAIL=

# POM
POM_SCM_URL=https://github.com/foolhorse/AndroidLightFrameView
POM_SCM_CONNECTION=scm:git:https://github.com/foolhorse/AndroidLightFrameView.git
POM_SCM_DEVELOPER_CONNECTION=scm:git:https://github.com/foolhorse/AndroidLightFrameView.git

# Bintray
BINTRAY_REPO=mavenrepo
BINTRAY_LABELS=android,animation
BINTRAY_VCS_TAG=
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;敏感信息，比如 bintray 的用户名密码等，放在 local.properties 里。&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BINTRAY_USER=your_user_name
BINTRAY_USER_ORG=
BINTRAY_API_KEY=your_api_key
BINTRAY_GPG_PASSPHRASE=
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;当然，这些属性也可以不用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle.properties&lt;/code&gt; 的形式，因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gradle.properties&lt;/code&gt; 不能很好的支持变量的类型。也可以在  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle&lt;/code&gt;  中添加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ext {}&lt;/code&gt; 直接写 groovy 代码的变量.&lt;/p&gt;

&lt;h2 id=&quot;上传&quot;&gt;上传&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./gradlew install&lt;/code&gt;，生成 libs docs poms，生成成功后，可以在项目的build目录下看见。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./gradlew bintrayUpload&lt;/code&gt; 上传到 bintray 的 jfrog 上。&lt;/p&gt;

&lt;p&gt;进入 jfrog bintray 的这个项目的页面，点击 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add to jcenter&lt;/code&gt; 申请添加到 jcenger 库中。&lt;/p&gt;

</description>
        <pubDate>Thu, 26 Apr 2018 04:07:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/04/26/android_lib_publish.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/04/26/android_lib_publish.html</guid>
        
        <category>develop</category>
        
        <category>android</category>
        
        <category>lib</category>
        
        
      </item>
    
      <item>
        <title>设计模式 结构篇</title>
        <description>&lt;h1 id=&quot;已发布设计模式-结构篇&quot;&gt;「已发布」设计模式 结构篇&lt;/h1&gt;

&lt;p&gt;装饰者、适配器、外观，代理，这几种模式在代码层面很相似，主要的区别在于对应的业务作用的区别。&lt;/p&gt;

&lt;h2 id=&quot;组合模式-composite-pattern&quot;&gt;组合模式 Composite Pattern&lt;/h2&gt;

&lt;p&gt;Android 里的 View 和 ViewGroup&lt;/p&gt;

&lt;p&gt;树形结构以表示”部分-整体”的层次结构( View 可以做为 ViewGroup 的一部分)。&lt;/p&gt;

&lt;p&gt;组合模式使得 户对  单个对象 View 和 组合对象 ViewGroup 的使用具有一致性。&lt;/p&gt;

&lt;h2 id=&quot;适配器模式-adapter-pattern&quot;&gt;适配器模式 Adapter Pattern&lt;/h2&gt;

&lt;p&gt;也叫 Wrapper 或者 Translator。&lt;/p&gt;

&lt;p&gt;国标插座转换成英标插座的电源适配器，这个适配器模式的作用与电源适配器作用完全一样。&lt;/p&gt;

&lt;p&gt;业务作用是将一个接口变为另一个接口。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface MusicPlayer{
    public void play() ;   
}

interface Singer{
    public void sing();
}

class MusicPlayerAdapter implements Singer{

    MusicPlayer musicPlayer

    public MusicPlayerAdapter(MusicPlayer musicPlayer){
        this.musicPlayer = musicPlayer;
    }
    public void singSong(){
        this.musicPlayer.play();
    }
}

public class AdpaterDemo{
    public static void main(String args[]){
        MusicPlayer musicPlayer = new MusicPlayer();
        Singer musicPlayerAdapter = new MusicPlayerAdapter(musicPlayer) ;
        musicPlayerAdapter.sing() ;
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;外观模式-facade&quot;&gt;外观模式 Facade&lt;/h2&gt;

&lt;p&gt;对于复杂的调用，比如有复杂顺序要求，有许多繁琐的 api 等，或者有些 api 的调用不想暴露出来，用一个外观类作为对外的门面，在这里用对外一个函数去调用那些复杂的内部函数。&lt;/p&gt;

&lt;p&gt;比如 Android 中的 Context，内部有很多复杂功能通过比如 startActivty、sendBroadcast、bindService 实现。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}

class HardDrive {
    public byte[] read(long lba, int size) { ... }
}

class Memory {
    public void load(long position, byte[] data) { ... }
}

/* Facade */
class ComputerFacade {
    private CPU processor;
    private Memory ram;
    private HardDrive hd;

    public ComputerFacade() {
        this.processor = new CPU();
        this.ram = new Memory();
        this.hd = new HardDrive();
    }

    public void start() {
        processor.freeze();
        ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE));
        processor.jump(BOOT_ADDRESS);
        processor.execute();
    }
}

/* Client */
class You {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;装饰者模式-decorator&quot;&gt;装饰者模式 Decorator&lt;/h2&gt;

&lt;p&gt;可以在运行时，动态的添加和删除某一个或者多个功能。贯彻单一职责原则的好方法。&lt;/p&gt;

&lt;p&gt;装饰者模式很像责任链模式，区别是在责任链中有一个明确的类来处理请求，而在装饰者模式中 ，所有的装饰者都参与处理请求。&lt;/p&gt;

&lt;p&gt;可以实现类似多继承的效果。（实际上是多层的继承）&lt;/p&gt;

&lt;p&gt;Java 的 IO 使用就是典型的装饰者模式&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// The interface Coffee defines the functionality of Coffee implemented by decorator
public interface Coffee {
    public double getCost(); // Returns the cost of the coffee
    public String getIngredients(); // Returns the ingredients of the coffee
}

// Extension of a simple coffee without any extra ingredients
public class SimpleCoffee implements Coffee {
    @Override
    public double getCost() {
        return 1;
    }

    @Override
    public String getIngredients() {
        return &quot;Coffee&quot;;
    }
}

// Abstract decorator class - note that it implements Coffee interface
public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }

    @Override
    public String getIngredients() {
        return decoratedCoffee.getIngredients();
    }
}

// Decorator WithMilk mixes milk into coffee.
// Note it extends CoffeeDecorator.
class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee c) {
        super(c);
    }

    @Overriding
    public double getCost() {
        return super.getCost() + 0.5;
    }

    @Overriding
    public String getIngredients() {
        return super.getIngredients() + &quot;, Milk&quot;;
    }
}

// Decorator WithSprinkles mixes sprinkles onto coffee.
// Note it extends CoffeeDecorator.
class WithSprinkles extends CoffeeDecorator {
    public WithSprinkles(Coffee c) {
        super(c);
    }

    @Overriding
    public double getCost() {
        return super.getCost() + 0.2;
    }

    @Overriding
    public String getIngredients() {
        return super.getIngredients() + &quot;, Sprinkles&quot;;
    }
}

public class Main {
    public static void printInfo(Coffee c) {
        System.out.println(&quot;Cost: &quot; + c.getCost() + &quot;; Ingredients: &quot; + c.getIngredients());
    }

    public static void main(String[] args) {
        Coffee c = new SimpleCoffee();
        printInfo(c);
        c = new WithMilk(c);
        printInfo(c);
        c = new WithSprinkles(c); // 类似于 c = new WithMilk(new WithSprinkles(c));
        printInfo(c);
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;代理模式-proxy-pattern&quot;&gt;代理模式 Proxy Pattern&lt;/h2&gt;

&lt;p&gt;对于调用者来说，操作并没有不同，看起来是一样的。&lt;/p&gt;

&lt;p&gt;但是真正的处理过程是通过代理人完成，代理人可以加上一些自己的操作，可以是增加操作，也可以是修改操作。&lt;/p&gt;

&lt;p&gt;代理与装饰者的一个重要区别是：代理中可以实例化真正的操作对象。而装饰者只能包装现有的操作对象。&lt;/p&gt;

&lt;p&gt;比如代理服务器就是修改请求，一些商品的销售代理就是把货品卖给你的同时自己也抽成。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;interface Give{
    public void giveMoney() ;
}

class RealGive implements Give{
    public void giveMoney(){
        System.out.println(real give money&quot;) ;
    }
}

class GiveProxy implements Give{

    private Give give = null ;

    public GiveProxy(Give give){
        this.give = give ;
    }

    public void before(){
        System.out.println(&quot;before&quot;) ;
    }
    
    public void after(){
        System.out.println(&quot;after&quot;) ;
    }
    public void giveMoney(){
        this.before() ;
        this.give.giveMoney() ;
        this.after() ;
    }
}

public class ProxyDemo{
    public static void main(String args[]){
        Give give = new GiveProxy(new RealGive()) ;
        give.giveMoney() ;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;桥接模式-bridge&quot;&gt;桥接模式 bridge&lt;/h2&gt;

&lt;p&gt;将抽象部分与实现部分分离，使他们独立地进行变化。 抽象部分和实现部分（固有操作）的区别依据具体的业务逻辑而定。&lt;/p&gt;

&lt;p&gt;其实就是，一个类除了本身的固有操作，还存在两个或者多个维度的变化，且这多个维度都扩展的可能，使用桥接模式可以方便在多个维度进行扩展。&lt;/p&gt;

&lt;p&gt;也可以达到类似多继承的效果。本质上是通过增加耦合，不用继承。&lt;/p&gt;

&lt;p&gt;与装饰者模式的区别在于：装饰者模式把多出来的部分放到单独的类里面，每个单独的类都是一个单一的功能。桥接模式则把多出来的功能按照某种维度抽象出来，形成不同的接口/抽象类，然后让具体的不同功能按照他的维度去实现不同的接口。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;某个功能的具体实现 是 类 的某个成员变量 来提供。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/** &quot;Implementor&quot; */
interface DrawingAPI {
    public void drawCircle(int x, int y, int radius);
    public void drawDot(int x, int y);
}

/** &quot;ConcreteImplementor&quot;  1/2 */
class DrawingAPIWindows implements DrawingAPI {
    public void drawCircle(int x, int y, int radius) {
        System.out.printf(&quot;API Windows circle);
    }
     public void drawCircle(int x, int y) {
        System.out.printf(&quot;API Windows dot);
    }
}

/** &quot;ConcreteImplementor&quot; 2/2 */
class DrawingAPIUbuntu implements DrawingAPI {
    public void drawCircle(int x, int y, int radius) {
        System.out.printf(&quot;API Ubuntu circle);
    }
   public void drawCircle(int x, int y) {
        System.out.printf(&quot;API Ubuntu dot);
    }
}

/** &quot;Abstraction&quot; */
abstract class Shape {
    protected DrawingAPI drawingAPI;

    protected Shape(DrawingAPI drawingAPI){
        this.drawingAPI = drawingAPI;
    }

    public abstract void draw();                                 // low-level
    public abstract void resizeByPercentage(final double pct);   // high-level
}

/** &quot;Refined Abstraction&quot; */
class CircleShape extends Shape {
    private int x, y, radius;
    public CircleShape(int x, double y, int radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;  this.y = y;  this.radius = radius;
    }

    // low-level i.e. Implementation specific
    public void draw() {
        drawingAPI.drawCircle(x, y, radius);
    }
    // high-level i.e. Abstraction specific
    public void resizeByPercentage(final double pct) {
        radius *= (1.0 + pct/100.0);
    }
}

/** &quot;Client&quot; */
class BridgePattern {
    public static void main(final String[] args) {
        Shape shape =  new CircleShape(1, 2, 3, new DrawingAPIWindows()),
        shape.resizeByPercentage(2.5);
        shape.draw();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;享元模式-flyweight&quot;&gt;享元模式 Flyweight&lt;/h2&gt;

&lt;p&gt;Flyweight，蝇量级，拳击里的一个体重级别，在 100 斤上下，基本是最轻的一个拳击级别。&lt;/p&gt;

&lt;p&gt;所以，这个模式的目的就是轻，也就是减少内存，减少 io 等各种性能开销，具体的实现方式，就是中文译名：共享单元。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;可以共享的相同内容称为内部状态(Intrinsic State)&lt;/li&gt;
  &lt;li&gt;需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State)&lt;/li&gt;
  &lt;li&gt;由于区分了内部状态和外部状态，因此可以通过设置不同的外部状态，使得相同的对象可以具有一些不同的特征，而相同的内部状态是可以共享的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有 Flyweight Factory，Flyweight 接口，Flyweight 实现类这几个角色。 通常使用某种工厂模式，使用Flyweight Factory  去生产 Flyweight。&lt;/p&gt;

&lt;p&gt;仔细一看代码会发现，这不就是个简单的缓存嘛。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public interface SmartPhoneModel {
   void double getScreenSize();
}

public IPhone4 implements SmartPhoneModel{

    private String color ;

    public IPhone4(String color){
        this.color = color ;
    }

    void double getScreenSize(){
        return 3.5 ;
    }
}
 
public class SmartPhoneFactory {
    private static final Map&amp;lt;String, SmartPhone&amp;gt; extrinsicStateKeyCache = new HashMap&amp;lt;&amp;gt;();

    public static SmartPhoneModel getSmartPhone(String color) {
        SmartPhoneModel smartPhoneModel = extrinsicStateKeyCache.get(modelStr);

        if(smartPhoneModel == null) {
            smartPhoneModel = new IPhone4(color);
            extrinsicStateKeyCache.put(color, smartPhoneModel);
        }
        return smartPhoneModel;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Design_Patterns&quot;&gt;https://en.wikipedia.org/wiki/Design_Patterns&lt;/a&gt;
&lt;a href=&quot;http://design-patterns.readthedocs.io/zh_CN/latest/index.html&quot;&gt;http://design-patterns.readthedocs.io/zh_CN/latest/index.html&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 25 Apr 2018 16:41:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/04/25/desion_patterns_structural.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/04/25/desion_patterns_structural.html</guid>
        
        <category>develop</category>
        
        <category>oo</category>
        
        
      </item>
    
      <item>
        <title>设计模式 创建篇</title>
        <description>&lt;h1 id=&quot;设计模式-创建篇&quot;&gt;设计模式 创建篇&lt;/h1&gt;

&lt;h2 id=&quot;单例模式&quot;&gt;单例模式&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class Singleton{
    private volatile static Singleton instance;
    private Singleton(){};
    public static Singleton getInstance(){
        if(instance==null){
            sychronized(Singleton.class){
                if(instance==null)
                    instance=new Singleton();
            }
        }
        return instatnce;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;builder-模式-builder-pattern&quot;&gt;Builder 模式 Builder Pattern&lt;/h2&gt;

&lt;p&gt;类似一个更可控的构造函数，可以方便控制参数的配置顺序，&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class MyData{
    private int id;
    private String num; 
    public void setId(int id){
        this.id=id;
    }
    public void setNum(String num){
        this.num = num + &quot;id&quot;; // 注意这里
    }

    public class MyBuilder{
        private int id;
        private String num;
        public MyData build(){
            MyData d = new MyData();
            d.setId(id);
            d.setNum(num);
            return t;
        }
        public MyBuilder setId(int id){
            this.id=id;
            return this;
        }
        public MyBuilder setNum(String num){
            this.num=num;
            return this;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;工厂模式&quot;&gt;工厂模式&lt;/h2&gt;

&lt;p&gt;工厂模式又有几个不同的实现方式：&lt;/p&gt;

&lt;h3 id=&quot;简单工厂模式-simple-factory--静态工厂方法-static-factory-method&quot;&gt;简单工厂模式 Simple Factory / 静态工厂方法 Static Factory Method&lt;/h3&gt;

&lt;p&gt;因为工厂方法是静态方法，所以使用起来很方便，可通过类名直接调用，只需要传入参数即可，在实际开发中，还可以在调用时将所传入的参数保存在 XML/JSON 等格式的配置文件中。&lt;/p&gt;

&lt;p&gt;简单工厂模式最大的问题在于工厂类的职责相对过重，增加新的产品需要修改工厂类的判断逻辑。&lt;/p&gt;

&lt;p&gt;所有的生产都在一个工厂中，工厂无法继承，也无法将不同的产品在不同的工厂中生产。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public abstract class Instrument{
    public abstract void play();
} 

public class Guitar extends Instrument{
    public void play(){
        System.out.println(&quot;guitar solo&quot;);
    }
}

public class Bass extends Instrument{
    public void play(){
        System.out.println(&quot;bass slap&quot;);
    }
}

public class FenderFactory{

    // 注意这里是 static 方法
    public static Instrument create(int type){
        if(type == 1){
            return new Guitar();
        }else if(type == 2){
            return new Bass();
        }else{
            return null;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;工厂方法模式-factory-method&quot;&gt;工厂方法模式 Factory method&lt;/h3&gt;

&lt;p&gt;当系统需要加入新产品时，无须修改现有代码，而只要添加相应的具体工厂和具体产品就可以了。&lt;/p&gt;

&lt;p&gt;在业务复杂的时候，也会使用工厂模式去生产 工厂方法模式中的具体工厂类。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public abstract class Instrument{
    public abstract void play();
} 

public class Guitar extends Instrument{
    public void play(){
        System.out.println(&quot;guitar solo&quot;);
    }
}

public class Bass extends Instrument{
    public void play(){
        System.out.println(&quot;bass slap&quot;);
    }
}

public abstract class InstrumentFactory{
    // 可以有参数，也可以没有
    public abstract Instrument create(int type);
}

public class FenderGuitarFactory extends InstrumentFactory{

    public Instrument create(int type){
        return new Guitar();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;抽象工厂模式-abstract-factory-pattern&quot;&gt;抽象工厂模式 Abstract Factory Pattern&lt;/h3&gt;

&lt;p&gt;通常用在多种产品有依赖关系，产品原料有不同来源的情况。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public abstract class Guitar{
    public abstract void assemble();
}

public abstract class Pickup{
    public abstract void create();
}

public class Telecaster extends Guitar{

    PickupFactory pickupFactory;

    public Telecaster(PickupFactory pickupFactory){
        this.pickupFactory = pickupFactory ;
    }

    public void assemble(){
        pickupFactory.createPickup();
        System.out.println(&quot;用各种部件，组装吉他&quot;);
    }
}

public class SingleCoil extends Pickup{
    public void createPickup(){
        System.out.println(&quot;生产单线圈拾音器&quot;);
    }
}

public abstract class GuitarFactory{
    public abstract Guitar createGuitar();
}

public abstract class PickupFactory{
    public abstract Pickup createPickup();
}

public class FenderFactory extends GuitarFactory{

    // 零件工厂也可以在 create 函数中实例化，视具体情况而定
    PickupFactory pickupFactory = new SeymourDuncanFactory();

    public Guitar createGuitar(int model){
        // 这里同样可以通过参数 if else 去生产不同产品
        return new Telecaster(pickupFactory);
    }
}

public class GibsonFactory extends GuitarFactory{
    
}

public class SeymourDuncanFactory extends PickupFactory{
    public Pickup createPickup(int model){
        // 这里同样可以通过参数 if else 去生产不同产品
        return new SingleCoil();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;原型模式-prototype&quot;&gt;原型模式 Prototype&lt;/h2&gt;

&lt;p&gt;这个模式感觉像个凑数的。😂。将一个对象进行拷贝。
JAVA 继承 Cloneable，重写 clone()&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Design_Patterns&quot;&gt;https://en.wikipedia.org/wiki/Design_Patterns&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 25 Apr 2018 16:41:00 +0000</pubDate>
        <link>http://yourdomain.com/2018/04/25/desion_patterns_creational.html</link>
        <guid isPermaLink="true">http://yourdomain.com/2018/04/25/desion_patterns_creational.html</guid>
        
        <category>develop</category>
        
        <category>oo</category>
        
        
      </item>
    
  </channel>
</rss>
