Android Handler和他的小伙伴们,消息机制详解

Handler一直是面试很热的话题,最近又看了好多文章,下面结合源码来总结一下。

Handler 是Android 消息机制的上层接口,Handler的运行需要底层的MessageQueue和Looper的支撑,他们是Handler的好基友。Handler的运行机制也就是Android的消息机制。

我们都知道Handler是用来更新UI的,其实更新UI只是开发者最常用的场景。概括来讲:有时候需要在子线中进行耗时较长的I/O操纵,而I/O操作完成后需要在UI上做一些改变,这个时候可以通过Handler将更新UI的操作切换到主线程中去执行。这就是Handler的意义。

那么问题来了,主线程中可以使用Handler嘛?

查看源码发现 在ActivityThread中是默认创建了主线程的Handler。

这里我们回想一下,在第一次学习java时,我们都知道java需要Main方法才可以执行。 其实Main方法就在ActivityThread类中,它是Android程序的启动入口,也就是常说道的UI主线程。
static Handler sMainThreadHandler;  // set once in main()

public static void main(String[] args) {

               ........省略部分代码.......

5204


5205

        Looper.prepareMainLooper();

5206


5207

        ActivityThread thread = new ActivityThread();

5208

        thread.attach(false);

5209


5210

        if (sMainThreadHandler == null) {

5211

            sMainThreadHandler = thread.getHandler();

5212

        }

5213


5214

        AsyncTask.init();

5215


5216

        if (false) {

5217

            Looper.myLooper().setMessageLogging(new

5218

                    LogPrinter(Log.DEBUG, "ActivityThread"));

5219

        }

5220


5221

        Looper.loop();

5222


5223

        throw new RuntimeException("Main thread loop unexpectedly exited");

5224

    }

可以发现

sMainThreadHandler是主线程的Handler。

sMainThreadHandler = thread.getHandler();
继续查看getHandler()方法定义。


final H mH = new H();
final Handler getHandler() {

return mH;

}

而H是ActivityThread的一个内部类,是Handler的子类。

源码是这样定义的:

private class H extends Handler {

。。。。省略内部代码。。。。。

  }

所以主线程有自己的Handler,而且创建的时候通过调用 Looper.prepareMainLooper()初始化了Looper,这就是主线程中默认可以使用Handler的原因。

初步认识完了Handler,下面再介绍一遍他的小伙伴们。

MessageQueue的中文翻译是消息队列,顾名思义他的内部存储了一组消息。以队列的形式对外提供插入和删除的工作。虽然叫消息对垒,但其实他的内部结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。

Looper的中文翻译是循环,这里可以理解问消息循环。由于MassageQueue只是一个消息的存储单元,他不能处理消息,而Looper正好填补了这个功能。Looper会以无限循环的形式在MessageQueue中查找是否有新消息。

ThreadLocal是Looper中的一个特殊概念。它并不是线程,它的作用是可以在每个线程中存储数据。我们知道Handler创建时会采用当前线程的Looper来构造消息循环系统。那么Handler内部如何获取当前线程的Looper呢?这就是使用了ThreadLocal了,ThreadLocal可以在不同的线程中互不干扰的存储并获取数据。通过ThreadLocal可以轻松获取每个线程的Looper。

当然需要注意,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper,我们上面提到的主线程使用 Looper.prepareMainLooper()也创建了自己Looper。在非UI线程中我们使用 Looper.prepareLooper()来创建当前线程的Looper。

我们顺便分析一下Looper的创建,来看一下源码


public static void prepare() {

prepare(true);

}

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

}

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

通过上面的源码我们可以清晰的发现,Looper.prepareLooper()方法是创建了一个新的Looper,而且是依附当前对应的线程上。

同样Looper.prepareMainLooper()是跟主线相对的Looper,也是创建了一个Looper实例。

在Context类中可以通过getMainLooper方法得到主线程的Looper。

public abstract Looper 
 getMainLooper();

那么问题又来了,我在子线程中使用使用Looper.prepareLooper()创建Looper后能更新主线程的UI嘛?

比如:

public void run(){

Looper.prepareLooper();

Toast.makeText(MainActivity.this,"提示信息",TOAST.LENGTH_SHORT).show();

Message msg=new Message();

loginHandler=new LoginHandler();

loginHandler.sendMessage(msg);

Looper.loop();

}

答案是不可以!会提示:

