Handler 源码分析 - Java 层

icya3508 8年前
   <p>Handler最常见的使用场景就是下载回调,为了不影响用户体验Android不支持在主线程中进行耗时时操作,长时间的耗时操作会产生ANR异常,而下载无疑是耗时操作,所以我们会在子线程中进行下载。但,下载完毕进行UI操作却会发生异常,原来谷歌为了不让UI的操作出现冲突(线程的不可确定性),所以规定只能在子线程中进行UI操作,可这就尴尬了...即不让在主线程中进行联网操作,又不让在子线程中进行UI操作,我们如何将告诉主线程我们已经下载完毕了呢?这时就要用到Handler了.</p>    <p>Handler的简单使用</p>    <p><img src="https://simg.open-open.com/show/587251e02c2a17caa9907005f9e0e4f7.gif"></p>    <p>test</p>    <p>下面是最简单的使用.</p>    <pre>  <code class="language-java">// 1.创建Handler对象, 重写方法.  mTestHandler = new TestHandler(this);    -----------------------------点击模拟下载---------------------------------  public void download(View view) {    text.setText("开始下载...");    new Thread(){      @Override      public void run() {        // 2.创建消息对象        Message msg = mTestHandler.obtainMessage();        msg.obj = "下载完毕";        msg.arg1 = 1;        // 3.发送延时消息        mTestHandler.sendMessageDelayed(msg, 2000);        }      }.start();  }  ---------------------------创建一个类继承Handler--------------------------------   // * 用软引用的方法持有Activity对象防止内存泄露  private WeakReference<MainActivity> activity ;    public TestHandler(MainActivity activity){      this.activity = new WeakReference<>(activity);  }    // 回调方法.    @Override    public void handleMessage(Message msg) {      switch (msg.arg1){        case 1:          activity.get().text.setText((String) msg.obj);        break;      }    }</code></pre>    <p>继承 <strong>Handler</strong> 对象并重写 handleMessage() ,然后创建 <strong>Handler</strong> 对象,调用 obtainMessagee() 方法获取 <strong>Message</strong> 对象,将数据赋予 <strong>Message</strong> ,并发送出去,而发送的消息会回调给 <strong>Handler</strong> 的 handleMessage() 方法.</p>    <p>在子线程中创建Handler对象.</p>    <p>刚才我们在主线程中创建了 <strong>Handler</strong> 对象,在子线程中调用 <strong>Handler</strong> 的 sendMessageDelayed() 方法将 <strong>Message</strong> 带到主线程间完成了线程中的通信,那我们能在子线程中创建 <strong>Handler</strong> 吗 ? 答案是不可以。在子线程中创建 <strong>Handler</strong> 对象会抛出如下异常:</p>    <pre>  <code class="language-java">Can't create handler inside thread that has not called Looper.prepare()</code></pre>    <p>异常说的很明白它需要调用 Looper.prepare() 。我们来看看源码,为什么在主线和中可以创建 <strong>Handler</strong> 对象呢?点击 <strong>new Handler()</strong> ,在两个参数的构造方法中我们发现了刚才抛出异常的代码.</p>    <pre>  <code class="language-java">mLooper = Looper.myLooper();    if (mLooper == null) {      throw new RuntimeException(      "Can't create handler inside thread that has not called Looper.prepare()");    }    mQueue = mLooper.mQueue;    mCallback = callback;    mAsynchronous = async;</code></pre>    <p>可以看到异常抛出的原因是因为mLooper这个对象为null,我们来看看myLooper里面做了什么?</p>    <p>-----------可我们并没有在主线程中调用这个方法啊?那是因为该方法在 <strong>ActivityThread</strong> 类里调用了,我们这里暂时不看它. 继续查看 Looper.myLooper() 里面的内容.</p>    <pre>  <code class="language-java">public static @Nullable Looper myLooper() {          return sThreadLocal.get();      }</code></pre>    <p>myLooper()实现调用了 <strong>ThreadLocal</strong> 身上的 get() 方法,它返回一个 <strong>Looper</strong> ,既然有 get() 必然在一个地方 set() 了,于是在 <strong>Looper</strong> 中发现以下代码</p>    <pre>  <code class="language-java">private static void prepare(boolean quitAllowed) {    if (sThreadLocal.get() != null) {      throw new RuntimeException("Only one Looper may be created per     thread");    }    sThreadLocal.set(new Looper(quitAllowed));  }</code></pre>    <p>ThreadLocal</p>    <p>我们把 <strong>Looper</strong> 对象存放在了 <strong>ThreadLocal</strong> 中,当需要 <strong>Looper</strong> 时也是从 <strong>ThreadLocal</strong> 中取的,为什么要将 <strong>Looper</strong> 存放在 <strong>ThreadLocal</strong> 中呢?下面是它的 get() 方法.</p>    <pre>  <code class="language-java">public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {    ThreadLocalMap.Entry e = map.getEntry(this);    if (e != null)      return (T)e.value;    }    return setInitialValue();  }</code></pre>    <p>ThreadLocal内部有一个 <strong>Map</strong> 集合,根据它所在的线程名取 <strong>Looper</strong> ,而 <strong>ThreadLocal</strong> 是在创建 <strong>Looper</strong> 时创建的所以 <strong>Looper</strong> 与 <strong>ThreadLocal</strong> 是同一线程的,而我们创建handler时会调用 myLooper() 方法,该方法调用 get() 方法时会根据handler所有线程名取 <strong>Looper</strong> 对象,所以我们要保证 Looper.prepare() 与 <strong>Handler</strong> 在同一线程中.</p>    <p>现在让我们来理一下思路,首先创建 <strong>Handler</strong> 需要 <strong>Looper</strong> ,而 <strong>Looper</strong> 是在 prepare() 方法中创建的,也就是说如果我们想在子线程中创建 <strong>Handler</strong> 只需要在之前调用</p>    <p><img src="https://simg.open-open.com/show/117f0fa68a3305e7d90bcbc691e400a0.gif"></p>    <p>test</p>    <pre>  <code class="language-java">``  public void run() {     Looper.prepare();     mTestHandler = new Handler(){      @Override      public void handleMessage(Message msg) {        Log.d("MainActivity", Thread.currentThread().getName());      }    };    mTestHandler.sendMessage(mTestHandler.obtainMessage());  }</code></pre>    <p>MesageQuene</p>    <p>可是问题又来了,虽然没有抛出异常, handleMessage() 方法却一直接收不到消息.既然是收不到消息,那我们来看看Handler内部是怎么发送消息的吧,我们继续查看 sendMessage() 方法.</p>    <pre>  <code class="language-java">// 这里我们看到了前面的发送延时消息方法,延时+当时时间 并调用下一方法  sendMessage(msg) -> sendMessageDelayed(msg, 0)      -> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)    ----------------------------------------------------------------------  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {    MessageQueue queue = mQueue;    if (queue == null) {       ...      return false;    }    return enqueueMessage(queue, msg, uptimeMillis);  }    ----------------------------------------------------------------------  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this; // this = handler; 把消息与handler进行绑定.    if (mAsynchronous) {      msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);  }</code></pre>    <p>最后调用了 <strong>MesageQuene</strong> 的 enqueueMessage() 方法,即入队列,那 <strong>MesageQuene</strong> 对象是在什么时候创建的呢?是在创建 <strong>Looper</strong> 对象时创建的。</p>    <pre>  <code class="language-java">private Looper(boolean quitAllowed) {     mQueue = new MessageQueue(quitAllowed);     mThread = Thread.currentThread();  }</code></pre>    <p>继续来看 <strong>MesageQuene</strong> 的 enqueueMessage() 方法.</p>    <pre>  <code class="language-java">...  Message prev;  for (;;) {    prev = p;    p = p.next;    if (p == null || when < p.when) {      break;    }    if (needWake && p.isAsynchronous()) {      needWake = false;    }  }  msg.next = p; // invariant: p == prev.next  prev.next = msg;  ...</code></pre>    <p>enqueueMessage() 就是入队列的意思, <strong>Message</strong> 是单链表结构,它持有下一个 <strong>Message</strong> 对象的引用,因为 <strong>MesageQuene</strong> 中可能存有多个未处理的消息,所以需要判断 <strong>MesageQuene</strong> 中有多少消息,若有多个则将当前消息放置在最后,可以看成集合的add方法.</p>    <p>Looper</p>    <p>sendMessage()方法只是将消息加入消息队列中,那消息是如何取出并发给handler的呢?我们来看下 Looper.loop()</p>    <pre>  <code class="language-java">...  for (;;) {    Message msg = queue.next(); // might block  ...    try {      msg.target.dispatchMessage(msg);    } finally {      if (traceTag != 0) {      Trace.traceEnd(traceTag);    }  }  ...</code></pre>    <p>这里的target就是handler(在handler的 enqueueMessage() 方法里,我们将消息与handler进行了绑定),所以调用了handler身上的 dispatchMessage() 方法,而该方法最终又调用了 handleMessage() 方法,并将消息传递进去.</p>    <pre>  <code class="language-java">public void dispatchMessage(Message msg) {    if (msg.callback != null) {      handleCallback(msg);    } else {      if (mCallback != null) {        if (mCallback.handleMessage(msg)) {          return;        }      }      handleMessage(msg);    }  }</code></pre>    <p>上面就是handler发送消息的大致流程最后我们总结一下.</p>    <p>总结:创建 <strong>Handler</strong> 对象时会调用 Looper.prepare() 方法,该方法会用从 <strong>ThreadLocal</strong> 对象中取出looper, 所以如果 <strong>ThreadLocal</strong> 中没有先存 <strong>Looper</strong> 或不在同一线程中则取不到对象, <strong>Handler</strong> 就会抛出异常.</p>    <ul>     <li>问题1.:主线程在哪里调用了 prepare() 方法的?见底部代码.</li>    </ul>    <p>用handler发送消息方法时,会调用到 <strong>MesageQuene</strong> 的方法, 而mQuene是在创建 <strong>Looper</strong> 对象时创建的, <strong>Looper</strong> 对象是在调用 prepare() 方法时创建的, 也就是说mQuene与 <strong>Looper</strong> 是在同一线程.</p>    <p>我们发送消息时会将所发送的消息加入消息队列,而后调用Looper.loop()方法才能将消息取出并传送给handler,如果不调用Looper.loop()则消息无法取出</p>    <ul>     <li>问题2:为什么在主线中创建可以接收消息?见底部代码.</li>    </ul>    <p>最后在子线程中创建handler那handleMessage是在哪个线程回调的呢?</p>    <p>这个问题在上面就已经说了,handleMessage在loop()方法调用,而loop()与Looper是在同一线程,也就是说最终会在子线程回调.那如何让回调在主线程呢?调用prepareMainLooper()方法,而不是prepare()就可让回调在主线程中运行.</p>    <p>下面代码很好回答了第1和第2问题.</p>    <pre>  <code class="language-java">public static final void main(String[] args) {  ....  // 1.主线程创建Looper   Looper.prepareMainLooper();  if (sMainThreadHandler == null) {    sMainThreadHandler = new Handler();  }    ActivityThread thread = new ActivityThread();  thread.attach(false);    if (false) {    Looper.myLooper().setMessageLogging(new    LogPrinter(Log.DEBUG, "ActivityThread"));  }    Looper.loop();  ...</code></pre>    <p> </p>    <p>来自:http://www.jianshu.com/p/1bd6e015653f</p>    <p> </p>