Android源码分析之Looper
先来说说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的更多相关文章
- Android源码分析-全面理解Context
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- Android源码分析(六)-----蓝牙Bluetooth源码目录分析
一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...
- Android源码分析(十七)----init.rc文件添加脚本代码
一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...
- Android源码分析(十六)----adb shell 命令进行OTA升级
一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...
- Android源码分析(十五)----GPS冷启动实现原理分析
一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...
- Android源码分析(十四)----如何使用SharedPreferencce保存数据
一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...
- Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关
一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...
- Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果
一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...
- Android源码分析(十一)-----Android源码中如何引用aar文件
一:aar文件如何引用 系统Settings中引用bidehelper-1.1.12.aar 文件为例 源码地址:packages/apps/Settings/Android.mk LOCAL_PAT ...
随机推荐
- fusioncharts图例(legend)属性
图例用来在多系列图和混合图中将图形和对应的系列名称联系起来. 从v3.2开始,每个系列的名称前面会展示对应的icon图标,这些图标具有交互作用,用户可以通过点击这些图标来显示或者隐藏对应的数 ...
- MongoDB入门三:MongoDB shell
MongoDB shell MongDB shell是一个功能完备的Javascript解释器,可以运行Javascript程序.也可以用于连接MongoDB服务器,执行脚本,对数据库进行操作.类似于 ...
- asynchronous-logging-with-log4j-2--转
原文地址:https://dzone.com/articles/asynchronous-logging-with-log4j-2 Log4J 2 is a logging framework des ...
- Linux bash - 常用操作命令
一.终端基础 本文摘录一些本人在学习Linux(CentOS 6.6) bash命令,并且会不定期保持更新. 在此先介绍一下Linux shell终端的常规命令输入格式,如下图: 上图中root是用户 ...
- [转载]Windows 2012 R2安装SharePoint 2013 手动安装工具软件
之前介绍过在window 2012中安装SharePoint 2013,这次,借着SharePoint 2013 sp1补丁发布之际,介绍下在window 2012 r2中安装SharePoint 2 ...
- Slip.js – 在触摸屏上实现列表的滑动排序功能
Slip.js 是一个很小的 JavaScript 库,用于实现对触摸屏的互动 Swipe 和对元素重新排序列表(Reordering).Slip.js 没有任何的依赖,你可以通过自定义 DOM 事件 ...
- Spring总结——AOP、JDBC和事务的总结
1.上一次总结了 Spring 的核心三大组件(Core,Beans,Context),今天总结的 AOP.JDBC和事务都可以看成是核心三大组件的应用. 其中 Spring 的事务管理又以 AOP ...
- 利用DropDownList实现下拉
在视图的Model<Vo>里面我们需要使用IEnumerable来将别的列表的数据全部的转化为下拉列表.下面是关于在项目中实际的写法. 一:实现下拉属性列表的写法 通过使用Select ...
- 基于KV Data Model实现Table Data Model
HBase对外暴露出来的是一个表格数据模型,如下图所示 rowkey应用程序可以自己设计.每一个Cell可以保存多个版本的数据,由timestamp标示版本.应用程序可以自己指定timestamp,如 ...
- .net开发微信公众平台
一.说明:公众平台信息接口为开发者提供了一种新的消息处理方式,只有申请成为开发者后,你才能使用公众平台的开发功能,在这里你需要填写一个URL和一个Token,这两项信息也需要你拥有自己的服务器(外网服 ...