Android系统Handler详解

news/2024/11/7 18:48:16/

目录

一,背景介绍

1.1 简介

1.2 核心概念

1.3 Handler 背后的生产者-消费者模型

二,Handler机制原理

2.1 消息模型

2.2 Handler原理概述

2.3 Handler与管道通信

三,实战

3.1 创建 Handler

3.2 子线程向主线程

3.3  主线程向子线程

四,源码分析

4.1 ActivityThread

4.2 Looper

4.3 Handler


一,背景介绍

1.1 简介

        Handler是一套 Android 消息传递机制,主要用于线程间通信。用最简单的话描述,handler其实就是主线程在起了一个子线程,子线程运行并生成Message,Looper获取message并传递给Handler,Handler逐个获取子线程中的Message。

        Binder/Socket用于进程间通信,而Handler消息机制用于同进程的线程间通信。可以说只要有异步线程与主线程通信的地方就一定会有 Handler。
 

1.2 核心概念

Handler、Message、Message Queue、Looper

  • Looper
            一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

  • MessageQueue
            MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

  • Handler
           简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道
    处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就会在主线程处理这个消息。那么怎么拿到主线程 MessageQueue的实例,是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

  • Message
           Message 很简单了,你想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。

1.3 Handler 背后的生产者-消费者模型

        生产者-消费者模型:生产者和消费者共用同一个存储空间,生产者往存储空间中放数据,消费者从存储空间中取数据。生产者和消费者互相不持有,当没有数据时,消费者线程挂起,有数据时将消费者线程唤醒。

 

二,Handler机制原理

2.1 消息模型

1,在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage ,向消息队列中添加消息。
2,当通过 Looper.loop 开启循环后,会不断地从线程池中读取消息,即调用 MessageQueue.next
3,然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage 方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage 方法,接收消息,处理消息。

2.2 Handler原理概述

普通的线程是没有looper的,如果需要looper对象,那么必须要先调用Looper.prepare方法,而且一个线程只能有一个looper

Handler是如何完成跨线程通信的?

Android中采用的是Linux中的 管道通信
关于管道,简单来说,管道就是一个文件
在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的
消息队列创建时
调用JNI函数,初始化NativeMessageQueue对象。NativeMessageQueue则会初始化Looper对象
Looper的作用就是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息

在这里插入图片描述

1,Handler通过sendMessage()发送Message到MessageQueue队列
2,Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理
3,经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理
4,将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;如果MessageQueue中- 没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作
 

2.3 Handler与管道通信

        管道,其本质是也是文件,但又和普通的文件会有所不同:管道缓冲区大小一般为1页,即4K字节。管道分为读端和写端,读端负责从管道拿数据,当数据为空时则阻塞;写端向管道写数据,当管道缓存区满时则阻塞。

        在Handler机制中,Looper.loop方法会不断循环处理Message,其中消息的获取是通过 Message msg = queue.next(); 方法获取下一条消息。该方法中会调用nativePollOnce()方法,这便是一个native方法,再通过JNI调用进入Native层,在Native层的代码中便采用了管道机制。

        我们知道线程之间内存共享,通过Handler通信,消息池的内容并不需要从一个线程拷贝到另一个线程,因为两线程可使用的内存时同一个区域,都有权直接访问,当然也存在线程私有区域ThreadLocal(这里不涉及)。即然不需要拷贝内存,那管道是何作用呢?

        Handler机制中管道作用就是当一个线程A准备好Message,并放入消息池,这时需要通知另一个线程B去处理这个消息。线程A向管道的写端写入数据1(对于老的Android版本是写入字符W),管道有数据便会唤醒线程B去处理消息。管道主要工作是用于通知另一个线程的,这便是最核心的作用。
在这里插入图片描述

三,实战

3.1 创建 Handler

Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。

这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

解决方案:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
 

public class HandlerActivity extends AppCompatActivity {private Button bt_handler_send;private static class MyHandler extends Handler {//弱引用持有HandlerActivity , GC 回收时会被回收掉private WeakReference<HandlerActivity> weakReference;public MyHandler(HandlerActivity activity) {this.weakReference = new WeakReference(activity);}@Overridepublic void handleMessage(Message msg) {HandlerActivity activity = weakReference.get();super.handleMessage(msg);if (null != activity) {//执行业务逻辑Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();}}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.HandlerActivity);//创建 Handlerfinal MyHandler handler = new MyHandler(this);bt_handler_send = findViewById(R.id.bt_handler_send);bt_handler_send.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {@Overridepublic void run() {//使用 handler 发送空消息handler.sendEmptyMessage(0);}}).start();}});}@Overrideprotected void onDestroy() {//移除所有回调及消息myHandler.removeCallbacksAndMessages(null);super.onDestroy();}
}

3.2 子线程向主线程

首先我们在MainActivity中添加一个静态内部类,并重写其handleMessage方法。

private static class MyHandler extends Handler {private final WeakReference<MainActivity> mTarget;public MyHandler(MainActivity activity) {mTarget = new WeakReference<MainActivity>(activity);}@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);HandlerActivity activity = weakReference.get();super.handleMessage(msg);if (null != activity) {//执行业务逻辑if (msg.what == 0) {Log.e("myhandler", "change textview");MainActivity ma = mTarget.get();ma.textView.setText("hahah");}Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();}}}

然后创建一个类型为MyHandler的私有属性:

