MainActivity如下:
package cc.cn;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.app.Activity;
/**
* Demo描述:
* Android异步消息机制分析(附图)
*
* =======================================================
*
* 问题的引入:
* 在子线程中直接调用Handler handler=new Handler()此时报错:
* Can't create handler inside thread that has not called Looper.prepare().
* 既然是在调用Handler的构造方法时报的错那就去看该构造方法的源码.
* public Handler() {
* 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 = null;
* }
*
* 从以上代码mLooper = Looper.myLooper()可以看到当mLooper==null时就会报错
* Can't create handler inside thread that has not called Looper.prepare().
*
*
* 继续看Looper.myLooper()的源码.
* *Return the Looper object associated with the current thread.
* *Returns null if the calling thread is not associated with a Looper.
* public static Looper myLooper() {
* return sThreadLocal.get();
* }
* 请看源码中的这两行注释:
* 返回与当前线程相关联的Looper.
* 如果当前线程没有一个与之相关联的Looper那么就返回null.
*
* 回看刚才的报错Can't create handler inside thread that has not called Looper.prepare().
* 提示我们调用Looper.prepare().
* 于是在子线程中这么写:
* Looper.prepare();
* Handler handler=new Handler();
* 该报错消失.
*
* 既然调用Looper.prepare()可以消除该错误.
* 那就看看Looper.prepare()的源码.
*
* /** Initialize the current thread as a looper.
* * This gives you a chance to create handlers that then reference
* * this looper, before actually starting the loop. Be sure to call
* * {@link #loop()} after calling this method, and end it by calling
* * {@link #quit()}.
* public static void prepare() {
* if (sThreadLocal.get() != null) {
* throw new RuntimeException("Only one Looper may be created per thread");
* }
* sThreadLocal.set(new Looper());
* }
* 从这段源码可以看出以下几点:
* 1 从注释文档可以看出
* 1.1 该prepare()方法的作用:
* 利用一个looper来初始化当前线程,或者说初始化一个带有Looper的线程.
* 这样就使得一个线程和属于它的Looper关联起来.
* 1.2 在调用该prepare()方法后需要调用loop()方法开始消息的轮询,也可调用quit()方法停止消息的轮询.
* 2 从代码中可以看出
* 2.1 注意该方法的异常提示:
* Only one Looper may be created per thread
* 一个线程只能创建一个Looper!!!!!!
* 2.2 在1.1中提到:
* "prepare()方法的作用,利用一个looper来初始化当前线程,或者说初始化一个带有Looper的线程.这样就使得一个线程和属于它的Looper关联起来."
* 它的代码体现就是sThreadLocal.set(new Looper())这句代码.
* 其中Looper构造方法如下:
* private Looper() {
* mQueue = new MessageQueue();
* mRun = true;
* mThread = Thread.currentThread();
* }
* 从此可以看到sThreadLocal.set(new Looper())这句代码的两个操作
* (1) 构造一个Looper
* (2) 将此Looper保存到sThreadLocal中,所以在上述的myLooper()方法中调用sThreadLocal.get()就不再为空.
* 换句话说将此Looper保存到sThreadLocal中也确保了一个线程只有一个Looper.
*
* 看到这里的时候或许有点疑问——凭啥说"将此Looper保存到sThreadLocal中也确保了一个线程只有一个Looper."???
* 那就重新仔细看sThreadLocal.set(new Looper())这句代码的两个操作,同上.
* (1) 构造一个Looper(即new Looper())
* 一个Looper包含了三个变量:
* mQueue---->MessageQueue(消息队列)
* mRun------>true
* mThread--->Thread.currentThread()(当前线程)
* 这三个变量中最重要的是mQueue(消息队列)和mThread(当前线程),这样就使得一个Looper和一个消息队列以及当前线程对应起来.
* (2) 将此Looper保存到sThreadLocal中(即sThreadLocal.set(new Looper()))
* 请注意sThreadLocal这个变量的声明和定义:
* static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
* 它是Looper类的全局的static final变量,它在类的加载的时候就已经存在了而且它是final的.
* 这就是说sThreadLocal本身是唯一的不可变的!!!!!!!!!!再看代码:
* public static void prepare() {
* if (sThreadLocal.get() != null) {
* throw new RuntimeException("Only one Looper may be created per thread");
* }
* sThreadLocal.set(new Looper());
* }
* 在调用Looper.prepare()方法时先会使用if (sThreadLocal.get() != null)
* 判断sThreadLocal中是否已经保存了Looper.
* 如果已经保存了Looper则会报错:Only one Looper may be created per thread.一个线程只能创建一个Looper
* 如果没有保存Looper,才会去调用sThreadLocal.set(new Looper());
* 这样就确保了当前线程和Looper的唯一对应.
*
* 至此可以看到:
* 1 一个线程对应一个Looper.
* 那么说一个线程对应一个Handler,这么说对不?这是不对的.因为可以在一个线程中创建几个Handler对象,但一个线程只有一个Looper.
* 所以线程和Handler不是一一对应的,一个线程中可以创建多个Handler(当然这样做,实际意义不大);且在同一线程创建多个Handler时
* 每个Handler使用的是同一个Looper.
* 类似的说法:线程绑定了Handler,这也是不正确的.
* 2 该Looper又对应一个消息队列和当前线程
* 简单地说就是:一个线程,对应一个Looper,对应一个消息队列.三者一一对应.
*
*
*
* 至此一个子线程中使用Handler的方式应该是这样的:
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }
* 该代码也是Google推荐的写法.
*
*
* 在平常使的MainActivity中的UI线程中使用Handler时并没有调用Looper.prepare();
* 这是为什么呢?
* 因为UI线程是主线程,系统已经自动帮我们调用了Looper.prepare()方法.
* 在此不再赘述.
*
*
*
* 以上讨论了线程(Thread)和Looper的使用,下面讨论消息的发送和处理过程.
* 平常最常用的方式:
* handler.sendMessage(message)---->发送消息
* handleMessage(Message msg)------>处理消息
*
* 那么handleMessage(Message msg)中是怎么获取到刚发出的这条消息呢???
* Handler发送消息的具体方法有好几个但除了sendMessageAtFrontOfQueue(Message msg)
* 以外的几个方法最终会调用sendMessageAtTime(Message msg, long uptimeMillis)方法:
*
* public boolean sendMessageAtTime(Message msg, long uptimeMillis){
* boolean sent = false;
* MessageQueue queue = mQueue;
* if (queue != null) {
* msg.target = this;
* sent = queue.enqueueMessage(msg, uptimeMillis);
* }
* else {
* RuntimeException e = new RuntimeException(
* this + " sendMessageAtTime() called with no mQueue");
* Log.w("Looper", e.getMessage(), e);
* }
* return sent;
* }
*
* 在该方法中有两句代码很重要:
* 1 msg.target = this;
* 给msg设置了target.
* 这里的this当然就是当前Handler对象本身!
* 2 sent = queue.enqueueMessage(msg, uptimeMillis);
* 将消息放入消息队列中.
* 这里的queue(mQueue)就是上述Looper构造方法中的mQueue.
* 在enqueueMessage(msg, uptimeMillis)方法中有一个队列.
* 距离触发时间最短的message排在队列最前面,同理距离触发时间最长的message排在队列的最尾端.
* 若调用sendMessageAtFrontOfQueue()方法发送消息它会调用该enqueueMessage(msg, uptimeMillis)
* 来让消息入队只不过时间为延迟时间为0,即它会插入到队列头部.
*
*
* 这就是消息的入队操作,那么消息怎么出队呢?
* 这就要看Looper中的loop()方法
* public static final void loop() {
* Looper me = myLooper();
* MessageQueue queue = me.mQueue;
* while (true) {
* Message msg = queue.next(); // might block
* if (msg != null) {
* if (msg.target == null) {
* return;
* }
* if (me.mLogging!= null) me.mLogging.println(
* ">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);
* msg.target.dispatchMessage(msg);
* if (me.mLogging!= null) me.mLogging.println(
* "<<<<< Finished to " + msg.target + " "+ msg.callback);
* msg.recycle();
* }
* }
* }
* 在该方法中是一个死循环while(true),即Looper一直在轮询消息队列(MessageQueue)
* 在该方法中有两句代码很重要:
* 1 Message msg = queue.next();
* queue.next()消息队列的出列.
* 2 msg.target.dispatchMessage(msg);
* 用调用msg里的target的dispatchMessage()方法.
* target是什么呢?
* 参见上述sendMessageAtTime(Message msg, long uptimeMillis)可知:
* target就是Handler!!!!在此回调了Handler的dispatchMessage方法,所以该消息就发送给了对应的Handler.
* 接下来看Handler的dispatchMessage(Message msg)方法:
*
* public void dispatchMessage(Message msg) {
* //1 message的callback
* if (msg.callback != null) {
* handleCallback(msg);
* } else {
* //2 handler的callback
* if (mCallback != null) {
* if (mCallback.handleMessage(msg)) {
* return;
* }
* }
* //3 Handler的handleMessage()
* handleMessage(msg);
* }
* }
*
* 其中涉及到的CallBack为:
* public interface Callback {
* public boolean handleMessage(Message msg);
* }
* Handler的其中一个构造方法为:
* Handler handler=new Handler(callback);
* 所以在dispatchMessage(Message msg)涉及到了CallBack
* 在多数情况下message和Handler的callBack均为空
* 所以会调用dispatchMessage(Message msg)方法:
* 这就回到了我们最熟悉的地方.
*
*
*
* =======================================================
*
* Android异步消息机制中主要涉及到:
* Thread Handler Looper MessageQueue
* 它们的相互关系如下:
* 1 Looper.prepare();
* 1.1 为当前线程生成对应的Looper.
* 一个Looper包含了三个变量:
* mQueue---->MessageQueue(消息队列)
* mRun------>true
* mThread--->Thread.currentThread()(当前线程)
* 1.2 将该Looper保存到ThreadLocal中.
* 之所以采用ThreadLocal来存放线程对应的Looper主要目的是确保
* 每个线程只有一个唯一的Looper.
*
* Looper和其所属线程的相互关联的代码体现:
* 1.3 Looper.myLooper()
* 得到与当前线程相关联的Looper
* 1.4 Looper.myLooper().getThread()
* 得到与Looper相关联的线程Thread
*
* 2 Handler handler=new Handler()
* 在第一步中利用Looper.prepare()实现了Looper与线程的关联.
* 在此接着看Handler与Looper的关系.
* 注意看上述Handler的构造方法中的代码:
* mLooper = Looper.myLooper();
* 这样就得到了与线程相关联的Looper.
* mQueue = mLooper.mQueue;
* 得到与线程相关联的Looper的消息队列(MessageQueue)mQueue
* 在Handler的构造方法中就看出了Handler与Looper的关联.
*
* 小结:
* (1) Looper.prepare();
* 实现了Looper和其所属线程的相互关联
* (2) Handler handler=new Handler()
* 实现了Handler与Looper的关联.
* (3) 一个线程可有多个Handler但只有一个Looper
*
*
* =======================================================
* 了解Thread Handler Looper MessageQueue几者间的关系之后,在此总结
* Android异步消息机制的流程:
*
* 1 Looper.prepare();
* 实现Looper和其所属线程的相互关联
* 一个Looper包含了三个变量:
* mQueue---->MessageQueue(消息队列)
* mRun------>true
* mThread--->Thread.currentThread()(当前线程)
* 在执行该方法以后每个Looper中就存在一个消息队列(MessageQueue)了.
* Handler发送的消息都会保存到该消息队列(MessageQueue)中.
* 2 Handler handler=new Handler();
* 实现了Handler与Looper的关联.
* 3 Looper.loop();
* 开始轮询消息队列(MessageQueue),并且会一直轮询.
* 按照队列处理里面的每个消息.当然刚开始的时候该队列为空.
* 4 mHandler.sendMessage(message)发送消息至消息队列.
* 具体过程可以参见以上的详细描述.
* 5 在第三步时Looper.loop()不是一直在轮询消息队列么?
* 当消息(Message)出队时,找到该消息的target(其实就是一个Handler)回调
* 其dispatchMessage(Message msg)方法;在该方法中会调用到我们非常熟悉
* 的handleMessage(Message msg).
*
* 以上就是Android异步消息机制的详细流程.
* 简单地可以这么说:
* Handler------>发送消息至消息队列(MessageQueue)和处理消息
* Looper------->用消息队列(MessageQueue)保存消息.使用loop()方法一直轮询消息队列.
* 并在消息出队的时候将其发送给合适的Handler.
*
* 以上流程分析可参见流程图.
*
* =======================================================
*/
public class MainActivity extends Activity {
private Thread mThread;
private Handler mHandler;
private final String TAG = "Handler";
private final int FLAG=9527;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testHandler();
}
//Can't create handler inside thread that has not called Looper.prepare()
private void testHandler() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == FLAG) {
Log.i(TAG, "收到消息 msg.arg1=" + msg.arg1);
}
}
};
Message message = new Message();
message.what = FLAG;
message.arg1 = 456;
mHandler.sendMessage(message);
Looper.loop();
}
});
mThread.start();
}
}
/**
* 1 演示错误,引入Looper.prepare();要读该方法的注释,会引入Looper.looper()方法
* 且注意此时的代码里的报错:Only one Looper may be created per thread
* 一个线程只能创建一个Looper
* 可以看到sThreadLocal中保存了一个Looper.
* 2 继续从Looper.prepare()中查看Looper的构造方法
* Looper构造方法如下:
* private Looper() {
* mQueue = new MessageQueue();
* mRun = true;
* mThread = Thread.currentThread();
* }
*
*/
附图如下:
main.xml如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Handler原理学习" />
</RelativeLayout>