Handler、Looper、MessageQueue、Message的联系

一般线程间的消息传递,有许多方法,有Activity.runOnUiThread、Handler.post、View.post等方法,这些方法本质上还是通过Handler传递一个Message来实现的。举个常见的场景:在子线程进行任务的下载,需要实时显示进度在UI上,但是AndroidUI是线程不安全的,直接在子线程刷新UI是会导致应用崩溃的。一般的做法是创建一个Handler对象,通过在子线程中发送message,在handleMessage中进行刷新界面就不会有问题了。这种处理方式被称为异步消息处理模型。

一、异步消息处理模型

该模型主要由以下几个构成:

1)Handler:负责消息的发送,接收;

2)Looper:工作线程,不断地从消息队列中取出message,不断地回调;

3)MessageQueue:消息队列,消息存放的地方,需要同步机制,保证在多个不同线程插入消息的有序性;

4)Message:消息

二、源码分析

1)创建Handler

通常,我们创建Handler话,是通过new一个对象,那么在主线程创建和子线程创建有什么不同吗?

public class MainActivity extends Activity {  
      
    private Handler handler1;  
      
    private Handler handler2;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        handler1 = new Handler();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                handler2 = new Handler();  
            }  
        }).start();  
    }  
  
}  

如果运行上面的代码,结果是handler1创建没有问题,handler2创建就导致程序崩溃了,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() ,意思是不能在线程中创建一个没有调用Looper.prepare方法的Handler。也许你会有疑问了,为什么在UI线程就可以呢?我们来看下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 = null;  
}  

关键的是这句:mLooper = Looper.myLooper();  获取一个Looper对象,如果返回null,就会抛出异常。我们来看看myLooper方法,

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

该方法中是从sThreadLocal中获取Looper对象,既然有get方法,显然会有与之对应的set方法,在该方法中创建一个Looper对象,分析源码,我们发现在prepare方法中调用了set方法,

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.get()返回的不为空,就会抛出异常,提示Looper只能创建一个,所以prepare方法只能调用1次;如果没有Looper对象,就创建一个,设置到sThreadLocal中。所以这也能解释为什么需要先调用prepare才能创建Handler对象了,而且每个Handler只有一个对应的Looper。

说到这,你可能要说了,这个也没有解释为什么在主线程中创建Handler就可以呀。别急,我们接着分析。这个是因为程序启动时 ,系统主动帮我们调用Looper.prepare方法了。查看ActivityThread类,我们发现了它的调用位置。

public static void main(String[] args) {  
    SamplingProfilerIntegration.start();  
    CloseGuard.setEnabled(false);  
    Environment.initForCurrentUser();  
    EventLogger.setReporter(new EventLoggingReporter());  
    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(new LogPrinter(Log.DEBUG, "ActivityThread"));  
    }  
    Looper.loop();  
    throw new RuntimeException("Main thread loop unexpectedly exited");  
} 
从Looper.prepareMainLooper();  从这个方法名来看,我们不难猜出是在这个方法中调用了prepare方法。
public static final void prepareMainLooper() {  
    prepare();  
    setMainLooper(myLooper());  
    if (Process.supportsProcesses()) {  
        myLooper().mQueue.mQuitAllowed = false;  
    }  
}  

到这里,总算明白为什么主线程中创建Handler可以了,而想在子线程创建Handler,必须先调用Looper.prepare方法才可以。  

2)发送message

创建完Handler后,一般我们通过创建Message对象,使用sendMessage(Message msg)等方法发送消息,然后在Handler的handlerMessage方法中接收消息。那么消息是发送到哪里?为什么Handler能接收到消息呢?我们继续往下分析。

通过阅读源码,我们发现发送消息的方法最终都会调用到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(  
            this + " sendMessageAtTime() called with no mQueue");  
        Log.w("Looper", e.getMessage(), e);  
    }  
    return sent;  
}  

从代码我们可以看到,第一个参数就是我们传递进来的message,第二个参数uptimeMillis是指系统自启动到当前时间的毫秒数加上延迟的时间,然后将这两个参数设置到enqueueMessage方法中。代码中的MessageQueue,就是消息队列的意思,消息都存放在MessageQueue中。MessageQueue是在Looper的构造函数中创建的,因此一个Looper对应一个MessageQueue。它有提供消息入列和出列的方法。enqueueMessage就是入列的方法。我们来下这个方法:

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) {  
            RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
            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;  
} 

首先你要知道,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的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(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            if (me.mLogging!= null) me.mLogging.println(  
                    ">>>>> Dispatching to " + msg.target + " "  
                    + msg.callback + ": " + msg.what  
                    );  
            msg.target.dispatchMessage(msg);  
            if (me.mLogging!= null) me.mLogging.println(  
                    "<<<<< Finished to    " + msg.target + " "  
                    + msg.callback);  
            msg.recycle();  
        }  
    }  
}  

从第4行可以看出,它进入一个死循环,不断地取出消息,如果当前的消息队列中存在待处理消息,就将这个消息出列,然后让下一条消息成为待处理,否则就进入堵塞,直至新消息入列。不难看出msg.targer就是指发送消息的Handler,每当有一个消息出列,就会传递到dispatchMessage方法。我们来下这个方法:

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);  
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
}  

如果callback不为空,就返回回调;如果为空,就返回handlerMessage。到这里,相信大家明白为什么Handler能够接收到之前发送出去的消息了。

三、总结

一个完整的异步消息处理模型的流程图如下:

创建Looper,它在构造方法中会创建一个MessageQueue,所以一个Looper对应一个MessageQueue;

Looper调用prepare方法,再次调用的话会报错,所以一个Handler对应一个Looper;

创建Handler对象,再调用loop方法,让队列无限循环,之后通过Handler的方法,将message插入到MessageQueue中,它会根据target属性找到对应的Handler,执行dispatchMessage方法,决定是调用callback还是handler方法,这样就消费了一个message。

 

参考链接:

Android异步消息处理机制完全分析

Handler源码分析

posted @ 2017-05-26 16:25  ha_cjy  阅读(313)  评论(0编辑  收藏  举报