  private Handler handler1 = new MyHandler(this);

最后在onCreate回调中创建一个线程,用于接收发送消息:

new Thread(new Runnable() {@Overridepublic void run() {handler1.sendEmptyMessage(0);}}).start();

总结一下:

Handler的子类对象一般是在主线程中进行创建,以便在两个线程中都能访问。我们创建了Handler类的子类MyHandler,并重写了handlerMessage方法,这个方法是当使用接收处理发送的消息的。然后我们创建了一个子线程,在子线程中我们使用MyHandler的对象调用sendEmptyMessage方法发送了一个空的Message。然后我们就能在主线程中接收到这个数据。
 

3.3  主线程向子线程

首先创建一个MyHandler类。
private static class MyHandler extends Handler {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 0) {Log.e("child thread", "receive msg from main thread");}}}声明一个Handler类型的私有变量,进行默认初始化为null。private Handler handler1;在主线程中向子线程中发送消息
while (handler1 == null) {}handler1.sendEmptyMessage(0);handler1.getLooper().quitSafely();创建子线程,使handler指向新创建的MyHandler对象。
new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();handler1 = new MyHandler();Looper.loop();Log.e("child thread", "child thread end");}}).start();

四,源码分析

        接下来我们会结合App主线程(UI线程)来讲解,从App启动后一步一步往下分析整个Android的消息处理机制

4.1 ActivityThread

        ActivityThread类的main的函数,App启动的代码的入口,UI线程本来只是一个普通线程,在这里会把UI线程转换成Looper线程。

public final class ActivityThread {public static final void main(String[] args) {......Looper.prepareMainLooper();......ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {    sMainThreadHandler = thread.getHandler();}......
//一直循环,保障进程一直执行。如果退出,说明应用关闭Looper.loop();......}
}

4.2 Looper

        ThreadLocal线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。

public final class Looper {// sThreadLocal 是static的变量,可以先简单理解它相当于map,key是线程,value是Looper,//那么你只要用当前的线程就能通过sThreadLocal获取当前线程所属的Looper。static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//主线程(UI线程)的Looper 单独处理,是static类型的,通过下面的方法getMainLooper() //可以方便的获取主线程的Looper。private static Looper sMainLooper; //Looper 所属的线程的消息队列final MessageQueue mQueue;//Looper 所属的线程final Thread mThread;public static void prepare() {prepare(true);}private static void prepare(boolean quitAllowed) {//如果线程的TLS已有数据,则会抛出异常,一个线程只能有一个Looper,prepare不能重复调用。if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}//往线程的TLS插入数据,简单理解相当于map.put(Thread.currentThread(),new Looper(quitAllowed));sThreadLocal.set(new Looper(quitAllowed));}//实际上是调用  prepare(false),并然后给sMainLooper赋值。public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}//static 方法,方便获取主线程的Looper.public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}}public static @Nullable Looper myLooper() {//具体看ThreadLocal类的源码的get方法,//简单理解相当于map.get(Thread.currentThread()) 获取当前线程的Looperreturn sThreadLocal.get();}
}

         Looper.prepareMainLooper()做的事件就是new了一个Looper实例并放入Looper类下面一个static的ThreadLocal<Looper> sThreadLocal静态变量中,同时给sMainLooper赋值,给sMainLooper赋值是为了方便通过Looper.getMainLooper()快速获取主线程的Looper,sMainLooper是主线程的Looper可能获取会比较频繁,避免每次都到 sThreadLocal 去查找获取。

Looper的构造函数,看看在new Looper的时候做了什么事?

    private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}

        这时候给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。执行完Looper.prepareMainLooper() 之后,主线程从普通线程转成一个Looper线程。目前的主线程线程已经有一个Looper对象和一个消息队列mQueue,mian 函数的下一句代码Looper.loop() 那么重点来了,我们来看下Looper.loop()的源码:

public static void loop() {final Looper me = myLooper();  //获取TLS存储的Looper对象,获取当前线程的Looper if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列....for (;;) { //主线程开启无限循环模式Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了return;}....//分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,//这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写//的handler的handleMessage方法。msg.target.dispatchMessage(msg);........msg.recycleUnchecked();  //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。}
}

        这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,这里用到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。

