前言
Handler是个老生常谈的问题,我相信几乎所有的Android开发者都会使用Handler,那关于Handler还有什么好讲的吗?Handler如果仅仅是使用的话,确实没什么好讲的,但是Handler却是一个几乎所有面试官都会问的问题,不同的要求问的深度也不一样,今天我就带大家学习一下关于Handler你所必须要掌握的知识。
Handler消息机制
首先有四个对象我们必须要了解一下Handler、Looper、ThreadLocal还有MessageQueue。
Handler
首先我们要明白Handler消息机制是用来干嘛的?Handler是把其他线程切换到Handler所在的线程,注意,这里不是UI线程。虽然我们的大多数使用Handler的场景,都是我们在子线程做了一下耗时的操作(IO或者数据库),当子线程执行完以后我们可能需要更新UI,这个时候我们用Handler来处理(sendMessage()或者post())就把子线程切换到了UI线程了。假如说,我们现在有这么一个需求,线程A发个信息给线程B(线程A、线程B都不是主线程),这个时候我们用Handler依然可以做,只需要在线程B中创建好Handler就可以了(关于如何在子线程创建Handler我会在下面详细说明)。所以,Handler并不是把其他线程切换到主线程(UI线程),而是切换到它所在的线程,这一点一定要弄清楚。
Looper
Handler消息机制里面最核心的类,消息轮询。Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper,然后调用Looper.loop()方法把MessageQueue里面的message轮询出来"交"给Handler来处理,至于如何“交”给Handler的,下面我会通过源码带大家分析。
ThreadLocal
ThreadLocal是Looper内部的一个,它虽然前面有个“Thread”,但其实它并不是线程,它相当于一个线程内部的存储类。刚才在讲Looper的时候我们说到,“Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper”,那如何获得当前线程的Looper呢?正是通过ThreadLocal,它可以在不同线程之间互不干扰地存储数据。ThreadLocal里面有一个内部静态类对象ThreadLocalMap,通过其set()和get()方法对数据对象进行保存和读取。
MessageQueue
MessageQueue——消息队列,虽然它叫消息队列,但实际上的结构并不是一个队列,而是一种单链表的数据结构,只是使用列队的形式对数据进场做添加或者移除。MessageQueue是在Looper被创建的时候由Looper生成的。同一线程下,Handler发送的所有message都会被加入到MessageQueue里面,当Message被loop()以后,就会从消息队列中移除。Message我没有单独拿出来,因为确实比较简单,就是消息本身,它可以携带两个int型参数,如果要传比较复杂的参数就用obj属性,what属性用来区分是哪个Handler发送的信息。
小结一下
Handler是把其他线程切换到它所在的线程,使用Handler发消息之前要先创建Looper对象,创建和读取Looper都需要使用ThreadLocal,它可以在不同线程之间互不干扰地保存数据。在创建Looper对象的同时也把MessageQueue创建好了,Handler所发的message会被添加到MessageQueue对象里面,Looper.loop()方法以后会把MessageQueue上的Message轮询出来。连接这几个对象的还有一条很重要的线,那就是线程,记住,以上的过程都要保证Handler、Looper、MessageQueue在同一线程,这样,才能保证Handler的消息机制被正确使用。
子线程创建Handler
注意:我们这里是为了让大家更好地学习Handler,所以要在子线程创建Handler,现实使用场景,很少会在子线程创建Handler
在主线程(UI线程)创建Handler,我相信所有人都会
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
mainHandler =
new Handler();
-
mainHandler.post(
new Runnable() {
-
@Override
-
public void run() {
-
Log.e(
"qige_test",
"thread_name=" + Thread.currentThread().getName());
-
}
-
});
-
}
我们先按照主线程的方式在子线程创建一个Handler试一下,看看会有什么样的结果
-
new Thread(){
-
@Override
-
public void run() {
-
childHandler=
new Handler();
-
childHandler.post(
new Runnable() {
-
@Override
-
public void run() {
-
Log.e(
"qige_test",
"thread_name="+Thread.currentThread().getName());
-
}
-
});
-
-
}
-
}.start();
-
没错,如图所示还没有创建Looper,那么如何创建Looper呢?图中有句话已经给出了答案——Looper.prepare(),同时为了让我们发送的消息被轮询到,还必须要调用Looper.loop(); 所以在完整的在子线程创建Handler的代码如下:
-
new Thread(){
-
@Override
-
public void run() {
-
//创建Looper,同时创建MessageQueue
-
Looper.prepare();
-
-
childHandler=
new Handler();
-
childHandler.post(
new Runnable() {
-
@Override
-
public void run() {
-
Log.e(
"qige_test",
"thread_name="+Thread.currentThread().getName());
-
}
-
});
-
-
//轮询
-
Looper.loop();
-
-
}
-
}.start();
这样就可以在子线程里面创建Handler了,看到这里,你可能会问,为什么我在主线程创建Handler的时候,没有调用Looper.prepare()和Looper.loop()?这个问题我们先放一下,我们先从源码角度把Handler消息机制分析一下。
从源码角度分析
先看一下创建Looper和MessageQueue的方法Looper.prepare()
-
public static void prepare() {
-
prepare(
true);
-
}
-
-
private static void prepare(boolean quitAllowed) {
-
//ThreadLocal来了
-
if (sThreadLocal.
get() !=
null) {
-
throw
new RuntimeException(
"Only one Looper may be created per thread");
-
}
-
sThreadLocal.
set(
new Looper(quitAllowed));
-
}
这里就出现了我刚才说的ThreadLocal对象。Android规定每个线程有且仅有一个Looper,所以为了防止不同的线程之间的Looper相互影响,使用ThreadLocal对象来存储Looper。
再看一眼new Looper()的源码
-
private Looper(boolean quitAllowed) {
-
mQueue =
new MessageQueue(quitAllowed);
-
mThread = Thread.currentThread();
-
}
MessageQueue也出来了,创建Looper的同时创建MessageQueue。
下面我们看Handler.post()或者是Handler.sendMessage()他们本质是一样的,把消息加入到消息队列当中
-
public final boolean post(Runnable r)
-
{
-
return sendMessageDelayed(getPostMessage(r),
0);
-
}
post()或者sendMessagexxx()最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)方法,我们直接看这个方法的源码
-
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);
-
}
都很简单,最后一步enqueueMessage(queue, msg, uptimeMillis);是把消息加入队列,咱们再点开看一下
-
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
-
//把当前的Handler赋值给msg.target
-
msg.target =
this;
-
if (mAsynchronous) {
-
msg.setAsynchronous(
true);
-
}
-
return
queue.enqueueMessage(msg, uptimeMillis);
-
}
关键要看的地方是 msg.target = this刚才讲Message的时候没有提,Message的target属性就是一个Handler对象,这里直接把当前的Handler对象赋值过去了。最后一行:queue.enqueueMessage(msg, uptimeMillis)是把message加入到消息队列里面,具体的实现我抓不到了,知道这句话是把message加入到MessageQueue里面就行。
下面分析* Looper.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.");
-
}
-
**标注
1**
-
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.
-
........................
-
......................
-
**标注
2**
-
for (;;) {
-
Message msg = queue.next();
// might block
-
if (msg ==
null) {
-
// No message indicates that the message queue is quitting.
-
return;
-
}
-
....................................
-
...................................
-
try {
-
**标注
3**
-
msg.target.dispatchMessage(msg);
-
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() :
0;
-
}
finally {
-
if (traceTag !=
0) {
-
Trace.traceEnd(traceTag);
-
}
-
}
-
........................................
-
........................................
-
**标注
4**
-
msg.recycleUnchecked();
-
}
-
}
看源码的时候注意看一下标注,一个个地来讲;
- 标注1:保证了Handler、Looper和MessageQueue在同一线程
- 标注2:没有看错,就是一个无线死循环,Looper会不停地对MessageQueue进行轮询。这时候,你可能会问,这种无限死循环会不会很浪费资源?其实并不会,因为当没有message被添加到队列的时候,程序会进入阻塞状态,对资源的消耗是很小的。
- 标注3:还记得刚才讲地msg.target吧,这里 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看Handler的dispatchMessage(msg)方法
-
public void dispatchMessage(Message msg) {
-
if (msg.callback !=
null) {
-
handleCallback(msg);
-
}
else {
-
if (mCallback !=
null) {
-
if (mCallback.handleMessage(msg)) {
-
return;
-
}
-
}
-
handleMessage(msg);
-
}
-
}
这里面msg.callback是个Runnable对象,也就是当我们使用handler.post(Runnable r)的方法的话,这时候就直接去调用Runnable对象里面的东西了,如果使用的是handler.sendMessagexxx(),就是去调用我们重写的handleMessage(msg)方法了。
如果调用post()方法发送消息
-
mainHandler.post(
new Runnable() {
-
@Override
-
public void run() {
-
//这里会被调用
-
Log.e(
"qige_test",
"thread_name="+Thread.currentThread().getName());
-
}
-
});
如果调用sendMessagexxx()
-
mainHandler.sendEmptyMessage(
0);
-
mainHandler=
new Handler(){
-
@Override
-
public void handleMessage(Message msg) {
-
//这里会被调用
-
Log.e(
"qige_test",
"what="+msg.what);
-
}
-
};
我们再回到上一级源码
- 标注4, msg.recycleUnchecked();其实这里就是msg完成了它的使命,被释放了以后又放回缓存池里面去了。
以上基本就是一个完整的Handler消息机制的过程,我再带大家好好回顾一下:
1.Looper.prepare();//这一步创建了Looper和MessageQueue
2.handler.sendMessagexxxx(); // 把message添加到MessageQueue上
3.Looper.loop();//轮询消息
4.handler.dispatchMessage(msg);//处理消息
关于在主线程创建Handler的疑问
好了,到了该解决历史遗留问题的时候了,为什么我们在主线程创建handler不需要调用Looper.prepare()和Looper.loop()呢?
这是因为,主线程已经给我们创建好了,在哪里创建好的呢?
在Java里面,有一个程序的主入口,就是静态main方法
-
public
class Test {
-
//程序入口
-
public static void main(String... args){
-
-
}
在Android里面呢,同样有这么一个main方法入口,它在ActivityThread类中。在我们App启动过程中,会调用到ActivityTread的main()方法(由于篇幅问题,该过程没有办法详细讲,后期会推文章单独说一下),下面我们直接看ActivityTread的main()方法的源码
为了方便理解,还是只看关键部分代码
-
public static void main(String[] args) {
-
....
-
-
//创建Looper和MessageQueue对象,用于处理主线程的消息
-
Looper.prepareMainLooper();
-
-
//创建ActivityThread对象
-
ActivityThread thread =
new ActivityThread();
-
-
//建立Binder通道 (创建新线程)
-
thread.attach(
false);
-
-
//消息轮询
-
Looper.loop();
-
-
}
看到这里,我想你应该明白了为啥在主线程里创建Handler不需要调用Looper.prepare()和Looper.loop()方法了吧,因为在App启动的时候,ActivityThread已经给我们创建好了。不过,需要注意的是,我刚才在讲Looper.loop()源码的时候说过这里面会有一个无限死循环,那么程序运行到这里岂不是要永远卡在这了呢,那我们的Activity、Service都是咋执行的呢? 看关键的源码这一句thread.attach(false),注释上其实解释的很清楚,通过Binder创建了新的线程,在这个新的线程里面运行了Activity、Service等组件,而之所以Android为什么采用这种方式,让我们留在以后再说,你只需要知道,Looper.loop()不会造成卡顿,主线程已经给我们创建好了Looper和MessageQueue对象就可以了。
为什么写这篇文章
开篇我就说过,Handler几乎所有人都会用,但仅仅会用是不够,要知其然更知其所以然。很多面试官愿意问Handler相关的问题,好好阅读这篇文章,它会让你在面试的时候事半功倍。
一些基础知识和理论肯定是要背的,要理解的背,用自己的语言总结一下背下来。
虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,我能明显感觉到国庆后多了很多高级职位,所以努力让自己成为高级工程师才是最重要的。
好了,希望对大家有所帮助。
接下来是整理的一些Android学习资料,有兴趣的朋友们可以关注下【我】查看免费领取方式。
①Android开发核心知识点笔记
②对标“阿里 P7” 40W+年薪企业资深架构师成长学习路线图
③面试精品集锦汇总
④全套体系化高级架构视频
Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
【Android进阶学习视频】、【全套Android面试秘籍】关注【我】查看免费领取方式!
转载:https://blog.csdn.net/weixin_44339238/article/details/103559744