专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
开发者全社区  ·  奇女子!钓男朋友翻车了 ·  17 小时前  
鸿洋  ·  Android×AI×鸿蒙生态周刊#2|跨端 ... ·  昨天  
开发者全社区  ·  WC!阿里P7总包超150w ·  昨天  
开发者全社区  ·  技术总监拿了45万年终奖突然离职,我们以为是 ... ·  2 天前  
开发者全社区  ·  上海某国企裁员10% ·  3 天前  
51好读  ›  专栏  ›  安卓开发精选

Android Handler 消息机制(解惑篇)上

安卓开发精选  · 公众号  · android  · 2016-10-12 09:59

正文

(点击 上方公众号 ,可快速关注)


来源:伯乐在线专栏作者 - PleaseCallMeCoder

链接:http://android.jobbole.com/84957/

点击 → 了解如何加入专栏作者


Android中的消息处理机制概述


大家对于Android中的消息处理机制的用法一定都比较熟悉,至于工作原理估计不少人有研究。就像我们自己写的类我们用起来比较熟悉一样,如果我们熟悉了消息处理机制的具体实现,那么我们用起来肯定也会事半功倍。


博主之前只是稍有涉猎,对其中一些地方也还心存疑虑,比如既然Looper.loop()里是一个死循环,那它会不会很消耗CPU呢?死循环阻塞了线程,那我们其他的事务是如何被处理的呢?Android的UI线程是在哪里被初始化的呢?等等。索性今天就把他们放到一起,说道说道。


Android中线程的分类


  • 带有消息队列,用来执行循环性任务(例如主线程、android.os.HandlerThread)

有消息时就处理

没有消息时就睡眠

  • 没有消息队列,用来执行一次性任务(例如java.lang.Thread)

任务一旦执行完成便退出


带有消息队列线程概述


四要素


  • Message(消息)

  • MessageQueue(消息队列)

  • Looper(消息循环)

  • Handler(消息发送和处理)


四要素的交互过程



具体工作过程


  • 消息队列的创建

  • 消息循环

  • 消息的发送最基本的两个API

    • 带一个Runnable参数,会被转换为一个Message参数

    • 带一个Message参数,用来描述消息的内容

    • Handler.sendMessage

    • Handler.post

  • 消息的处理


基于消息的异步任务接口


  • android.os.HandlerThread

    • 适合用来处于不需要更新UI的后台任务

  • android.os.AyncTask

    • 适合用来处于需要更新UI的后台任务


带有消息队列线程的具体实现


ThreadLocal


ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。


从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。


Looper


用于在指定线程中运行一个消息循环,一旦有新任务则执行,执行完继续等待下一个任务,即变成Looper线程。Looper类的注释里有这样一个例子:


class LooperThread extends Thread {

public Handler mHandler ;

public void run () {

//将当前线程初始化为Looper线程

Looper . prepare ();

// ...其他处理,如实例化handler

mHandler = new Handler () {

public void handleMessage ( Message msg ) {

// process incoming messages here

}

};

// 开始循环处理消息队列

Looper . loop ();

}

}


其实核心代码就两行,我们先来看下Looper.prepare()方法的具体实现


public final class Looper {

private static final String TAG = "Looper" ;

// sThreadLocal.get() will return null unless you've called prepare().

static final ThreadLocal Looper > sThreadLocal = new ThreadLocal Looper > ();

private static Looper sMainLooper ; // guarded by Looper.class

//Looper内的消息队列

final MessageQueue mQueue ;

// 当前线程

final Thread mThread ;

private Printer mLogging ;

private Looper ( boolean quitAllowed ) {

mQueue = new MessageQueue ( quitAllowed );

mThread = Thread . currentThread ();

}

/** 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 () {

prepare ( true );

}

private static void prepare ( boolean quitAllowed ) {

//试图在有Looper的线程中再次创建Looper将抛出异常

if ( sThreadLocal . get () != null ) {

throw new RuntimeException ( "Only one Looper may be created per thread" );

}

sThreadLocal . set ( new Looper ( quitAllowed ));

}

/**

* Initialize the current thread as a looper, marking it as an

* application's main looper. The main looper for your application

* is created by the Android environment, so you should never need

* to call this function yourself.  See also: {@link #prepare()}

*/

public static void prepareMainLooper () {

prepare ( false );

synchronized ( Looper . class ) {

if ( sMainLooper != null ) {

throw new IllegalStateException ( "The main Looper has already been prepared." );

}

sMainLooper = myLooper ();

}

}

//~省略部分无关代码~

}


