Android多线程(Handler篇)

article/2025/9/15 7:10:45

【齐天的博客】转载请注明出处(万分感谢!):
https://blog.csdn.net/qijinglai/article/details/80685226

关联文章:
Android多线程(Handler篇)
Android多线程(AsyncTask篇)
Android多线程(HandlerThread篇)
Android多线程(IntentService篇)

先放流程图:
这里写图片描述
由于Android中的耗时操作不能放入主线程中,所以实现多线程是必须的。今天的主角是Handler,本文将从使用及源码来分析探索其奥秘。

使用

步骤

  1. 创建Handler对象,实现handlMessage()方法
  2. 创建Runnable线程
  3. 此时产生一个Looper,并自动创建一个消息队列MessageQueue()
  4. Looper轮询MessageQueue交给Handler
  5. Handler做处理

其中1、2、5为使用步骤,其他在后面分析源码时会讲到
使用方法

public class MainActivity extends AppCompatActivity {@SuppressLint("HandlerLeak")Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what){case 1:
//                    处理事件break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new Thread(new Runnable() {@Overridepublic void run() {
//                耗时操作Message message = new Message();message.what = 1;handler.sendMessage(message);}}).start();}
}

使用就写这么多,接下来重点分析Handler原理。

原理分析

首先列一下将会讲到的几个对象

  1. ThreadLocal
  2. Looper
  3. MessageQueue
  4. Handler
  5. Message

ThreadLocal

关于他我看了几篇博客,很大一部分写的是错的,所以
注意

  • 他并不是解决共享对象的多线程访问问题的!!!
  • 他并没有创建对象的拷贝或副本!!!

目的:他只是为了保证每个线程都拥有同一个类的不同对象
实质:每个线程里都new了同一个类的对象
作用:你或许觉得这样做很傻,但是如果使用全局变量就要考虑线程安全问题,而线程安全是以消耗性能为前提的,所以这种设计可以说很巧妙
场景:每个线程都需要相同类型的对象,又各自独立,并且与他相关的操作不少时,如,Looper,ActivityThread,AMS 等

private static final ThreadLocal tSession = new ThreadLocal();
public static Session getSession() throws Exception{Session s = tSession.get();try{if(s==null){s = getSessionFactory.openSession();tSession.set(s);}}catch(Exception e){}return s;
}

我们看一下ThreadLocal的源码,主要分析get()和set()

	public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

set、get操作的都是ThreadLocalMap,key=当前线程,value=线程局部变量缓存值。可以看到get()实际上都是调用getMap(传入当前线程)来取得当前线程的ThreadLocalMap对象

  • set(),实际上是调用了ThreadLocalMap的set(),ThreadLocalMap的set()涉及到哈希散列算法,我会在之后博客里详细分析这里先不提算法的事。
  • get(),从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,如果map为null,走初始化方法

由此看出不是如很多博主写的各线程用了同一个对象又相互独立那么神奇,只不过是用线程当做键在其中维护了一个私有变量而已。得到ThreadLocalMap后如何得到维护的变量呢,在这一句

ThreadLocalMap.Entry e = map.getEntry(this);
//this指代的ThreadLocal对象

所以过程就很清晰了,我们来总结一下:

  1. 声明一个全局公用的ThreadLocal实例作为key
  2. 在线程中new一个或取出已经存了的对象作为value
  3. 将此key-value放入ThreadLocalMap中
  4. 将当前线程作为key,ThreadLocalMap作为值放入ThreadLocal中

比较绕多读几遍就能明白了,再放一张图加深理解
这里写图片描述

Looper

两件事

  1. 创建消息队列
  2. 在队列中循环取消息

两个方法

  1. prepare()
  2. loop()

两个作用

  1. 保证当前线程只有一个looper和一个MessageQueue
  2. 从MessageQueue中取消息交给target的dispatchMessage

下面我们分析源码

#####构造方法

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

很容易看出当Looper构造时创建了一个消息队列

#####prepare()方法

    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));}

通过前面ThreadLocal的分析可知sThreadLocal.get()得到了Looper对象,当Looper存在是报错,不存在是创建一个存入ThreadLocal中,保证线程里有且只有一个Looper对象。

