深入浅出,Handler机制外科手术式的剖析(ThreadLocal,Looper,MessageQueen,Message)(上)...
2019独角兽企业重金招聘Python工程师标准>>>
为什么会有handler机制?
在Android中,所有的UI控件都是运行在主线程中的, 如果我们从子线程访问UI,系统会报异常。为什么不允许子线程访问UI呢?因为Android的UI控件不是线程安全的,为了防止UI控件处于不可控的状态,就禁止了。主线程是不允许做任何的网络请求的,那么请求回来的数据如何同步到UI呢。所以为了方便线程间通讯,就产生了Handler机制,目的就是方便数据在线程间传递切换。而更新UI是我们用的最常见的。
先来看一段代码:
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO: 9/7/17 do what you wanna
}
};
这个是典型的handler用法,我们在使用的时候一般都在主线程初始化Handler,那么有没有可能在子线程创建handler呢?答案是可以的,但是如果直接在子线程创建Handler,会报异常:
RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
1
说的很清楚,需要我们在子线程初始化Looper,通过Looper.prepare();那么这个Looper是个什么?它和handler是什么关系呢?别急,听我细细道来。
在说Looper之前,先说一下MessageQueen,顾名思义他是一个消息队列,遵循的是先进先出的原则,但是他的内部不是一个队列的形式,而是以单链表的数据结构来实现的,单链表的优势就不用说了吧,插入和删除的速度相对快。分别对应的是enqueueMessage和next方法。但是他不处理消息,而Looper就是负责处理消息的。
Looper可以说是handler的核心类,他是链接MessageQueen和handler的媒介,它开启后,会无限循环的去MessageQueen中获取消息,然后交给Handler处理。在初始化Handler之前,必须先初始化Looper,否则就会报上边的异常,Looper是和线程绑定的,每一个线程,只有一个Looper。这一点我们从Looper的静态初始化方法中也可以看出来:
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));
}
if语句的判断就是判断是否有当前thread的Looper,如果有就报异常。我们注意到,他的判断依据是sThreadLocal,这是一个ThreadLocal实例。这是一个重要的线程工具类,下边我们详细讲一下。
ThreadLocal
ThreadLocal是java里边一个线程数据存储类,他并不是一个Thread,其实作用非常简单,就是每一个使用该变量的线程,都提供一个变量值的副本,每一个线程可以独立的改变自己的副本,而不和其他线程的副本冲突。当某些数据是以线程为作用域并且不同线程有不同的数据副本的时候,可以考虑用ThreadLocal,很明显Looper的作用域就是当前线程,所以非常适合了。通过下边的下例子,简单展示下ThreadLocal的特点和用法:
public static void main(String[] args){
final ThreadLocal<String> mThreadLocal = new ThreadLocal<String>();
mThreadLocal.set("main");
new Thread("Thread-1") {
public void run() {
mThreadLocal.set(Thread.currentThread().getName());
System.out.println("currentThread:"+Thread.currentThread().getName()+","+mThreadLocal.get());
};
}.start();
System.out.println("currentThread:"+Thread.currentThread().getName()+","+mThreadLocal.get());
}
首先我初始化了一个ThreadLocal的变量mThreadLocal,然后在主线程调用set方法,放入字符串”main“,然后开启一个子线程,放入子线程的名字,然后分别在子线程和主线程打印get方法获取的数据,打印结果如下:
currentThread:main,main
currentThread:Thread-1,Thread-1
从日志中我们可以看出来,尽管塞数据的时候调用的都是同一个ThreadLocal,但是在不同的线程获取到的数据是不一样的。是不是很厉害,但是到底他是如何实现不同的线程有不同的副本的呢,我们来看一下它的set方法的源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
从set方法中我们可以看到,通过getMap拿到一个ThreadLocalMap对象,然后通过ThreadLocalMap的set方法设置进去,如果这个ThreadLocalMap对象为null,则执行createMap方法。
从getMap的方法内容我们可以看出,他返回的Thread内部的一个成员变量:ThreadLocal.ThreadLocalMap。那么这个ThreadLocalMap就是数据存储的关键,他是一个内部静态类,我们看一下new ThreadLocalMap(this, firstValue)具体做了什么:
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
table是一个数组,Entry是ThreadLocalMap的静态内部类,继承自WeakReference并以ThreadLocal作为key,ThreadLocal的泛型实例作为value。在new ThreadLocalMap的时候,把key和value存入Entry,然后通过一定的算法(算法的具体内容不在分析,有兴趣的兄弟可以自己看看),把Entry放入数组。接着让我们来看下ThreadLocalMap的set(ThreadLocal,Object)方法:
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
可以看出它内部是开启一个for循环,来遍历数组table,当Entry不为null的时候,拿到Entry里边的value然后替换掉。到这里我们算搞明白了ThreadLocal和ThreadLocalMap的关系,以及他们的创建和set方法。
接下来我们看看ThreadLocal的get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
搞明白了刚才的内容再看这些应该很容易了,代码逻辑也是比较清晰的,通过ThreadLocalMap拿到Entry,然后拿到Entry的value。值得注意的是,initialValue()直接返回null,所以没有在相应的线程调用ThreadLocal.set的时候,那么我们通过get拿到的就是null。
通过分析源码我们可以得出,ThreadLocal的get和set方法依赖于ThreadLocalMap对象,而ThreadLocalMap对象依赖于Thread被创建,所以不同的线程中访问的数据是不一样的。理解了这些,我们再去理解Looper就非常简单了。
Looper
Looper是一个轮训器,他会不断地去MessageQueen中查看是否有新消息,如果有就立即处理。Looper有以下几个原则:
1.Handler的创建依赖于对应线程的Looper;
2.一个线程只能有一个Looper,否则会出异常;
前边我们讲了,在子线程初始化Handler,会报RunTimeException,正确的创建方式如下:
new Thread(){
@Override
public void run() {
Looper.prepare();
Handler threadHandler = new Handler();
Looper.loop();
}
}.start();
Looper.prepare()方法做了什么,下边我们看一下源码:
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
代码中有一个成员变量sThreadLocal,他就是一个ThreadLocal实例,里边的泛型是Looper,刚才我们分析完ThreadLocal,所以很容易我们就能知道,handler机制是通过这个sThreadLocal来保有当前线程的Looper的。从代码上我们能看出来,Looper在prepare的时候,会通过sThreadLocal拿到当前线程的looper,如果不为null,就报异常,如果为null,就new一个Looper,塞到sThreadLocal中。
那么你可能会有一个疑问,为什么主线程不需要执行prepare方法呢?其实,主线程也执行了初始化方法,只不过不是prepare,而是prepareMainLooper方法,这个是专门为主线程提供的创建Looper的方法。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
从代码上可以看出,他也执行了prepare,本质上也是一样的。
prepare之后,Looper并不会去执行任何操作,只有调用了Looper.loop()方法之后,消息循环系统才算生效。loop()方法是Looper的主要方法,我们具体来看一下:
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();
}
}
代码逻辑也是比较清晰的,首先会做一个校验,检查当前线程是否有初始化Looper,然后会开启一个无条件的for循环,不断地从MessageQueen中获取消息,如果MessageQueen的next()返回null,则终止方法。message的next()方法是一个阻塞操作,当没有消息的时候,next方法会阻塞,这也就导致loop方法阻塞。如果从MessageQueen中拿到了message,那么会通过message调用target的dispatchMessage(Message)。这个target其实就是Handler,一会讲Handler的时候我们会讲到。这样就成功的把消息切换到了指定的线程中去执行,因为dispatchMessage是在创建Handler时候所使用的Looper执行的。
Looper还提供了退出的方法:quit()和quitSafely();他们俩执行的都是同一个方法,都是MessageQueen中的quit(boolean),但是传入的参数不一样。quit会直接退出looper,但是quitSafely只是设定了一个标记,然后把消息队列的消息处理完毕后才安全退出。quit之后,handler发送消息会失败,send方法会返回false。在子线程中,执行完所有的handler回调之后应该调用quit方法来终止Looper,否则这个线程会一直处于等待状态,不能被gc回收。
第一部分就先剖析到这里,还有一半放在第二篇博客讲。
转载于:https://my.oschina.net/u/920274/blog/2999470
深入浅出,Handler机制外科手术式的剖析(ThreadLocal,Looper,MessageQueen,Message)(上)...相关推荐
- 自己写个C++版本Handler来理解Android的Handler机制
由于日常工作不需要经常写android上层应用,对Android的Handler机制一直处于模模糊糊的状态.使用Handler之后,回去写c++代码时,时刻怀念Android里面的Handler,希望 ...
- android Handler机制之ThreadLocal详解
概述 我们在谈Handler机制的时候,其实也就是谈Handler.Message.Looper.MessageQueue之间的关系,对于其工作原理我们不做详解(Handler机制详解). Messa ...
- Android多线程:深入分析 Handler机制源码(二)
前言 在Android开发的多线程应用场景中,Handler机制十分常用 接下来,深入分析 Handler机制的源码,希望加深理解 目录 1. Handler 机制简介 定义 一套 Android 消 ...
- handler机制的原理_Handler机制竟然可以这样解释,我惊呆了!
Handler的相关博客太多了,随便一搜都一大把,但是基本都是上来就贴源码,讲姿势,短时间不太好弄明白整体的关系,和流程,本文就以生活点餐的例子再结合源码原理进行解析.希望对你有一点帮助. 来,咱们进 ...
- 【Android】Handler 机制 ( Handler | Message | Looper | MessageQueue )
文章目录 I . Handler 机制简介 II . Handler 机制 Handler Message Looper MessageQueue 四组件对应关系 III . Handler ( 消息 ...
- handler机制的原理
andriod提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange). 1)Loo ...
- 浅谈Android中的Handler机制
Handler是Android中提供的一种异步回调机制,也可以理解为线程间的消息机制.为了避免ANR,我们通常会把一些耗时操作(比如:网络请求.I/O操作.复杂计算等)放到子线程中去执行,而当子线程需 ...
- [转]Android中handler机制的原理
Andriod提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange). 1)Loo ...
- Android开发之Handler机制记录
1.Handler 机制 说到 Handler,就不得不提与之密切相关的这几个类:Message.MessageQueue,Looper: Message: Message 中有两个成员变量值得关注: ...
最新文章
- 【十大经典数据挖掘算法】k-means
- CSS中控制不换行属性
- 324. Wiggle Sort II | 324. 摆动排序 II(降序穿插)
- 记一次对学校的渗透测试
- 设置 NSZombieEnabled 定位 EXC_BAD_ACCESS 错误
- c语言用命令行编译运行程序_使用C程序执行系统命令
- Codeforces 482 - Diverse Permutation 构造题
- java arraylist_死磕 java集合之ArrayList源码分析
- 《SEO的艺术(原书第2版)》——3.11 为意识形态影响力开展SEO
- 2020 年,你必须知道发展最快和衰落最快的软件技能
- win11天气小组件如何开启 Windows11开启天气组件的设置方法
- 浅谈Hybrid技术的设计与实现
- 绿点 | 区块链介入下的绿色经济 x 妳格局LadyVision x WinMap+!
- 【精华】拒绝国外IP海外IP访问的几种方法
- 有对象的程序猿都是怎么写代码的
- 双非本科生进大厂,而我还在底层默默地爬树(上)
- 快速了解电力IEC104协议规约
- 相机镜头选择:相机焦距、视场角和景深(可视距离)之间的关系
- Gateway断言功能详解
- 前沿技术,目前为止功能最全最强大的PLC智能远程模块,物联网模块
热门文章
- 从小白到精通python要多久-小白学Python需要多久?老男孩Python培训教程
- 成都python工作-成都python就业
- python3.8.5安装-centos7 编译安装python3.8.5
- python 调用linux命令-Python执行Linux系统命令的4种方法
- python转行it好学吗-想转行学python过来人提醒大家几点
- python能处理多大的数据-Python 适合大数据量的处理吗?
- python input与返回值-Python 详解基本语法_函数_返回值
- python菜鸟教程h-Python 命令行参数
- Super SloMo神经网络生成极慢视频(PyTorch实现)
- mysql协议解析器_mysql协议解析