本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

本文首发于本人简书

前言


好记性不如烂笔头。

这是一个系列文章,将会包括:

  • Handler全家桶之 —— Handler 源码解析
  • Handler全家桶之 —— HandlerThread & IntentService 源码解析
  • Handler全家桶之 —— AsyncTask 源码解析
  • Handler全家桶之 —— View.post() 源码解析

Handler 是 Android 开发中举足轻重的一种线程机制,很多地方,包括 Android 源码都不少地方用到了这个。

这篇文章将会从源码角度分析 Handler 机制以及一些常见的疑惑点。

注意:文章较长,推荐阅读顺序如下

  • 顺序阅读(推荐)
  • 时间不充足,或者感觉枯燥,可以先看3.7的梳理以及第5点总结部分,再回头看其他细节
  • 查看目录,根据感兴趣的内容阅读
  • 收藏后慢慢看(墙裂推荐【手动滑稽】)

目录

1. 作用

2. 基本用法

3. 源码解析

  • 3.1 为什么 Handler 能够切换线程执行?
  • 3.2 Handler.post(Runnable) 方法是运行在新的线程吗?
  • 3.3 Handler(Callback) 跟 Handler() 这两个构造方法的区别在哪?
  • 3.4 子线程可以创建 Handler 吗?
  • 3.5 为什么主线程不用调用 Looper.prepare() ?
  • 3.6 为什么创建 Message 对象推荐使用 Message.obtain()获取?
  • 3.7 梳理

4. 常见问题&技巧

  • 4.1 为什么 Handler 会造成内存泄漏?
  • 4.2 怎么防止 Handler 内存泄漏?
  • 4.3 Loop.loop() 为什么不会造成应用卡死?

5. 总结


1. 作用

Handler 是一种用于线程间的消息传递机制。

因为 Android 中不允许在非主线程更新UI,所以最常使用的地方就是用于子线程获取某些数据后进行UI的更新。


2.基本用法

step1:创建Handler实例

//1.自定义Handler类
static class CustomHandler extends Handler{@Overridepublic void handleMessage(Message msg) {//更新UI等操作}
}CustomHandler customHandler = new CustomHandler();//2.内部类
Handler innerHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {//更新UI等操作}
};//3.callback方式
Handler callbackHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {//更新UI等操作return true;}
});
复制代码

step2:发送消息

 //1.发送普通消息Message msg = Message.obtain();msg.what = 0; //标识msg.obj = "这是消息体"; //消息内容innerHandler.sendMessage(msg);//2.发送Runnale消息innerHandler.post(new Runnable() {@Overridepublic void run() {//更新UI等操作,消息接收后执行此方法}});
复制代码

Handler 的创建以及消息的发送都有很多种方法,各种方式的异同会在下面讲到。


3. 源码分析

带着问题看源码 —— 鲁某

ps:本文源码基于 API 28 ,即 Android P / Android 9

先抛出我们的第一个问题:

3.1 为什么 Handler 能够切换线程执行?

我们在发送 Message 的时候在子线程,为什么执行的时候就切换成了主线程?想要知道答案,基本就要把 Handler 的运行流程给了解一遍。

因为最终的处理是在 handleMessage方法中进行的,所以我们看看 handleMessage方法是怎么被调用起来的。

先打个 debug , 看看调用链:

画个图直观一点:

可能有点奇怪,整个调用流程都没有出现我们发送消息的方法,那我们发送的 Message 对象在哪里被使用了呢?

看下上图的 Step 2 ,在 loop() 方法里面调用了 msg.target.dispatchMessage(msg) 方法,debug 中查看 msg 对象的属性,发现这个 msg 正是我们发送的那个 Message对象,这个 target 就是在 MainActivity 中创建的 Handler 对象。

也就是说,我们发送消息后,不知道什么原因,Looper.loop() 方法内会拿到我们发送的消息,并且最终会调用发送该消息的 Handler 的 handleMessage(Message msg)方法。先看看 loop() 方法是怎么拿到我们的 Message 的:

// Looper.java ,省略部分代码
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;for (;;) {Message msg = queue.next(); // might block  , 从队列取出一个msgif (msg == null) {// No message indicates that the message queue is quitting.return;}msg.target.dispatchMessage(msg); //Handler处理消息msg.recycleUnchecked();  //回收msg}
}
复制代码

