Android的Handler线程切换原理
Handler是我们在开发中经常会接触到的类,因为在Android中,子线程一般是不能更新UI的.
所以我们会使用Handler切换到主线程来更新UI,那Handler是如何做到实现不同线程之间的切换呢?
先看一个例子
1.ThreadLocal的简单使用
public class HandlerActivity extends AppCompatActivity {
private final static String TAG = "HandlerActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1.创建一个ThreadLocal对象,
final ThreadLocal<IThreadCallback> threadLocal = new ThreadLocal<>();
//对ThreadLocal赋值
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log.d(TAG,"当前线程:" + Thread.currentThread().getName());
}
});
//新开一个线程,并对threadLocal赋值
Thread thread = new Thread(){
@Override
public void run() {
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log.d(TAG,"当前线程:" + Thread.currentThread().getName());
}
});
threadLocal.get().onExecute();
}
};
//获取IThreadCallback接口实例并执行onExecute()方法
threadLocal.get().onExecute();
thread.setName("otherThread");
//开启线程,并在线程内部获取IThreadCallback接口实例并执行onExecute()方法
thread.start();
}
//定义了一个回调接口
public interface IThreadCallback{
void onExecute();
}
}
可以看到,我们总共调用了两次set()方法,而执行的线程也是在不同线程下,所以,Handler是通过ThreadLocal来
进行线程切换的.那么ThreadLocal是怎么做到的呢?
2.ThreadLocal的原理
首先我们先看看ThreadLocal.set()方法做了什么,我们一行一行代码来看
public void set(T value) {
//获取当前方法(set方法)所执行的线程
Thread t = Thread.currentThread();
//getMap() ====> t.threadLocals,它是Thread类中定义的一个成员变量,也就是说
//每一个线程都一个独一无二的threadLocals,它是一个ThreadLocal.ThreadLocalMap类型,
//它是ThreadLocal的内部类
ThreadLocalMap map = getMap(t);
if (map != null)
//直接将value和当前ThreadLocal绑定
map.set(this, value);
else
//为空则会直接new ThreadLocalMap(this, value);
createMap(t, value);
}
getMap() ====> t.threadLocals,它是Thread类中定义的一个ThreadLocal.ThreadLocalMap对象,
ThreadLocalMap是ThreadLocal的一个内部静态类,如果map不为空,它就会调用set方法把我们传进来的
对象封装成了一个Entry对象,并保存到Entry[]数组中
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
...
...
//new了一个Entry()对象,也是ThreadLocal的内部静态类
//内部有一个Object类型的成员变量,用来保存我们传进来的值
tab[i] = new Entry(key, value);
...
...
}
get()方法
public T get() {
//获取当前方法(get方法)所在线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//从Entry[] table数组中去获取保存的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//将我们传进来的value强转为T所表示的类型,这个value就是对应线程的对象了
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
上面大抵描述了一个ThreadLocal的基本流程,就算你还是看不懂、理解不了也没关系,
你只要知道,你在哪个线程中调用set方法,那么那个对象就属于那个线程,因为每个线程是有一块自己的
内存区域,在线程中定义的变量,都会存在于当前线程自己的内存区域.
比如:
//这是在主线程初始化,new的实例,那它就存在于主线程的内存区域
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log.d(TAG,"当前线程:" + Thread.currentThread().getName());
}
});
Thread thread = new Thread(){
@Override
public void run() {
//不在主线程,所以new IThreadCallback()
//它是保存在当前线程(otherThread),还记得吗,我们给它
//设置了一个名称otherThread
threadLocal.set(new IThreadCallback() {
@Override
public void onExecute() {
Log.d(TAG,"当前线程:" + Thread.currentThread().getName());
}
});
threadLocal.get().onExecute();
}
};
3.Handler的UI线程切换原理
在知道了ThreadLocal的使用原理之后,其实就很简单了,跟我们上面的例子类似.
我们知道,在子线程(非UI线程)中创建Handler会报错.
解决方法也很简单,只要在创建Handler之前,调用Looper.prepare()方法就不会报错了,
我们看一下Looper.prepare()做了什么
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));
}
关于消息循环的机制,读者自己去找相关资料了解,这不是本文的重点.这里只简单提一下.
可以看到,Looper的内部有一个ThreadLocal的实例sThreadLocal,调用prepare()方法会直接new Looper()对象,将Looper对象通过ThreadLocal绑定到相应的线程,这样Looper中所有执行的方法和变量都在对应的线程中,达到不同线程之间切换的效果.Looper类是Handler能完成消息发送的一个重要成员,在创建了Handler之后,调用Looper.loop()就能进行消息循环,内部有一个死循环不断会从消息队列中去取Message,按照先后顺序依次调用Message.target.dispatchMessage()方法,最后调用到handleMessage()方法,Message.target是一个Handler类型的对象,它在sendMessage()的时候被赋值.
我们可以在子线程中尝试使用Toast,如果不调用Looper.loop()方法,在以前版本的系统上,Toast是不会显示,必须要调用Looper.loop()进行消息循环才能显示.Android11不调用也能显示,应该是内部帮我们调用了.
handleMessage之所以能执行在主线程,是因为Loop对象本身就是在主线程创建的.有人会说,我又没有调用Looper.prepare()方法,它为什么不报错呢,因为主线程的Looper对象已经在ActivityThread.main方法中
已经调用过了,所以我们不需要调用Looper.prepare()就能使用.如果要获取主线程的Looper对象,可以调用Looper.getMainLooper()方法获取,可以利用这个Looper对象来创建一个Handler对象,让它的HandleMessage方法执行在主线程.
Android的Handler线程切换原理的更多相关文章
- EventBus 线程切换原理
主要问题其实只有两个,其一:如何判断当前发送事件的线程是否是主线程:其二:如何在接收事件时指定线程并执行: 一个一个来看. 1.如何判断是否在主线程发送 EventBus在初始化的时候会初始化一个Ma ...
- [转]Android中handler机制的原理
Andriod提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange). 1)Loo ...
- 【转载】Android 的 Handler 机制实现原理分析
handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段.使用起来很简单,就两个步骤,在主线程重写handler的handleMessage( )方法,在工作线程发送消息.但是,有没有 ...
- android的子线程切换到主线程
在子线程中,如果想更新UI,必须切换到主线程,方法如下: if (Looper.myLooper() != Looper.getMainLooper()) { // If we finish mark ...
- Android Handler 消息机制原理解析
前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...
- Android更新主线程UI的两种方式handler与runOnUiThread()
在android开发过程中,耗时操作我们会放在子线程中去执行,而更新UI是要主线程(也叫做:UI线程)来更新的,自然会遇到如何更新主线程UI的问题.如果在主线程之外的线程中直接更新页面显示常会报错.抛 ...
- android高级---->Handler的原理
andriod提供了Handler来满足线程间的通信,上次在更新UI的时候也提到过Handler的使用,关于Handler的基本使用,参见博客(android基础---->子线程更新UI).今天 ...
- Android 为什么要有handler机制?handler机制的原理
为什么要有handler机制? 在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过 ...
- EventBus 消息的线程切换模型与实现原理
一. 序 EventBus 是一个基于观察者模式的事件订阅/发布框架,利用 EventBus 可以在不同模块之间,实现低耦合的消息通信. EventBus 因为其使用简单且稳定,被广泛应用在一些生产项 ...
随机推荐
- Visual Studio 2010 ~ 2022 全系列密钥
更新记录 2022年6月10日 修改序列号顺序. Visual Studio 2022 Professional(专业版): TD244-P4NB7-YQ6XK-Y8MMM-YWV2J Enterpr ...
- vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9
项目从vue2 升级vue3,VueI18n需要做适当的调整.主要是Vue I18n v8.x 到Vue I18n v9 or later 的变化,其中初始化: 具体可以参看:https://vue- ...
- Vue关于echats的使用(浅显易懂)
安装 npm install echarts --save 引入 (全局) main.js import * as echarts from 'echarts'; Vue.prototype.$ech ...
- 基于thinkphp6 layui的优秀极速后台开发框架推荐
很多时候我们在做项目开发的时候,苦于没有好一点的轮子,自己动手开发的话,太耗费时间了,如果采用VUE的话,学习成本跟调试也比较麻烦, 而且有时候选用的东西甲方也不太容易接受,现在给大家介绍一款优秀的极 ...
- UiPath培训教程
匠厂出品,必属精品 Uipath中文社区qq交流群:465630324 uipath中文交流社区:https://uipathbbs.comRPA之家qq群:465620839 第一课--UiPa ...
- weiphp 插件"通用表单"BUG修改
修改文件目录 在类FormsValueController 中添加函数 // 匹配函数 //$value:字符串 //$validate_rule:正则规则 // return true:比配成功,f ...
- Multiparty Cardinality Testing for Threshold Private Set-2021:解读
本文记录阅读该论文的笔记. 本文基于阈值加法同态加密方案提出了一个新的允许\(N\)方检查其输入集的交集是否大于\(n-t\)的IPSI方案,该协议的通信复杂度为\(O(Nt^2)\). 注意:\(N ...
- Spring XmlBeanFactory 容器的基本实现
容器的基本用法 熟悉 Spring 的朋友应该都很了解下段代码: public void testBeanFactory() { BeanFactory bf = new XmlBeanFactory ...
- 降低PDF质量
如果PDF尺寸过大,则需要降低其质量以缩小尺寸.在Linux下一条命令即可实现,而且效果不错: gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFS ...
- private关键字的作用及使用和this关键字的作用
封装的操作--private关键字 private的含义 1. private是一个权限修饰符,代表最小权限. 2. 可以修饰成员变量和成员方法. 3. 被private修饰后的成员变量和成员方法,只 ...