欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Handler消息机制

程序员文章站 2022-05-14 07:49:56
...

一、概述

在安卓开发里面,当子线程在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成,也不是调用Thread.wait()或是Thread.sleep()。安卓采取的方法是,主线程为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。
一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。Android里面,线程分两种:

主线程:也叫UI线程,或称ActivityThread,或者MainThread,用于运行四大组件和处理他们用户的交互。 ActivityThread类管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。

ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。更新UI的工作必须交给主线程,在安卓里子线程是不允许更新UI的。

二、几个基本概念:
1、消息机制:就是不同线程之间的通信机制或规则。
2、安卓的消息机制:简单的讲就是就是 Handler的运行机制。
3、Handler 运行机制的作用:有效避免ANR的发生,一旦发生ANR,程序就Crash了。
4、触发ANR的条件: a、在activity中超过5秒的时间未能响应下一个事件;b、BroadcastReceive超过10s未响应。以上a和b两个条件任一个出现都会引发ANR。而造成a、b两点的原因有很多,比如网络请求, 大文件的操作, 复杂的耗时计算等。
如何避免ANR:A、主线程不能执行耗时操作;B、子线程不直接更新UI界面(UI界面更新方式可参阅下问《Android开发中更新UI的几种常用方式》http://blog.csdn.net/haoyuegongzi/article/details/78406342)。
5、Handler的四角生死恋:

1)、Message:用于存放消息的对象,消息发送的载体。
2)、MessageQueue:消息队列(单表链的方式实现),用来存放通过Handler发布的消息,管理Message,遵循先进先出的原则。
3)、Handler:负责处理消息,将Message添加到消息队列以及对消息队列中的Message进行处理。
4)、Looper:一个死循环,扮演MessageQueue和Handler之间纽带的角色,循环取出MessageQueue里面的Message,并交付给相应的Handler进行处理。
简而言之,Handler消息机制主要就是就是Handler、Looper、MessageQueue、Message*一台戏。

三、Handler的工作机制概要:
1、Handler通过其对象调用sendxxx方法插入一条信息到MessageQueue;
2、Looper不断轮询调用MeaasgaQueue的next方法;
3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()。大致过程见下图。

Handler消息机制

四、Handler的工作机制详解:

1、Looper对象的创建
在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面会创建一个Looper对象:Looper.prepareMainLooper(),然后Looper.loop();完成Looper对象的创建(下面红色部分代码)。实际上.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。

public static void main(String[] args) {
    SamplingProfilerIntegration.start();    
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();    
    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(newLogPrinter(Log.DEBUG, "ActivityThread")); 
    }
    Looper.loop();      
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

这里要注意一点就是:在子线程中通过关键字new创建的Handler对象时,Looper对象是没有被创建的,直接使用的话,会报异常。如果要在子线程中创建Handler对象,那么就需要在创建前添加代码:Looper.prepare();来解决问题。

new Thread(new Runnable() {  
    @Override  
    public void run() {  
        Looper.prepare();      
    }
}).start();

原因:
在我们new一个Handler的时候,调用了Handler的构造方法,其构造方法主要如下:

public Handler() {
    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;
}

可以看到在if语句判断mLooper是否为空前,调用了mLooper = Looper.myLooper();来获取mLooper对象,如果Looper对象为空,则会抛出一个运行时异常。
那什么时候Looper对象才可能为空呢?我们来看看判断mLooper是否为空的if语句的前一段代码myLooper的源码:

public static final Looper myLooper() {
    return (Looper)sThreadLocal.get();    
}

这里通过sThreadLocal的get方法获取了一个不为空的mLooper对象。提到get方法,自然就会想到set方法,并给sThreadLocal设置Looper对象,通过前面在子线程中创建Handler对象可知,Looper.prepare()方法是给sThreadLocal设置Looper对象的根本所在。Prepare()如下:

public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper());
}

可以看到,首先判断sThreadLocal中Looper对象是否为空,如果为空new一个新的Looper并设置进去。这样也就完全解释了为什么在子线程中我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
Looper的作用:通过一个while(true)de 死循环,不断轮询MessageQueue,有新的消息就交给Handler处理。而MessageQueue对象是在其构造方法中创建的:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);    
    mThread = Thread.currentThread();
}

2、Handler发送消息
使用Handler发送消息的流程想必不多说了。直接上代码:

new Thread(new Runnable() {
    @Override
    public void run() {
        Message message = new Message();        
        message.arg1 = 1;
        bundle.putString("data", "data");
        message.setData(bundle);            
        handler.sendMessage(message);
    }
}).start();

Handler调用sendMessage方法后把Message发送到哪里去了呢?又如何在Handler的handleMessage()方法中重新得到这条Message呢?
Handler的send系列方法:

sendEmptyMessage(int what);
sendEmptyMessageDelayed(int what, long delayMillis);
sendEmptyMessageAtTime(int what, long uptimeMillis);
sendMessage(Message msg);
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
sendMessageAtFrontOfQueue(Message msg);

除了sendMessageAtFrontOfQueue()方法之外,其它发送消息的方法最终都会辗转调用到sendMessageAtTime()方法中。源码如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis){
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;        
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }else {
        RuntimeException e = new RuntimeException(" sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }    
    return sent;    
}

sendMessageAtTime()方法接收两个参数, msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

这里MessageQueue,前面提到过就是消息队列,通过表单链的形式用于保存和管理Meassage对象,并提供入队和出队的方法,遵循先进先出的原则。它的对象是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);    
    mThread = Thread.currentThread();
}

enqueueMessage()方法字面翻译过来就是入(en: entrance)队的方法了:

final boolean enqueueMessage(Message msg, long when) {
    if (msg.when != 0) {
        throw new AndroidRuntimeException(msg + " This message is already in use.");
    }
    if (msg.target == null && !mQuitAllowed) {
        throw new RuntimeException("Main thread not allowed to quit");
    }
    synchronized (this) {
        if (mQuiting) {
            String exception = " sending message to a Handler on a dead thread";
            RuntimeException e = new RuntimeException(msg.target + exception);
            Log.w("MessageQueue", e.getMessage(), e);
            return false;
        } else if (msg.target == null) {            
            mQuiting = true;        
        }
        msg.when = when;        
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;            
            mMessages = msg;            
            this.notify();
        } else {
            Message prev = null;
            while (p != null && p.when <= when) { 
                prev = p;
                p = p.next;
            }
            msg.next = prev.next;            
            prev.next = msg;
            this.notify();
        }
    }       
    return true;        
}

从源码可以知道,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间就是我们前面介绍的uptimeMillis参数。操作上就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。如果你是通sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

明白另外入队操作,再看看出队操作。提到出队操作,就要考虑到我们前面说到的四角生死恋中的Looper对象的loop()方法,源码如下:

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {  
        Message msg = queue.next();
        if (msg != null) {        
            if (msg.target == null) {     
                return;
            }
            msg.target.dispatchMessage(msg);
            msg.recycle();  
        }  
    }  
}  

从源码可以看到,整个while 循环都是一个死循环,不断地调用的MessageQueue的next()方法,也就是前面讲到的消息队列的出队方法。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中(第7行)。这里的msg.target,其实就是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);  
    }  
}  

如果msg.callback 不为空,则调用mCallback的handleCallback()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样就可以解释为什么在Handler中复写它的handleMessage()方法就可以获取到之前发送的消息!

为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler通常是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。