#####loop()

    /*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {msg.target.dispatchMessage(msg);end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (slowDispatchThresholdMs > 0) {final long time = end - start;if (time > slowDispatchThresholdMs) {Slog.w(TAG, "Dispatch took " + time + "ms on "+ Thread.currentThread().getName() + ", h=" +msg.target + " cb=" + msg.callback + " msg=" + msg.what);}}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}

1)拿到ThreadLocal中的Looper,若没有则prepare()
2)拿到Looper的MessageQueue
3)进入死循环,调用msg.target.dispatchMessage(msg),发给Handler
4)释放资源

MessageQueue

一个单链表结构的消息队列

Handler

#####构造函数

public Handler(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());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

构造时通过Looper.myLooper获取当前线程保存的Looper实例,再获取这个Looper的MessageQueue

#####发送消息

public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}/*** Sends a Message containing only the what value.*  * @return Returns true if the message was successfully placed in to the *         message queue.  Returns false on failure, usually because the*         looper processing the message queue is exiting.*/public final boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);}/*** Sends a Message containing only the what value, to be delivered* after the specified amount of time elapses.* @see #sendMessageDelayed(android.os.Message, long) * * @return Returns true if the message was successfully placed in to the *         message queue.  Returns false on failure, usually because the*         looper processing the message queue is exiting.*/public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);}

发送消息时所有方法都实际调用了sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}

是获取MessageQueue调用enqueueMessage();
#####接下来看enqueueMessage中

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

将msg的target属性赋值为Handler自己,实现了Message与Handler的绑定,并调用了MessageQueue中的enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

对前一个方法进行补充,把msg放入MessageQueue中,这时候轮询取消息(在前面Looper已经分析),调用dispatchMessage()

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

最终调用handlerMessage()方法

/*** Subclasses must implement this to receive messages.*/public void handleMessage(Message msg) {}

一个空方法,在这里处理操作,end

##总结

  1. Looper.prepare()在本线程中存入ThreadLocalMap中一个Looper并创建一个MessageQueue对象
  2. Looper.loop()循环取Message中的消息,回调msg.target.dispatchMessage(msg)
  3. Handler构造得到Looper并与MessageQueue关联
  4. Handler.sendMessage会给msg.target赋值为自己并加入MessageQueue中
  5. 重写handlMessage()处理

http://chatgpt.dhexx.cn/article/6sumebXZ.shtml

相关文章

Android 多线程实现方式

该原创文章首发于微信公众号“字节流动” Android 多线程实现方式 通常来说&#xff0c;一个应用至少有一个进程&#xff0c;而一个进程至少有一个线程。 线程是 CPU 调度的基本单位&#xff0c;进程是系统资源分配的基本单位。 进程独享内存资源&#xff0c;一个进程可以看…

Java多线程,Android多线程

目录 一、线程的概念 二、线程创建的方式及特点 三、线程创建方式 1、继承Thread类 2、实现Runnable接口 3、实现Callable接口&#xff08;我觉得了解即可&#xff09; 4、AsyncTask异步任务&#xff08;被弃用&#xff09; 5、AsyncTask替代方案 四、线程的基础操作 …

Android多线程开发详解

一、基本概念 1、时间片轮转机制 如果在时间片结束时进程还在运行&#xff0c;则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结来,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。 每个进程被分…

Android开发中四种常用的多线程实现方式

前言 一般来说&#xff0c;一个应用至少有一个进程&#xff0c;一个进程至少有一个线程。线程是CPU调度的基本单位&#xff0c;进程是系统资源分配的基本单位。 进程拥有独占的内存资源&#xff0c;一个进程可以看作一个JVM一个进程崩溃后&#xff0c;一般不会影响保护模式下…

Android 中的多线程简介

一、概念讲解 进程&#xff1a;是程序运行过程中系统进行资源分配和调度的一个独立单位&#xff0c;使多个程序可 并发执行&#xff0c;以提高系统的资源利用率和吞吐量。 线程&#xff1a;一个基本的CPU执行单元 & 程序执行流的最小单元。 线程自己不拥有系统资源&#…

anchor free和anchor based的区别

链接&#xff1a;https://www.zhihu.com/question/356551927/answer/926659692 1.目标检测算法一般可分为anchor-based、anchor-free、两者融合类&#xff0c;区别就在于有没有利用anchor提取候选目标框。A. anchor-based类算法代表是fasterRCNN、SSD、YoloV2/V3等fasterRCNN-…

Anchor based and Anchor free(无锚VS有锚)【总结】

anchor-free 和 anchor-based 区别 anchor-free和anchor-based是两种不同的目标检测方法&#xff0c;区别在于是否使用预定义的anchor框来匹配真实的目标框。 anchor-based方法使用不同大小和形状的anchor框来回归和分类目标&#xff0c;例如faster rcnn、retinanet和yolo等。a…

2 anchor-base和anchor_free两者的优缺点

anchor-base和anchor_free两者的优缺点 anchor-base和anchor_free两者的优缺点 一、什么是anchor二、anchor-base和anchor-free的区别三、anchor-free和single anchor三、anchor-base和anchor-free的优缺点 参考 一、什么是anchor 从字面的意思解释&#xff0c;anchor就是船锚…

