Handler与异步消息处理

VMQJesse 8年前
   <p>Handler 在 Android 中的应用很广泛,基本上每个 Android 开发人员都会使用到它。本篇文章将会介绍 Handler 和异步消息机制相关的使用方法。</p>    <p>Android 中的异步消息处理框架由 Handler 、MessageQueue、Looper 和 ThreadLocal 等组成。Handler 是我们使用最多的一个类,主要负责发送和处理消息,MessageQueue 是一个队列,用来存储 Handler 发送来的消息,而 Looper 则是一个死循环。</p>    <h2><strong>Handler 的使用场景</strong></h2>    <p>由于 Android 系统不允许在主线程进行耗时任务,因此网络请求等一般都会开新的线程执行,然而,Android 中的控件不是线程安全的,因此只能在主线程中访问 UI 控件,否则就会报错。那么从子线程中得到的数据怎么返回到主线程给 UI 使用呢?答案很简单,大家都知道,可以使用 Handler 将数据返回到主线程。</p>    <p>但有时候你会发现,有些项目里面没有开启子线程,却在使用 Handler,这就是 Handler 的另一个使用场景:消息处理。如果你有一个个的任务需要排队处理,那么使用 Handler 是很合适的。</p>    <h2><strong>Handler 的使用</strong></h2>    <p>Handler 必须与一个 Looper 关联才能使用,而且是在 Handler 创建的时候就要传入一个 Looper 的。由于主线程中默认创建了一个 Looper,因此在主线程中不用传入 Looper。如果不传 Handler,那么系统就会去获取当前线程的 Looper,如果找不到,就会报错。因此,如果在子线程没有手动创建 Looper 的情况下直接使用 Handler handler = new Handler() 的话,是会报错的。</p>    <pre>  <code class="language-java">// 不传入 Looper,系统会自己去获取当前线程中的 Looper。  Handler handler = new Handler();    // 在子线程中,可以传入自定的 Looper。  Handler  handler = new Handler(Looper);  </code></pre>    <p>每一个线程有且只能有一个 Looper,在创建 Looper 的时候,系统会检查该线程是否已经有 Looper 对象了,已经有 Looper 对象了,则会报错。</p>    <pre>  <code class="language-java">// 创建一个 Looper 很简单。  Looper.prepare();  </code></pre>    <p>再次强调: <strong>Handler 必须和 Looper 相关联才能使用</strong> 。一个 Handler 只能关联一个 Looper,而且一旦关联上了,就不能更改。当然,一个 Looper 可以关联多个 Handler。</p>    <h3><strong>在子线程中创建 Handler</strong></h3>    <p>一般情况下,我们都是在主线程中创建 Handler,但是有时候也需要在子线程中处理消息队列。由于主线程是唯一一个自带了 Looper 的线程。因此在主线程中创建和使用 Handler 相对是比较简单的,也是最常见的。但是还有些情况是需要在子线程中创建一个消息队列的,在子线程中使用 Handler 需要提供一个 Looper,于是代码就应该像下面那样:</p>    <pre>  <code class="language-java">// 为异步消息处理框架准备一个线程  class LooperThread extends Thread {   public Handler mHandler;   public void run() {    // 准备一个 Looper    Looper.prepare();    // Handler 对象会和该线程的 Looper 关联起来。    mHandler = new Handler() {     public void handlerMessage(Message msg) {      // 在这里处理传入的消息     }    };    // 使用该 Looper,启动一个循环的消息队列。    Looper.loop();   }  }  </code></pre>    <h3><strong>使用 HandlerThread</strong></h3>    <p>可以使用 HandlerThread 来简化在子线程中创建 Handler 的流程。HalderThread 是一个自带了 Looper 的线程类,</p>    <pre>  <code class="language-java">public class MyHandlerThread extends HandlerThread {   // 你只需要添加一个 Handler   private Handler handler;   public MyHandlerThread(String name) {    super(name)   }  }  </code></pre>    <p>HandlerThread 也并不神秘,它只是帮你调用了 Looper.prepare() 方法和 Looper.loop() 方法而已。也就是说如果你一个类继承了 ThreadHandler,你可以像在主线程那样使用 Handler。</p>    <h2><strong>发送和处理消息</strong></h2>    <p>准备好 Handler 的环境后,就可以使用 Handler 来发送和处理消息了。处理消息是在 Handler 的 handleMessage() 方法中进行 的,而发送消息有两种方法:发送一个 Message 对象,和投递一个 Runnable 对象。下面分别介绍这两类方法。</p>    <h3><strong>发送 Message 对象</strong></h3>    <p>Message 对象可以包含一些简单的数据,并且可以通过 Handler 进行发送和处理。Message 对象可以通过 Message.what(int) 方法中的 int 参数来标志该对象。Message 对象使用两个 int 类型的字段来存储需要发送的信息,也可以使用一个 Object 类型的对象来存储一个简单对象的信息。</p>    <ul>     <li><strong>Message.what</strong> :标识一个 Message 对象。</li>     <li><strong>Message.arg1</strong> :需要传递的 int 类型的数据。</li>     <li><strong>Message.arg2</strong> :需要传递的 int 类型的数据。</li>     <li><strong>Message.obj</strong> :存储任意数据类型(Object)的对象。</li>    </ul>    <p>怎么创建一个 Message 对象呢?你当然可以使用 Message msg = new Message() 方法来创建一个 Message 对象,但是不建议这么做。因为我们有更高效的方法来获得 Message 对象:使用 Message msg = MMessage.obtain() 或 Handler.obtainMessage() 来获取一个 Message。这个 Message 对象被 Handler 发送到 MessageQueue 之后,并不会被销毁,可以重复利用,因此比使用 new 方法来创建一个 Message 对象效率更高。</p>    <p>当你需要将消息传递到一个后台线程时,建议使用 Handler.obtainMessage 来创建一个 Message 对象:</p>    <pre>  <code class="language-java">int what = 0;  String hello = "Hello!";  // 获取一个和后台线程关联的 Message 对象  Message msg = mHandler.obtainMessage(what, hello);  // 发送一个消息到后台线程。  mHandler.sendMessage(msg);  </code></pre>    <h3><strong>sendMessage() 的相关方法</strong></h3>    <p>发送 Message 对象的方法同投递一个 Runnable 对象的方法很类似:</p>    <ul>     <li><strong>Handler.sendMessage( Message msg )</strong> :在 MessageQueue 中添加一个 Message 对象。</li>     <li><strong>Handler.sendMessageAtFrontOfQueue( Message msg )</strong> :添加一个 Message 对象到 MessageQueue 的前面。</li>     <li>Handler.sendMessageAtTime ( Message msg, long timeInMills ) :在指定的时间发送一个 Message 对象。</li>     <li>Handler.sendMessageDelayed( Message msg, long timeInMillis ) :在指定的时间之后,发送 Message 对象。</li>    </ul>    <h3><strong>投递一个 Runnable 对象</strong></h3>    <p>使用 Handler 发送任务的另一个方法就是投递一个 Runnable 对象(“投递”一词,主要是翻译 post() 方法)。创建一个 Runnable 对象必须要实现 run() 方法,因此,我们需要投递执行的任务就要写在 run() 方法中。</p>    <pre>  <code class="language-java">// 声明一个 Runnable  Runnable r = new Runnable() {   @Override   public void run() {    // 任务的具体内容   }  }  </code></pre>    <p>在 Handler 中,有多种方式投递一个 Runnable 对象:</p>    <ul>     <li><strong>Handler.post(Runnable r)</strong> :在 MessageQueue 中添加一个 Runnable 对象。</li>     <li><strong>Handler.postAtFrontOfQueue</strong> :在 MessageQueue 的头部添加一个 Runnable 对象。</li>     <li><strong>Handler.postAtTime(Runnable r, long timeMillis)</strong> :在指定的时间将 Runnable 对象添加到 MessageQueue 中。</li>     <li><strong>Handler.postDelay(Runnable r, long delay)</strong> :经过了指定的时间后,将 Runnable 对象添加到 MessageQueue 中。</li>    </ul>    <pre>  <code class="language-java">// 投递一个 Runnable 对象  Handler handler = new Handler();  handler.post(   new Runnable() {    @Override    public void run() {     // 任务的具体内容    }   });  </code></pre>    <p>当然,你也可以使用 Activity.runOnUiThread() 来投递一个 Runnable 对象。</p>    <pre>  <code class="language-java">Activity.runOnUiThread (   new Runnable() {    @Override    public void run() {     // 任务的具体内容    }   });  </code></pre>    <p>该方法如果是在主线程上调用的,那么会立即执行。如果是在其他线程上使用的,那么就会将该 Runnable 对象投递到主线程的消息队列中去。</p>    <p>有一点需要记住, Runnable 对象和 Message 对象不同, Runnable 对象不能重复使用。</p>    <h2><strong>实例:倒计时</strong></h2>    <p>下面,我们用 Handler 来实现倒计时功能。先来分析一下 倒计时 的相关功能需求:</p>    <ul>     <li>需要知道总时间和每一步的间隔时间。</li>     <li>每次到了间隔时间就回调通知,总时间结束后也要回调通知。</li>    </ul>    <p>根据这个需求,我们可以大致猜想一下如何实现:</p>    <ul>     <li>需要提供两个构造参数的构造方法,一个是总时间,一个是间隔时间。</li>     <li>需要提供两个抽象方法供子类实现,一个是每次到了间隔时间的回调通知,另一个是总时间结束后的回调通知。</li>    </ul>    <p>有了这个思路,实现就很简单了,下面直接给出代码:</p>    <pre>  <code class="language-java">public abstract class Timer {        private long mMillisInFuture;      private long mCountdownInterval;      private long mStopTimeInFuture;      private Handler mHandler = new Handler() {          @Override          public void handleMessage(Message msg) {              // 计算剩余时间              long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();              if (millisLeft <= 0) {                  // 如果剩余时间小于或等于 0, 则回调 onFinish() 方法。                  onFinish();              } else if (millisLeft < mCountdownInterval) {                  // 当剩余时间小于一次间隔时间时,不回调 onTick() 方法,直接等到最后再发送                  sendMessageDelayed(obtainMessage(), millisLeft);              } else {                  // 回调 onTick() 方法,并在一次间隔时间后再次发送消息。                  onTick(millisLeft);                  sendMessageDelayed(obtainMessage(), mCountdownInterval);              }          }      };        public Timer(long millisInFuture, long countdownInterval) {          mMillisInFuture = millisInFuture;          mCountdownInterval = countdownInterval;      }        /**       * 调用 start() 方法以启动倒计时       * @return       */      public Timer start() {          if (mMillisInFuture < 0) {              onFinish();              return this;          }          // 获取倒计时完成的系统时间          mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;          // 发送第一个消息          mHandler.sendMessage(mHandler.obtainMessage());          return this;      }        /**       * 每经过一次间隔时间就回调一次 onTick 方法       * @param millionLeft   剩余时间       */      abstract void onTick(long millionLeft);        /**       * 倒计时完成后,回调 onFinish() 方法       */      abstract void onFinish();  }  </code></pre>    <p>其实上面的代码是 Android 系统中 CountDownTimer 类的简化版, CountDownTimer 增加了线程安全机制并处理了一些特殊情况,有兴趣的朋友可以自行查阅。</p>    <p> </p>    <p>来自:http://wl9739.github.io/2016/10/31/Handler与异步消息处理/</p>    <p> </p>