Only the original thread that created a view hierarchy can touch its views. 
原因Looper.prepareLooper()方法会以当前线程为依附创建其Looper,如果换成另一种方式创建Handler
new Handler(Looper.getMainLooper())这种方式是依附主线程上,是可以正常更新UI的。

接下来再来解释一下MessageQueue。为什么说它是单链表的数据结构?

MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,

插入和读取操作分别对应的方法为:(Message msg, long when)和next();

插入消息操作:

boolean enqueueMessage(Message msg, long when) {

if (msg.target == null) {

throw new IllegalArgumentException("Message must have a target.");

}

if (msg.isInUse()) {

throw new IllegalStateException(msg + " This message is already in use.");

}

synchronized (this) {

if (mQuitting) {

IllegalStateException e = new IllegalStateException(

msg.target + " sending message to a Handler on a dead thread");

Log.w("MessageQueue", e.getMessage(), e);

msg.recycle();

return false;

}

msg.markInUse();

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;

}

从enqueueMessage的实现来看,他的主要操作其实就是单链表的插入操作,就不做过多解释了。

查询消息方法:

Message next() {

// Return here if the message loop has already quit and been disposed.

// This can happen if the application tries to restart a looper after quit

// which is not supported.

final long ptr = mPtr;

if (ptr == 0) {

return null;

}

int pendingIdleHandlerCount = -1; // -1 only during first iteration

int nextPollTimeoutMillis = 0;

for (;;) {

if (nextPollTimeoutMillis != 0) {

Binder.flushPendingCommands();

}

nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {

// Try to retrieve the next message.  Return if found.

final long now = SystemClock.uptimeMillis();

Message prevMsg = null;

Message msg = mMessages;

if (msg != null && msg.target == null) {

// Stalled by a barrier.  Find the next asynchronous message in the queue.

do {

prevMsg = msg;

msg = msg.next;

} while (msg != null && !msg.isAsynchronous());

}

if (msg != null) {

if (now < msg.when) {

// Next message is not ready.  Set a timeout to wake up when it is ready.

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

} else {

// Got a message.

mBlocked = false;

if (prevMsg != null) {

prevMsg.next = msg.next;

} else {

mMessages = msg.next;

}

msg.next = null;

if (false) Log.v("MessageQueue", "Returning message: " + msg);

return msg;

}

} else {

// No more messages.

nextPollTimeoutMillis = -1;

}

// Process the quit message now that all pending messages have been handled.

if (mQuitting) {

dispose();

return null;

}

// If first time idle, then get the number of idlers to run.

// Idle handles only run if the queue is empty or if the first message

// in the queue (possibly a barrier) is due to be handled in the future.

if (pendingIdleHandlerCount < 0

&& (mMessages == null || now < mMessages.when)) {

pendingIdleHandlerCount = mIdleHandlers.size();

}

if (pendingIdleHandlerCount <= 0) {

// No idle handlers to run.  Loop and wait some more.

mBlocked = true;

continue;

}

if (mPendingIdleHandlers == null) {

mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];

}

mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);

}

// Run the idle handlers.

// We only ever reach this code block during the first iteration.

for (int i = 0; i < pendingIdleHandlerCount; i++) {

final IdleHandler idler = mPendingIdleHandlers[i];

mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;

try {

keep = idler.queueIdle();

} catch (Throwable t) {

Log.wtf("MessageQueue", "IdleHandler threw exception", t);

}

if (!keep) {

synchronized (this) {

mIdleHandlers.remove(idler);

}

}

}

// Reset the idle handler count to 0 so we do not run them again.

pendingIdleHandlerCount = 0;

// While calling an idle handler, a new message could have been delivered

// so go back and look again for a pending message without waiting.

nextPollTimeoutMillis = 0;

}

}

上面代码比较长,但是你只要注意到 for (;;) ,应该已经明白next()方法是一个无限循环的方法。如果消息队列中没有消息那么next()方法就会一直阻塞在这里。当消息到来时,next方法会返回这条消息并且将其从单链表中删除。

现在MessageQueue中有数据了,那么Looper是怎么来操作MessageQueue的呢?

在Android的消息机制中Looper主要扮演的是消息循环的角色,具体来说就是它会不停的从MessageQueue中查找是否有新的消息。在Looper的构造方法中它会创建一个MessageQueue 即消息队列,并且将当前线程的对象保存起来。

代码如下:

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

通过Looper.prepareLooper()创建了当前线程的Looper后,通过调用Looper.loop()来开启消息循环。也是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();

}

}