首先,loop()方法会判断当前线程是否已经调用了 Looper.prepare(),如果没有,则抛异常,这就是我们创建非主线程的 Handler 为什么要调用Looper.prepare()的原因。而主线程中会在上面流程图的 Step 1 中,即 main() 方法里面调用了 prepare 方法,所以我们创建默认(主线程)的 Handler 不需要额外创建 Looper 。

loop() 里面是一个死循环,只有当msg为空时才退出该方法。msg 是从 queue.next 中取出来的,这个 queue 就是我们经常听到的消息队列了(MessageQueue ),看看 next 方法的实现:

//MessageQueue.java ,删减部分代码
Message next() {final long ptr = mPtr;if (ptr == 0) {//如果队列已经停止了(quit or dispose)return null;}for (;;) {synchronized (this) {final long now = SystemClock.uptimeMillis();   //获取当前时间Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {//msg == target 的情况只能是屏障消息,即调用postSyncBarrier()方法//如果存在屏障,停止同步消息,异步消息还可以执行do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());  //找出异步消息,如果有的话}if (msg != null) {if (now < msg.when) {//当前消息还没准备好(时间没到)nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 消息已准备,可以取出if (prevMsg != null) {//有屏障,prevMsg 为异步消息 msg 的前一节点,相当于拿出 msg ,链接前后节点prevMsg.next = msg.next;} else {//没有屏障,msg 即头节点,将 mMessages 设为新的头结点mMessages = msg.next;}msg.next = null;  //断开即将执行的 msgmsg.markInUse(); //标记为使用状态return msg;  //返回取出的消息,交给Looper处理}} // Process the quit message now that all pending messages have been haif (mQuitting) {//队列已经退出dispose();return null;  //返回null后Looper.loop()方法也会结束循环}}
}
复制代码

源码中可以发现,虽然 MessageQueue 叫消息队列,但却是使用了链表的数据结构来存储消息。 next()方法会从链表的头结点开始,先看看头结点是不是消息屏障(ViewRootImpl使用了这个机制),如果是,那么就停止同步消息的读取,异步消息照常运作。

如果有消息,还会判断是否到了消息的使用时间,比如我们发送了延时消息,这个消息不会马上调用,而是继续循环等待,直到消息可用。这里就有一个新的问题2:**问什么 next()中一直循环却不会导致应用卡死?**这个问题等下再说。

到这里,我们就大致能理清 Handler.handleMessage()方法是怎么调起来的了。但是MessageQueue里面的消息是怎么来的呢?这个其实不看源码也能猜出来了,肯定是由我们发送的消息那里传过来的,但是为了理解更深刻,还是得看看消息是怎么传递到消息队列中的(MessageQueue );

//Handler.java
public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);
}public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}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);
}
复制代码

可以看到,sendMessage()方法最终是调用了 sendMessageAtTime()方法,分析下这个方法,首先将会拿到一个消息队列 mQueue,这个队列是在创建 Looper的时候默认初始化的,然后会调用enqueueMessage()方法进队:

//Handler.java
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,也就是上面说到的 Looper.loop()方法内最终调用的msg.target.dispatchMessage(msg)的这个 msg 的 target 来源。

如果当前 Handler 是异步的话,还会将发送的消息置为同步消息,这个 mAsynchronous 标识是我们构造 Handler 的是否传递的参数,默认为 false

最后就是真正的进队方法 MessageQueue.enqueueMessage :

//MessageQueue.java  删减部分代码
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) {msg.recycle();return false;}msg.markInUse();msg.when = when;  //赋值调用时间Message p = mMessages;  //头结点if (p == null || when == 0 || when < p.when) {//队列中没有消息 或者 时间为0 或者 比头结点的时间早//插入到头结点中msg.next = p;mMessages = msg;} else {Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {  //类似插入排序,找到合适的位置break;}}// 结点插入msg.next = p; prev.next = msg;}}return true;
}
复制代码

刚开始会进行一系列的判断,然后根据时间来作为一个排队依据进行进队操作,需要注意的是:消息队列是使用链表作为数据的存储结构,是可以插队的,即不存在发送了延时消息不会阻塞消息队列。

