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

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. 洛谷 P3410 拍照

    洛谷 P3410 拍照 题目描述 小B有n个下属,现小B要带着一些下属让别人拍照. 有m个人,每个人都愿意付给小B一定钱让n个人中的一些人进行合影.如果这一些人没带齐那么就不能拍照,小B也不会得到钱. ...

  2. 学习笔记-express路径问题

    在页面渲染成功之后,报错出现静态文件css样式引用路径出错,于是我就根据express api文档,托管静态文件作出修改,最后全是徒劳.于是我又从引用开始找起,<link rel="s ...

  3. 【Java学习笔记之十八】Javadoc注释的用法

    Javadoc注释的用法 Java 文档 // 注释一行/* ...... */ 注释若干行/** ...... */ 注释若干行,并写入 javadoc 文档 通常这种注释的多行写法如下: /*** ...

  4. BZOJ 1088: [SCOI2005]扫雷Mine【思维题,神奇的模拟+枚举】

    1088: [SCOI2005]扫雷Mine Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3791  Solved: 2234[Submit][St ...

  5. BZOJ2441: [中山市选2011]小W的问题

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2441 首先要注意到x1>x3且x5>x3(要是没有这个设定就是树状数组水题了.. ...

  6. BZOJ1758: [Wc2010]重建计划(01分数规划+点分治+单调队列)

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1758 01分数规划,所以我们对每个重心进行二分.于是问题转化为Σw[e]-mid>=0, ...

  7. sublime text 3如何安装插件

    原博客地址:http://blog.csdn.net/weixin_40682842/article/details/78727266 我自己的部分操作如下: 学习Sublime Text扩展插件的安 ...

  8. 完整CentOS搭建OpenVPN服务详细教程

    一.介绍 1.定义 ① OpenVPN是一个用于创建虚拟专用网络加密通道的软件包,最早由James Yonan编写.OpenVPN允许创建的VPN使用公开密钥.电子证书.或者用户名/密码来进行身份验证 ...

  9. C++异常层次结构

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class MyArray { publi ...

  10. IOS开发之纯代码界面--基本控件使用篇 ┊

    http://www.cocoachina.com/bbs/read.php?tid=131516