Handler的工作原理,为什么在子线程中执行 new Handler() 会抛出异常?

写下来,总比看一下印象要深刻。

1. HandlerLooperMessageQueue 的关系

这部分只是简单介绍一下。

  • Handler 主要用来发送和接收消息;
  • Looper 主要用来轮询消息;
  • MessageQueue 主要用来存储消息。

2. Handler 的工作原理

2.1 引入

先看一段代码,这也是使用 Handler 的常见方式:

/**
 * @author wzc
 * @date 2018/8/19
 */
public class MyActivity extends Activity {
    private static final String TAG = MyActivity.class.getSimpleName();
    private static final int MSG_CODE = 1;
    private static Handler sHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_CODE:
                    Log.d(TAG, "接收到的消息是: " + msg.obj);
                    break;
                default:
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Message msg = new Message();
        msg.what = MSG_CODE;
        msg.obj = "这是发送的消息";
        Log.d(TAG, "发送消息为" + msg.obj);
        sHandler.sendMessage(msg);
    }
}

运行一下,查看日志:

D/MyActivity: 发送消息为这是发送的消息
D/MyActivity: 接收到的消息是: 这是发送的消息

可以看到通过 Handler 实现了消息的发送和接收。那么在消息的发送和接收之间发生了什么?或者说,为什么在这里发送消息,而在那里就可以接收到消息呢?

2.2 发送过程

从发送的地方看起:

 public final boolean sendMessage(Message msg)
 {
     return sendMessageDelayed(msg, 0);
 }

这个方法的作用是把 Message 发送到 MessageQueue 的尾部。接收消息的是在 handleMessage() 方法中,是在和 Handler 相关联的线程中。
若是成功地把消息放置到 MessageQueue 的尾部,那么这个方法就会返回 true;失败的话,就返回 false。失败通常是因为处理 MessageLooper 正在退出。

接着看 sendMessageDelayed() 方法:

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
 {
      if (delayMillis < 0) {
          delayMillis = 0;
      }
      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
  }

这个方法比上个方法多了一个延时毫秒数的参数,这个参数的值设置为小于 0 的话,会在这里被设置为 0 。

接着看 sendMessageAtTime() 方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
      MessageQueue queue = mQueue;
      if (queue == null) {
         RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
          Log.w("Looper", e.getMessage(), e);
          return false;
      }
      return enqueueMessage(queue, msg, uptimeMillis);
}

在这个方法中,先获取成员变量 mQueue 的引用,然后判断 MessageQueue queue 的值是否为 null,最后会调用 enqueueMessage(queue, msg, uptimeMillis) 方法。重要的就是 mQueue 的获取和判断。mQueue 是在哪里被赋值的呢?是在构造 Handler 对象的过程中赋值的,具体看 public Handler(Callback callback, boolean async) 方法的 mQueue = mLooper.mQueue; 这行代码。而因为我们是在主线程构造的 Handler 对象,所以我们获取到的 mQueue 是主线程对应 Looper 对象的 mQueue,它是不为 null 的。

接着看:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
 }

在这个方法中,首先把 this 赋值给 Message 对象的 target 属性。this 代表就是这里的 Handler 对象。下面的 if 语句,默认不会成立,最后是调用 MessageQueueenqueueMessage(Message msg, long when) 方法。这个方法的重点是让 Message 对象持有当前 Handler 对象的引用。

到这里,消息已经发送到了 MessageQueue 中了。

2.3 接收过程

上面我们已经看到,消息通过 HandlersendMessage(Message msg) 方法最终发送到了 MessageQueue 里。那么如何再接收到发送的消息呢?

需要介绍一个从 MessageQueue 里取消息的角色:LooperLooper 会通过 Looper.loop() 方法轮询消息。

     /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

在这个方法里,先获取到当前线程的 Looper 对象,就是主线程的 Looper 对象。接着就进入了 for 的无限循环,这个无限循环是这个方法的重点。
在无限循环中,先从 MessageQueue 中取出下一个 Message 对象;判断 Message 对象为 null 的话,就结束无限循环,当然 loop() 方法也随之结束。

Note
这个地方曾经被面试问到过:
面试官:Looper 的 loop() 方法什么时候结束呢?
我:当 MessageQueue 的 next() 方法返回 null 的时候,loop() 方法就会结束。
面试官:那么,MessageQueue 的 next() 方法什么时候返回 null ?
我:这个…..

接着看 msg.target.dispatchMessage(msg); 这行代码,这里的 msg.target 就是 当前的 Handler 对象。在发送过程的分析中,刚刚提过这一点。

进入HandlerdispatchMessage(Message msg) 方法:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在这个方法中,依照实例代码,会调用最后一行的 handleMessage(msg),在这个方法中会接收到发送的消息。

3. 为什么在子线程中执行 new Handler() 会抛出异常?

在子线程中 new Handler()

/**
 * @author wzc
 * @date 2018/8/19
 */
public class MyActivity2 extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Thread("Thread1"){
            @Override
            public void run() {
                super.run();
                Handler handler = new Handler();
            }
        }.start();
    }
}

运行一下,查看日志:

E/AndroidRuntime: FATAL EXCEPTION: Thread1
    Process: com.wzc.chapter_10, PID: 5263
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:208)
        at android.os.Handler.<init>(Handler.java:122)
        at com.wzc.chapter_10.MyActivity2$1.run(MyActivity2.java:20)

从日志里,可以知道:不能在还没有调用 Looper.prepare() 方法的线程中创建 Handler。
那我们就在线程中调用一次 Looper.prepare() 方法,再次运行果然不报错了。

Looper.prepare();
Handler handler = new Handler();

但是具体原因是什么?

Hander 的构造方法入手:

public Handler() {
        this(null, false);
}

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
 }

在带参的构造函数中,我们发现了抛出异常的地方,在 mLooper 对象为 null 的时候, 会抛出异常。说明这里的 Looper.myLooper(); 的返回值是 null
看一下 Looper.myLooper() 方法:

  public static Looper myLooper() {
        return sThreadLocal.get();
  }

是从 Looper 类 的 sThreadLocal 这个静态成员变量中取出 Looper 对象。
需要查看一下,sThreadLocal.set() 方法的调用:

  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));
  }

可以看到只有调用了 Looper.prepare() 方法,才会构造一个 Looper 对象并在 ThreadLocal 存储当前线程的 Looper 对象。
这样在调用 Looper.myLooper() 时,获取的结果就不会为 null

参考

Android开发艺术探索

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值