再跟上面的出队方法联系起来,就会发现,异步消息并不会立刻执行,而是根据时间,完全跟同步消息一样的顺序插入队列中。异步消息与同步消息唯一的区别就是当有消息屏障时,异步消息还可以执行,而同步消息则不行。

整个Handler的大体运行机制到此应该有了一个比较清晰的轮廓了。

总结一下:Handler 发送的线程不处理消息,只有Looper.loop()将消息取出来后再进行处理,所以在Handler机制中,无论发送消息的Handler对象处于什么线程,最终的处理都是运行在 Looper.loop() 所在的线程。

比如:一个新的线程 Thread1 发送了一个消息 Msg1,这个线程的工作仅仅是将消息存储到消息队列而已,并没有下一步了,然后等待 Looper.loop() 处理到 Msg1 的时候(loop()方法一直运行在最开始调用它的线程,比如主线程),再将 Msg1 进行处理,所以最终就从 Thread1 切换到了主线程中运行。

可以拉到下面3.7小节看下流程图,更清晰一些


3.2 Handler.post(Runnable) 方法是运行在新的线程吗?

Handler 中发送消息的方法多达十几个,分为 sendXXX 以及 postXXX ,这里看看主要的几个 post 类型方法:

//Handler.java
public final boolean post(Runnable r){return  sendMessageDelayed(getPostMessage(r), 0);
}public final boolean postAtTime(Runnable r, long uptimeMillis){return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}...
复制代码

几个 post 方法都是调用了相应的sendXXX 方法,然后用getPostMessage(Runnable r) 构建 Message 对象:

private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;
}
复制代码

这里获取到消息后,将 Runnable赋值给 Message.callback ,那这个 callback 有什么用呢?上面的整体流程分析中,我们知道 Looper.loop()会调用 msg.target.dispatchMessage(msg),这个target 就是 Handler 了,那么看一下这个方法的具体实现:

// Handler.java
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}private static void handleCallback(Message message) {message.callback.run();
}
复制代码

到这一步终于水落石出了,如果是用 postXXX 方法发送的消息,就会调用 handleCallback(msg) 方法,即调用我们post方法里传递的 Runnable 对象的run()方法。

也就是说,Runnable 跟线程没有半毛钱关系,他只是一个回调方法而已,只不过我们平时创建线程的时候使用多了,误以为他跟线程有什么py交易。


3.3 Handler(Callback) 跟 Handler() 这两个构造方法的区别在哪?

接着看3.2讲到的 dispatchMessage() 方法剩下的逻辑。

如果 msg 没有 callback 的话,那么将会判断 mCallback 是否为空,这个 mCallback 就是构造方法种传递的那个 Callback ,如果 mCallback为空,那么就调用 Handler 的 handleMessage(msg) 方法,否则就调用 mCallback.handleMessage(msg) 方法,然后根据 mCallback.handleMessage(msg)的返回值判断是否拦截消息,如果拦截(返回 true),则结束,否则还会调用 Handler#handleMessage(msg)方法。

也就是说:Callback.handleMessage() 的优先级比 Handler.handleMessage()要高 。如果存在Callback,并且Callback#handleMessage() 返回了 true ,那么Handler#handleMessage()将不会调用。

除了这点,还有什么区别吗?暂时真没发现。


3.4 子线程可以创建 Handler 吗?

问题可能有些模糊,意思是可以在子线程回调 handleMessage()吗。

上面理清了 Handler 的运行流程,但是创建流程好像还没怎么说,先看看 Handler 是怎么创建的:

public Handler() {this(null, false);
}public Handler(Callback callback) {this(callback, false);
}public Handler(boolean async) {this(null, async);
}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 " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;
}复制代码

先看上面这部分不传 Looper 的构造方法,这些方法最终都是调用了Handler(Callback callback, boolean async) 方法,所以直接看这个方法就行,一开始会在方法体内检测是否有潜在的内存泄漏风险,相信大家都有过被这东西烦过,看图:

这种被黄色支配的感觉不太舒服,可以在实例上面添加注解@SuppressLint("HandlerLeak")来去掉提示,但是这只是去掉提示而已,别忘了处理潜在的内存泄漏。

