Handler,Looper,Thread,Message,MessageQueue

【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内部会从各自的线程中取出一个数组,

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容