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 因为其使用简单且稳定,被广泛应用在一些生产项 ...
随机推荐
- javaweb获取客户端真实ip
在安全性要求较高的web项目中,我们经常有这样的需求: 黑名单:禁止指定ip访问. 白名单:允许指定ip访问. 根据ip追踪恶意入侵系统者. 在java中我们通常可以这样获取客户端ip地址: requ ...
- JavaGUI——Java图形用户界面
1.Java GUI 概述 GUI(Graphical User Interface,简称 GUI,图形用户界面)是指采用图形方式显示的计算机操作用户界面,与早期计算机使用的命令行界面相比,图形界面对 ...
- NC16430 [NOIP2016]蚯蚓
NC16430 [NOIP2016]蚯蚓 题目 题目描述 本题中,我们将用符号 \(\lfloor c \rfloor\) 表示对 c 向下取整,例如:\(\lfloor 3.0 \rfloor = ...
- springboot项目上传存储图片到七牛云服务器
springboot项目上传存储图片到七牛云服务器 问题描述: 当图片存在本地时会出现卡顿的现象.比如一篇图文混排的文章,如果图片没有加载完,可能整个文章都显示不出来,因为它们都是用的同一个服务器. ...
- Linux,Centos系统下配置java Jdk(附下载地址)
一.下载jdk 官网下载地址:https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 需要登录Oracle ...
- Linux操作系统(2):组管理和权限管理
组管理和权限管理 Outline 1.查看文件所有者:ls -ahl 2.更改文件或目录权限命令:chmod 3.更改文件或目录所有者命令:chown 4.更改文件或目录所属组命令:chgrp 1)组 ...
- pip安装报错Could not install packages due to an EnvironmentError: Missing dependencies for SOCK
unset all_proxy && unset ALL_PROXY
- Idea 的Test测试报错:java.lang.IllegalStateException: Failed to load ApplicationContext
因为在Test里面使用了注解@Autowired 引入来至bean.xml文件的内容 ,而在Test没有没有办法自动引入,需要在Test类上加上注解 @ContextConfiguration(loc ...
- 非root用户安装科学计算包blas、lapack和FFTW
一.安装FFTW 1.下载安装包 下载地址:http://www.fftw.org/download.html 2.编译安装 tar -zxvf fftw-3.3.10.tar.gz cd fftw ...
- C#常见的集合
3中数组式的 Array 在内存上是连续分配的,而且元素类型是一样的 特点:读取快,可以坐标访问,增删慢.长度不变. ArrayList 不定长,连续分配的,元素没有类型限制,任何元素都当成Objec ...