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

Handler,Looper,Thread,Message,MessageQueue

程序员文章站 2022-07-14 19:58:01
...

【1】Looper如何和Thread联系起来?

答:以主线程为例解释:

在ActivityThread类中的程序的入口,即main方法,该方法中调用了:
Looper.prepareMainLooper();
Step:接下来我们解析Looper类中的该方法:
public static void prepareMainLooper() {
prepare(false); synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared."); }
sMainLooper = myLooper(); }
}
官方对该方法的解释:

(1)将当前线程初始化为looper,将其标记为应用程序的主looper。

(2)应用程序的主looper是由Android环境创建的,所以你应该永远不要自己调用该方法。

Step:接下来我们解析:
prepare(false);
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread"); }
sThreadLocal.set(new Looper(quitAllowed));}
官方对该方法的解释:

(1)该方法让你有机会创建handlers,然后引用这个looper,然后才开始真正循环(loop)。
(2)调用此方法后一定要调用loop(),通过调用quit()来结束它。

Step:接下来我们解析:sThreadLocal.set(new Looper(quitAllowed));

(1)首先我们关注创建Looper对象:Looper looper = new Looper(quitAllowed)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}
这里主要是:
(1)创建了MessageQueue的对象mQueue;
(2)创建了Thread的对象mThread;

Thread.currentThread()方法可以获取到当前的线程,当应用程序启动的时候,系统会为该应用程序创建一个线程,我们叫它主线程。

(2)其次我们关注Looper对象的存储:sThreadLocal.set(looper)

我们看看sThreadLocal是什么东东?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

官方对ThreadLocal的解释:

用来实现线程本地存储,即每个线程的变量有自己对应的值。
所有的线程共享相同的ThreadLocal对象,但是每个线程访问它时都会看到不同的值,并且有一个线程更改不会影响其他线程。

Step:接下来我们解析:set(looper)
public void set(T value) {
Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) {
values = initializeValues(currentThread); }
values.put(this, value);}
官方对该方法的解释:

为当前线程设置此变量的值。

这里不对ThreadLocal类进行深入的了解,到这里我们知道使用该类可以实现存储Thread和Looper就可以了,类似于(key-value)。

因为Thread默认没有与它关联的消息循环,(Thread默认不能进行消息循环)
要创建一个,我们在运行循环的线程中调用prepare(),
然后调用loop()方法让他处理消息,
最后调用quit()方法推出循环

Looper最重要的方法以及顺序:prepare() -> loop() ->quit()

实际开发中的使用:参考HandlerThread

【1】extends Thread
【2】Looper.prepare() //将Thread初始化为looper
【3】创建一个或者多个Handler,用来处理message
【4】Looper.loop() //分发message,直到loop被告知quit()
【2】Handler如何和Thread联系起来?

主要从分析Handler源码来解析:

Step:先看默认构造函数:
public Handler() {
this(null, false);}
官方对该构造函数的解释:
默认构造函数将该Handler与当前线程的Looper关联起来。如果此线程没有looper,该Handler将无法接收Message,因此会抛出异常。

Step:再看看另一个构造函数:
public Handler(Callback callback, boolean async) {
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;}

官方对该构造函数的解释:
对于具有指定的回调接口的当前Thread使用Looper,并设置该Handler是否应该是异步的。
Handler默认情况下是同步的,除非此构造函数用于创建严格异步的。

mLooper:在Looper中通过prepare()方法创建的,这样Handler就和Looper联系起来了,【同时和Thread联系起来。】
mQueue:Handler中的MessageQueue和Looper中的MessageQueue是一致的。

Step:接着看Handler的两个主要用途:
【1】调度message 和 runnable ,在未来的某个点执行。
【2】在与自己不同的线程上执行某个事件。

调度消息可以通过如下方法完成,大致有两类型:post 和 send
post方式允许在接收到消息时将Runnable对象入队;
send方式允许在接收到消息时将Message对象入队;

Step:关于post方式做如下说明:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);}
官方解释:
将Runnable添加到MessageQueue.Runnable将在Handler所在的Thread中运行。

Step:再看看getPostMessage(r):
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); m.callback = r; return m;}
将Runnable用Message包裹起来,以Message的形式发送出去。

记住:无论是post方式,还是send方式,最终都只调用一个方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; }
return enqueueMessage(queue, msg, uptimeMillis);}
官方解释:
将Message置入MessageQueue中。(这里时间先不做解释了)

