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. Java创建数据库新建表及初始化表

    方法一 package com.crt.openapi; import java.sql.DriverManager;import java.sql.ResultSet;import java.io. ...

  2. 接口偶尔超时,竟又是JVM停顿的锅!

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介 继上次我们JVM停顿十几秒的问题解决后,我们系统终于稳定了,再也不会无故重启了! 这是之前的文章:耗时几个月,终于 ...

  3. Python基础学习_03

    程序的流程控制 1.程序的组织结构 (1)顺序结构 (2)选择结构 (3)循环结构 2.对象的布尔值 以下对象的布尔值为False False,数值0,None,空字符串,空列表,空元组,空字典,空集 ...

  4. react native 0.6x 在创建项目时,CocoaPods 的依赖安装步骤卡解决方案

    前言 你需要做两件事 gem换源 pod repo 换源 实战 如果你已经成功安装了CocoaPods.那么这里你需要卸载它.gem换源1. 卸载CocoaPods 查看gem安装的东西 gem li ...

  5. SAP 实例- 下拉框

    效果图 源代码 REPORT rsdemo_dropdown_listbox . DATA init. TABLES scarr. TABLES spfli. TABLES sflight. TABL ...

  6. vue.js中英文api

    全局配置 Vue.config is an object containing Vue's global configurations. You can modify its properties l ...

  7. 使用EasyExcel导出图片及异常处理

    1.使用String类型导出   定义自己的Converter,不使用默认的StringImageConverter 如果图片路径为空或者图片路径是错误的,返回相应的内容 import java.io ...

  8. Mybatis-Plus介绍

    Mybatis-Plus介绍 Mybatis-Plus概念 Mybatis-Plus介绍 官网https://mybatis-plus/或https://mp.baomidou.com/ mybati ...

  9. [开源] .Net ORM 访问 Firebird 数据库

    前言 Firebird 是一个跨平台的关系数据库系统,目前能够运行在 Windows.linux 和各种 Unix 操作系统上,提供了大部分 SQL-99 标准的功能.它既能作为多用户环境下的数据库服 ...

  10. Qt点名器

    项目已开源,点击跳转 废话不多说,直接上代码. CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(qt-caller) find_ ...