Handler相关知识记录
1. 什么是Handler
Handler常被我们用来做主线程与子线程之间的通信工具,他是Android中最基础最重要的机制。由于Android系统设计为主线程操作UI,不仅APP需要在开发需求中常用到子线程执行耗时操作后切换主线程操作UI,原生AOSP中诸如生命周期管理等都是通过Handler实现。
Handler类位于SDK中,目录如下
SDK\platforms\android-33\android.jar!\android\os\Handler.class
注:这里我是基于33版本的SDK源码来看的
我们在日常使用中和面试中常说的Handler是一个广义的概念而不仅是单指这个类,它包含了实现跨线程操作的几个重要类,我们需要掌握其概念、基本机制
在应用开发中,我们常会遇到做耗时操作的场景,比如在进入某个页面时同时请求网络数据来加载并填充一个图片或者列表,这时如果在Activity的生命周期直接做耗时操作并等待结果很容易出现ANR,这时就需要在子线程做这个耗时操作。注意,这里的耗时操作其实并不能用Service来解决,Service也是运行在主线程的,之后的文章会提到
那么在子线程执行完毕或得到需要的返回结果后,如何取更新UI呢。当我们此时直接获取到对象操作UI控件,会出现下面的报错
Only the original thread that created a view hierarchy can touch its views
提示我们不能在应用的子线程操作UI,这样做是为了保证UI操作的线程安全,虽然这种设计牺牲了一部分性能,但是在实际开发中节省了大量的编码量和debug量
为了在子线程通知主线程执行特定的行为,这就需要用到Handler机制
2. Handler初入门
Handler机制中重要的类总共有下面四个:
- Handler
- Looper
- MessageQuene
- Message
我们在实际开发的场景中会这么使用这套机制:
1. 在类中创建Handler
public class Activity extends android.app.Activity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println(msg.what);
}
};
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
...............耗时操作
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
}
}).start();
}
}
从上面的代码可以看出我们new了一个Hander对象,并在onCreate时,创建了一个子线程执行一个耗时操作。
在耗时操作执行完毕后调用Message.obtain()创建了一个Message对象
然后用之前new出的Handler对象调用sendMessage发送了这个Message对象
这样包含着数据或信息的Message对象就从子线程发送到了在主线程创建的Handler
这里看似只是用到了Handler和Message两个类,实际上很多工作都已经在另外两个类中做了处理,接下来看原理
3. Handler机制原理
1. 四个重要的类
Message:需要传递的消息,可以传递数据;
MessageQueue:消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
Looper:不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。
他们之间的关系为:
Looper有一个MessageQueue消息队列;
MessageQueue有一组待处理的Message;
Message中记录发送和处理消息的Handler;
Handler中有Looper和MessageQueue。
2. 源码
1. Looper
为什么先看这个类呢,在上面的例子代码中我们可以尝试在子线程中new一个Handler对象,会报下面的错误
Can't create handler inside thread xxxxxxxxxxx that has not called Looper.prepare()
异常抛出的逻辑如下
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) {
// 这里有个对类信息的判断,看创建Handler是否为匿名类、内部类、静态类。弹出worren提醒需要声明静态否则可能会导致内存泄露
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 调用myLooper()尝试获取了一个Looper对象,为空的时候则抛出异常
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.myLooper()
从这里看当myLooper返回空时就会导致Handler的创建受阻,再继续往下看myLooper()做了什么:
//这里很贴心的给开发者加了注释,如果不走prepare(),get出来就是null
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
只是一个从sThreadLocal的取操作,为空则报错
然后我们去报错信息和上面注释提到的Looper.prepare()方法看一下
Looper.prepare()
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
能看到有一个boolean入参,含义为是否允许退出
至于逻辑更是非常单一,先判空,不为空则抛异常;为空则new一个Looper对象set进之前提到的ThreadLocal
说明一个线程中只能new一个Looper出来,多了不整
这里明确一个概念
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
ThreadLock实际可以理解成线程的一个全局变量,每个线程都有且只有一个并且完全隔离互不通
接下来new Looper就走了Looper的构造
Looper(boolean quitAllowed) 构造
/**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
能看到传入的boolean值继续用作创建MessageQuene的构造,同时还get了一下当前的线程
同时提供了一个获取MessageQuene的get接口
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!