Android消息处理机制——Looper,Handler,Message,MessageQueue

题外话:

将近半载没有更新博客了。由于某些原因,这段时间自身的处境及对身边事物的看法发生了很大的变化。最近在看他人博客的时候产生了一些体会,写博客并不是为了写博客而写博客。自己以前写的博客质量都太低了,基本都是按照别人的思维方式复制一遍。实际上,这样的博客毫无意义。因此,从现在起博客更新频率会大幅度减少,但更多的将会是自己的研究与体会。那些网上满天飞的知识点,不再打算去整理了。Anyway,虽然我的博客没啥名气,我也不是什么大咖,但我享受写博客的乐趣,记录着我付出过的点滴——更重要的是,让更多的人看到我写的文章,那种分享的快乐难以言传。


Looper,Handler,Message,MessageQueue简介


Message:消息,信息传递的最小原子。其中包含了消息ID,并可选地包含了消息处理对象及回调等。由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper

这四个类在Android起到了举足轻重的作用,几乎所有的线程通信都离不开他们。这正是Android县城设计的精妙之处。


线程阻塞:Looper


众所皆知,一个程序至少应当有一个线程,程序才得以保持。同理,程序是从main函数开始,且结束于main函数。因此,想要让程序得以保持,就要阻塞main线程。

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中,在Android中指的就是UI线程),我们经常会需要一个线程阻塞,一旦有新任务则执行,执行完后继续阻塞,直到有下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        
        // ...其他处理,如实例化handler
        
        // 开始循环处理消息队列
        Looper.loop();
        // 从这里开始的代码将不会被执行,因为已经被Looper.loop()阻塞了

    }
}

就这样,我们已经产生了一个Looper线程了。

Looper.prepare()


先看图:

Android消息处理机制——Looper,Handler,Message,MessageQueue_第1张图片

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ(MessageQueue)。注意!一个Thread只能有一个Looper对象。为什么呢?

public class Looper {
    // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    // 其他属性

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 我们调用该方法会在调用线程的TLS中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。


Looper.loop()

Android消息处理机制——Looper,Handler,Message,MessageQueue_第2张图片


调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:

public static final void loop() {
        Looper me = myLooper();  //得到当前线程Looper
        MessageQueue queue = me.mQueue;  //得到当前looper的MQ
        
        // 这两行没看懂= = 不过不影响理解
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        // 开始循环
        while (true) {
            Message msg = queue.next(); // 取出message
            if (msg != null) {
                if (msg.target == null) {
                    // message没有target为结束信号,退出循环
                    return;
                }
                // 日志。。。
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler
                msg.target.dispatchMessage(msg);
                // 还是日志。。。
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                
                // 下面没看懂,同样不影响理解
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "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);
                }
                // 回收message资源
                msg.recycle();
            }
        }
    }

就这样,如果没有新的Message,就会queue.next()处阻塞当前线程。因此,除非调用quit()方法,线程是不会退出的。

注意,如果之前没有调用perpare()方法,会抛出异常。


quit() && quitSafely()


先来看看他们俩的区别。

使用quit()方法终结Looper,将会直接抛弃仍未处理的Message。

而quitSagely()的注释是这么写的:

Causes the loop() method to terminate as soon as all remaining messages in the message queue that are already due to be delivered have been handled. However pending delayed messages with due times in the future will not be delivered before the loop terminates.

Any attempt to post messages to the queue after the looper is asked to quit will fail. For example, thesendMessage(Message) method will return false. 

这句话什么意思呢?

也就是说啊,如果你使用quitSagely()方法来退出,那么还未处理完的message就会被处理完毕。而在这个阶段,任何通过sendMessage()传入的Message都会失败。这个方法保证了仍在队列中的Message能够被正确处理,是一种数据安全的方式。


另外在退出方式,从4.1.1版本发生了变化。在4.1.1以前的版本:

public void quit() {
        // 创建一个空的message,它的target为NULL,表示结束循环消息
        Message msg = Message.obtain();
        // 发出消息
        mQueue.enqueueMessage(msg, 0);
    }

在4.1.1以前,直接通过发送一个空消息来告知QM要退出,注意观察loop()方法。

而在4.1.1及以后的版本中:

public void quit() {
        mQueue.quit(false);
}

话说这里面就有点复杂了。暂未对其进行分析,估计是考虑到设计模式及其它因素吧。


prepareMainLooper()


既然都讲到这了,就来看看App的第一个线程——Main线程(即UI线程)的启动吧!

Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper)。详细实现请参考ActivityThread.java文件。Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的:

public final class ActivityThread {
	......

	public static final void main(String[] args) {
		......

		Looper.prepareMainLooper();

		......

		ActivityThread thread = new ActivityThread();
		thread.attach(false);
		
		......

		Looper.loop();

		......

		thread.detach();

		......
	}
}

注意看这个prepareMainLooper()。这就是在你的app启动初期要调用的方法。这个方法,以后你都不能够去调用,他只为Main线程准备!


异步调用:Handler


handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

public class handler {

    final MessageQueue mQueue;  // 关联的MQ
    final Looper mLooper;  // 关联的looper
    final Callback mCallback; 
    // 其他属性

    public Handler() {
        // 没看懂,直接略过,,,
        if (FIND_POTENTIAL_LEAKS) {
            final Class 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());
            }
        }
        // 默认将关联当前线程的looper
        mLooper = Looper.myLooper();
        // looper不能为空,即该默认的构造方法只能在looper线程中使用
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
    
    // 其他方法
}

下面我们就可以为之前的LooperThread类加入Handler:

public class LooperThread extends Thread {
    private Handler handler1;
    private Handler handler2;

    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        
        // 实例化两个handler
        handler1 = new Handler();
        handler2 = new Handler();
        
        // 开始循环处理消息队列
        Looper.loop();
    }
}

加入handler后的效果如下图:

Android消息处理机制——Looper,Handler,Message,MessageQueue_第3张图片

可得知,一个线程可以有多个Handler,但是能有一个Looper。


Handler发送消息


有了handler之后,我们就可以使用 post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int),sendMessage(Message), sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:

// 此方法用于向关联的MQ上发送Runnable对象,它的run方法将在handler关联的looper线程中执行
    public final boolean post(Runnable r)
    {
       // 注意getPostMessage(r)将runnable封装成message
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private final Message getPostMessage(Runnable r) {
        Message m = Message.obtain();  //得到空的message
        m.callback = r;  //将runnable设为message的callback,
        return m;
    }

    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;  // message的target必须设为该handler!
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }

其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象


Handler处理消息


说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Messagemsg)完成的,见源码

// 处理消息,该方法由looper调用
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果message设置了callback,即runnable消息,处理callback!
            handleCallback(msg);
        } else {
            // 如果handler本身设置了callback,则执行callback
            if (mCallback != null) {
                 /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果message没有callback,则调用handler的钩子方法handleMessage
            handleMessage(msg);
        }
    }
    
    // 处理runnable消息
    private final void handleCallback(Message message) {
        message.callback.run();  //直接调用run方法!
    }
    // 由子类实现的钩子方法
    public void handleMessage(Message msg) {
    }

可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处.


消息原子:Message


在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点需要注意(待补充):

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

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

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


你可能感兴趣的:(Android开发)