当前位置:网站首页>再探Handler(下)(Handler核心原理最全解析)
再探Handler(下)(Handler核心原理最全解析)
2022-06-27 03:13:00 【AD钙奶-lalala】
再探Handler(上)(Handler核心原理最全解析)_AD钙奶-lalala的博客-CSDN博客
我们都知道可以在主线程直接创建Handler,那么问题来了:我们可以在子线程创建一个Handler吗?如何去做呢?
方式1:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
try {
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "lzy";
mHandler.sendMessageDelayed(msg, 2000);
}
Handler mHandler;
@SuppressLint("HandlerLeak")
public void test() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
Toast.makeText(MainActivity.this,
msg.obj.toString(),
Toast.LENGTH_LONG).show();
super.handleMessage(msg);
}
};
Looper.loop();
}
}).start();
}
}这种方式肯定是可以实现主线程到子线程的通信的,但是这种方式很不友好。首先我们不确定handler对象何时创建好,第二个这个handler只能用在一个地方。那我们就应该思考如何去改进。
系统其实给我们提供了一个类HandlerThread,我们可以参考这个类来优化我们的代码。
方式2:
public class LzyHandlerThread extends Thread {
Looper mLooper;
public LzyHandlerThread(String name) {
super(name);
}
@Override
public void run() {
Looper.prepare();
synchronized (this){
mLooper = Looper.myLooper();
notifyAll();
}
Looper.loop();
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return mLooper;
}
}我们来思考一下,这个类为什么要这样设计?首先我们要明白notifyAll()和wait()必须要都必须使用在synchronized包住的同步代码块或者同步方法之中。
- wait():一旦执行此方法,当前线程就会进入阻塞状态,并且释放同步锁;
- notifyAll():一旦执行此方法,就会唤醒所有被wait()阻塞的线程。
看下使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LzyHandlerThread lzyHandlerThread = new LzyHandlerThread("子线程1");
lzyHandlerThread.start();
Handler handler = new Handler(lzyHandlerThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
Log.e("lzy",Thread.currentThread().getName());
super.handleMessage(msg);
}
};
Message msg = Message.obtain();
msg.obj = "hello_k";
handler.sendMessageDelayed(msg, 5000);
}
}打印:
2022-06-23 21:03:24.044 6312-6333/com.example.lzyhandler E/lzy: 子线程1我们回过头来思考一下设计的时候为什么需要用到notifyAll和wait?
其实主要是由于并发问题,调用线程start方法后就回去执行run方法,随后初始化Handler需要传入Looper对象,而run方法的执行和Handler的初始化是在两个线程里面执行的,也就是说getLooper方法是执行在主线程的,run是执行在我们创建的名为子线程1的线程的。这样我们就无法保证mLooper在getLooper里面一定不为空。如果为空,而且主线程抢到了锁,就让子线程执行到赋值步骤阻塞。然后主线程释放锁,子线程获取锁,继续赋值操作,完成后唤醒主线程继续执行同步代码块里面的代码,注意wait不会阻塞主线程。
我们继续来思考下一个问题:子线程维护的Looper,消息队列无消息时处理方案是什么?有什么用?主线程呢?
我们来看一看我们前面设计的代码有什么隐患,当我们消息处理完毕后,Looper.loop我们知道里面是一个死循环,这样的话,MessageQueue <- Looper <- Thread <- Handler <- MainActivity这一条引用链就不会断,造成内存泄漏。那我们该如何去处理这个问题呢?
我们可以参考HandlerThread的源码,我们注意到这样一个方法:
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}我们再去Looper源码里面去看quit方法:
public void quit() {
mQueue.quit(false);
}再深入MessageQueue里面看quit方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
再来看Looper的loop方法:
public static void loop() {
···
for (;;) {
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
}
}我们再看看MessageQueue的next方法,我这里只挑重点:
Message next() {
...
if (mQuitting) {
dispose();
return null;
}
...
}调用MessageQueue方法后会将mQuiting设置为true,这样next就会返回null,loop死循环就会跳出。主线程的Looper里面的死循环是不能退出的,退出了程序也就没了。
我们都知道Handler是可以发延迟消息的,那么问题又来了:Handler是如何处理发送延时消息的呢?
我们还是来看源码:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}最终延时的时间会换算成一个具体的时间。最终会走到MessageQueue的enqueueMesasge方法(不理解调用流程的去看上一篇文章):
boolean enqueueMessage(Message msg, long when) {
···
synchronized (this) {
···
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
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.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
注意:如果when < p.when,说明什么?很明显了,就是新插入的消息执行时间小于链表第一个消息的时间,这个时候将新消息插入链表头。if里面的代码就是这个意思,能看懂吧,看不懂的赶紧回去补下链表的数据结构。简而言之:插入消息的时候已经按时间顺序排列好了!
那么我们又有疑惑,如果链表头的消息执行时间仍然在后面不在当前该如何处理呢?这个时候就需要看一下去消息的函数,上MessageQueue的next方法:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}这个方法很长,我们只需要关注我们需要关注的核心点,我们看这几行代码:
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}老哥是不是见笑了,原来谷歌的代码也是如此朴实无华,当前时间小于消息执行时间的话,计算时间差。这一次循环并不会返回msg,也不会退出循环,进入下一次循环。注意:
nativePollOnce(ptr, nextPollTimeoutMillis);
//注:
private native void nativePollOnce(long ptr, int timeoutMillis); 这次一个native方法,什么意思呢,就是等一段时间再执行呗,底层运用了epoll机制。这里就不细说了,有时间单独讲。
还记得我们的demo里面Message是如何创建的吗?Message.obtain,我们为什么不直接new 一个Message?
我们知道,整个主线程的运行是基于Looper的loop方法的,届时会有大量的消息被创建,如果没有复用机制,将会频繁的触发GC!而GC触发的时候,是会暂停进程中所有线程的。频繁GC必然会导致卡顿!
我们再来看看obtain到底做了怎样的优化:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}很明显:设计了一个缓存消息链表,如果缓存链表有对象,直接取出来一个。
最后一个问题了:Handler在没有消息处理时时阻塞的还是非阻塞的?为什么不会出现ANR?
我们还是来看源码:
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}没有消息的时候,Looper的loop方法里面这一行代码:
Message msg = queue.next(); // might block会阻塞。ANR出现的原因是定时器机制,跟Handler没什么关系。比如说在主线程执行耗时操作,主线程一直在执行耗时操作而没有办法干别的,比如说刷新UI啥的,很多系统消息无法处理,定时器就会及时报出ANR的错误提醒。handler如果是空闲状态,说明没有任何消息需要处理,如果有消息了,阻塞就会消失,又怎么会ANR呢?
边栏推荐
- Implementation of window encryption shell
- servlet与JSP期末复习考点梳理 42问42答
- Flink learning 4:flink technology stack
- 剑指Offer || :栈与队列(简单)
- Pat grade a 1026 table tennis
- Super détaillé, 20 000 caractères détaillés, mangez à travers es!
- Overview of Tsinghua & Huawei | semantic communication: Principles and challenges
- Pat grade a 1018 public bike management
- pytorch_grad_cam——pytorch下的模型特征(Class Activation Mapping, CAM)可视化库
- PAT甲级 1018 Public Bike Management
猜你喜欢