上面的loop()方法的工作工程也比较好理解,loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。

当Looper调用quit方式时

/**

* Quits the looper.

* <p>

* Causes the {@link #loop} method to terminate without processing any

* more messages in the message queue.

* </p><p>

* Any attempt to post messages to the queue after the looper is asked to quit will fail.

* For example, the {@link Handler#sendMessage(Message)} method will return false.

* </p><p class="note">

* Using this method may be unsafe because some messages may not be delivered

* before the looper terminates.  Consider using {@link #quitSafely} instead to ensure

* that all pending work is completed in an orderly manner.

* </p>

*

* @see #quitSafely

*/

public void quit() {

mQueue.quit(false);

}

mQueue就是MessageQueue的实例。所以Looper.quit()是让MessageQueue清空数据。

另外还有

public void quitSafely() {

mQueue.quit(true);

}

这里传入的boolean值是用来控制是否将当前状态下消息队列中的方法执行完毕后再清空操作的。quitSafely就是在消息要求执行完当前队列中的所有消息后,再做清空操作。从而Looper退出。

而一般情况下不会调用quit方法。这个时候由于  MessageQueue.next(); // might block  在没有消息时处于阻塞状态,从而Looper.loop()也处于阻塞状态,等待消息的到来。

待续。。。


				
时间: 2024-10-27 08:45:55

Android Handler和他的小伙伴们,消息机制详解的相关文章

Handler详解系列(一)——Handler异步消息机制详解(附图)

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消息机制详解

能简单说得我们尽量不复杂: 为了避免ANR,我们会通常把 耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新的UI的时候就需要借助到安卓的消息机制,也就是Handler机制了. 注意:在安卓的世界里面,当 子线程 在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成--也不是调用 Thread.wait()或是Thread.sleep().安卓采取的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程.以这种方式设计你的应用程序,将能

Windows消息机制详解

消息是指什么?      消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送一个消息给应用程序. 消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息.例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标.这个记录类型叫做MSG,MSG含有来

Android 触摸事件 点击事件的分发机制 详解三---责任链模式

前面两节  我们讲述了 android 点击事件的分发流程.其实大家可以细细体会一下,这个分发的过程 始终是从顶层到下层.一层一层的按照顺序进行. 当然了,传到哪一层停止,我们可以通过重写某些方法来完成. 这个地方 android的开发人员很好的利用了 责任链模式来完成这边代码的编写. 下面我们就来讲一下 责任链模式到底是什么.以及如何运用. 大家知道 一个软件公司的基本架构就是 程序员----leader---project manager---boss 这种基础架构. 我们一般都会有team

Windows 消息机制详解

总的来说: MSG包括: 窗口句柄,指示MSG发送的目的窗口 消息标识 lPARAM.wParam 发送时间 发送时的鼠标位置   关于消息队列: Windows系统有一个系统消息队列 每个线程都有一个自己的消 息队列(由于发送消息MSG需 要提供一个窗口HWnd,而基 本有窗口的线程,都是UI线 程),因此基本上如果线程使用了GDI函数,则windows给该线程分配一个线程消息队列,这个消息队列负责该线程的所有窗口的消息.   所有的窗口都有自己的句柄(HWND),消息被发送时,这个句柄就已经

Android 触摸事件 点击事件的分发机制 详解二

现在我们来看看 事件分发的流程.view group 怎么传递给view的. 首先自定义一个layout 1 package com.example.testtouch; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import android.view.MotionEvent; 7 import android.widget.Linear

windows消息机制详解(转载)

消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送一个消息给应用程序.消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息.例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标.这个记录类型叫做TMsg, 它在Windows单元中是这样声明的:typeTMsg = packed recordhwnd: HWND; / /窗口句柄message: UINT; / /

Android 触摸事件 点击事件的分发机制 详解

最近发现团队里有些员工在做一些自定义控件的时候感觉比较吃力.尤其是做触摸事件这种东西的时候.很多人对机制并不理解.因为百度出来的东西都太理论化了.确实不好理解. 今天带大家坐几个小demo.帮助理解一下. 先从简单的view 的事件分发机制开始解释. 我们首先自定义一个工程 package com.example.testtouch; import android.app.Activity; import android.os.Bundle; import android.util.Log; i

Android应用AsyncTask处理机制详解及源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机制详解及源码分析>文章),这里继续分析Android的另一个异步机制AsyncTask的原理. 当使用线程和Handler组合实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,性能开销会比