Message next() final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iteration//nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis //才会返回int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)//一种是等到有消息产生就会返回,//另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回nativePollOnce(ptr, nextPollTimeoutMillis);//nativePollOnce 返回之后才能往下执行synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// 循环找到一条不是异步而且msg.target不为空的messagedo {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,//计算出离执行时间还有多久赋值给nextPollTimeoutMillis,//表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 获取到消息mBlocked = false;//链表一些操作,获取msg并且删除该节点 if (prevMsg != null) prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;msg.markInUse();//返回拿到的消息return msg;}} else {//没有消息,nextPollTimeoutMillis复位nextPollTimeoutMillis = -1;}..........}

        nativePollOnce()很重要,是一个native的函数,在native做了大量的工作,主要涉及到epoll机制的处理(在没有消息处理时阻塞在管道的读端).

        分析到这里,从应用启动创建Looper,创建消息队列,到进入loop方法执行无限循环中,那么这一块就告一段落了,主线程已经在死循环里轮询等待消息了。

4.3 Handler

        构造函数如下,

    public Handler(@Nullable Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}//获取当前线程的Looper,还记得前面讲过 Looper.myLooper()方法了吗?//Looper.myLooper()内部实现可以先简单理解成:map.get(Thread.currentThread())//获取当前线程的LoopermLooper = Looper.myLooper();if (mLooper == null) {//没有调用Looper.prepare()给线程创建Looper对象throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}//让Handler 持有当前线程消息队列的引用mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

        Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。


http://www.ppmy.cn/news/355875.html

相关文章

入侵检测系统(IDS)简介

文章目录 一、入侵检测系统&#xff08;IDS&#xff09;简介二、入侵检测系统的组成参考链接 一、入侵检测系统&#xff08;IDS&#xff09;简介 入侵检测系统&#xff08;intrusion detection system&#xff0c;简称“IDS”&#xff09;是一种对 网络传输 进行即时监视&…

综述类_网络入侵检测技术综述

文章目录 网络入侵检测技术综述大纲一、入侵检测系统分类1.基于数据来源划分2.基于检测技术划分 二、基于传统机器学习的入侵检测1.入侵数据处理2.监督机器学习技术3.无监督机器学习技术4.小结 三、基于深度学习的入侵检测1.生成方法2.判别方法3.生成对抗网络4.小结 四、基于强…

RK3588平台开发系列讲解(以太网篇)PHY驱动

文章目录 一、PHY驱动初始化二、PHY 配置初始化三、PHY 的扫描四、PHY 的STATUS状态读取五、PHY的RESET 复位六、PHY 的注册phy_device_register七、PHY 的状态变化沉淀、分享、成长,让自己和他人都能有所收获!😄 一、PHY驱动初始化 完成了mdio总线的注册,以及对不一样厂家…

【如何正确处理工作中的错误?】

在工作中&#xff0c;失误是不可避免的。无论你多么仔细地完成任务&#xff0c;都有可能会发生意外的错误。但是&#xff0c;关键在于如何处理这些失误&#xff0c;以便从中吸取教训并避免再次犯错。 当出现失误时&#xff0c;请不要惊慌失措。保持冷静&#xff0c;收集所有相…

【哈佛积极心理学笔记】第5讲 环境的力量

第5讲 环境的力量 dissemination 传播 To be a practical idealist: Premise 5: happiness is important, it also ought to be important. Aristotle’s law of identity, A is A. Negative emotion: -> narrow & constricts -> sad -> n&c Positive emo…

【JAVA】CAS总结

什么是CAS CAS的全称为Compare-And-Swap&#xff0c;直译就是对比交换。是一条CPU的原子指令&#xff0c;其作用是让CPU先进行比较两个值是否相等&#xff0c;然后原子地更新某个位置的值&#xff0c;其实现方式是基于硬件平台的汇编指令&#xff0c;就是说CAS是靠硬件实现的&…

3D动态视频屏保热带鱼水族馆

下载地址 https://download.csdn.net/download/file_data/12113080 简介 3d热带鱼水族箱屏保中文版是受到微软推荐的水族箱屏保,现在这个屏幕保护程序已被收录进了微软的 WinXP Plus Pack 当中。最新版本不仅修复了先前版本中存在的一些不足&#xff0c;而且又增加了新品种鱼…

Google宽松的工作环境

Google 在世界各地的办公室经常是很多人羡慕的对象&#xff0c;而这类话题早已泛滥&#xff0c;不过&#xff0c;下面这些来自 Google 加州 Mountain View 和瑞士苏黎世的办公室的图片还是让人免不了感慨一番&#xff0c;不得不承认&#xff0c;创造力来自宽松和自由的环境。 吃…