• 再探Handler(下)(Handler核心原理最全解析)


    ​​​​​​​​​​​​​​再探Handler(上)(Handler核心原理最全解析)_AD钙奶-lalala的博客-CSDN博客

    我们都知道可以在主线程直接创建Handler,那么问题来了:我们可以在子线程创建一个Handler吗?如何去做呢?

    方式1:

    1. public class MainActivity extends AppCompatActivity {
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. setContentView(R.layout.activity_main);
    6. test();
    7. try {
    8. Thread.sleep(3000);
    9. }catch (Exception e){
    10. e.printStackTrace();
    11. }
    12. Message msg = Message.obtain();
    13. msg.what = 1;
    14. msg.obj = "lzy";
    15. mHandler.sendMessageDelayed(msg, 2000);
    16. }
    17. Handler mHandler;
    18. @SuppressLint("HandlerLeak")
    19. public void test() {
    20. new Thread(new Runnable() {
    21. @Override
    22. public void run() {
    23. Looper.prepare();
    24. mHandler = new Handler() {
    25. @Override
    26. public void handleMessage(@NonNull Message msg) {
    27. Toast.makeText(MainActivity.this,
    28. msg.obj.toString(),
    29. Toast.LENGTH_LONG).show();
    30. super.handleMessage(msg);
    31. }
    32. };
    33. Looper.loop();
    34. }
    35. }).start();
    36. }
    37. }

    这种方式肯定是可以实现主线程到子线程的通信的,但是这种方式很不友好。首先我们不确定handler对象何时创建好,第二个这个handler只能用在一个地方。那我们就应该思考如何去改进。

    系统其实给我们提供了一个类HandlerThread,我们可以参考这个类来优化我们的代码。

    方式2:

    1. public class LzyHandlerThread extends Thread {
    2. Looper mLooper;
    3. public LzyHandlerThread(String name) {
    4. super(name);
    5. }
    6. @Override
    7. public void run() {
    8. Looper.prepare();
    9. synchronized (this){
    10. mLooper = Looper.myLooper();
    11. notifyAll();
    12. }
    13. Looper.loop();
    14. }
    15. public Looper getLooper() {
    16. if (!isAlive()) {
    17. return null;
    18. }
    19. synchronized (this) {
    20. while (isAlive() && mLooper == null) {
    21. try {
    22. wait();
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. }
    28. return mLooper;
    29. }
    30. }

    我们来思考一下,这个类为什么要这样设计?首先我们要明白notifyAll()和wait()必须要都必须使用在synchronized包住的同步代码块或者同步方法之中。

    • wait():一旦执行此方法,当前线程就会进入阻塞状态,并且释放同步锁;
    • notifyAll():一旦执行此方法,就会唤醒所有被wait()阻塞的线程。

    看下使用:

    1. public class MainActivity extends AppCompatActivity {
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. setContentView(R.layout.activity_main);
    6. LzyHandlerThread lzyHandlerThread = new LzyHandlerThread("子线程1");
    7. lzyHandlerThread.start();
    8. Handler handler = new Handler(lzyHandlerThread.getLooper()) {
    9. @Override
    10. public void handleMessage(@NonNull Message msg) {
    11. Log.e("lzy",Thread.currentThread().getName());
    12. super.handleMessage(msg);
    13. }
    14. };
    15. Message msg = Message.obtain();
    16. msg.obj = "hello_k";
    17. handler.sendMessageDelayed(msg, 5000);
    18. }
    19. }

    打印:

    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的源码,我们注意到这样一个方法:

    1. public boolean quit() {
    2. Looper looper = getLooper();
    3. if (looper != null) {
    4. looper.quit();
    5. return true;
    6. }
    7. return false;
    8. }

    我们再去Looper源码里面去看quit方法:

    1. public void quit() {
    2. mQueue.quit(false);
    3. }

    再深入MessageQueue里面看quit方法:

    1. void quit(boolean safe) {
    2. if (!mQuitAllowed) {
    3. throw new IllegalStateException("Main thread not allowed to quit.");
    4. }
    5. synchronized (this) {
    6. if (mQuitting) {
    7. return;
    8. }
    9. mQuitting = true;
    10. if (safe) {
    11. removeAllFutureMessagesLocked();
    12. } else {
    13. removeAllMessagesLocked();
    14. }
    15. // We can assume mPtr != 0 because mQuitting was previously false.
    16. nativeWake(mPtr);
    17. }
    18. }

    再来看Looper的loop方法:

    1. public static void loop() {
    2. ···
    3. for (;;) {
    4. Message msg = queue.next();
    5. if (msg == null) {
    6. // No message indicates that the message queue is quitting.
    7. return;
    8. }
    9. ...
    10. }
    11. }

    我们再看看MessageQueue的next方法,我这里只挑重点:

    1. Message next() {
    2. ...
    3. if (mQuitting) {
    4. dispose();
    5. return null;
    6. }
    7. ...
    8. }

    调用MessageQueue方法后会将mQuiting设置为true,这样next就会返回null,loop死循环就会跳出。主线程的Looper里面的死循环是不能退出的,退出了程序也就没了。

    我们都知道Handler是可以发延迟消息的,那么问题又来了:Handler是如何处理发送延时消息的呢?

    我们还是来看源码:

    1. public final boolean sendMessageDelayed(Message msg, long delayMillis)
    2. {
    3. if (delayMillis < 0) {
    4. delayMillis = 0;
    5. }
    6. return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    7. }

    最终延时的时间会换算成一个具体的时间。最终会走到MessageQueue的enqueueMesasge方法(不理解调用流程的去看上一篇文章):

    1. boolean enqueueMessage(Message msg, long when) {
    2. ···
    3. synchronized (this) {
    4. ···
    5. msg.markInUse();
    6. msg.when = when;
    7. Message p = mMessages;
    8. boolean needWake;
    9. if (p == null || when == 0 || when < p.when) {
    10. msg.next = p;
    11. mMessages = msg;
    12. needWake = mBlocked;
    13. } else {
    14. needWake = mBlocked && p.target == null && msg.isAsynchronous();
    15. Message prev;
    16. for (;;) {
    17. prev = p;
    18. p = p.next;
    19. if (p == null || when < p.when) {
    20. break;
    21. }
    22. if (needWake && p.isAsynchronous()) {
    23. needWake = false;
    24. }
    25. }
    26. msg.next = p; // invariant: p == prev.next
    27. prev.next = msg;
    28. }
    29. if (needWake) {
    30. nativeWake(mPtr);
    31. }
    32. }
    33. return true;
    34. }

    注意:如果when < p.when,说明什么?很明显了,就是新插入的消息执行时间小于链表第一个消息的时间,这个时候将新消息插入链表头。if里面的代码就是这个意思,能看懂吧,看不懂的赶紧回去补下链表的数据结构。简而言之:插入消息的时候已经按时间顺序排列好了

    那么我们又有疑惑,如果链表头的消息执行时间仍然在后面不在当前该如何处理呢?这个时候就需要看一下去消息的函数,上MessageQueue的next方法:

    1. Message next() {
    2. // Return here if the message loop has already quit and been disposed.
    3. // This can happen if the application tries to restart a looper after quit
    4. // which is not supported.
    5. final long ptr = mPtr;
    6. if (ptr == 0) {
    7. return null;
    8. }
    9. int pendingIdleHandlerCount = -1; // -1 only during first iteration
    10. int nextPollTimeoutMillis = 0;
    11. for (;;) {
    12. if (nextPollTimeoutMillis != 0) {
    13. Binder.flushPendingCommands();
    14. }
    15. nativePollOnce(ptr, nextPollTimeoutMillis);
    16. synchronized (this) {
    17. // Try to retrieve the next message. Return if found.
    18. final long now = SystemClock.uptimeMillis();
    19. Message prevMsg = null;
    20. Message msg = mMessages;
    21. if (msg != null && msg.target == null) {
    22. // Stalled by a barrier. Find the next asynchronous message in the queue.
    23. do {
    24. prevMsg = msg;
    25. msg = msg.next;
    26. } while (msg != null && !msg.isAsynchronous());
    27. }
    28. if (msg != null) {
    29. if (now < msg.when) {
    30. // Next message is not ready. Set a timeout to wake up when it is ready.
    31. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    32. } else {
    33. // Got a message.
    34. mBlocked = false;
    35. if (prevMsg != null) {
    36. prevMsg.next = msg.next;
    37. } else {
    38. mMessages = msg.next;
    39. }
    40. msg.next = null;
    41. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    42. msg.markInUse();
    43. return msg;
    44. }
    45. } else {
    46. // No more messages.
    47. nextPollTimeoutMillis = -1;
    48. }
    49. // Process the quit message now that all pending messages have been handled.
    50. if (mQuitting) {
    51. dispose();
    52. return null;
    53. }
    54. // If first time idle, then get the number of idlers to run.
    55. // Idle handles only run if the queue is empty or if the first message
    56. // in the queue (possibly a barrier) is due to be handled in the future.
    57. if (pendingIdleHandlerCount < 0
    58. && (mMessages == null || now < mMessages.when)) {
    59. pendingIdleHandlerCount = mIdleHandlers.size();
    60. }
    61. if (pendingIdleHandlerCount <= 0) {
    62. // No idle handlers to run. Loop and wait some more.
    63. mBlocked = true;
    64. continue;
    65. }
    66. if (mPendingIdleHandlers == null) {
    67. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
    68. }
    69. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    70. }
    71. // Run the idle handlers.
    72. // We only ever reach this code block during the first iteration.
    73. for (int i = 0; i < pendingIdleHandlerCount; i++) {
    74. final IdleHandler idler = mPendingIdleHandlers[i];
    75. mPendingIdleHandlers[i] = null; // release the reference to the handler
    76. boolean keep = false;
    77. try {
    78. keep = idler.queueIdle();
    79. } catch (Throwable t) {
    80. Log.wtf(TAG, "IdleHandler threw exception", t);
    81. }
    82. if (!keep) {
    83. synchronized (this) {
    84. mIdleHandlers.remove(idler);
    85. }
    86. }
    87. }
    88. // Reset the idle handler count to 0 so we do not run them again.
    89. pendingIdleHandlerCount = 0;
    90. // While calling an idle handler, a new message could have been delivered
    91. // so go back and look again for a pending message without waiting.
    92. nextPollTimeoutMillis = 0;
    93. }
    94. }

    这个方法很长,我们只需要关注我们需要关注的核心点,我们看这几行代码:

    1. if (now < msg.when) {
    2. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    3. }

    老哥是不是见笑了,原来谷歌的代码也是如此朴实无华,当前时间小于消息执行时间的话,计算时间差。这一次循环并不会返回msg,也不会退出循环,进入下一次循环。注意:

    1. nativePollOnce(ptr, nextPollTimeoutMillis);
    2. //注:
    3. private native void nativePollOnce(long ptr, int timeoutMillis);

    这次一个native方法,什么意思呢,就是等一段时间再执行呗,底层运用了epoll机制。这里就不细说了,有时间单独讲。

    还记得我们的demo里面Message是如何创建的吗?Message.obtain,我们为什么不直接new 一个Message?

    我们知道,整个主线程的运行是基于Looper的loop方法的,届时会有大量的消息被创建,如果没有复用机制,将会频繁的触发GC!而GC触发的时候,是会暂停进程中所有线程的。频繁GC必然会导致卡顿!

    我们再来看看obtain到底做了怎样的优化:

    1. public static Message obtain() {
    2. synchronized (sPoolSync) {
    3. if (sPool != null) {
    4. Message m = sPool;
    5. sPool = m.next;
    6. m.next = null;
    7. m.flags = 0; // clear in-use flag
    8. sPoolSize--;
    9. return m;
    10. }
    11. }
    12. return new Message();
    13. }

    很明显:设计了一个缓存消息链表,如果缓存链表有对象,直接取出来一个。

    最后一个问题了:Handler在没有消息处理时时阻塞的还是非阻塞的?为什么不会出现ANR?

    我们还是来看源码:

    1. if (pendingIdleHandlerCount <= 0) {
    2. // No idle handlers to run. Loop and wait some more.
    3. mBlocked = true;
    4. continue;
    5. }

    没有消息的时候,Looper的loop方法里面这一行代码:

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

    会阻塞。ANR出现的原因是定时器机制,跟Handler没什么关系。比如说在主线程执行耗时操作,主线程一直在执行耗时操作而没有办法干别的,比如说刷新UI啥的,很多系统消息无法处理,定时器就会及时报出ANR的错误提醒。handler如果是空闲状态,说明没有任何消息需要处理,如果有消息了,阻塞就会消失,又怎么会ANR呢?

  • 相关阅读:
    2022年Redis最新面试题 - Redis运维和部署
    python项目2to3方案预研
    mysql-6-主从复制搭建
    kubernetesr进阶--污点和容忍之概述
    SpringAOP详解
    物联网的概念
    便携烙铁开源系统IronOS,支持多款便携DC, QC, PD供电烙铁,支持所有智能烙铁标准功能
    当网络隔离成了必须,跨网文件传输该如何实现?
    废水含镍如何处理
    RabbitMQ:hello结构
  • 原文地址:https://blog.csdn.net/qq_36428821/article/details/125434154