Handler消息机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haoyuegongzi/article/details/79226354

一、概述

在安卓开发里面,当子线程在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成,也不是调用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的工作机制详解:

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操作了。

猜你喜欢

转载自blog.csdn.net/haoyuegongzi/article/details/79226354