一个菜鸟理解的looper、Handler、MessageQueue、Message
如果你是一个android的开发者(哪怕像我一样的菜)你肯定知道我们应该尽量的避免在UI线程中处理耗时的操作,让我们的界面能够及时的响应用户的操作,避免ANR。一般的耗时操作包括:网络访问、文件读写等。所以我们的代码中经常会出现如下写法:
private TextView mTextView;
Handler mUihandler=new Handler(){
@Override
public void handleMessage(Message msg) {
mTextView.setText(msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_net_work);
mTextView = (TextView) findViewById(R.id.net_btn);
new Thread(new Runnable() {
@Override
public void run() {
String string = doRequest("http://www.dashutu.com");
Message msg=new Message();
msg.obj=string;
mUihandler.sendMessage(msg);
}
}).start();
}
以上代码非常简单,就是在Activity的onCreate方法中新启动了一条线程,然后请求了一个网站(貌似这个网站的东西还不错),把结果封装到了Message对象,然后handler对象调用了sendMessage()方法,接着我们就可以在handlerMessage方法中接受Message对象、进一步取出请求结果更新到UI上。至此你必须吐槽:”卧槽!android线程间的通信也太简单了吧”,想必那些认为”android不就是把数据从网络上面请求下来然后显示到界面上吗!“的人,心中又对android程序员鄙视的情绪加重了(TMD什么玩意)。卧槽,感觉说多了~~~并没有什么卵用~,切入正题。
角色扮演
android线程间的通信主要就是靠这几个类共同的协作完成的,他们在整个机制中扮演的角色是这样的:
对象 | 作用 | 初始化优先级 |
---|---|---|
looper | 初始化MessageQueue、循环MessageQueue | 首先初始化 |
Handler | 分发Message、处理Message | 必须在Looper之后实例化 |
MessageQueue | 装载Message对象 | 在Looper之中初始化 |
Message | 封装数据 | 任何时候 |
这个表格简单的说明了他们的作用和实例化的时机,对于他们工作的原理理解并没有什么卵用。但是为什么说他们实例化的时机很重要呢?很简单看一下android的大神是怎么实现的就行了。
源码分析
首先来看看Handler是如何实现的
public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Callback callback Looper looper);
public Handler(boolean async);
public Handler(Callback callback,boolean async);
public Handler(Looper looper,Callback callback,boolean async);
上面是Handler的七个构造方法,其中有三个构造方法需要传递一个Looper参数,而另外几个构造方法最终都会去调用Handler(Callback callback,boolean async)这个构造方法,ok我们看看这个构造方法
public Handler(Callback callback, boolean async) {
/**
* 省略了几行看起不爽的代码
*/
mLooper = Looper.myLooper();//获取一个looper对象
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;
}
通过Looper.myLooper()获取一个looper对象(注意此方法并不是实例化looper对象,后面会讲到)同时获取了looper的Messagequeue对象赋值给了handler中的messageQueue,,如果这个对象为空就会抛出一个异常“在当前线程中Looper没有调用prepare()方法的话就不能创建Handler对象“,所以从Handler创建中我们可以看出来Looper必须先于Handler实例化。如果你在Activity中执行以下代码:
new Thread(new Runnable() {
Handler mHandler=null;
@Override
public void run() {
mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
}).start();
你肯定会得到你想要的异常的,但是你肯定会说文章开头的代码片段中我tmd的并没有Looper.prepare()为毛Handler却乖乖的初始化了呢?
这是因为Activity的所在的UI线程在创建的时候已经实例化了Looper对象,请看ActivityThread(Application的启动入口)类的入口程序:
public static void main(String[] args) {
/*
* 省略一些看起不爽的代码
*/
Looper.prepareMainLooper();//实例化looper对象
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
/*
*省略代码
*/
}
说简单一点在Activity创建之前android的framework已经为UI线程通过调用prepareMainLooper创建了一个Looper对象,这个looper对象是专门为UI线程服务的。还有问题就是Looper还要负责创建MessageQueue对象。所以在Looper的构造方法中你可以看到实例化了MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
注意这是一个私有构造方法,所以他只能在内部被调用(这里不说反射),而且只在这个私有静态方法内被调用
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.prepareMainLooper()最终都会调用prepare(boolean quitAllowed)这个私有静态方法,在这个方法中我们看到首先判断sThreadLocal.get()返回的是不是null,如果不是null为抛一个异常:“每一个线程只能有一个looper被创建”,如果是null就会创建一个looper对象然后调用set方法,在java中set和get总是对应的,如果你继续的深究会发现会把looper对象put进一个thread的成员LoacalThread里面,说简单一点在looper创建的时候就会和当前所在的线程做一一对应的关系保证一个线程只能有一个Looper对象,作为菜鸟就先这样理解吧!(囧)
从前面的分析总共可以得出以下三点:
1. 在handler创建的时候,当前线程必须有且只有一个Looper对象。
2. 一个线程只能允许一个looper对象
3. 主线程(UI线程)的looper对象由framework为我们创建。
go on
到目前为止我们还不知道android是如何利用这几个类实现跨线程通信的so接着看源码吧(其实看源码没这么恼火吧 哈哈~,但是这是android最简单的但是又很重要的一段~~~哎实在是囧!!)。
从ActivityThread源码中看到当looper对象创建完毕之后就会去Looper.loop(),我们来看看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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
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);
}
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();
}
}
从上可以看出来这段代码做了这几件事:1首先得到本线程中的looper对象,2得到looper对象的Messagequeue成员(在looper的构造方法中实例化的),3通过一个for循环不停的取出MessageQueue中的Message对象, 4调用message对象的target成员的dispatchMessage(msg)方法,注意这个target就是一个handler。
到这里为止貌似looper的工作顺利完成,把消息从消息队里取出来然后给handler来处理,刚刚也看到了确实是handler调用了dispatchmessage方法,我们追踪到方法内部:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这个方法有三个走向: 1 . 执行message的callback(runable),2 调用handler自身的成员callback的handleMessage方法,3 调用handler自身的handle的handleMessage(msg)方法。此时会焕然大悟,原来我们处理的消息的回调函数就是在这个地方调用的。注意分发和处理消息都是handler做的,msg的target成员也是handler对象,所以这个target是何时被赋值的就成了关键,因为平时我们都是通过handler.sendMessage 这个方法来发送消息的,所以我们一路追踪,最后发现handler最终调用了这两个方法:
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//把自己赋值给了msg.target
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//MessageQueue 调用自己的同名方法把消息压入消息队列
}
一目了然,就是在enqueueMessage方法中handler把自己赋值给了Message的target,又搞清楚了 原来刚刚在loop方法中msg.target.dispatchmessage中的target其实就是在handler发送消息的时候把自己传给了每一个需要处理的Message对象,最终在looper的loop方法中回调handler自己的dispatchMessage方法,注意把消息压入消息队列的操作也是在handler的enqueueMessage方法中进行的。
说到这里感觉有点明白了,在message被放到MessageQueue之前,message已经持有了handler的引用,而且这个时候Looper.loop()也正在不停的等待消息的到来,持有handler的message一旦被放入到MessageQueue就会在loop中被take出来然后执行。 说了这么多其实原理已经很清楚了就是:共享内存。
总结
handler和Looper持有同一个MessageQueue对象。不管message是在哪个线程中被handler发送的或者说被加入MessageQueue中的,message最终被处理也是在looper所在的线程中,looper和handler也必须是在同一个线程中。