Step:再看看enqueueMessage()方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; if (mAsynchronous) {
msg.setAsynchronous(true); }
return queue.enqueueMessage(msg, uptimeMillis);}
重点:msg.target = this;这是非常重要的,因为Looper中的loop() 方法会用到它,即:msg.target.dispatchMessage(msg);
目的是要求发送Message的Handler和处理Message的Handler是一致的。

Step:接下来重点就是MessageQueue的enqueueMessage()方法,
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target."); }
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use."); }

synchronized (this) {
    if (mQuitting) {
        IllegalStateException e = new IllegalStateException(
                msg.target + " sending message to a Handler on a dead thread");            Log.w(TAG, e.getMessage(), e);            msg.recycle();            return false;        }

    msg.markInUse();        msg.when = when;        Message p = mMessages;        boolean needWake;        if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.            msg.next = p;            mMessages = msg;            needWake = mBlocked;        } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake            // up the event queue unless there is a barrier at the head of the queue            // and the message is the earliest asynchronous message in the queue.            needWake = mBlocked && p.target == null && msg.isAsynchronous();            Message prev;            for (;;) {
            prev = p;                p = p.next;                if (p == null || when < p.when) {
                break;                }
            if (needWake && p.isAsynchronous()) {
                needWake = false;                }
        }
        msg.next = p; // invariant: p == prev.next            prev.next = msg;        }

    // We can assume mPtr != 0 because mQuitting is false.        if (needWake) {
        nativeWake(mPtr);        }
}
return true;}

至此,Message已经放入MessageQueue中了;

Step:接下来就是从MessageQueue中取Message了,这时候就需要发挥Looper的作用了,我们看看loop()方法:
public static void loop() {
final Looper me = myLooper(); if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }
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        final Printer logging = me.mLogging;        if (logging != null) {
        logging.println(">>>>> Dispatching to " + msg.target + " " +
                msg.callback + ": " + msg.what);        }

    final long traceTag = me.mTraceTag;        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
        Trace.traceBegin(traceTag, msg.target.getTraceName(msg));        }
    try {
        msg.target.dispatchMessage(msg);        } finally {
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);            }
    }

    if (logging != null) {
        logging.println("<<<<< Finished to " + 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();    }

}
这里重点看:msg.target.dispatchMessage(msg)
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); } else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return; }
}
handleMessage(msg); }
}
这里我们看到它分情况处理了,msg.callback是否为null。

如果msg.callback不为null,说明传递过来的是Runnable,然后进行handleCallback(msg)
private static void handleCallback(Message message) {
message.callback.run();}
如果msg.callback为null,说明传递过来的是Message,然后进行handleMessage(msg)
public void handleMessage(Message msg) {
}
我们需要在run()方法和handleMessage()方法中写自己的实现。

【3】通过对【1】和【2】的分析,我发现:

我们主要了解Thread,Looper,Handler它们三个之间的关系就可以了,Message和MessageQueue可以把它们当作媒介就可以。

出场顺序是这样的:Thread -> Looper -> Handler

Thread创建后,就需要对Looper进行操作,主要是执行两个方法:prepare(),loop(), 这是准备工作。
然后就是对Handler的使用,创建Handler对象,并将其和Looper联系起来,并和Thread联系起来,
Handler就可以发挥它强大的功能。

【4】思考:
UI线程是如何创建的?
应用启动时,系统会为应用创建一个名为主线程的执行线程。主线程负责将事件分派给相应的用户界面小部件,其中包括绘图事件。
此外,UI线程也是应用与Android UI工具包组件进行交互的线程。

UI线程才能处理UI相关的操作,为什么?
答:Android UI工具包不是线程安全的。因此,单线程模型确保UI不能被不同的线程同时修改。

Android关于线程的详细说明:https://developer.android.com/guide/components/processes-and-threads.html

【5】Handler的工作原理:

Handler创建时会采用当前线程的Looper来构建内部的消息循环系统。接下来看Handler的运行机制

【6】ThreadLocal:

在不同的线程中访问的是同一个ThreadLocal对象,但是他们通过ThreadLocal获取到的值是不同的。

why? ->不同线程访问同一个ThreadLocal的get()方法,ThreadLocal内部会从各自的线程中取出一个数组,