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线程切换原理的更多相关文章

  1. EventBus 线程切换原理

    主要问题其实只有两个,其一:如何判断当前发送事件的线程是否是主线程:其二:如何在接收事件时指定线程并执行: 一个一个来看. 1.如何判断是否在主线程发送 EventBus在初始化的时候会初始化一个Ma ...

  2. [转]Android中handler机制的原理

    Andriod提供了Handler 和 Looper 来满足线程间的通信.Handler先进先出原则.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange). 1)Loo ...

  3. 【转载】Android 的 Handler 机制实现原理分析

    handler在安卓开发中是必须掌握的技术,但是很多人都是停留在使用阶段.使用起来很简单,就两个步骤,在主线程重写handler的handleMessage( )方法,在工作线程发送消息.但是,有没有 ...

  4. android的子线程切换到主线程

    在子线程中,如果想更新UI,必须切换到主线程,方法如下: if (Looper.myLooper() != Looper.getMainLooper()) { // If we finish mark ...

  5. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  6. Android更新主线程UI的两种方式handler与runOnUiThread()

    在android开发过程中,耗时操作我们会放在子线程中去执行,而更新UI是要主线程(也叫做:UI线程)来更新的,自然会遇到如何更新主线程UI的问题.如果在主线程之外的线程中直接更新页面显示常会报错.抛 ...

  7. android高级---->Handler的原理

    andriod提供了Handler来满足线程间的通信,上次在更新UI的时候也提到过Handler的使用,关于Handler的基本使用,参见博客(android基础---->子线程更新UI).今天 ...

  8. Android 为什么要有handler机制?handler机制的原理

    为什么要有handler机制? 在Android的UI开发中,我们经常会使用Handler来控制主UI程序的界面变化.有关Handler的作用,我们总结为:与其他线程协同工作,接收其他线程的消息并通过 ...

  9. EventBus 消息的线程切换模型与实现原理

    一. 序 EventBus 是一个基于观察者模式的事件订阅/发布框架,利用 EventBus 可以在不同模块之间,实现低耦合的消息通信. EventBus 因为其使用简单且稳定,被广泛应用在一些生产项 ...

随机推荐

  1. Acwing785.快速排序

    Acwing785.快速排序 快排模板: y总教学大法好~: #include <iostream> using namespace std; const int N = 1000010; ...

  2. 五种方式实现 Java 单例模式

    前言 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创建自 ...

  3. robotframework之环境安装

    一.安装python2.7环境,python --version查询python安装的版本 二.setuptools安装

  4. RPA教程

    匠厂出品,必属精品   Uipath中文社区qq交流群:465630324 uipath中文交流社区:https://uipathbbs.comRPA之家qq群:465620839 第一课--UiPa ...

  5. 从一道算法题实现一个文本diff小工具

    众所周知,很多社区都是有内容审核机制的,除了第一次发布,后续的修改也需要审核,最粗暴的方式当然是从头再看一遍,但是编辑肯定想弄死你,显然这样效率比较低,比如就改了一个错别字,再看几遍可能也看不出来,所 ...

  6. cenos 7 zookeeper Error contacting service. It is probably not running

    zkServer.sh status 命令查看zookeeper集群的状态,发现异常 Error contacting service. It is probably not running 最开始以 ...

  7. elastic-job和spring cloud版本冲突2

    ***************************APPLICATION FAILED TO START*************************** Description: An at ...

  8. 5-16 Docker 容器 || Linux

    Docker 简介 Linux简介 Linux和windows一样,都是操作系统 只不过windows更易用,适合家庭和非编程人员使用 如果用作服务器,尤其是java项目的服务器,Linux会更加合适 ...

  9. 你有没有想过用电子表格也能做MES、WMS、SRM和MRP

    曾几何时,小微企业多半选择excel和WPS等电子表格,中小企业更喜欢选择用友金蝶和浪潮这样的国产大品牌软件,而大型企业则偏爱SAP.oracle这样的国外大品牌. 时至今日,风向却变了,品牌软件已不 ...

  10. 串口应用:遵循uart协议,发送多个字节的数据(状态机)

    上一节中,我们遵循uart协议,它发送一次只能发送6/7/8位数据,我们不能随意更改位数(虽然在代码上可行),不然就不遵循uart协议了,会造成接收端无法接收. 在现实生活中,我们有时候要发的数据不止 ...