接着看下面,首先会调用 Looper.myLooper()方法拿到当前线程的 Looper 实例,如果为空,则抛异常,看看myLooper()具体是怎样的:

//Looper.java
public static @Nullable Looper myLooper() {return sThreadLocal.get();
}
复制代码

直接就是调用了 sThreadLocal的 get 方法,这个sThreadLocal是一个静态的 ThreadLocal 常量,看名字就能猜到与线程相关,具体的就不深究了。可以先把他看成一个线程id 与 Looper 的 map 键值对。既然有 get() ,那么就应该有 set() ,那么 Looper 是在哪里被存进去的呢?

//Looper.java
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.prepare() 方法中被传进去的,并且 sThreadLocal 中每个线程都只能有一个 Looper 实例。需要注意的是,prepare()方法并没有调用 Looper#loop()方法,经过上面的流程分析也知道,这个 loop() 方法启动才能处理发送的消息,所以子线程创建 Handler 除了需要调用 Looper.prepare()外,还需要调用 Looper.loop()启动。

也就说明,任何线程都可以创建 Handler,只要当前线程调用了 Looper.prepare()方法,那么就可以使用 Handler 了,而且同一线程内就算创建 n 个 Handler 实例,也只对应一个 Looper,即对应一个消息队列。

理一理逻辑:Handler 机制要求创建 Handler 的线程必须先调用 Looper.prepare() 方法来初始化,初始化过程中会将当前线程的 Looper 存起来,如果没有进行 Looper 的初始化,将会抛异常,要启动 Looper ,还需要调用 loop() 方法。


3.5 为什么主线程不用调用 Looper.prepare() ?

上面说了,每个线程要创建 Handler 就必须要调用 Looper.prepare进行初始化,那么为什么我们平时在主线程创建 Handler 则不需要调用?

通过3.1 中的 debug 调用链就可以知道,主线程的 loop()方法是在 ActivityThread#main()方法中被调用的,那么看看 main() 方法:

//ActivityThread.java 删减部分代码
public static void main(String[] args) {Looper.prepareMainLooper();Looper.loop();
}
复制代码

到这里就能明白了,在App启动的时候系统默认启动了一个主线程的 Looper,prepareMainLooper()也是调用了 prepare()方法,里面会创建一个不可退出的 Looper,并 set 到 sThreadLocal对象当中。


3.6 为什么创建 Message 对象推荐使用 Message.obtain()获取?

Message 对象有两种方式可以获得,一种是直接 new 一个实例,另一种就是调用 Message.obtain()方法了,Handler.obtainMessage() 也是调用Message.obtain()实现的,看看这个方法:

//Message.javaprivate static Message sPool;public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();
}
复制代码

可以看到,obtain() 方法会在以 sPool作为头结点的消息池(链表)中遍历,如果找到,那么取出来,并置为非使用状态,然后返回,如果消息池为空,则新建一个消息。

知道有消息池这个东西了,那么这个消息池的消息是怎么来的呢?

使用 AS 搜索一下,发现只有两个方法对 sPool 这个节点进行了赋值,一个是上面的 obtain(),另一个是下面这个:

//Message.javaprivate static final int MAX_POOL_SIZE = 50;void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}
}
复制代码

看方法名也可以知道,这是一个回收的方法,方法体内将 Message 对象的各种参数清空,如果消息池的数量小于最大数量(50)的话,就当前消息插入缓存池的头结点中。

已经知道 Message 是会被回收的了,那么什么情况才会被回收呢?

继续查看调用链:

// Looper.java ,省略部分代码
loop(){final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next(); // might block  , 从队列取出一个msgif (msg == null) {// No message indicates that the message queue is quitting.return;}msg.target.dispatchMessage(msg); //Handler处理消息...msg.recycleUnchecked();  //回收msg}
}
复制代码

其中的一个调用是在 Looper.loop()方法中,调用时机是在 Handler 处理事件之后,既然是 Handler 处理后就会回收,那么如果在 Handler.handleMessage() 中用新的线程使用这个 msg 会怎样呢?

