想实现Android队列功能?Handler内功心法,你值得拥有!(一)——Handler源码和常见问题的解答

本文详细探讨了Android中Handler、Looper和MessageQueue的关系,解释了如何在子线程中创建Looper并处理消息,以及如何避免内存泄漏。文章还介绍了主线程的Looper为何不会导致应用卡死,分析了Handler的消息处理顺序和使用场景,提供了面试复习建议。
摘要由CSDN通过智能技术生成

//Looper.java

public static void loop() {

//获取ThreadLocal中的Looper

final Looper me = myLooper();

···

final MessageQueue queue = me.mQueue;

···

for (;😉 { //死循环

//获取消息

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

···

msg.target.dispatchMessage(msg);

···

//回收复用

msg.recycleUnchecked();

}

}

在loop方法中是一个死循环,在这里从消息队列中不断的获取消息queue.next(),然后通过Handler(msg.target)进行消息的分发,其实并没有什么具体的绑定,因为Handler在每个线程中对应只有一个Looper和消息队列MessageQueue,自然要靠它来处理,也就是是调用Looper.loop()方法。在Looper.loop()的死循环中不断的取消息,最后回收复用。

这里要强调一下Message中的参数target(Handler),正是这个变量,每个Message才能找到对应的Handler进行消息分发,让多个Handler同时工作。

再来看看子线程中是如何处理的,首先在子线程中创建一个Handler并发送Runnable。

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_three);

new Thread(new Runnable() {

@Override

public void run() {

new Handler().post(new Runnable() {

@Override

public void run() {

Toast.makeText(HandlerActivity.this,“toast”,Toast.LENGTH_LONG).show();

}

});

}

}).start();

}

运行后可以看到错误日志,可以看到提示我们需要在子线程中调用Looper.prepare()方法,实际上就是要创建一个Looper和你的Handler进行“关联”。

--------- beginning of crash

2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2

Process: com.jackie.testdialog, PID: 21122

java.lang.RuntimeException: Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()

at android.os.Handler.(Handler.java:207)

at android.os.Handler.(Handler.java:119)

at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)

at java.lang.Thread.run(Thread.java:919)

添加Looper.prepare()创建Looper,同时调用Looper.loop()方法开始处理消息。

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_three);

new Thread(new Runnable() {

@Override

public void run() {

//创建Looper,MessageQueue

Looper.prepare();

new Handler().post(new Runnable() {

@Override

public void run() {

Toast.makeText(HandlerActivity.this,“toast”,Toast.LENGTH_LONG).show();

}

});

//开始处理消息

Looper.loop();

}

}).start();

}

这里需要注意在所有事情处理完成后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于循环等待的状态,因此不需要的时候终止Looper,调用Looper.myLooper().quit()。

看完上面的代码可能你会有一个疑问,在子线程中更新UI(进行Toast)不会有问题吗,我们Android不是不允许在子线程更新UI吗,实际上并不是这样的,在ViewRootImpl中的checkThread方法会校验mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的构造器中,也就是说一个创建ViewRootImpl线程必须和调用checkThread所在的线程一致,UI的更新并非只能在主线程才能进行。

void checkThread() {

if (mThread != Thread.currentThread()) {

throw new CalledFromWrongThreadException(

“Only the original thread that created a view hierarchy can touch its views.”);

}

}

这里需要引入一些概念,Window是Android中的窗口,每个Activity和Dialog,Toast分别对应一个具体的Window,Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此,它是以View的形式存在的。我们来看一下Toast中的ViewRootImpl的创建过程,调用toast的show方法最终会调用到其handleShow方法。

/

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值