Anchor-Free总结

目录 Anchor-Free综述 一. CornerNet 1.1 概述1.2 模块介绍 1.2.1 Heatmap1.2.2 Offset1.2.3 Grouping Corners1.2.4 Corner Pooling1.3 总结二. CenterNet 2.1 概述2.2 Center-Regression三. FCOS 3.1. 概述3.2. 模块介绍 3.2.1 论文思路简介3.3.2 回归形式3.3 参考文献四 ATS…

【AI面试】Anchor based 、 Anchor free 和 no anchor 的辨析

深度学习的目标检测算法,通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整(回归)区域边界,从而更准确地预测目标的真实边界框(ground-truth bounding box)。 目标检测算法会需要做两个事情: 推荐区域框是否有目标(positive or …

一文读懂anchor-base和anchor-free

1. 从Faster-RCNN看Anchor Faster-RCNN相对于Fast-RCNN的一个改进是引入了RPN网络&#xff0c;RPN用于区域推荐&#xff0c;替换了此前的SS算法使得网络在整体上更加的CNN化。那么RPN是怎么进行区域推荐的&#xff1f; 简单来说RPN先列举出数万个矩形框&#xff0c;然后用卷积…

目标检测3--AnchorFree的FCOS

文章目录 1.介绍2.FCOS中使用的方法2.1 网络结构2.2FCOS中使用FPN的多级预测2.3FCOS中的中心度 3.mmdetection中FCOS源码参考资料 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 1.介绍 论文:《FCOS: Fully Convolutional One-Stage Obj…

浅谈Anchor-Free发展历程

1.早期探索&#xff1a; DenseBox: https://arxiv.org/abs/1509.04874 YOLO: https://arxiv.org/abs/1506.02640 2.基于关键点&#xff1a; CornerNet: https://arxiv.org/abs/1808.01244 ExtremeNet: https://arxiv.org/abs/1901.08043 3.密集预测: FSAF: https://arxiv.org/a…

Anchor-Free系列之FCOS:A Simple and Strong Anchor-free Object Detector

Anchor-Free系列之CornerNet: Detecting Objects as Paired Keypoints_程大海的博客-CSDN博客 Anchor-Free系列之CenterNet&#xff1a;Objects as Points_程大海的博客-CSDN博客 Anchor-Free系列之FCOS&#xff1a;A Simple and Strong Anchor-free Object Detector_程大海的…

Anchor Based和Anchor Free

Anchor Based和Anchor Free之间区别主要有以下两点&#xff1a;1.分类差异&#xff08;关键正负样本定义&#xff09;2.回归差异 1.分类差异&#xff1a; 现阶段的算法多尺度预测&#xff0c;即GT是由哪一个特征层和位置Anchor预测。 Anchor Based是由IoU来确定哪层和哪个位置…

解读《Bridging the Gap Between Anchor-based and Anchor-free Detection》

张士峰大佬近期发了一篇论文解读Anchor-base和Anchor-free方法间的差别&#xff0c;其本质在于正负样本的选取方式不同。 论文&#xff1a;《Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection》 链接&#xff1a;ht…

anchor-free方法总结

cornernet&#xff0c;centernet&#xff0c;onenet&#xff0c;fcos 这几篇论文的引用关系&#xff08;提出先后顺序&#xff09;&#xff1a; 将按照上面的顺序&#xff0c;从背景、标签分配等方面说明区别于联系。 一、背景&#xff1a; Cornernet&#xff1a;认为使用a…

anchor free和anchor base

仅供个人学习使用 1、anchor base anchor base的方法需要先在图片上生成候选框&#xff0c;无论是RPN生成还是通过k-means生成的先验框&#xff0c;都需要在分类回归之前有存在的框可使用。在框的基础上进行之后的操作。 超参数较为难调&#xff0c;正负样本不平衡&#xff…

Anchor free的心得

问题&#xff1a; 没有了Anchor框的监督信息&#xff0c;我们怎么针对检测任务做到正确回归&#xff1f; 本质&#xff1a;样本与ground truth的对应&#xff0c;如何选择合适样本与真实场景对应 Anchor&#xff1a; 其加入降低了回归问题难度&#xff0c;为分类问题提供选择…

Anchor-based 与 Anchor-free

参考 Anchor-based 与 Anchor-free - 云社区 - 腾讯云 1. Feature Selective Anchor-Free Module for Single-Shot Object Detection 参考&#xff1a;CVPR2019 | CMU提出Single-Shot目标检测最强算法&#xff1a;FSAF 2. FCOS: Fully Convolutional One-Stage Object Det…