[Android][消息机制]
1.下笔缘由
虽然一直在使用handler,只是简单的知道他是一个异步的消息机制,什么耗时任务不能在主线程操作;什么主线程将任务分发给子线程执行,子线程执行完成后将执行结果sendMessage给回主线程,然后在handlerMessage中处置子线程返回的结果。但是并没有对它进行比较详细的了解,所以今天打算仔细的了解。
2.消息机制原理
android的消息机制主要涉及到几个关键词:Handler,MessageQueue,Message和Looper。我们现在通过模拟消息机制的一个执行流程来介绍这几个关键词在消息机制中的作用。现在主线程要分发一个任务给子线程,子线程执行完成任务后,通过sendMessage将处理结果打包成一个Message对象发送到MessageQueue,队列中,而Looper是不断轮询MessageQueue队列的,所以当MessageQueue队列中有Message的时候就会取出来,让主线程在Handler对象的handlerMessage方法中处理子线程返回的结果。
在消息机制中有两种发送消息的方法,一种是sendMessage,另一种是post。当然,使用post方法发送消息也是一样的,他们的作用都是将Message发送到消息队列中。从它的源码可以看出,它也是调用了sendMessage的方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
2.1消息机制流程图
消息机制流程图3.handler与Looper如何关联在一起
首先,到Handler的类里,先看一下他的构造方法。
(1)
public Handler() {
this(null, false);
}
(2)
public Handler(Callback callback) {
this(callback, false);
}
(3)
public Handler(Looper looper) {
this(looper, null, false);
}
(4)
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
(3)和(4)直接传值Looper这里直接将两个关联在一起了就没啥好说的了。我们多数情况是使用(1),(2)来实例化Handler对象。它们其实调用的是下面这个方法:
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 = Looper.myLooper()和mQueue = mLooper.mQueue。这个方法将Handler,Looper和MessageQueue都关联在一起了。而在myLooper()这个方法里,我们可以看到它调用了ThreadLocal类的get()方法。
public static Looper myLooper() {
return sThreadLocal.get();
}
我们再看一下get()的代码:
/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
可以看方法上面的英文注释,这个方法返回的是当前线程的线程局部变量。网上搜索了一下ThreadLocal的作用,网上说它是local variable(线程局部变量)。为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。我们现在这里返回的变量当然是Looper。
3.1 Handler必须在调用了Looper.prepare()的线程中才能实例化
Handler与Looper关联在一起的过程上面已经展示,但是这里需要注意的一个细节是Handler必须在调用了Looper.prepare()的线程中才能实例化。因为每个实现了消息机制的线程,都必须且仅对应一个Looper和MessageQueue。
而Looper的prepare()方法的作用就是将Looper与Looper所在的线程关联起来。
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));
}
可以看到它调用了ThreadLocal的set()方法j将Looper和线程关联。下面是一个自定义在子线程实现消息机制的例子:
@Override
public void run()
{
super.run();
//Looper完成轮询前的准备,这里的prepare将Looper与当前线程关联在一起
Looper.prepare();
handler = new Handler();
//调用loop()开始轮询
Looper.loop();
}
其实主线程中也是这么实现的,如果想看主线程实现消息机制的源码,可以到SDK文件目录下直接打开文件方能查看
…\sdk\sources\android-19\android\app下的ActivityThread.java类,因为这个文件是隐藏的,api下看不到,所以我们只能这么做了:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们可以看到,Looper.prepareMainLooper()其实也就类似上面自定义子线程中的prepare()方法。
其实如果要在子线程自定义消息机制,可以使用Android提供的类HandlerThread,只要继承了这个类即可:
public class MyHandlerThread extends HandlerThread
{
int i = 0;
public MyHandlerThread(String name)
{
super(name);
}
/* (non-Javadoc)
* @see android.os.HandlerThread#onLooperPrepared()
* 在开启looper循环之前调用的,顾名思义是Looper循环前的准备工作可以在这个类进行
* 这个方法只调用一次
*/
@Override
protected void onLooperPrepared()
{
super.onLooperPrepared();
Log.i("lgy", "lgy"+i++);
}
}
使用例子:
public class TestHandlerThread extends Activity implements Callback
{
// private HandlerThread handlerThread = null;
private MyHandlerThread handlerThread = null;
private Button button,button2;
private Handler handler = null;
int j = 0;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.test_handler);
button = (Button) findViewById(R.id.click);
handlerThread = new MyHandlerThread("lgy_hello");
handlerThread.start();
handler = new Handler(handlerThread.getLooper(),this);
button.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
handler.sendEmptyMessage(1000);
}
});
button2 = (Button) findViewById(R.id.click2);
button2.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
handler.sendEmptyMessage(999);
}
});
}
@Override
public boolean handleMessage(Message msg)
{
if (msg.what == 1000)
{
Toast.makeText(this, "hello lgy"+" j:"+j++, Toast.LENGTH_SHORT).show();
System.out.println("hello 2017");
}else if (msg.what == 999)
{
Toast.makeText(this, "current thread:" + Thread.currentThread()+" j:"+j++, Toast.LENGTH_SHORT).show();
System.out.println("current thread:" + Thread.currentThread());
}
return false;
}
}
4.Thread.currentThread()方法获取的是当前线程?
Thread.currentThread()这个方法在ThreadLocal中get()和set()方法中都出现了,虽然网上都说他是获取当前线程的作用,但是不能别人说是什么就是什么,实践出真知。
看一下Thread.java的代码:
/**
* Returns the Thread of the caller, that is, the current Thread.
*
* @return the current Thread.
*/
public static Thread currentThread() {
return VMThread.currentThread();
}
看英文注释确实是返回当前线程。再往下看,VMThread是什么,这个类也是隐藏的,又要到SDK文件目录下查看,在...\sdk\sources\android-19\java\lang目录下:
static native Thread currentThread();
看到源码是这样的,它调用的是底层的方法。既然这样,就不看了,那么接受它返回的是当前线程这个观点吧。
5.Handler与MessageQueue
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageAtTime(msg, uptimeMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
Handler与MessageQueue的关系主要是Handler发送Message给MessageQueue。发送消息的方法有很多,这里我们只说sendMessage。
查看Handler下的源码,可以看到sendMessage方法:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
看到的是它又调用了sendMessageDelayed方法,
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
我的天,老是调用其他方法,代码不贴了,调用sendMessageAtTime,这个方法里面其实又是调用了Handler里的enqueueMessage方法,而Handler里的enqueueMessage方法又调用了MessageQueue的enqueueMessage方法,在这个方法里:
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
synchronized (this) {
if (mQuitting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
}
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
可以看到它将Handler发的Message压到MessageQueue队列中。而Looper不断轮询MessageQueue,如果有待处理的消息,就回调Handler的dispatchMessage(msg)方法:
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);
}
//这里调用了Handler的dispatchMessage,提供一个处理结果的接口
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.recycle();
}
}
而dispatchMessage方法中,又调用了handleMessage()方法:
/**
* 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);
}
}
消息机制的处理过程基本上就讲完了。最后再捋一下思路。可以假设这么一个场景,因为UI线程不能执行耗时操作,所以要将耗时的操作分发给子线程执行,子线程执行完成后,就将执行完成的结果反馈给UI线程。这个过程通过Handler实现,即子线程执行完成任务后,通过Handler的sendMessage等发消息的方法将处理结果发送到消息队列MessageQueue中,然后Looper是在不断轮询MessageQueue队列的,Looper拿到消息队列的Message后,通过Message对象中的target拿到Handler对象的引用,然后调用Handler的dispathMessage方法,在dispathMessage中回调了handlerMessage方法,来实现UI线程对该消息的处理。
6.资源下载
[android API 19 ActivityThread.java类]
http://download.csdn.net/detail/lgywsdy/9732093
[android API 19 VMThread.java类]
http://download.csdn.net/detail/lgywsdy/9732091
7.参考文章
http://www.tuicool.com/articles/MJjq6j
http://blog.csdn.net/guolin_blog/article/details/9991569