//MainActivity.java
@SuppressLint("HandlerLeak")
static Handler innerHandler = new Handler() {@Overridepublic void handleMessage(final Message msg) {new Thread(new Runnable() {@Overridepublic void run() {boolean isRecycle = msg.obj == null;Log.e("====是否已经回收===", "" + isRecycle);}}).start();}
};private void send(){Message msg = Message.obtain();msg.what = 0; //标识msg.obj = "这是消息体"; //消息内容innerHandler.sendMessage(msg);
}
复制代码

当调用 send()方法发送消息后,发现打出 log:

E/====是否已经回收===: true
复制代码

也就说明我们的推断是正确的。所以在平时使用中,不要在 handleMessage(Message msg)方法中对 msg 进行异步处理,因为异步处理后,该方法会马上返回,相当于告诉 Looper 已经处理完成了,Looper 就会将其回收。

如果真要在异步中使用,那么可以创建一个新的 Message 对象,并将值赋值过去。

回到前面的问题,我们目前发现了一个 Message 被回收的地方,那么其他地方有调用这个 Message .recycleUnchecked() 吗?接着看看:

//Message.java
public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}
复制代码

Message 还有一个公共的回收方法,就是上面这个了,我们可以手动调用这个进行回收。还有就是消息队列中各种 removeMessage 也会触发回收,调用链太多了,就不贴代码了。

总而言之,因为 Handler 机制在整个 Android 系统中使用太频繁,所以 Android 就采用了一个缓存策略。就是 Message 里面会缓存一个静态的消息池,当消息被处理或者移除的时候就会被回收到消息池,所以推荐使用 Message.obtain()来获取消息对象。


3.7 梳理

到此就把Handler的大致流程分析完了,再画个图重新梳理一下思路:

把整个Handler机制比作一个流水线的话,那么 Handler 就是工人,可以在不同线程传递 Message到传送带(MessageQueue),而传送带是被马达(Looper)运输的,马达又是一开始就运行了(Looper.loop()),并且只会在一开始的线程,所以无论哪个工人(Handler)在哪里(任意线程)传递产品(Message),都只会在一条传送带(MessageQueue)上被唯一的马达(Looper)运送到终点处理,即 Message 只会在调用 Looper.loop() 的线程被处理。

4 常见问题&技巧


4.1 为什么 Handler 会造成内存泄漏?

先来回顾下基础知识,可能造成内存泄漏的原因可以大致概括如下:

生命周期长的对象引用了生命周期短的对象。

Handler 跟其他一些类一样,本身是不会造成内存泄漏的,Handler 造成内存泄漏的一般原因都是由于匿名内部类引起的,因为匿名内部类隐性地持有外部类的引用(如果不持有引用怎么可以使用外部类的变量方法呢?)。

所以当内部类的生命周期比较长,如跑一个新的线程,碰巧又碰到生命周期短的对象(如Activity)需要回收,就会导致生命周期短的对象还在被生命周期长的对象所引用,进而回收不了。

典型的例子:

