先来说说summary,Looper就是用来在某个线程中跑一个message loop。一个线程默认是没有message loop与之相关联的,

为了创建一个你必须在这个线程中调用Looper.prepare方法,然后还得调用Looper.loop来开始消息循环,直到loop被停止。大部分

和message loop的交互是通过Handler类来进行的。一个典型的例子在上一篇Handler中已经给过了,这里为了方便再重复下:

This is a typical example of the implementation of a Looper thread, using the separation of {@link #prepare} and {@link #loop} to create an initial Handler to communicate with the Looper.

class LooperThread extends Thread {
public Handler mHandler; public void run() {
Looper.prepare(); mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
}; Looper.loop();
}
}

这个例子展示了怎样将一个Thread,Looper,Handler关联起来一起work。

  接下来我们看看Looper类的关键字段,代码如下:

  // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue;
final Thread mThread;

sThreadLocal表示和某个线程关联的Looper对象,每个线程有且仅有一个;sMainLooper就是传说中的与UI线程关联的Looper,

什么touch事件、key事件等等UI发出的事件(message)都在此looper中处理,这里顺便插句,main looper如此重要以至于

Android系统在每个app启动的时候都已经给我们创建好了,客户端代码不需要操心,相关代码在android.app.ActivityThread.main()方法中:

  public static void main(String[] args) {
SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread();
thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} AsyncTask.init(); if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
} Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

注意关于Looper的相关调用,先是prepareMainLooper(),然后是loop()方法,这也就是我们一开始说的一个线程为了有一个

message loop必须要做的2件事情。在这里UI线程已经做了,所以这也就解释了为啥UI线程一开始就有相关的message loop了。

  接下来先看看ctor,如下:

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

Looper有一个private的ctor,内部会初始化mQueue,设置mThread为当前线程。

  几个相关的小方法,如下:

  /**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
} /**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
public static MessageQueue myQueue() {
return myLooper().mQueue;
} /**
* Returns true if the current thread is this looper's thread.
* @hide
*/
public boolean isCurrentThread() {
return Thread.currentThread() == mThread;
}

分别返回与当前线程关联的Looper对象,MessageQueue对象,还有个help方法用来判断looper线程是否是当前线程。

  接下来我们看一组prepare相关的方法:

  /** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
} 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));
} /**
* 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();
}
} /** Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}

客户端代码要用到的是无参数的prepare方法,它将创建一个允许退出的message queue;Android系统调用prepareMainLooper,

内部调用参数为false的prepare方法,创建一个不允许退出的message queue,客户端代码永远不要调用这个方法(因为系统已经

调用过了,你在调用的话会抛IllegalStateException)。在prepare(boolean quitAllowed)内部sThreadLocal对象会被设置,

new一个Looper赋给它,这时与本线程相关的Looper对象也就产生了。sMainLooper在prepareMainLooper方法里也会被设置

成合适的值,以便客户端代码后面调用。

  然后是Looper的重头戏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
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} msg.target.dispatchMessage(msg); 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.recycle();
}
}

loop()一开始首先检查与当前线程关联的looper是否存在,如果还没有就会抛一个RuntimeException,说明还没Looper与当前线程相关联;

否则开始进入message循环。这个无限for循环的主体大概是这样:从MessageQueue中取出一个msg(阻塞型操作),如果msg是null说明

没有更多的message了(MessageQueue正在退出),退出for循环;否则将msg交给它的target(Handler)处理,最后回收msg,然后

开始下一轮同样的处理。

  最后我们看一组quit方法,如下:

  /**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
} /**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be
* delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}

quit方法表示尽快退出message loop,着急模式,队列里任何pending的消息都不会被处理了;其他的注意事项参看方法的doc;

有时候你可能希望处理完队列里面的消息在退出且不接收新的消息了,这时你可以考虑quitSafely方法。但是超过当前时间的message

还是不会被处理而是回收掉了,也就是说退出时,时间已经到了(msg.when <= now)的message才会被最后处理;比如现在message

queue要求退出了,你队列里面还有10个1h之后才要执行的message,那么这些message会被回收掉而不会派发;其他事项请参看方法的doc。

  Looper类的分析就到这了。

Android源码分析之Looper的更多相关文章

  1. Android源码分析-全面理解Context

    前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...

  2. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

  3. Android源码分析(十七)----init.rc文件添加脚本代码

    一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...

  4. Android源码分析(十六)----adb shell 命令进行OTA升级

    一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...

  5. Android源码分析(十五)----GPS冷启动实现原理分析

    一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...

  6. Android源码分析(十四)----如何使用SharedPreferencce保存数据

    一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...

  7. Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关

    一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...

  8. Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果

    一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...

  9. Android源码分析(十一)-----Android源码中如何引用aar文件

    一:aar文件如何引用 系统Settings中引用bidehelper-1.1.12.aar 文件为例 源码地址:packages/apps/Settings/Android.mk LOCAL_PAT ...

随机推荐

  1. 编写高质量JS代码的68个有效方法(七)

    [20141220]编写高质量JS代码的68个有效方法(七) *:first-child { margin-top: 0 !important; } body>*:last-child { ma ...

  2. notepad++ 各类插件学习记录

    js文件的规范格式排列: 插件里安装 jstools, 然后重启notepad++再去插件里面的jstool里面用jsformat就可以格式化排列不规则的js代码了 notepad++ 自动补全: 在 ...

  3. SerializationUtility

    public static T LoadFromXml<T>(string fileName) { FileStream fs = null; try { XmlSerializer se ...

  4. Http客户端识别的方法

      Http用户识别的机制方法分为如下几种: 1).承载用户身份的HTTP首部 2).客户端IP地址跟踪,根据客户端IP地址进行识别 3).用户登录,用认证方式设别用户 4).使用胖URL,一种在UR ...

  5. ios基础之入门(一)

    最近找到了一个可以接触ios开发的职位,可以系统的学习和练习了.先从最基本的开始: 一.获取控件的两种方式 1)第一种,也是经常使用的一种,通过IBOutlet方式.直接按住control键,将控件和 ...

  6. SpringMVC基础——@ModelAttribute和@SessionAttribute

    一.@ModelAttribute 注解 对方法标注 @ModelAttribute 注解,在调用各个目标方法前都会去调用 @ModelAttribute 标记的注解.本质上来说,允许我们在调用目标方 ...

  7. redhat Nginx 安装

    1.Nginx 简介 Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器. Nginx 是由 ...

  8. 在GridView列表中使用图片显示记录是否包含附件

    在我的前面很多文章中,都介绍过通用附件模块的管理,本篇随笔主要介绍在一些应用模块中的列表展示中,包含附件的记录,在GridView列表界面中使用图标来快速显示是否有附件的情况. 1.通用附件模块的应用 ...

  9. 通过UIView获取UIViewController

    需求很简单,通过UIViewController.view内的任意控件获取当前UIViewController: 立马开写: /** * @brief 通过viewController内的view,获 ...

  10. csharp: DataTable Rename ColumnName and remove Column

    enum ChangeNume { /// <summary> /// 简体 /// </summary> gbk=1, /// <summary> /// 英文 ...