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

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. 小乔注:java关键字this

    java中当一个对象创建后,java虚拟机就会给这个对象分配一个指向自己的指针,称为this.this随实例化对象而产生,因此this只用于非静态方法体内.主要有以下四点应用: 1.调用当前类的成员变 ...

  2. 一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](五)

    前言 Hi,大家好,我是Rector 时间飞逝,一个星期又过去了,今天还是星期五,Rector在图享网继续跟大家分享系列文本:一步一步创建ASP.NET MVC5程序[Repository+Autof ...

  3. Java与算法之(4) - 数字全排列

    全排列是指n个数(或其他字符)所有可能的排列顺序,例如1 2 3三个数字的全排列是 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 那么问题来了,任意输入一个大于1的数字n,列 ...

  4. C/C++中__builtin_popcount()的使用及原理

    __builtin_popcount()用于计算一个 32 位无符号整数有多少个位为1 Counting out the bits     可以很容易的判断一个数是不是2的幂次:清除最低的1位(见上面 ...

  5. hdu_1034(模拟题)

    很久没有打模拟题了,再次总结一下模拟题的做法: 仔细分析题意,弄清楚过程 理清楚模拟步骤,严格按照步骤编写代码 添加中间输出测试每步结果 虽然这是一个很简单的水题,但是没有松哥帮忙还是卡了很久,因为没 ...

  6. B. Duff in Love

    B. Duff in Love time limit per test 2 seconds memory limit per test 256 megabytes input standard inp ...

  7. php常用数据结构

    # 常用数据结构--------------------------------------------------------------------------------## 树(Tree)- ...

  8. 如何控制input框!

    ENTER键可以让光标移到下一个输入框  只能是中文   屏蔽输入法   只能输入英文和数字   只能是数字 只能显示,不能修改 只能输数字,判断按键的值 function   onlyNum() { ...

  9. ubuntu-apache下隐藏thinkphp入口文件index.php

    按照thinkphp手册中来讲,apache服务器下,隐藏thinkphp入口文件有3步: httpd.conf配置文件中加载了mod_rewrite.so模块 AllowOverride None ...

  10. Linux虚拟主机通过FTP软件创建目录时提示550 Create Directory Operation Failed

    更新时间:2017-06-07 13:26:11   分享: 问题描述 通过FTP软件连接Linux虚拟主机,在尝试创建新目录时,服务器返回错误提示:550 Create Directory Oper ...