public class Main {int _10m = 10*1024*1024;byte[] bytes = new byte[4*_10m];public void run() {new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(100*1000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}public static void main(String args[]) {Main object = new Main();object.run();object =null;System.gc();}
}
复制代码

输出log:

[GC (System.gc()) [PSYoungGen: 3341K->880K(38400K)] 44301K->41848K(125952K) [Full GC (System.gc()) [PSYoungGen: 880K->0K(38400K)] [ParOldGen: 40968K->41697K(87552K)] 41848K->41697K(125952K)

可以看到,即使object引用为空,object 对象还是没有被回收。这就会发生了内存泄漏,如果出现很多次这样的情况,那么就很有可能发生内存溢出(OutOfMemery)。

在 Handler 里面其实是类似的道理,匿名内部类的 Handler 持有 Activity 的引用,而发送的 Message 又持有 Handler 的引用,Message 又存在于 MessageQueue 中,而 MessageQueue 又是 Looper 的成员变量,并且 Looper 对象又是存在于静态常量 sThreadLocal 中。

所以反推回来,因为 sThreadLocal 是方法区常量,所以不会被回收,而 sThreadLocal 又持有 Looper 的引用...balabala...还是看图吧:

即 sThreadLocal 间接的持有了 Activity 的引用,当 Handler 发送的消息还没有被处理完毕时,比如延时消息,而 Activity 又被用户返回了,即 onDestroy() 后,系统想要对 Activity 对象进行回收,但是发现还有引用链存在,回收不了,就造成了内存泄漏。


4.2 怎么防止 Handler 内存泄漏?

从上面的分析中,可以知道,想要防止 Handler 内存泄漏,一种方法是把 sThreadLocal 到 Activity 的引用链断开就行了。

**最简单的方法就是在 onPause()中使用 Handler 的 removeCallbacksAndMessages(null)方法清除所有消息及回调。**就可以把引用链断开了。

Android 源码中这种方式也很常见,不在 onDestroy()里面调用主要是 onDestroy() 方法不能保证每次都能执行到。

第二种方法就是使用静态类加弱引用的方式:

public class MainActivity extends AppCompatActivity {public TextView textView;static class WeakRefHandler extends Handler {//弱引用private WeakReference<MainActivity> reference;public WeakRefHandler(MainActivity mainActivity) {this.reference = new WeakReference<MainActivity>(mainActivity);}@Overridepublic void handleMessage(Message msg) {MainActivity activity = reference.get();if (activity != null) {activity.textView.setText("鸡汤程序员");}}}
}
复制代码

因为静态类不会持有外部类的引用,所以需要传一个 Activity 过来,并且使用一个弱引用来引用 Activity 的实例,弱引用在 gc 的时候会被回收,所以也就相当于把强引用链给断了,自然也就没有内存泄漏了。


4.3 Loop.loop() 为什么不会造成应用卡死?

上面也提了这个问题,按照一般的想法来说,loop() 方法是一个死循环,那么肯定会占用大量的 cpu 而导致应用卡顿,甚至说 ANR 。

但是 Android 中即使使用大量的 Looper ,也不会造成这种问题,问什么呢?

由于这个问题涉及到的知识比较深,主要是通过 Linux 的 epoll 机制实现的,这里需要 Linux 、 jni 等知识,我等菜鸟就不分析了,推荐一些相关文章:

Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

深入理解 MessageQueue

5. 总结


以上就是篇文章的全部分析了,这里总结一下:

1. Handler 的回调方法是在 Looper.loop()所调用的线程进行的;
2. Handler 的创建需要先调用 Looper.prepare() ,然后再手动调用 loop()方法开启循环;
3. App 启动时会在ActivityThread.main()方法中创建主线程的 Looper ,并开启循环,所以主线程使用 Handler 不用调用第2点的逻辑;
4. 延时消息并不会阻塞消息队列;
5. 异步消息不会马上执行,插入队列的方式跟同步消息一样,唯一的区别是当有消息屏障时,异步消息可以继续执行,同步消息则不行;
6. Callback.handleMessage() 的优先级比 Handler.handleMessage()要高
7. Handler.post(Runnable)传递的 Runnale 对象并不会在新的线程执行;
8. Message 的创建推荐使用 Message.obtain() 来获取,内部采用缓存消息池实现;
9. 不要在 handleMessage()中对消息进行异步处理;
10. 可以通过removeCallbacksAndMessages(null)或者静态类加弱引用的方式防止内存泄漏;
11. Looper.loop()不会造成应用卡死,里面使用了 Linux 的 epoll 机制。

写文章真是费时费力,特别是想写点详细点的文章,实习了多半年,越学越发现需要学的东西越多,越多东西就说明不记下来就很容易忘记,而且经过系统的整理与记录,会加深自己的理解。

本菜鸟的水平有限,如果发现有错误或不足恳请指正。

如果文章对你有帮助,欢迎点赞收藏。

感谢阅读。

作者:

简书:鸡汤程序员

掘金:gminibird

微信公众号:鸡汤程序员


  • 我们正在招募小伙伴,有兴趣的小伙伴可以把简历发到 app@talkmoney.cn,备注:来自掘金社区
  • 详情可以戳这里--> 广州芦苇信息科技

Handler全家桶之 —— Handler 源码解析相关推荐

  1. spring3.x企业应用开发实战 pdf_吃透Spring全家桶:Spring源码+SpringBoot+SpringCloud实战...

    一.Spring技术内幕 深入解析Spring架构与设计原理 本书探讨了Spring框架的设计原理.架构和运行机制.作为在Java领域最为成功的开源软件之一,Spring在Java EE开发中,使用者 ...

  2. Android多线程之IntentService源码解析

    想要了解 IntentService 的工作原理需要先对 Android 系统中以 Handler.Looper.MessageQueue 组成的异步消息处理机制以及 HandlerThread 有所 ...

  3. Handler消息机制(九):IntentService源码解析

    作者:jtsky 链接:https://www.jianshu.com/p/0a150ec09a32 简介 首先我们先来了解HandlerThread和IntentService是什么,以及为什么要将 ...

  4. [Android] Handler源码解析 (Java层)

    之前写过一篇文章,概述了Android应用程序消息处理机制.本文在此文基础上,在源码级别上展开进行概述 简单用例 Handler的使用方法如下所示: Handler myHandler = new H ...

  5. Handler源码解析2

    Handler源码解析1 https://blog.csdn.net/qq_44076155/article/details/110676740 Handler源码解析2 https://blog.c ...

  6. Handler 源码解析——Handler的创建

    前言 Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信.这是两个完全不同的概念. Handler先进先出原则,Looper类用来管理特定 ...

  7. 源码解析 Handler 面试宝典

    Handler 面试源码解析面试宝典 前言 1.一个线程有几个Handler 考点 答案 2.一个线程有几个Looper?如何保证 考点 答案 3.Handler 内存泄漏原因?为什么其他的内部类没有 ...

  8. 点击页面上的按钮后更新TextView的内容,谈谈你的理解?(阿里面试题 参照Alvin笔记 Handler源码解析)

    阿里面试题: 点击页面上的按钮后更新TextView的内容,谈谈你的理解? 首先,这个一个线程间通信的问题,可以从Handler的角度进行解释,可以从五个角度分析这个问题: 1.需要在主线程更新UI, ...

  9. Android 系统(177)---Android消息机制分析:Handler、Looper、MessageQueue源码分析

    Android消息机制分析:Handler.Looper.MessageQueue源码分析 1.前言 关于Handler消息机制的博客实际上是非常多的了. 之前也是看别人的博客过来的,但是过了一段时间 ...

最新文章

  1. 高能预警!各路大神正火速奔赴8月WOT2016 移动互联网技术峰会
  2. opencv 2d人脸姿态计算
  3. ubuntu安装 rust nightly_Rust 嵌入式开发环境搭建指南 (一):让世界闪烁吧
  4. Python_迭代器与生成器
  5. promise用法_图解 Promise 实现原理(一)—— 基础实现
  6. C#网络编程(基本概念和操作) - Part.1[转自JimmyZhang博客]
  7. 特别看好高校团队的联想创投,在中科大拉开高校AI精英挑战赛大幕
  8. 【NOIP2005】【Luogu1046】陶陶摘苹果
  9. bulk insert java_oracle bulk Insert 比insert 快不是一点啊.
  10. DynamipsGUI 模拟pix防火墙
  11. Java文字转图片超出内容自动换行
  12. 牛客oj 习题11.1 找出直系亲属(前驱记录)
  13. 如何让网站在浏览器网址前面显示小图标?ico图标怎么放?
  14. Au入门系列之六:多轨剪辑编辑
  15. 如何将苹果手机中的M4A音乐转换为MP3格式 1
  16. volumetric obscur ence
  17. [转帖]超能网--显示接口
  18. rabbit-1简介
  19. java判断long相等_java判断long类型字符是否相等的方法
  20. Linux环境下安装TASSEL_5_standalone

热门文章

  1. 2021Java常见面试题,程序员,看完这十本书再谈工资
  2. android不显示http的网络图片
  3. php中克隆对象,复制与克隆对象《 PHP 面向对象 》
  4. python源程序的书写风格_以下关于Python程序的书写风格,正确的描述是:???????????????????????????????????????...
  5. “qvod专用播放器”是专门为H网而做的吗?
  6. Wes Anderson调色盘的R实现
  7. 香港云服务器提升性能,香港云服务器性能介绍
  8. k8s的集群搭建前的相关操作--必看
  9. JSN-SR04T倒车雷达超声波测距模块
  10. python的锁机制_python互斥锁、加锁、同步机制、异步通信知识总结