从中我们可以看到以下几点:


  • prepare()其核心就是将looper对象定义为ThreadLocal

  • 一个Thread只能有一个Looper对象

  • prepare()方法会调用Looper的构造方法,初始化一个消息队列,并且指定当前线程

  • 在调用Looper.loop()方法之前,确保已经调用了prepare(boolean quitAllowed)方法,并且我们可以调用quite方法结束循环


说到初始化MessageQueue,我们来看下它是干什么的


/**

* Low-level class holding the list of messages to be dispatched by a

* {@link Looper}. Messages are not added directly to a MessageQueue,

* but rather through {@link Handler} objects associated with the Looper.

*

*


You can retrieve the MessageQueue for the current thread with

* {@link Looper#myQueue() Looper.myQueue()}.

*/


它是一个低等级的持有Messages集合的类,被Looper分发。Messages并不是直接加到MessageQueue的,而是通过Handler对象和Looper关联到一起。我们可以通过Looper.myQueue()方法来检索当前线程的MessageQueue。


接下来再看看Looper.loop()


/**

* Run the message queue in this thread. Be sure to call

* {@link #quit()} to end the loop.

*/

public static void loop () {

//得到当前线程Looper

final Looper me = myLooper ();

if ( me == null ) {

throw new RuntimeException ( "No Looper; Looper.prepare() wasn't called on this thread." );

}

//得到当前looper的MessageQueue

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

}

//将真正的处理工作交给message的target,即handler

msg . target . dispatchMessage ( msg );

if ( logging != null ) {

logging . println ( " + 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方法后,Looper线程就开始真正工作了,它不断从自己的MessageQueue中取出队头的消息(或者说是任务)执行。


除了prepare()和loop()方法,Looper类还有一些比较有用的方法,比如


  • Looper.myLooper()得到当前线程looper对象

  • getThread()得到looper对象所属线程

  • quit()方法结束looper循环这里需要注意的一点是,quit()方法其实调用的是MessageWueue的quite(boolean safe)方法。


void quit ( boolean safe ) {

if ( ! mQuitAllowed ) {

throw new IllegalStateException ( "Main thread not allowed to quit." );

}

synchronized ( this ) {

if ( mQuitting ) {

return ;

}

mQuitting = true ;

if ( safe ) {

removeAllFutureMessagesLocked ();

} else {

removeAllMessagesLocked ();

}

// We can assume mPtr != 0 because mQuitting was previously false.

nativeWake ( mPtr );

}

}


我们看到其实主线程是不能调用这个方法退出消息队列的。至于mQuitAllowed参数是在Looper初始化的时候初始化的,主线程初始化调用的是Looper.prepareMainLooper()方法,这个方法把参数设置为false。


Message


在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。 我们看下这个类的注释


/**

*

* Defines a message containing a description and arbitrary data object that can be

* sent to a {@link Handler}. This object contains two extra int fields and an

* extra object field that allow you to not do allocations in many cases.

*

*


While the constructor of Message is public, the best way to get

* one of these is to call {@link #obtain Message.obtain()} or one of the

* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull

* them from a pool of recycled objects.


*/


这个类定义了一个包含描述和一个任意类型对象的对象,它可以被发送给Handler。


从注释里我们还可以了解到以下几点:


  • 尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

  • 如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

  • 用message.what来标识信息,以便用不同方式处理message。


Handler


从MessageQueue的注释中,我们知道添加消息到消息队列是通过Handler来操作的。我们通过源码来看下具体是怎么实现的


/**

* A Handler allows you to send and process {@link Message} and Runnable

* objects associated with a thread’s {@link MessageQueue}. Each Handler

* instance is associated with a single thread and that thread’s message

* queue. When you create a new Handler, it is bound to the thread /

* message queue of the thread that is creating it – from that point on,

* it will deliver messages and runnables to that message queue and execute

* them as they come out of the message queue.

*

*


There are two main uses for a Handler: (1) to schedule messages and

* runnables to be executed as some point in the future; and (2) to enqueue

* an action to be performed on a different thread than your own.

*

*/


注释比较简单,这里就不过多翻译了,主要内容是: 每一个Handler实例关联了一个单一的ghread和这个thread的messagequeue,当Handler的实例被创建的时候它就被绑定到了创建它的thread。它用来调度message和runnables在未来某个时间点的执行,还可以排列其他线程里执行的操作。


public class Handler {

//~省略部分无关代码~

final MessageQueue mQueue ;

final Looper mLooper ;

public Handler () {

this ( null







请到「今天看啥」查看全文