Uni-app 之uParse 富文本解析 完美解析富文本!

Anaconda3安装过程及安装后缺失大量文件,没有scripts等目录

STM32入门介绍

2021:Greedy Gradient Ensemble for Robust Visual Question Answering

1. Project preparation and creation

Enterprise digital transformation: informatization and digitalization

Is the division of each capability domain of Dama, dcmm and other data management frameworks reasonable? Is there internal logic?

PAT甲级 1019 General Palindromic Number

Learning Tai Chi Maker - mqtt (VII) advanced mqtt theme

Yuantou firm offer weekly record 20220627
随机推荐
2021:AdaVQA: Overcoming Language Priors with Adapted Margin Cosine Loss∗自适应的边缘余弦损失解决语言先验
pytorch_ grad_ Cam -- visual Library of class activation mapping (CAM) under pytorch
Enterprise digital transformation: informatization and digitalization
Getting started with Scala_ Immutable list and variable list
学习太极创客 — MQTT(七)MQTT 主题进阶
2022 Chinese pastry (Advanced) recurrent training question bank and online simulation test
Geometric distribution (a discrete distribution)
CVPR2021:Separating Skills and Concepts for Novel Visual Question Answering将技巧与概念分开的新视觉问答
Mmdetection uses yolox to train its own coco data set
2020:MUTANT: A Training Paradigm for Out-of-Distribution Generalizationin Visual Question Answering
超級詳細,2 萬字詳解,吃透 ES!
Learn Tai Chi maker mqtt (IX) esp8266 subscribe to and publish mqtt messages at the same time
人群模拟
Calculation of average wind direction and speed (unit vector method)
Quicksand painting simulator source code
Flink Learning 2: Application Scenarios
2021:Graphhopper: Multi-Hop Scene Graph Reasoning for Visual Question Answering
【微服务|Sentinel】降级规则|慢调用比例|异常比例|异常数
Flink学习3:数据处理模式(流批处理)
对数器