ι 版权声明:本文为博主原创文章,未经博主允许不得转载。

Looper在Android的消息机制中就是用来进行消息循环的。它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻处理该消息,否则就一直等待。

Looper中有一个属性:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

这也就解释了,前面我们所说的我们可以通过ThreadLocal实现Looper在线程中的存取。

除此之外,还有两个属性需要注意:

final MessageQueue mQueue;
final Thread mThread;

下面我们先看下Looper的构造函数:

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

在构造函数中,创建了一个MessageQueue消息队列,并且将当前线程的对象保存了起来。

接下来看loop方法,只有调用了loop方法后,消息循环系统才真正地起到了作用。

    /**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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.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();
}
}

loop方法中首先调用了myLooper方法:

    /**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}

myLooper方法会返回与当前线程相关联的Looper对象。如果当前线程没有关联任何Looper对象的话,该方法则返回null。

查看loop方法的源码,可以知道,当当前线程没有关联任何Looper对象时,loop方法会抛出运行时异常,提示当前线程中没有Looper。若想解决该问题,可以在loop方法被调用前,先执行Looper.prepare()方法,创建一个looper对象。继续看loop方法的源码,可以看到该方法是一个死循环,唯一可以跳出该循环的方法就是queue.next()返回的对象为null。在上面的文章中,我们分析过,queue.next()即读取MessageQueue中的消息,next()方法返回null,说明MessageQueue中没有Message,即该MessgaeQueue调用了quit方法。那么何时MessageQueue会调用quit方法呢?来看下Looper的quit方法:

public void quit() {
mQueue.quit(false);
}

以及Looper的quitSafely方法:

public void quitSafely() {
mQueue.quit(true);
}

Looper的quit方法和quitSafely方法都会导致MessageQueue调用quit方法,所以当不需要Looper的时候,建议调用Looper的quit()方法或quitSafely()方法,以避免loop方法无限循环下去。

要想知道Looper的quit方法和quitSafely方法的区别,我们看下MessgaeQueue的quit方法:

    void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
} synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true; if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
} // We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}

安全退出,则调用removeAllFutureMessagesLocked()方法,该方法会设定一个标记,当消息队列中的已有消息全部处理完毕后才会安全退出;quit则会调用removeAllMessagesLocked(),直接退出。

下面接着看loop方法,重点看这一句:

msg.target.dispatchMessage(msg);

在Android的消息机制概述中,我们已经说过,target是Message的一个属性,其类型为Handler,msg.target也就是发送这条消息的对象。由此一来,Handler发送的Message最终又交给了它自己来调用dispatchMessage方法来处理,但是dispatchMessage方法是在Looper的loop方法中被调用的,那么Looper的loop方法是在哪里执行的呢?在创建Handler时所在的线程中执行的。

ActivityThread(主线程)在创建时,会初始化Looper,所以我们可以在主线程中直接使用Handler,当需要更新UI时,可以通过Handler发送消息,最后就可以回到主线程去更新UI啦,啦啦啦。

除此之外,Looper还提供了一些其他的方法,例如prepareMainLooper方法:

    /**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

该方法会实例化当前线程作为一个looper,但是是主线程的looper啦。Android系统会为我们创建主线程的looper,我们也不需要自己手动去调用该方法了。该方法的实质还是通过prepare方法实现的。

再如getMainLooper方法:

    /**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

该方法使得我们可以在任何地方获取到主线程的Looper了。

【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理的更多相关文章

  1. 【原创】源码角度分析Android的消息机制系列(六)——Handler的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Handler的定义: /** * A Handler allows you to send and process {@link Mes ...

  2. 【原创】源码角度分析Android的消息机制系列(一)——Android消息机制概述

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.为什么需要Android的消息机制 因为Android系统不允许在子线程中去访问UI,即Android系统不允许在子线程中更新UI. 为什 ...

  3. 【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其 ...

  4. 【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Android源码(API24)中对ThreadLocal的定义: public class ThreadLocal<T> 即 ...

  5. 【原创】源码角度分析Android的消息机制系列(四)——MessageQueue的工作原理

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. MessageQueue,主要包含2个操作:插入和读取.读取操作会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和ne ...

  6. 源码角度分析-newFixedThreadPool线程池导致的内存飙升问题

    前言 使用无界队列的线程池会导致内存飙升吗?面试官经常会问这个问题,本文将基于源码,去分析newFixedThreadPool线程池导致的内存飙升问题,希望能加深大家的理解. (想自学习编程的小伙伴请 ...

  7. Android的Message Pool是什么——源码角度分析

    原文地址: http://blog.csdn.net/xplee0576/article/details/46875555 Android中,我们在线程之间通信传递通常采用Android的消息机制,而 ...

  8. 【react】什么是fiber?fiber解决了什么问题?从源码角度深入了解fiber运行机制与diff执行

    壹 ❀ 引 我在[react] 什么是虚拟dom?虚拟dom比操作原生dom要快吗?虚拟dom是如何转变成真实dom并渲染到页面的?一文中,介绍了虚拟dom的概念,以及react中虚拟dom的使用场景 ...

  9. 从源码角度理解android动画Interpolator类的使用

    做过android动画的人对Interpolator应该不会陌生,这个类主要是用来控制android动画的执行速率,一般情况下,如果我们不设置,动画都不是匀速执行的,系统默认是先加速后减速这样一种动画 ...

随机推荐

  1. 字典 (dict) 的增删改查及其他方法

    一.字典 1.字典简介: 字典是python中唯一的映射类型,采用键值对(key-value)的形式存储数据.python对key进行哈希函数运算,根据计算的结果决定value的存储地址,所以字典是无 ...

  2. CTF---密码学入门第三题 奇怪的短信

    奇怪的短信分值:10 来源: Ayn 难度:易 参与人数:5117人 Get Flag:2623人 答题人数:2858人 解题通过率:92% 收到一条奇怪的短信: 335321414374744361 ...

  3. ex_gcd(个人模版)

    ex_gcd: #include<stdio.h> #include<string.h> using namespace std; int x,y; int ex_gcd(in ...

  4. bzoj 1935: [Shoi2007]Tree 园丁的烦恼

    Description 很久很久以前,在遥远的大陆上有一个美丽的国家.统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草.有一天国王漫步在花园里,若有所思,他问一个园丁道: ...

  5. [bzoj3282]Tree (lct)

    昨天看了一天的lct..当然幸好最后看懂了(也许吧..) 论善良学长的重要性T_T,老司机带带我! 这题主要是删边的时候还要判断一下..蒟蒻一开始天真的以为存在的边才能删结果吃了一发wa... 事实是 ...

  6. A very hard Aoshu problem(dfs或者数位)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4403 A very hard Aoshu problem Time Limit: 2000/1000 ...

  7. N的N次方

    题目描述 现给你一个正整数N,请问N^N的最左边的数字是什么? 输入 输入包含多组测试数据.每组输入一个正整数N(N<=1000000). 输出 对于每组输入,输出N^N的最左边的数字. 样例输 ...

  8. Spark算子--cogroup

    转载请标明出处http://www.cnblogs.com/haozhengfei/p/b612b1e6d9b951fad5574cd0ce573d7e.html cogroup--Transform ...

  9. Spark算子--coalesce和repartition

    coalesce和repartition--Transformation类算子 代码示例

  10. gettype

    取得变量的类型. 语法: string gettype(mixed var); 返回值: 字符串 函数种类: PHP 系统功能 内容说明 本函数用来取得变量的类型.返回的类型字符串可能为下列字符串其中 ...